Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/jinja2/debug.py @ 1:56ad4e20f292 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
| author | guerler |
|---|---|
| date | Fri, 31 Jul 2020 00:32:28 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:d30785e31577 | 1:56ad4e20f292 |
|---|---|
| 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 |
