WIP
This commit is contained in:
		
							
								
								
									
										259
									
								
								myvenv/lib/python3.10/site-packages/jinja2/debug.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										259
									
								
								myvenv/lib/python3.10/site-packages/jinja2/debug.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,259 @@
 | 
			
		||||
import platform
 | 
			
		||||
import sys
 | 
			
		||||
import typing as t
 | 
			
		||||
from types import CodeType
 | 
			
		||||
from types import TracebackType
 | 
			
		||||
 | 
			
		||||
from .exceptions import TemplateSyntaxError
 | 
			
		||||
from .utils import internal_code
 | 
			
		||||
from .utils import missing
 | 
			
		||||
 | 
			
		||||
if t.TYPE_CHECKING:
 | 
			
		||||
    from .runtime import Context
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def rewrite_traceback_stack(source: t.Optional[str] = None) -> BaseException:
 | 
			
		||||
    """Rewrite the current exception to replace any tracebacks from
 | 
			
		||||
    within compiled template code with tracebacks that look like they
 | 
			
		||||
    came from the template source.
 | 
			
		||||
 | 
			
		||||
    This must be called within an ``except`` block.
 | 
			
		||||
 | 
			
		||||
    :param source: For ``TemplateSyntaxError``, the original source if
 | 
			
		||||
        known.
 | 
			
		||||
    :return: The original exception with the rewritten traceback.
 | 
			
		||||
    """
 | 
			
		||||
    _, exc_value, tb = sys.exc_info()
 | 
			
		||||
    exc_value = t.cast(BaseException, exc_value)
 | 
			
		||||
    tb = t.cast(TracebackType, tb)
 | 
			
		||||
 | 
			
		||||
    if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated:
 | 
			
		||||
        exc_value.translated = True
 | 
			
		||||
        exc_value.source = source
 | 
			
		||||
        # Remove the old traceback, otherwise the frames from the
 | 
			
		||||
        # compiler still show up.
 | 
			
		||||
        exc_value.with_traceback(None)
 | 
			
		||||
        # Outside of runtime, so the frame isn't executing template
 | 
			
		||||
        # code, but it still needs to point at the template.
 | 
			
		||||
        tb = fake_traceback(
 | 
			
		||||
            exc_value, None, exc_value.filename or "<unknown>", exc_value.lineno
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        # Skip the frame for the render function.
 | 
			
		||||
        tb = tb.tb_next
 | 
			
		||||
 | 
			
		||||
    stack = []
 | 
			
		||||
 | 
			
		||||
    # Build the stack of traceback object, replacing any in template
 | 
			
		||||
    # code with the source file and line information.
 | 
			
		||||
    while tb is not None:
 | 
			
		||||
        # Skip frames decorated with @internalcode. These are internal
 | 
			
		||||
        # calls that aren't useful in template debugging output.
 | 
			
		||||
        if tb.tb_frame.f_code in internal_code:
 | 
			
		||||
            tb = tb.tb_next
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        template = tb.tb_frame.f_globals.get("__jinja_template__")
 | 
			
		||||
 | 
			
		||||
        if template is not None:
 | 
			
		||||
            lineno = template.get_corresponding_lineno(tb.tb_lineno)
 | 
			
		||||
            fake_tb = fake_traceback(exc_value, tb, template.filename, lineno)
 | 
			
		||||
            stack.append(fake_tb)
 | 
			
		||||
        else:
 | 
			
		||||
            stack.append(tb)
 | 
			
		||||
 | 
			
		||||
        tb = tb.tb_next
 | 
			
		||||
 | 
			
		||||
    tb_next = None
 | 
			
		||||
 | 
			
		||||
    # Assign tb_next in reverse to avoid circular references.
 | 
			
		||||
    for tb in reversed(stack):
 | 
			
		||||
        tb_next = tb_set_next(tb, tb_next)
 | 
			
		||||
 | 
			
		||||
    return exc_value.with_traceback(tb_next)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fake_traceback(  # type: ignore
 | 
			
		||||
    exc_value: BaseException, tb: t.Optional[TracebackType], filename: str, lineno: int
 | 
			
		||||
) -> TracebackType:
 | 
			
		||||
    """Produce a new traceback object that looks like it came from the
 | 
			
		||||
    template source instead of the compiled code. The filename, line
 | 
			
		||||
    number, and location name will point to the template, and the local
 | 
			
		||||
    variables will be the current template context.
 | 
			
		||||
 | 
			
		||||
    :param exc_value: The original exception to be re-raised to create
 | 
			
		||||
        the new traceback.
 | 
			
		||||
    :param tb: The original traceback to get the local variables and
 | 
			
		||||
        code info from.
 | 
			
		||||
    :param filename: The template filename.
 | 
			
		||||
    :param lineno: The line number in the template source.
 | 
			
		||||
    """
 | 
			
		||||
    if tb is not None:
 | 
			
		||||
        # Replace the real locals with the context that would be
 | 
			
		||||
        # available at that point in the template.
 | 
			
		||||
        locals = get_template_locals(tb.tb_frame.f_locals)
 | 
			
		||||
        locals.pop("__jinja_exception__", None)
 | 
			
		||||
    else:
 | 
			
		||||
        locals = {}
 | 
			
		||||
 | 
			
		||||
    globals = {
 | 
			
		||||
        "__name__": filename,
 | 
			
		||||
        "__file__": filename,
 | 
			
		||||
        "__jinja_exception__": exc_value,
 | 
			
		||||
    }
 | 
			
		||||
    # Raise an exception at the correct line number.
 | 
			
		||||
    code: CodeType = compile(
 | 
			
		||||
        "\n" * (lineno - 1) + "raise __jinja_exception__", filename, "exec"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # Build a new code object that points to the template file and
 | 
			
		||||
    # replaces the location with a block name.
 | 
			
		||||
    location = "template"
 | 
			
		||||
 | 
			
		||||
    if tb is not None:
 | 
			
		||||
        function = tb.tb_frame.f_code.co_name
 | 
			
		||||
 | 
			
		||||
        if function == "root":
 | 
			
		||||
            location = "top-level template code"
 | 
			
		||||
        elif function.startswith("block_"):
 | 
			
		||||
            location = f"block {function[6:]!r}"
 | 
			
		||||
 | 
			
		||||
    if sys.version_info >= (3, 8):
 | 
			
		||||
        code = code.replace(co_name=location)
 | 
			
		||||
    else:
 | 
			
		||||
        code = CodeType(
 | 
			
		||||
            code.co_argcount,
 | 
			
		||||
            code.co_kwonlyargcount,
 | 
			
		||||
            code.co_nlocals,
 | 
			
		||||
            code.co_stacksize,
 | 
			
		||||
            code.co_flags,
 | 
			
		||||
            code.co_code,
 | 
			
		||||
            code.co_consts,
 | 
			
		||||
            code.co_names,
 | 
			
		||||
            code.co_varnames,
 | 
			
		||||
            code.co_filename,
 | 
			
		||||
            location,
 | 
			
		||||
            code.co_firstlineno,
 | 
			
		||||
            code.co_lnotab,
 | 
			
		||||
            code.co_freevars,
 | 
			
		||||
            code.co_cellvars,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    # Execute the new code, which is guaranteed to raise, and return
 | 
			
		||||
    # the new traceback without this frame.
 | 
			
		||||
    try:
 | 
			
		||||
        exec(code, globals, locals)
 | 
			
		||||
    except BaseException:
 | 
			
		||||
        return sys.exc_info()[2].tb_next  # type: ignore
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.Dict[str, t.Any]:
 | 
			
		||||
    """Based on the runtime locals, get the context that would be
 | 
			
		||||
    available at that point in the template.
 | 
			
		||||
    """
 | 
			
		||||
    # Start with the current template context.
 | 
			
		||||
    ctx: "t.Optional[Context]" = real_locals.get("context")
 | 
			
		||||
 | 
			
		||||
    if ctx is not None:
 | 
			
		||||
        data: t.Dict[str, t.Any] = ctx.get_all().copy()
 | 
			
		||||
    else:
 | 
			
		||||
        data = {}
 | 
			
		||||
 | 
			
		||||
    # Might be in a derived context that only sets local variables
 | 
			
		||||
    # rather than pushing a context. Local variables follow the scheme
 | 
			
		||||
    # l_depth_name. Find the highest-depth local that has a value for
 | 
			
		||||
    # each name.
 | 
			
		||||
    local_overrides: t.Dict[str, t.Tuple[int, t.Any]] = {}
 | 
			
		||||
 | 
			
		||||
    for name, value in real_locals.items():
 | 
			
		||||
        if not name.startswith("l_") or value is missing:
 | 
			
		||||
            # Not a template variable, or no longer relevant.
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            _, depth_str, name = name.split("_", 2)
 | 
			
		||||
            depth = int(depth_str)
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        cur_depth = local_overrides.get(name, (-1,))[0]
 | 
			
		||||
 | 
			
		||||
        if cur_depth < depth:
 | 
			
		||||
            local_overrides[name] = (depth, value)
 | 
			
		||||
 | 
			
		||||
    # Modify the context with any derived context.
 | 
			
		||||
    for name, (_, value) in local_overrides.items():
 | 
			
		||||
        if value is missing:
 | 
			
		||||
            data.pop(name, None)
 | 
			
		||||
        else:
 | 
			
		||||
            data[name] = value
 | 
			
		||||
 | 
			
		||||
    return data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if sys.version_info >= (3, 7):
 | 
			
		||||
    # tb_next is directly assignable as of Python 3.7
 | 
			
		||||
    def tb_set_next(
 | 
			
		||||
        tb: TracebackType, tb_next: t.Optional[TracebackType]
 | 
			
		||||
    ) -> TracebackType:
 | 
			
		||||
        tb.tb_next = tb_next
 | 
			
		||||
        return tb
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
elif platform.python_implementation() == "PyPy":
 | 
			
		||||
    # PyPy might have special support, and won't work with ctypes.
 | 
			
		||||
    try:
 | 
			
		||||
        import tputil  # type: ignore
 | 
			
		||||
    except ImportError:
 | 
			
		||||
        # Without tproxy support, use the original traceback.
 | 
			
		||||
        def tb_set_next(
 | 
			
		||||
            tb: TracebackType, tb_next: t.Optional[TracebackType]
 | 
			
		||||
        ) -> TracebackType:
 | 
			
		||||
            return tb
 | 
			
		||||
 | 
			
		||||
    else:
 | 
			
		||||
        # With tproxy support, create a proxy around the traceback that
 | 
			
		||||
        # returns the new tb_next.
 | 
			
		||||
        def tb_set_next(
 | 
			
		||||
            tb: TracebackType, tb_next: t.Optional[TracebackType]
 | 
			
		||||
        ) -> TracebackType:
 | 
			
		||||
            def controller(op):  # type: ignore
 | 
			
		||||
                if op.opname == "__getattribute__" and op.args[0] == "tb_next":
 | 
			
		||||
                    return tb_next
 | 
			
		||||
 | 
			
		||||
                return op.delegate()
 | 
			
		||||
 | 
			
		||||
            return tputil.make_proxy(controller, obj=tb)  # type: ignore
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
else:
 | 
			
		||||
    # Use ctypes to assign tb_next at the C level since it's read-only
 | 
			
		||||
    # from Python.
 | 
			
		||||
    import ctypes
 | 
			
		||||
 | 
			
		||||
    class _CTraceback(ctypes.Structure):
 | 
			
		||||
        _fields_ = [
 | 
			
		||||
            # Extra PyObject slots when compiled with Py_TRACE_REFS.
 | 
			
		||||
            ("PyObject_HEAD", ctypes.c_byte * object().__sizeof__()),
 | 
			
		||||
            # Only care about tb_next as an object, not a traceback.
 | 
			
		||||
            ("tb_next", ctypes.py_object),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def tb_set_next(
 | 
			
		||||
        tb: TracebackType, tb_next: t.Optional[TracebackType]
 | 
			
		||||
    ) -> TracebackType:
 | 
			
		||||
        c_tb = _CTraceback.from_address(id(tb))
 | 
			
		||||
 | 
			
		||||
        # Clear out the old tb_next.
 | 
			
		||||
        if tb.tb_next is not None:
 | 
			
		||||
            c_tb_next = ctypes.py_object(tb.tb_next)
 | 
			
		||||
            c_tb.tb_next = ctypes.py_object()
 | 
			
		||||
            ctypes.pythonapi.Py_DecRef(c_tb_next)
 | 
			
		||||
 | 
			
		||||
        # Assign the new tb_next.
 | 
			
		||||
        if tb_next is not None:
 | 
			
		||||
            c_tb_next = ctypes.py_object(tb_next)
 | 
			
		||||
            ctypes.pythonapi.Py_IncRef(c_tb_next)
 | 
			
		||||
            c_tb.tb_next = c_tb_next
 | 
			
		||||
 | 
			
		||||
        return tb
 | 
			
		||||
		Reference in New Issue
	
	Block a user