Completed
Push — master ( 449e73...c6f33b )
by Ionel Cristian
01:09
created

Event.stdlib()   A

Complexity

Conditions 3

Size

Total Lines 12

Duplication

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