Completed
Pull Request — master (#19)
by Ionel Cristian
01:00
created

src.hunter.Event.filename()   A

Complexity

Conditions 4

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 4
dl 0
loc 15
rs 9.2
1
from __future__ import absolute_import
2
3
import linecache
4
import re
5
import tokenize
6
from functools import partial
7
8
from fields import Fields
9
10
from .const import SITE_PACKAGES_PATH
11
from .const import SYS_PREFIX_PATHS
12
from .util import cached_property
13
14
STARTSWITH_TYPES = list, tuple, set
15
16
17
class Event(Fields.kind.function.module.filename):
18
    """
19
    Event wrapper for ``frame, kind, arg`` (the arguments the settrace function gets).
20
21
    Provides few convenience properties.
22
    """
23
    frame = None
24
    kind = None
25
    arg = None
26
    tracer = None
27
28
    def __init__(self, frame, kind, arg, tracer):
29
        self.frame = frame
30
        self.kind = kind
31
        self.arg = arg
32
        self.tracer = tracer
33
34
    @cached_property
35
    def locals(self):
36
        """
37
        A dict with local variables.
38
        """
39
        return self.frame.f_locals
40
41
    @cached_property
42
    def globals(self):
43
        """
44
        A dict with global variables.
45
        """
46
        return self.frame.f_globals
47
48
    @cached_property
49
    def function(self):
50
        """
51
        A string with function name.
52
        """
53
        return self.code.co_name
54
55
    @cached_property
56
    def module(self):
57
        """
58
        A string with module name (eg: ``"foo.bar"``).
59
        """
60
        module = self.frame.f_globals.get('__name__', '')
61
        if module is None:
62
            module = ''
63
64
        return module
65
66
    @cached_property
67
    def filename(self):
68
        """
69
        A string with absolute path to file.
70
        """
71
        filename = self.frame.f_globals.get('__file__', '')
72
        if filename is None:
73
            filename = ''
74
75
        if filename.endswith(('.pyc', '.pyo')):
76
            filename = filename[:-1]
77
        elif filename.endswith('$py.class'):  # Jython
78
            filename = filename[:-9] + ".py"
79
80
        return filename
81
82
    @cached_property
83
    def lineno(self):
84
        """
85
        An integer with line number in file.
86
        """
87
        return self.frame.f_lineno
88
89
    @cached_property
90
    def code(self):
91
        """
92
        A code object (not a string).
93
        """
94
        return self.frame.f_code
95
96
    @cached_property
97
    def stdlib(self):
98
        """
99
        A boolean flag. ``True`` if frame is in stdlib.
100
        """
101
        if self.filename.startswith(SITE_PACKAGES_PATH):
102
            # if it's in site-packages then its definitely not stdlib
103
            return False
104
        if self.filename.startswith(SYS_PREFIX_PATHS):
105
            return True
106
107
    @cached_property
108
    def fullsource(self):
109
        """
110
        A string with the sourcecode for the current statement (from ``linecache`` - failures are ignored).
111
112
        May include multiple lines if it's a class/function definition (will include decorators).
113
        """
114
        try:
115
            return self._raw_fullsource
116
        except Exception as exc:
117
            return "??? NO SOURCE: {!r}".format(exc)
118
119
    @cached_property
120
    def source(self, getline=linecache.getline):
121
        """
122
        A string with the sourcecode for the current line (from ``linecache`` - failures are ignored).
123
124
        Fast but sometimes incomplete.
125
        """
126
        try:
127
            return getline(self.filename, self.lineno)
128
        except Exception as exc:
129
            return "??? NO SOURCE: {!r}".format(exc)
130
131
    @cached_property
132
    def _raw_fullsource(self,
133
                        getlines=linecache.getlines,
134
                        getline=linecache.getline,
135
                        generate_tokens=tokenize.generate_tokens):
136
        if self.kind == 'call' and self.code.co_name != "<module>":
137
            lines = []
138
            try:
139
                for _, token, _, _, line in generate_tokens(partial(
140
                    next,
141
                    yield_lines(self.filename, self.lineno - 1, lines.append)
142
                )):
143
                    if token in ("def", "class", "lambda"):
144
                        return ''.join(lines)
145
            except tokenize.TokenError:
146
                pass
147
148
        return getline(self.filename, self.lineno)
149
150
    __getitem__ = object.__getattribute__
151
152
153
def yield_lines(filename, start, collector,
154
                limit=10,
155
                getlines=linecache.getlines,
156
                leading_whitespace_re=re.compile('(^[ \t]*)(?:[^ \t\n])', re.MULTILINE)):
157
    dedent = None
158
    amount = 0
159
    for line in getlines(filename)[start:start + limit]:
160
        if dedent is None:
161
            dedent = leading_whitespace_re.findall(line)
162
            dedent = dedent[0] if dedent else ""
163
            amount = len(dedent)
164
        elif not line.startswith(dedent):
165
            break
166
        collector(line)
167
        yield line[amount:]
168