123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053 |
- """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:
- @functools.wraps(dict_method)
- def f_all(self: "Context") -> t.Any:
- return dict_method(self.get_all())
- return t.cast(F, f_all)
- @abc.Mapping.register
- 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)
- @internalcode
- def call(
- __self, __obj: t.Callable, *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 # type: ignore
- ):
- __obj = __obj.__call__ # type: ignore
- 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
- @property
- 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)
- @internalcode
- 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
- @internalcode
- 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
- @staticmethod
- def _to_iterator(iterable: t.Iterable[V]) -> t.Iterator[V]:
- return iter(iterable)
- @property
- 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
- @property
- def depth(self) -> int:
- """How many levels deep a recursive loop currently is, starting at 1."""
- return self.depth0 + 1
- @property
- def index(self) -> int:
- """Current iteration of the loop, starting at 1."""
- return self.index0 + 1
- @property
- 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
- @property
- 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
- @property
- 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
- @property
- 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
- @property
- 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
- @property
- 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
- @internalcode
- 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
- @staticmethod
- def _to_iterator( # type: ignore
- iterable: t.Union[t.Iterable[V], t.AsyncIterable[V]]
- ) -> t.AsyncIterator[V]:
- return auto_aiter(iterable)
- @property
- 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
- @property
- async def revindex0(self) -> int: # type: ignore
- return await self.length - self.index
- @property
- 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
- @property
- async def last(self) -> bool: # type: ignore
- return await self._peek_next() is missing
- @property
- 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
- @internalcode
- @pass_eval_context
- 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
- @property
- 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}"
- )
- @internalcode
- 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)
- @internalcode
- 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( # type: ignore
- "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__,
- )
|