comparison env/lib/python3.7/site-packages/jinja2/debug.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
1 import sys
2 from types import CodeType
3
4 from . import TemplateSyntaxError
5 from ._compat import PYPY
6 from .utils import internal_code
7 from .utils import missing
8
9
10 def rewrite_traceback_stack(source=None):
11 """Rewrite the current exception to replace any tracebacks from
12 within compiled template code with tracebacks that look like they
13 came from the template source.
14
15 This must be called within an ``except`` block.
16
17 :param exc_info: A :meth:`sys.exc_info` tuple. If not provided,
18 the current ``exc_info`` is used.
19 :param source: For ``TemplateSyntaxError``, the original source if
20 known.
21 :return: A :meth:`sys.exc_info` tuple that can be re-raised.
22 """
23 exc_type, exc_value, tb = sys.exc_info()
24
25 if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated:
26 exc_value.translated = True
27 exc_value.source = source
28
29 try:
30 # Remove the old traceback on Python 3, otherwise the frames
31 # from the compiler still show up.
32 exc_value.with_traceback(None)
33 except AttributeError:
34 pass
35
36 # Outside of runtime, so the frame isn't executing template
37 # code, but it still needs to point at the template.
38 tb = fake_traceback(
39 exc_value, None, exc_value.filename or "<unknown>", exc_value.lineno
40 )
41 else:
42 # Skip the frame for the render function.
43 tb = tb.tb_next
44
45 stack = []
46
47 # Build the stack of traceback object, replacing any in template
48 # code with the source file and line information.
49 while tb is not None:
50 # Skip frames decorated with @internalcode. These are internal
51 # calls that aren't useful in template debugging output.
52 if tb.tb_frame.f_code in internal_code:
53 tb = tb.tb_next
54 continue
55
56 template = tb.tb_frame.f_globals.get("__jinja_template__")
57
58 if template is not None:
59 lineno = template.get_corresponding_lineno(tb.tb_lineno)
60 fake_tb = fake_traceback(exc_value, tb, template.filename, lineno)
61 stack.append(fake_tb)
62 else:
63 stack.append(tb)
64
65 tb = tb.tb_next
66
67 tb_next = None
68
69 # Assign tb_next in reverse to avoid circular references.
70 for tb in reversed(stack):
71 tb_next = tb_set_next(tb, tb_next)
72
73 return exc_type, exc_value, tb_next
74
75
76 def fake_traceback(exc_value, tb, filename, lineno):
77 """Produce a new traceback object that looks like it came from the
78 template source instead of the compiled code. The filename, line
79 number, and location name will point to the template, and the local
80 variables will be the current template context.
81
82 :param exc_value: The original exception to be re-raised to create
83 the new traceback.
84 :param tb: The original traceback to get the local variables and
85 code info from.
86 :param filename: The template filename.
87 :param lineno: The line number in the template source.
88 """
89 if tb is not None:
90 # Replace the real locals with the context that would be
91 # available at that point in the template.
92 locals = get_template_locals(tb.tb_frame.f_locals)
93 locals.pop("__jinja_exception__", None)
94 else:
95 locals = {}
96
97 globals = {
98 "__name__": filename,
99 "__file__": filename,
100 "__jinja_exception__": exc_value,
101 }
102 # Raise an exception at the correct line number.
103 code = compile("\n" * (lineno - 1) + "raise __jinja_exception__", filename, "exec")
104
105 # Build a new code object that points to the template file and
106 # replaces the location with a block name.
107 try:
108 location = "template"
109
110 if tb is not None:
111 function = tb.tb_frame.f_code.co_name
112
113 if function == "root":
114 location = "top-level template code"
115 elif function.startswith("block_"):
116 location = 'block "%s"' % function[6:]
117
118 # Collect arguments for the new code object. CodeType only
119 # accepts positional arguments, and arguments were inserted in
120 # new Python versions.
121 code_args = []
122
123 for attr in (
124 "argcount",
125 "posonlyargcount", # Python 3.8
126 "kwonlyargcount", # Python 3
127 "nlocals",
128 "stacksize",
129 "flags",
130 "code", # codestring
131 "consts", # constants
132 "names",
133 "varnames",
134 ("filename", filename),
135 ("name", location),
136 "firstlineno",
137 "lnotab",
138 "freevars",
139 "cellvars",
140 ):
141 if isinstance(attr, tuple):
142 # Replace with given value.
143 code_args.append(attr[1])
144 continue
145
146 try:
147 # Copy original value if it exists.
148 code_args.append(getattr(code, "co_" + attr))
149 except AttributeError:
150 # Some arguments were added later.
151 continue
152
153 code = CodeType(*code_args)
154 except Exception:
155 # Some environments such as Google App Engine don't support
156 # modifying code objects.
157 pass
158
159 # Execute the new code, which is guaranteed to raise, and return
160 # the new traceback without this frame.
161 try:
162 exec(code, globals, locals)
163 except BaseException:
164 return sys.exc_info()[2].tb_next
165
166
167 def get_template_locals(real_locals):
168 """Based on the runtime locals, get the context that would be
169 available at that point in the template.
170 """
171 # Start with the current template context.
172 ctx = real_locals.get("context")
173
174 if ctx:
175 data = ctx.get_all().copy()
176 else:
177 data = {}
178
179 # Might be in a derived context that only sets local variables
180 # rather than pushing a context. Local variables follow the scheme
181 # l_depth_name. Find the highest-depth local that has a value for
182 # each name.
183 local_overrides = {}
184
185 for name, value in real_locals.items():
186 if not name.startswith("l_") or value is missing:
187 # Not a template variable, or no longer relevant.
188 continue
189
190 try:
191 _, depth, name = name.split("_", 2)
192 depth = int(depth)
193 except ValueError:
194 continue
195
196 cur_depth = local_overrides.get(name, (-1,))[0]
197
198 if cur_depth < depth:
199 local_overrides[name] = (depth, value)
200
201 # Modify the context with any derived context.
202 for name, (_, value) in local_overrides.items():
203 if value is missing:
204 data.pop(name, None)
205 else:
206 data[name] = value
207
208 return data
209
210
211 if sys.version_info >= (3, 7):
212 # tb_next is directly assignable as of Python 3.7
213 def tb_set_next(tb, tb_next):
214 tb.tb_next = tb_next
215 return tb
216
217
218 elif PYPY:
219 # PyPy might have special support, and won't work with ctypes.
220 try:
221 import tputil
222 except ImportError:
223 # Without tproxy support, use the original traceback.
224 def tb_set_next(tb, tb_next):
225 return tb
226
227 else:
228 # With tproxy support, create a proxy around the traceback that
229 # returns the new tb_next.
230 def tb_set_next(tb, tb_next):
231 def controller(op):
232 if op.opname == "__getattribute__" and op.args[0] == "tb_next":
233 return tb_next
234
235 return op.delegate()
236
237 return tputil.make_proxy(controller, obj=tb)
238
239
240 else:
241 # Use ctypes to assign tb_next at the C level since it's read-only
242 # from Python.
243 import ctypes
244
245 class _CTraceback(ctypes.Structure):
246 _fields_ = [
247 # Extra PyObject slots when compiled with Py_TRACE_REFS.
248 ("PyObject_HEAD", ctypes.c_byte * object().__sizeof__()),
249 # Only care about tb_next as an object, not a traceback.
250 ("tb_next", ctypes.py_object),
251 ]
252
253 def tb_set_next(tb, tb_next):
254 c_tb = _CTraceback.from_address(id(tb))
255
256 # Clear out the old tb_next.
257 if tb.tb_next is not None:
258 c_tb_next = ctypes.py_object(tb.tb_next)
259 c_tb.tb_next = ctypes.py_object()
260 ctypes.pythonapi.Py_DecRef(c_tb_next)
261
262 # Assign the new tb_next.
263 if tb_next is not None:
264 c_tb_next = ctypes.py_object(tb_next)
265 ctypes.pythonapi.Py_IncRef(c_tb_next)
266 c_tb.tb_next = c_tb_next
267
268 return tb