Completed
Push — master ( ebc7cd...b3cc5a )
by Ionel Cristian
10s
created

Traceback.as_traceback()   D

Complexity

Conditions 8

Size

Total Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 8
c 6
b 0
f 0
dl 0
loc 47
rs 4.3478
1
import re
2
import sys
3
from types import CodeType
4
from types import TracebackType
5
6
try:
7
    from __pypy__ import tproxy
8
except ImportError:
9
    tproxy = None
10
try:
11
    from .cpython import tb_set_next
12
except ImportError:
13
    tb_set_next = None
14
15
if not tb_set_next and not tproxy:
16
    raise ImportError("Cannot use tblib. Runtime not supported.")
17
18
__version__ = '1.3.0'
19
__all__ = 'Traceback',
20
21
PY3 = sys.version_info[0] == 3
22
FRAME_RE = re.compile(r'^\s*File "(?P<co_filename>.+)", line (?P<tb_lineno>\d+)(, in (?P<co_name>.+))?$')
23
24
25
class _AttrDict(dict):
26
    __slots__ = ()
27
    __getattr__ = dict.__getitem__
28
29
30
# noinspection PyPep8Naming
31
class __traceback_maker(Exception):
32
    pass
33
34
35
class TracebackParseError(Exception):
36
    pass
37
38
39
class Code(object):
40
    def __init__(self, code):
41
        self.co_filename = code.co_filename
42
        self.co_name = code.co_name
43
44
45
class Frame(object):
46
    def __init__(self, frame):
47
        self.f_globals = dict([
48
            (k, v)
49
            for k, v in frame.f_globals.items()
50
            if k in ("__file__", "__name__")
51
        ])
52
        self.f_code = Code(frame.f_code)
53
54
55
class Traceback(object):
56
57
    tb_next = None
58
59
    def __init__(self, tb):
60
        self.tb_frame = Frame(tb.tb_frame)
61
        # noinspection SpellCheckingInspection
62
        self.tb_lineno = int(tb.tb_lineno)
63
64
        # Build in place to avoid exceeding the recursion limit
65
        tb = tb.tb_next
66
        prev_traceback = self
67
        cls = type(self)
68
        while tb is not None:
69
            traceback = object.__new__(cls)
70
            traceback.tb_frame = Frame(tb.tb_frame)
71
            traceback.tb_lineno = int(tb.tb_lineno)
72
            prev_traceback.tb_next = traceback
73
            prev_traceback = traceback
74
            tb = tb.tb_next
75
76
    def as_traceback(self):
77
        if tproxy:
78
            return tproxy(TracebackType, self.__tproxy_handler)
79
        if not tb_set_next:
80
            raise RuntimeError("Cannot re-create traceback !")
81
82
        current = self
83
        top_tb = None
84
        tb = None
85
        while current:
86
            f_code = current.tb_frame.f_code
87
            code = compile('\n' * (current.tb_lineno - 1) + 'raise __traceback_maker', current.tb_frame.f_code.co_filename, 'exec')
88
            if PY3:
89
                code = CodeType(
90
                    0, code.co_kwonlyargcount,
91
                    code.co_nlocals, code.co_stacksize, code.co_flags,
92
                    code.co_code, code.co_consts, code.co_names, code.co_varnames,
93
                    f_code.co_filename, f_code.co_name,
94
                    code.co_firstlineno, code.co_lnotab, (), ()
95
                )
96
            else:
97
                code = CodeType(
98
                    0,
99
                    code.co_nlocals, code.co_stacksize, code.co_flags,
100
                    code.co_code, code.co_consts, code.co_names, code.co_varnames,
101
                    f_code.co_filename.encode(), f_code.co_name.encode(),
102
                    code.co_firstlineno, code.co_lnotab, (), ()
103
                )
104
105
            # noinspection PyBroadException
106
            try:
107
                exec(code, current.tb_frame.f_globals, {})
108
            except:
109
                next_tb = sys.exc_info()[2].tb_next
110
                if top_tb is None:
111
                    top_tb = next_tb
112
                if tb is not None:
113
                    tb_set_next(tb, next_tb)
114
                tb = next_tb
115
                del next_tb
116
117
            current = current.tb_next
118
        try:
119
            return top_tb
120
        finally:
121
            del top_tb
122
            del tb
123
124
    # noinspection SpellCheckingInspection
125
    def __tproxy_handler(self, operation, *args, **kwargs):
126
        if operation in ('__getattribute__', '__getattr__'):
127
            if args[0] == 'tb_next':
128
                return self.tb_next and self.tb_next.as_traceback()
129
            else:
130
                return getattr(self, args[0])
131
        else:
132
            return getattr(self, operation)(*args, **kwargs)
133
134
    def to_dict(self):
135
        """Convert a Traceback into a dictionary representation"""
136
        if self.tb_next is None:
137
            tb_next = None
138
        else:
139
            tb_next = self.tb_next.to_dict()
140
141
        code = {
142
            'co_filename': self.tb_frame.f_code.co_filename,
143
            'co_name': self.tb_frame.f_code.co_name,
144
        }
145
        frame = {
146
            'f_globals': self.tb_frame.f_globals,
147
            'f_code': code,
148
        }
149
        return {
150
            'tb_frame': frame,
151
            'tb_lineno': self.tb_lineno,
152
            'tb_next': tb_next,
153
        }
154
155
    @classmethod
156
    def from_dict(cls, dct):
157
        if dct['tb_next']:
158
            tb_next = cls.from_dict(dct['tb_next'])
159
        else:
160
            tb_next = None
161
162
        code = _AttrDict(
163
            co_filename=dct['tb_frame']['f_code']['co_filename'],
164
            co_name=dct['tb_frame']['f_code']['co_name'],
165
        )
166
        frame = _AttrDict(
167
            f_globals=dct['tb_frame']['f_globals'],
168
            f_code=code,
169
        )
170
        tb = _AttrDict(
171
            tb_frame=frame,
172
            tb_lineno=dct['tb_lineno'],
173
            tb_next=tb_next,
174
        )
175
        return cls(tb)
176
177
    @classmethod
178
    def from_string(cls, string, strict=True):
179
        frames = []
180
        header = strict
181
182
        for line in string.splitlines():
183
            line = line.rstrip()
184
            if header:
185
                if line == 'Traceback (most recent call last):':
186
                    header = False
187
                continue
188
            frame_match = FRAME_RE.match(line)
189
            if frame_match:
190
                frames.append(frame_match.groupdict())
191
            elif line.startswith('  '):
192
                pass
193
            elif strict:
194
                break  # traceback ended
195
196
        if frames:
197
            previous = None
198
            for frame in reversed(frames):
199
                previous = _AttrDict(
200
                    frame,
201
                    tb_frame=_AttrDict(
202
                        frame,
203
                        f_globals=_AttrDict(
204
                            __file__=frame['co_filename'],
205
                            __name__='?',
206
                        ),
207
                        f_code=_AttrDict(frame),
208
                    ),
209
                    tb_next=previous,
210
                )
211
            return cls(previous)
212
        else:
213
            raise TracebackParseError("Could not find any frames in %r." % string)
214