Source code for hunter.event

from __future__ import absolute_import

import linecache
import re
import tokenize
from functools import partial

from fields import Fields

from .const import SITE_PACKAGES_PATH
from .const import SYS_PREFIX_PATHS
from .util import cached_property

STARTSWITH_TYPES = list, tuple, set


[docs]class Event(Fields.kind.function.module.filename): """ Event wrapper for ``frame, kind, arg`` (the arguments the settrace function gets). Provides few convenience properties. """ frame = None kind = None arg = None tracer = None def __init__(self, frame, kind, arg, tracer): self.frame = frame self.kind = kind self.arg = arg self.tracer = tracer @cached_property def locals(self): """ A dict with local variables. """ return self.frame.f_locals @cached_property def globals(self): """ A dict with global variables. """ return self.frame.f_globals @cached_property def function(self): """ A string with function name. """ return self.code.co_name @cached_property def module(self): """ A string with module name (eg: ``"foo.bar"``). """ module = self.frame.f_globals.get('__name__', '') if module is None: module = '' return module @cached_property def filename(self): """ A string with absolute path to file. """ filename = self.frame.f_globals.get('__file__', '') if filename is None: filename = '' if filename.endswith(('.pyc', '.pyo')): filename = filename[:-1] elif filename.endswith('$py.class'): # Jython filename = filename[:-9] + ".py" return filename @cached_property def lineno(self): """ An integer with line number in file. """ return self.frame.f_lineno @cached_property def code(self): """ A code object (not a string). """ return self.frame.f_code @cached_property def stdlib(self): """ A boolean flag. ``True`` if frame is in stdlib. """ if self.filename.startswith(SITE_PACKAGES_PATH): # if it's in site-packages then its definitely not stdlib return False elif self.filename.startswith(SYS_PREFIX_PATHS): return True else: return False @cached_property def fullsource(self): """ A string with the sourcecode for the current statement (from ``linecache`` - failures are ignored). May include multiple lines if it's a class/function definition (will include decorators). """ try: return self._raw_fullsource except Exception as exc: return "??? NO SOURCE: {!r}".format(exc) @cached_property def source(self, getline=linecache.getline): """ A string with the sourcecode for the current line (from ``linecache`` - failures are ignored). Fast but sometimes incomplete. """ try: return getline(self.filename, self.lineno) except Exception as exc: return "??? NO SOURCE: {!r}".format(exc) @cached_property def _raw_fullsource(self, getlines=linecache.getlines, getline=linecache.getline, generate_tokens=tokenize.generate_tokens): if self.kind == 'call' and self.code.co_name != "<module>": lines = [] try: for _, token, _, _, line in generate_tokens(partial( next, yield_lines(self.filename, self.lineno - 1, lines.append) )): if token in ("def", "class", "lambda"): return ''.join(lines) except tokenize.TokenError: pass return getline(self.filename, self.lineno) __getitem__ = object.__getattribute__
def yield_lines(filename, start, collector, limit=10, getlines=linecache.getlines, leading_whitespace_re=re.compile('(^[ \t]*)(?:[^ \t\n])', re.MULTILINE)): dedent = None amount = 0 for line in getlines(filename)[start:start + limit]: if dedent is None: dedent = leading_whitespace_re.findall(line) dedent = dedent[0] if dedent else "" amount = len(dedent) elif not line.startswith(dedent): break collector(line) yield line[amount:]