|
17 | 17 | # pylint: disable=invalid-name |
18 | 18 | """Error handling.""" |
19 | 19 | import re |
| 20 | +import types |
| 21 | +import sys |
| 22 | +import ast |
20 | 23 | from . import core |
21 | 24 |
|
22 | 25 |
|
@@ -48,11 +51,76 @@ def _parse_traceback(traceback): |
48 | 51 | return result |
49 | 52 |
|
50 | 53 |
|
| 54 | +class TracebackManager: |
| 55 | + """ |
| 56 | + Helper to manage traceback generation |
| 57 | + """ |
| 58 | + |
| 59 | + def __init__(self): |
| 60 | + self._code_cache = {} |
| 61 | + |
| 62 | + def _get_cached_code_object(self, filename, lineno, func): |
| 63 | + # Hack to create a code object that points to the correct |
| 64 | + # line number and function name |
| 65 | + key = (filename, lineno, func) |
| 66 | + # cache the code object to avoid re-creating it |
| 67 | + if key in self._code_cache: |
| 68 | + return self._code_cache[key] |
| 69 | + # Parse to AST and zero out column info |
| 70 | + # since column info are not accurate in original trace |
| 71 | + tree = ast.parse("_getframe()", filename=filename, mode="eval") |
| 72 | + for node in ast.walk(tree): |
| 73 | + if hasattr(node, "col_offset"): |
| 74 | + node.col_offset = 0 |
| 75 | + if hasattr(node, "end_col_offset"): |
| 76 | + node.end_col_offset = 0 |
| 77 | + # call into get frame, bt changes the context |
| 78 | + code_object = compile(tree, filename, "eval") |
| 79 | + # replace the function name and line number |
| 80 | + code_object = code_object.replace(co_name=func, co_firstlineno=lineno) |
| 81 | + self._code_cache[key] = code_object |
| 82 | + return code_object |
| 83 | + |
| 84 | + def _create_frame(self, filename, lineno, func): |
| 85 | + """Create a frame object from the filename, lineno, and func""" |
| 86 | + code_object = self._get_cached_code_object(filename, lineno, func) |
| 87 | + # call into get frame, but changes the context so the code |
| 88 | + # points to the correct frame |
| 89 | + context = {"_getframe": sys._getframe} |
| 90 | + # pylint: disable=eval-used |
| 91 | + return eval(code_object, context, context) |
| 92 | + |
| 93 | + def append_traceback(self, tb, filename, lineno, func): |
| 94 | + """Append a traceback to the given traceback |
| 95 | +
|
| 96 | + Parameters |
| 97 | + ---------- |
| 98 | + tb : types.TracebackType |
| 99 | + The traceback to append to. |
| 100 | + filename : str |
| 101 | + The filename of the traceback |
| 102 | + lineno : int |
| 103 | + The line number of the traceback |
| 104 | + func : str |
| 105 | + The function name of the traceback |
| 106 | +
|
| 107 | + Returns |
| 108 | + ------- |
| 109 | + new_tb : types.TracebackType |
| 110 | + The new traceback with the appended frame. |
| 111 | + """ |
| 112 | + frame = self._create_frame(filename, lineno, func) |
| 113 | + return types.TracebackType(tb, frame, frame.f_lasti, lineno) |
| 114 | + |
| 115 | + |
| 116 | +_TRACEBACK_MANAGER = TracebackManager() |
| 117 | + |
| 118 | + |
51 | 119 | def _with_append_traceback(py_error, traceback): |
52 | 120 | """Append the traceback to the py_error and return it""" |
53 | 121 | tb = py_error.__traceback__ |
54 | 122 | for filename, lineno, func in reversed(_parse_traceback(traceback)): |
55 | | - tb = core._append_traceback_frame(tb, filename, lineno, func) |
| 123 | + tb = _TRACEBACK_MANAGER.append_traceback(tb, filename, lineno, func) |
56 | 124 | return py_error.with_traceback(tb) |
57 | 125 |
|
58 | 126 |
|
|
0 commit comments