Source code for hunter.tracer
import sys
import threading
import traceback
import hunter
from .event import Event
__all__ = ('Tracer',)
[docs]class Tracer:
"""
Tracer object.
Args:
threading_support (bool): Hooks the tracer into ``threading.settrace`` as well if True.
"""
[docs] def __init__(self, threading_support=None, profiling_mode=False):
self._handler = None
self._previous = None
self._threading_previous = None
#: True if threading support was enabled. Should be considered read-only.
#:
#: :type: bool
self.threading_support = threading_support
#: True if profiling mode was enabled. Should be considered read-only.
#:
#: :type: bool
self.profiling_mode = profiling_mode
#: Tracing depth (increases on calls, decreases on returns)
#:
#: :type: int
self.depth = 0
#: A counter for total number of 'call' frames that this Tracer went through.
#:
#: :type: int
self.calls = 0
@property
def handler(self):
"""
The current predicate. Set via :func:`hunter.Tracer.trace`.
"""
return self._handler
@property
def previous(self):
"""
The previous tracer, if any (whatever ``sys.gettrace()`` returned prior to :func:`hunter.Tracer.trace`).
"""
return self._previous
[docs] def __repr__(self):
return '<hunter.tracer.Tracer at 0x{:x}: threading_support={}, {}{}{}{}>'.format(
id(self),
self.threading_support,
'<stopped>' if self._handler is None else 'handler=',
'' if self._handler is None else repr(self._handler),
'' if self._previous is None else ', previous=',
'' if self._previous is None else repr(self._previous),
)
[docs] def __call__(self, frame, kind, arg):
"""
The settrace function.
.. note::
This always returns self (drills down) - as opposed to only drilling down when ``predicate(event)`` is True
because it might match further inside.
"""
if self._handler is not None:
if kind == 'return' and self.depth > 0:
self.depth -= 1
event = Event(frame, kind, arg, self)
try:
self._handler(event)
except Exception as exc:
traceback.print_exc(file=hunter._default_stream)
hunter._default_stream.write(f'Disabling tracer because handler {self._handler!r} failed ({exc!r}) at {event!r}.\n\n')
self.stop()
return
if kind == 'call':
self.depth += 1
self.calls += 1
return self
[docs] def trace(self, predicate):
"""
Starts tracing with the given callable.
Args:
predicate (callable that accepts a single :obj:`~hunter.event.Event` argument):
Return:
self
"""
self._handler = predicate
if self.profiling_mode:
if self.threading_support is None or self.threading_support:
self._threading_previous = getattr(threading, '_profile_hook', None)
threading.setprofile(self)
self._previous = sys.getprofile()
sys.setprofile(self)
else:
if self.threading_support is None or self.threading_support:
self._threading_previous = getattr(threading, '_trace_hook', None)
threading.settrace(self)
self._previous = sys.gettrace()
sys.settrace(self)
return self
[docs] def stop(self):
"""
Stop tracing. Reinstalls the :attr:`~hunter.tracer.Tracer.previous` tracer.
"""
if self._handler is not None:
if self.profiling_mode:
sys.setprofile(self._previous)
self._handler = self._previous = None
if self.threading_support is None or self.threading_support:
threading.setprofile(self._threading_previous)
self._threading_previous = None
else:
sys.settrace(self._previous)
self._handler = self._previous = None
if self.threading_support is None or self.threading_support:
threading.settrace(self._threading_previous)
self._threading_previous = None
[docs] def __enter__(self):
"""
Does nothing. Users are expected to call :meth:`~hunter.tracer.Tracer.trace`.
Returns: self
"""
return self
[docs] def __exit__(self, exc_type, exc_val, exc_tb):
"""
Wrapper around :meth:`~hunter.tracer.Tracer.stop`. Does nothing with the arguments.
"""
self.stop()