Spaces:
Running
Running
"""The runtime functions and state used by compiled templates.""" | |
import functools | |
import sys | |
import typing as t | |
from collections import abc | |
from itertools import chain | |
from markupsafe import escape # noqa: F401 | |
from markupsafe import Markup | |
from markupsafe import soft_str | |
from .async_utils import auto_aiter | |
from .async_utils import auto_await # noqa: F401 | |
from .exceptions import TemplateNotFound # noqa: F401 | |
from .exceptions import TemplateRuntimeError # noqa: F401 | |
from .exceptions import UndefinedError | |
from .nodes import EvalContext | |
from .utils import _PassArg | |
from .utils import concat | |
from .utils import internalcode | |
from .utils import missing | |
from .utils import Namespace # noqa: F401 | |
from .utils import object_type_repr | |
from .utils import pass_eval_context | |
V = t.TypeVar("V") | |
F = t.TypeVar("F", bound=t.Callable[..., t.Any]) | |
if t.TYPE_CHECKING: | |
import logging | |
import typing_extensions as te | |
from .environment import Environment | |
class LoopRenderFunc(te.Protocol): | |
def __call__( | |
self, | |
reciter: t.Iterable[V], | |
loop_render_func: "LoopRenderFunc", | |
depth: int = 0, | |
) -> str: ... | |
# these variables are exported to the template runtime | |
exported = [ | |
"LoopContext", | |
"TemplateReference", | |
"Macro", | |
"Markup", | |
"TemplateRuntimeError", | |
"missing", | |
"escape", | |
"markup_join", | |
"str_join", | |
"identity", | |
"TemplateNotFound", | |
"Namespace", | |
"Undefined", | |
"internalcode", | |
] | |
async_exported = [ | |
"AsyncLoopContext", | |
"auto_aiter", | |
"auto_await", | |
] | |
def identity(x: V) -> V: | |
"""Returns its argument. Useful for certain things in the | |
environment. | |
""" | |
return x | |
def markup_join(seq: t.Iterable[t.Any]) -> str: | |
"""Concatenation that escapes if necessary and converts to string.""" | |
buf = [] | |
iterator = map(soft_str, seq) | |
for arg in iterator: | |
buf.append(arg) | |
if hasattr(arg, "__html__"): | |
return Markup("").join(chain(buf, iterator)) | |
return concat(buf) | |
def str_join(seq: t.Iterable[t.Any]) -> str: | |
"""Simple args to string conversion and concatenation.""" | |
return concat(map(str, seq)) | |
def new_context( | |
environment: "Environment", | |
template_name: t.Optional[str], | |
blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]], | |
vars: t.Optional[t.Dict[str, t.Any]] = None, | |
shared: bool = False, | |
globals: t.Optional[t.MutableMapping[str, t.Any]] = None, | |
locals: t.Optional[t.Mapping[str, t.Any]] = None, | |
) -> "Context": | |
"""Internal helper for context creation.""" | |
if vars is None: | |
vars = {} | |
if shared: | |
parent = vars | |
else: | |
parent = dict(globals or (), **vars) | |
if locals: | |
# if the parent is shared a copy should be created because | |
# we don't want to modify the dict passed | |
if shared: | |
parent = dict(parent) | |
for key, value in locals.items(): | |
if value is not missing: | |
parent[key] = value | |
return environment.context_class( | |
environment, parent, template_name, blocks, globals=globals | |
) | |
class TemplateReference: | |
"""The `self` in templates.""" | |
def __init__(self, context: "Context") -> None: | |
self.__context = context | |
def __getitem__(self, name: str) -> t.Any: | |
blocks = self.__context.blocks[name] | |
return BlockReference(name, self.__context, blocks, 0) | |
def __repr__(self) -> str: | |
return f"<{type(self).__name__} {self.__context.name!r}>" | |
def _dict_method_all(dict_method: F) -> F: | |
def f_all(self: "Context") -> t.Any: | |
return dict_method(self.get_all()) | |
return t.cast(F, f_all) | |
class Context: | |
"""The template context holds the variables of a template. It stores the | |
values passed to the template and also the names the template exports. | |
Creating instances is neither supported nor useful as it's created | |
automatically at various stages of the template evaluation and should not | |
be created by hand. | |
The context is immutable. Modifications on :attr:`parent` **must not** | |
happen and modifications on :attr:`vars` are allowed from generated | |
template code only. Template filters and global functions marked as | |
:func:`pass_context` get the active context passed as first argument | |
and are allowed to access the context read-only. | |
The template context supports read only dict operations (`get`, | |
`keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`, | |
`__getitem__`, `__contains__`). Additionally there is a :meth:`resolve` | |
method that doesn't fail with a `KeyError` but returns an | |
:class:`Undefined` object for missing variables. | |
""" | |
def __init__( | |
self, | |
environment: "Environment", | |
parent: t.Dict[str, t.Any], | |
name: t.Optional[str], | |
blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]], | |
globals: t.Optional[t.MutableMapping[str, t.Any]] = None, | |
): | |
self.parent = parent | |
self.vars: t.Dict[str, t.Any] = {} | |
self.environment: "Environment" = environment | |
self.eval_ctx = EvalContext(self.environment, name) | |
self.exported_vars: t.Set[str] = set() | |
self.name = name | |
self.globals_keys = set() if globals is None else set(globals) | |
# create the initial mapping of blocks. Whenever template inheritance | |
# takes place the runtime will update this mapping with the new blocks | |
# from the template. | |
self.blocks = {k: [v] for k, v in blocks.items()} | |
def super( | |
self, name: str, current: t.Callable[["Context"], t.Iterator[str]] | |
) -> t.Union["BlockReference", "Undefined"]: | |
"""Render a parent block.""" | |
try: | |
blocks = self.blocks[name] | |
index = blocks.index(current) + 1 | |
blocks[index] | |
except LookupError: | |
return self.environment.undefined( | |
f"there is no parent block called {name!r}.", name="super" | |
) | |
return BlockReference(name, self, blocks, index) | |
def get(self, key: str, default: t.Any = None) -> t.Any: | |
"""Look up a variable by name, or return a default if the key is | |
not found. | |
:param key: The variable name to look up. | |
:param default: The value to return if the key is not found. | |
""" | |
try: | |
return self[key] | |
except KeyError: | |
return default | |
def resolve(self, key: str) -> t.Union[t.Any, "Undefined"]: | |
"""Look up a variable by name, or return an :class:`Undefined` | |
object if the key is not found. | |
If you need to add custom behavior, override | |
:meth:`resolve_or_missing`, not this method. The various lookup | |
functions use that method, not this one. | |
:param key: The variable name to look up. | |
""" | |
rv = self.resolve_or_missing(key) | |
if rv is missing: | |
return self.environment.undefined(name=key) | |
return rv | |
def resolve_or_missing(self, key: str) -> t.Any: | |
"""Look up a variable by name, or return a ``missing`` sentinel | |
if the key is not found. | |
Override this method to add custom lookup behavior. | |
:meth:`resolve`, :meth:`get`, and :meth:`__getitem__` use this | |
method. Don't call this method directly. | |
:param key: The variable name to look up. | |
""" | |
if key in self.vars: | |
return self.vars[key] | |
if key in self.parent: | |
return self.parent[key] | |
return missing | |
def get_exported(self) -> t.Dict[str, t.Any]: | |
"""Get a new dict with the exported variables.""" | |
return {k: self.vars[k] for k in self.exported_vars} | |
def get_all(self) -> t.Dict[str, t.Any]: | |
"""Return the complete context as dict including the exported | |
variables. For optimizations reasons this might not return an | |
actual copy so be careful with using it. | |
""" | |
if not self.vars: | |
return self.parent | |
if not self.parent: | |
return self.vars | |
return dict(self.parent, **self.vars) | |
def call( | |
__self, | |
__obj: t.Callable[..., t.Any], | |
*args: t.Any, | |
**kwargs: t.Any, # noqa: B902 | |
) -> t.Union[t.Any, "Undefined"]: | |
"""Call the callable with the arguments and keyword arguments | |
provided but inject the active context or environment as first | |
argument if the callable has :func:`pass_context` or | |
:func:`pass_environment`. | |
""" | |
if __debug__: | |
__traceback_hide__ = True # noqa | |
# Allow callable classes to take a context | |
if ( | |
hasattr(__obj, "__call__") # noqa: B004 | |
and _PassArg.from_obj(__obj.__call__) is not None | |
): | |
__obj = __obj.__call__ | |
pass_arg = _PassArg.from_obj(__obj) | |
if pass_arg is _PassArg.context: | |
# the active context should have access to variables set in | |
# loops and blocks without mutating the context itself | |
if kwargs.get("_loop_vars"): | |
__self = __self.derived(kwargs["_loop_vars"]) | |
if kwargs.get("_block_vars"): | |
__self = __self.derived(kwargs["_block_vars"]) | |
args = (__self,) + args | |
elif pass_arg is _PassArg.eval_context: | |
args = (__self.eval_ctx,) + args | |
elif pass_arg is _PassArg.environment: | |
args = (__self.environment,) + args | |
kwargs.pop("_block_vars", None) | |
kwargs.pop("_loop_vars", None) | |
try: | |
return __obj(*args, **kwargs) | |
except StopIteration: | |
return __self.environment.undefined( | |
"value was undefined because a callable raised a" | |
" StopIteration exception" | |
) | |
def derived(self, locals: t.Optional[t.Dict[str, t.Any]] = None) -> "Context": | |
"""Internal helper function to create a derived context. This is | |
used in situations where the system needs a new context in the same | |
template that is independent. | |
""" | |
context = new_context( | |
self.environment, self.name, {}, self.get_all(), True, None, locals | |
) | |
context.eval_ctx = self.eval_ctx | |
context.blocks.update((k, list(v)) for k, v in self.blocks.items()) | |
return context | |
keys = _dict_method_all(dict.keys) | |
values = _dict_method_all(dict.values) | |
items = _dict_method_all(dict.items) | |
def __contains__(self, name: str) -> bool: | |
return name in self.vars or name in self.parent | |
def __getitem__(self, key: str) -> t.Any: | |
"""Look up a variable by name with ``[]`` syntax, or raise a | |
``KeyError`` if the key is not found. | |
""" | |
item = self.resolve_or_missing(key) | |
if item is missing: | |
raise KeyError(key) | |
return item | |
def __repr__(self) -> str: | |
return f"<{type(self).__name__} {self.get_all()!r} of {self.name!r}>" | |
class BlockReference: | |
"""One block on a template reference.""" | |
def __init__( | |
self, | |
name: str, | |
context: "Context", | |
stack: t.List[t.Callable[["Context"], t.Iterator[str]]], | |
depth: int, | |
) -> None: | |
self.name = name | |
self._context = context | |
self._stack = stack | |
self._depth = depth | |
def super(self) -> t.Union["BlockReference", "Undefined"]: | |
"""Super the block.""" | |
if self._depth + 1 >= len(self._stack): | |
return self._context.environment.undefined( | |
f"there is no parent block called {self.name!r}.", name="super" | |
) | |
return BlockReference(self.name, self._context, self._stack, self._depth + 1) | |
async def _async_call(self) -> str: | |
rv = concat( | |
[x async for x in self._stack[self._depth](self._context)] # type: ignore | |
) | |
if self._context.eval_ctx.autoescape: | |
return Markup(rv) | |
return rv | |
def __call__(self) -> str: | |
if self._context.environment.is_async: | |
return self._async_call() # type: ignore | |
rv = concat(self._stack[self._depth](self._context)) | |
if self._context.eval_ctx.autoescape: | |
return Markup(rv) | |
return rv | |
class LoopContext: | |
"""A wrapper iterable for dynamic ``for`` loops, with information | |
about the loop and iteration. | |
""" | |
#: Current iteration of the loop, starting at 0. | |
index0 = -1 | |
_length: t.Optional[int] = None | |
_after: t.Any = missing | |
_current: t.Any = missing | |
_before: t.Any = missing | |
_last_changed_value: t.Any = missing | |
def __init__( | |
self, | |
iterable: t.Iterable[V], | |
undefined: t.Type["Undefined"], | |
recurse: t.Optional["LoopRenderFunc"] = None, | |
depth0: int = 0, | |
) -> None: | |
""" | |
:param iterable: Iterable to wrap. | |
:param undefined: :class:`Undefined` class to use for next and | |
previous items. | |
:param recurse: The function to render the loop body when the | |
loop is marked recursive. | |
:param depth0: Incremented when looping recursively. | |
""" | |
self._iterable = iterable | |
self._iterator = self._to_iterator(iterable) | |
self._undefined = undefined | |
self._recurse = recurse | |
#: How many levels deep a recursive loop currently is, starting at 0. | |
self.depth0 = depth0 | |
def _to_iterator(iterable: t.Iterable[V]) -> t.Iterator[V]: | |
return iter(iterable) | |
def length(self) -> int: | |
"""Length of the iterable. | |
If the iterable is a generator or otherwise does not have a | |
size, it is eagerly evaluated to get a size. | |
""" | |
if self._length is not None: | |
return self._length | |
try: | |
self._length = len(self._iterable) # type: ignore | |
except TypeError: | |
iterable = list(self._iterator) | |
self._iterator = self._to_iterator(iterable) | |
self._length = len(iterable) + self.index + (self._after is not missing) | |
return self._length | |
def __len__(self) -> int: | |
return self.length | |
def depth(self) -> int: | |
"""How many levels deep a recursive loop currently is, starting at 1.""" | |
return self.depth0 + 1 | |
def index(self) -> int: | |
"""Current iteration of the loop, starting at 1.""" | |
return self.index0 + 1 | |
def revindex0(self) -> int: | |
"""Number of iterations from the end of the loop, ending at 0. | |
Requires calculating :attr:`length`. | |
""" | |
return self.length - self.index | |
def revindex(self) -> int: | |
"""Number of iterations from the end of the loop, ending at 1. | |
Requires calculating :attr:`length`. | |
""" | |
return self.length - self.index0 | |
def first(self) -> bool: | |
"""Whether this is the first iteration of the loop.""" | |
return self.index0 == 0 | |
def _peek_next(self) -> t.Any: | |
"""Return the next element in the iterable, or :data:`missing` | |
if the iterable is exhausted. Only peeks one item ahead, caching | |
the result in :attr:`_last` for use in subsequent checks. The | |
cache is reset when :meth:`__next__` is called. | |
""" | |
if self._after is not missing: | |
return self._after | |
self._after = next(self._iterator, missing) | |
return self._after | |
def last(self) -> bool: | |
"""Whether this is the last iteration of the loop. | |
Causes the iterable to advance early. See | |
:func:`itertools.groupby` for issues this can cause. | |
The :func:`groupby` filter avoids that issue. | |
""" | |
return self._peek_next() is missing | |
def previtem(self) -> t.Union[t.Any, "Undefined"]: | |
"""The item in the previous iteration. Undefined during the | |
first iteration. | |
""" | |
if self.first: | |
return self._undefined("there is no previous item") | |
return self._before | |
def nextitem(self) -> t.Union[t.Any, "Undefined"]: | |
"""The item in the next iteration. Undefined during the last | |
iteration. | |
Causes the iterable to advance early. See | |
:func:`itertools.groupby` for issues this can cause. | |
The :func:`jinja-filters.groupby` filter avoids that issue. | |
""" | |
rv = self._peek_next() | |
if rv is missing: | |
return self._undefined("there is no next item") | |
return rv | |
def cycle(self, *args: V) -> V: | |
"""Return a value from the given args, cycling through based on | |
the current :attr:`index0`. | |
:param args: One or more values to cycle through. | |
""" | |
if not args: | |
raise TypeError("no items for cycling given") | |
return args[self.index0 % len(args)] | |
def changed(self, *value: t.Any) -> bool: | |
"""Return ``True`` if previously called with a different value | |
(including when called for the first time). | |
:param value: One or more values to compare to the last call. | |
""" | |
if self._last_changed_value != value: | |
self._last_changed_value = value | |
return True | |
return False | |
def __iter__(self) -> "LoopContext": | |
return self | |
def __next__(self) -> t.Tuple[t.Any, "LoopContext"]: | |
if self._after is not missing: | |
rv = self._after | |
self._after = missing | |
else: | |
rv = next(self._iterator) | |
self.index0 += 1 | |
self._before = self._current | |
self._current = rv | |
return rv, self | |
def __call__(self, iterable: t.Iterable[V]) -> str: | |
"""When iterating over nested data, render the body of the loop | |
recursively with the given inner iterable data. | |
The loop must have the ``recursive`` marker for this to work. | |
""" | |
if self._recurse is None: | |
raise TypeError( | |
"The loop must have the 'recursive' marker to be called recursively." | |
) | |
return self._recurse(iterable, self._recurse, depth=self.depth) | |
def __repr__(self) -> str: | |
return f"<{type(self).__name__} {self.index}/{self.length}>" | |
class AsyncLoopContext(LoopContext): | |
_iterator: t.AsyncIterator[t.Any] # type: ignore | |
def _to_iterator( # type: ignore | |
iterable: t.Union[t.Iterable[V], t.AsyncIterable[V]], | |
) -> t.AsyncIterator[V]: | |
return auto_aiter(iterable) | |
async def length(self) -> int: # type: ignore | |
if self._length is not None: | |
return self._length | |
try: | |
self._length = len(self._iterable) # type: ignore | |
except TypeError: | |
iterable = [x async for x in self._iterator] | |
self._iterator = self._to_iterator(iterable) | |
self._length = len(iterable) + self.index + (self._after is not missing) | |
return self._length | |
async def revindex0(self) -> int: # type: ignore | |
return await self.length - self.index | |
async def revindex(self) -> int: # type: ignore | |
return await self.length - self.index0 | |
async def _peek_next(self) -> t.Any: | |
if self._after is not missing: | |
return self._after | |
try: | |
self._after = await self._iterator.__anext__() | |
except StopAsyncIteration: | |
self._after = missing | |
return self._after | |
async def last(self) -> bool: # type: ignore | |
return await self._peek_next() is missing | |
async def nextitem(self) -> t.Union[t.Any, "Undefined"]: | |
rv = await self._peek_next() | |
if rv is missing: | |
return self._undefined("there is no next item") | |
return rv | |
def __aiter__(self) -> "AsyncLoopContext": | |
return self | |
async def __anext__(self) -> t.Tuple[t.Any, "AsyncLoopContext"]: | |
if self._after is not missing: | |
rv = self._after | |
self._after = missing | |
else: | |
rv = await self._iterator.__anext__() | |
self.index0 += 1 | |
self._before = self._current | |
self._current = rv | |
return rv, self | |
class Macro: | |
"""Wraps a macro function.""" | |
def __init__( | |
self, | |
environment: "Environment", | |
func: t.Callable[..., str], | |
name: str, | |
arguments: t.List[str], | |
catch_kwargs: bool, | |
catch_varargs: bool, | |
caller: bool, | |
default_autoescape: t.Optional[bool] = None, | |
): | |
self._environment = environment | |
self._func = func | |
self._argument_count = len(arguments) | |
self.name = name | |
self.arguments = arguments | |
self.catch_kwargs = catch_kwargs | |
self.catch_varargs = catch_varargs | |
self.caller = caller | |
self.explicit_caller = "caller" in arguments | |
if default_autoescape is None: | |
if callable(environment.autoescape): | |
default_autoescape = environment.autoescape(None) | |
else: | |
default_autoescape = environment.autoescape | |
self._default_autoescape = default_autoescape | |
def __call__(self, *args: t.Any, **kwargs: t.Any) -> str: | |
# This requires a bit of explanation, In the past we used to | |
# decide largely based on compile-time information if a macro is | |
# safe or unsafe. While there was a volatile mode it was largely | |
# unused for deciding on escaping. This turns out to be | |
# problematic for macros because whether a macro is safe depends not | |
# on the escape mode when it was defined, but rather when it was used. | |
# | |
# Because however we export macros from the module system and | |
# there are historic callers that do not pass an eval context (and | |
# will continue to not pass one), we need to perform an instance | |
# check here. | |
# | |
# This is considered safe because an eval context is not a valid | |
# argument to callables otherwise anyway. Worst case here is | |
# that if no eval context is passed we fall back to the compile | |
# time autoescape flag. | |
if args and isinstance(args[0], EvalContext): | |
autoescape = args[0].autoescape | |
args = args[1:] | |
else: | |
autoescape = self._default_autoescape | |
# try to consume the positional arguments | |
arguments = list(args[: self._argument_count]) | |
off = len(arguments) | |
# For information why this is necessary refer to the handling | |
# of caller in the `macro_body` handler in the compiler. | |
found_caller = False | |
# if the number of arguments consumed is not the number of | |
# arguments expected we start filling in keyword arguments | |
# and defaults. | |
if off != self._argument_count: | |
for name in self.arguments[len(arguments) :]: | |
try: | |
value = kwargs.pop(name) | |
except KeyError: | |
value = missing | |
if name == "caller": | |
found_caller = True | |
arguments.append(value) | |
else: | |
found_caller = self.explicit_caller | |
# it's important that the order of these arguments does not change | |
# if not also changed in the compiler's `function_scoping` method. | |
# the order is caller, keyword arguments, positional arguments! | |
if self.caller and not found_caller: | |
caller = kwargs.pop("caller", None) | |
if caller is None: | |
caller = self._environment.undefined("No caller defined", name="caller") | |
arguments.append(caller) | |
if self.catch_kwargs: | |
arguments.append(kwargs) | |
elif kwargs: | |
if "caller" in kwargs: | |
raise TypeError( | |
f"macro {self.name!r} was invoked with two values for the special" | |
" caller argument. This is most likely a bug." | |
) | |
raise TypeError( | |
f"macro {self.name!r} takes no keyword argument {next(iter(kwargs))!r}" | |
) | |
if self.catch_varargs: | |
arguments.append(args[self._argument_count :]) | |
elif len(args) > self._argument_count: | |
raise TypeError( | |
f"macro {self.name!r} takes not more than" | |
f" {len(self.arguments)} argument(s)" | |
) | |
return self._invoke(arguments, autoescape) | |
async def _async_invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str: | |
rv = await self._func(*arguments) # type: ignore | |
if autoescape: | |
return Markup(rv) | |
return rv # type: ignore | |
def _invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str: | |
if self._environment.is_async: | |
return self._async_invoke(arguments, autoescape) # type: ignore | |
rv = self._func(*arguments) | |
if autoescape: | |
rv = Markup(rv) | |
return rv | |
def __repr__(self) -> str: | |
name = "anonymous" if self.name is None else repr(self.name) | |
return f"<{type(self).__name__} {name}>" | |
class Undefined: | |
"""The default undefined type. This undefined type can be printed and | |
iterated over, but every other access will raise an :exc:`UndefinedError`: | |
>>> foo = Undefined(name='foo') | |
>>> str(foo) | |
'' | |
>>> not foo | |
True | |
>>> foo + 42 | |
Traceback (most recent call last): | |
... | |
jinja2.exceptions.UndefinedError: 'foo' is undefined | |
""" | |
__slots__ = ( | |
"_undefined_hint", | |
"_undefined_obj", | |
"_undefined_name", | |
"_undefined_exception", | |
) | |
def __init__( | |
self, | |
hint: t.Optional[str] = None, | |
obj: t.Any = missing, | |
name: t.Optional[str] = None, | |
exc: t.Type[TemplateRuntimeError] = UndefinedError, | |
) -> None: | |
self._undefined_hint = hint | |
self._undefined_obj = obj | |
self._undefined_name = name | |
self._undefined_exception = exc | |
def _undefined_message(self) -> str: | |
"""Build a message about the undefined value based on how it was | |
accessed. | |
""" | |
if self._undefined_hint: | |
return self._undefined_hint | |
if self._undefined_obj is missing: | |
return f"{self._undefined_name!r} is undefined" | |
if not isinstance(self._undefined_name, str): | |
return ( | |
f"{object_type_repr(self._undefined_obj)} has no" | |
f" element {self._undefined_name!r}" | |
) | |
return ( | |
f"{object_type_repr(self._undefined_obj)!r} has no" | |
f" attribute {self._undefined_name!r}" | |
) | |
def _fail_with_undefined_error( | |
self, *args: t.Any, **kwargs: t.Any | |
) -> "te.NoReturn": | |
"""Raise an :exc:`UndefinedError` when operations are performed | |
on the undefined value. | |
""" | |
raise self._undefined_exception(self._undefined_message) | |
def __getattr__(self, name: str) -> t.Any: | |
if name[:2] == "__": | |
raise AttributeError(name) | |
return self._fail_with_undefined_error() | |
__add__ = __radd__ = __sub__ = __rsub__ = _fail_with_undefined_error | |
__mul__ = __rmul__ = __div__ = __rdiv__ = _fail_with_undefined_error | |
__truediv__ = __rtruediv__ = _fail_with_undefined_error | |
__floordiv__ = __rfloordiv__ = _fail_with_undefined_error | |
__mod__ = __rmod__ = _fail_with_undefined_error | |
__pos__ = __neg__ = _fail_with_undefined_error | |
__call__ = __getitem__ = _fail_with_undefined_error | |
__lt__ = __le__ = __gt__ = __ge__ = _fail_with_undefined_error | |
__int__ = __float__ = __complex__ = _fail_with_undefined_error | |
__pow__ = __rpow__ = _fail_with_undefined_error | |
def __eq__(self, other: t.Any) -> bool: | |
return type(self) is type(other) | |
def __ne__(self, other: t.Any) -> bool: | |
return not self.__eq__(other) | |
def __hash__(self) -> int: | |
return id(type(self)) | |
def __str__(self) -> str: | |
return "" | |
def __len__(self) -> int: | |
return 0 | |
def __iter__(self) -> t.Iterator[t.Any]: | |
yield from () | |
async def __aiter__(self) -> t.AsyncIterator[t.Any]: | |
for _ in (): | |
yield | |
def __bool__(self) -> bool: | |
return False | |
def __repr__(self) -> str: | |
return "Undefined" | |
def make_logging_undefined( | |
logger: t.Optional["logging.Logger"] = None, base: t.Type[Undefined] = Undefined | |
) -> t.Type[Undefined]: | |
"""Given a logger object this returns a new undefined class that will | |
log certain failures. It will log iterations and printing. If no | |
logger is given a default logger is created. | |
Example:: | |
logger = logging.getLogger(__name__) | |
LoggingUndefined = make_logging_undefined( | |
logger=logger, | |
base=Undefined | |
) | |
.. versionadded:: 2.8 | |
:param logger: the logger to use. If not provided, a default logger | |
is created. | |
:param base: the base class to add logging functionality to. This | |
defaults to :class:`Undefined`. | |
""" | |
if logger is None: | |
import logging | |
logger = logging.getLogger(__name__) | |
logger.addHandler(logging.StreamHandler(sys.stderr)) | |
def _log_message(undef: Undefined) -> None: | |
logger.warning("Template variable warning: %s", undef._undefined_message) | |
class LoggingUndefined(base): # type: ignore | |
__slots__ = () | |
def _fail_with_undefined_error( # type: ignore | |
self, *args: t.Any, **kwargs: t.Any | |
) -> "te.NoReturn": | |
try: | |
super()._fail_with_undefined_error(*args, **kwargs) | |
except self._undefined_exception as e: | |
logger.error("Template variable error: %s", e) # type: ignore | |
raise e | |
def __str__(self) -> str: | |
_log_message(self) | |
return super().__str__() # type: ignore | |
def __iter__(self) -> t.Iterator[t.Any]: | |
_log_message(self) | |
return super().__iter__() # type: ignore | |
def __bool__(self) -> bool: | |
_log_message(self) | |
return super().__bool__() # type: ignore | |
return LoggingUndefined | |
class ChainableUndefined(Undefined): | |
"""An undefined that is chainable, where both ``__getattr__`` and | |
``__getitem__`` return itself rather than raising an | |
:exc:`UndefinedError`. | |
>>> foo = ChainableUndefined(name='foo') | |
>>> str(foo.bar['baz']) | |
'' | |
>>> foo.bar['baz'] + 42 | |
Traceback (most recent call last): | |
... | |
jinja2.exceptions.UndefinedError: 'foo' is undefined | |
.. versionadded:: 2.11.0 | |
""" | |
__slots__ = () | |
def __html__(self) -> str: | |
return str(self) | |
def __getattr__(self, _: str) -> "ChainableUndefined": | |
return self | |
__getitem__ = __getattr__ # type: ignore | |
class DebugUndefined(Undefined): | |
"""An undefined that returns the debug info when printed. | |
>>> foo = DebugUndefined(name='foo') | |
>>> str(foo) | |
'{{ foo }}' | |
>>> not foo | |
True | |
>>> foo + 42 | |
Traceback (most recent call last): | |
... | |
jinja2.exceptions.UndefinedError: 'foo' is undefined | |
""" | |
__slots__ = () | |
def __str__(self) -> str: | |
if self._undefined_hint: | |
message = f"undefined value printed: {self._undefined_hint}" | |
elif self._undefined_obj is missing: | |
message = self._undefined_name # type: ignore | |
else: | |
message = ( | |
f"no such element: {object_type_repr(self._undefined_obj)}" | |
f"[{self._undefined_name!r}]" | |
) | |
return f"{{{{ {message} }}}}" | |
class StrictUndefined(Undefined): | |
"""An undefined that barks on print and iteration as well as boolean | |
tests and all kinds of comparisons. In other words: you can do nothing | |
with it except checking if it's defined using the `defined` test. | |
>>> foo = StrictUndefined(name='foo') | |
>>> str(foo) | |
Traceback (most recent call last): | |
... | |
jinja2.exceptions.UndefinedError: 'foo' is undefined | |
>>> not foo | |
Traceback (most recent call last): | |
... | |
jinja2.exceptions.UndefinedError: 'foo' is undefined | |
>>> foo + 42 | |
Traceback (most recent call last): | |
... | |
jinja2.exceptions.UndefinedError: 'foo' is undefined | |
""" | |
__slots__ = () | |
__iter__ = __str__ = __len__ = Undefined._fail_with_undefined_error | |
__eq__ = __ne__ = __bool__ = __hash__ = Undefined._fail_with_undefined_error | |
__contains__ = Undefined._fail_with_undefined_error | |
# Remove slots attributes, after the metaclass is applied they are | |
# unneeded and contain wrong data for subclasses. | |
del ( | |
Undefined.__slots__, | |
ChainableUndefined.__slots__, | |
DebugUndefined.__slots__, | |
StrictUndefined.__slots__, | |
) | |