codekingpro/portable-devtools
114k
1"""The runtime functions and state used by compiled templates."""2 3import functools4import sys5import typing as t6from collections import abc7from itertools import chain8 9from markupsafe import escape # noqa: F40110from markupsafe import Markup11from markupsafe import soft_str12 13from .async_utils import auto_aiter14from .async_utils import auto_await # noqa: F40115from .exceptions import TemplateNotFound # noqa: F40116from .exceptions import TemplateRuntimeError # noqa: F40117from .exceptions import UndefinedError18from .nodes import EvalContext19from .utils import _PassArg20from .utils import concat21from .utils import internalcode22from .utils import missing23from .utils import Namespace # noqa: F40124from .utils import object_type_repr25from .utils import pass_eval_context26 27V = t.TypeVar("V")28F = t.TypeVar("F", bound=t.Callable[..., t.Any])29 30if t.TYPE_CHECKING:31 import logging32 33 import typing_extensions as te34 35 from .environment import Environment36 37 class LoopRenderFunc(te.Protocol):38 def __call__(39 self,40 reciter: t.Iterable[V],41 loop_render_func: "LoopRenderFunc",42 depth: int = 0,43 ) -> str: ...44 45 46# these variables are exported to the template runtime47exported = [48 "LoopContext",49 "TemplateReference",50 "Macro",51 "Markup",52 "TemplateRuntimeError",53 "missing",54 "escape",55 "markup_join",56 "str_join",57 "identity",58 "TemplateNotFound",59 "Namespace",60 "Undefined",61 "internalcode",62]63async_exported = [64 "AsyncLoopContext",65 "auto_aiter",66 "auto_await",67]68 69 70def identity(x: V) -> V:71 """Returns its argument. Useful for certain things in the72 environment.73 """74 return x75 76 77def markup_join(seq: t.Iterable[t.Any]) -> str:78 """Concatenation that escapes if necessary and converts to string."""79 buf = []80 iterator = map(soft_str, seq)81 for arg in iterator:82 buf.append(arg)83 if hasattr(arg, "__html__"):84 return Markup("").join(chain(buf, iterator))85 return concat(buf)86 87 88def str_join(seq: t.Iterable[t.Any]) -> str:89 """Simple args to string conversion and concatenation."""90 return concat(map(str, seq))91 92 93def new_context(94 environment: "Environment",95 template_name: t.Optional[str],96 blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]],97 vars: t.Optional[t.Dict[str, t.Any]] = None,98 shared: bool = False,99 globals: t.Optional[t.MutableMapping[str, t.Any]] = None,100 locals: t.Optional[t.Mapping[str, t.Any]] = None,101) -> "Context":102 """Internal helper for context creation."""103 if vars is None:104 vars = {}105 if shared:106 parent = vars107 else:108 parent = dict(globals or (), **vars)109 if locals:110 # if the parent is shared a copy should be created because111 # we don't want to modify the dict passed112 if shared:113 parent = dict(parent)114 for key, value in locals.items():115 if value is not missing:116 parent[key] = value117 return environment.context_class(118 environment, parent, template_name, blocks, globals=globals119 )120 121 122class TemplateReference:123 """The `self` in templates."""124 125 def __init__(self, context: "Context") -> None:126 self.__context = context127 128 def __getitem__(self, name: str) -> t.Any:129 blocks = self.__context.blocks[name]130 return BlockReference(name, self.__context, blocks, 0)131 132 def __repr__(self) -> str:133 return f"<{type(self).__name__} {self.__context.name!r}>"134 135 136def _dict_method_all(dict_method: F) -> F:137 @functools.wraps(dict_method)138 def f_all(self: "Context") -> t.Any:139 return dict_method(self.get_all())140 141 return t.cast(F, f_all)142 143 144@abc.Mapping.register145class Context:146 """The template context holds the variables of a template. It stores the147 values passed to the template and also the names the template exports.148 Creating instances is neither supported nor useful as it's created149 automatically at various stages of the template evaluation and should not150 be created by hand.151 152 The context is immutable. Modifications on :attr:`parent` **must not**153 happen and modifications on :attr:`vars` are allowed from generated154 template code only. Template filters and global functions marked as155 :func:`pass_context` get the active context passed as first argument156 and are allowed to access the context read-only.157 158 The template context supports read only dict operations (`get`,159 `keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`,160 `__getitem__`, `__contains__`). Additionally there is a :meth:`resolve`161 method that doesn't fail with a `KeyError` but returns an162 :class:`Undefined` object for missing variables.163 """164 165 def __init__(166 self,167 environment: "Environment",168 parent: t.Dict[str, t.Any],169 name: t.Optional[str],170 blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]],171 globals: t.Optional[t.MutableMapping[str, t.Any]] = None,172 ):173 self.parent = parent174 self.vars: t.Dict[str, t.Any] = {}175 self.environment: "Environment" = environment176 self.eval_ctx = EvalContext(self.environment, name)177 self.exported_vars: t.Set[str] = set()178 self.name = name179 self.globals_keys = set() if globals is None else set(globals)180 181 # create the initial mapping of blocks. Whenever template inheritance182 # takes place the runtime will update this mapping with the new blocks183 # from the template.184 self.blocks = {k: [v] for k, v in blocks.items()}185 186 def super(187 self, name: str, current: t.Callable[["Context"], t.Iterator[str]]188 ) -> t.Union["BlockReference", "Undefined"]:189 """Render a parent block."""190 try:191 blocks = self.blocks[name]192 index = blocks.index(current) + 1193 blocks[index]194 except LookupError:195 return self.environment.undefined(196 f"there is no parent block called {name!r}.", name="super"197 )198 return BlockReference(name, self, blocks, index)199 200 def get(self, key: str, default: t.Any = None) -> t.Any:201 """Look up a variable by name, or return a default if the key is202 not found.203 204 :param key: The variable name to look up.205 :param default: The value to return if the key is not found.206 """207 try:208 return self[key]209 except KeyError:210 return default211 212 def resolve(self, key: str) -> t.Union[t.Any, "Undefined"]:213 """Look up a variable by name, or return an :class:`Undefined`214 object if the key is not found.215 216 If you need to add custom behavior, override217 :meth:`resolve_or_missing`, not this method. The various lookup218 functions use that method, not this one.219 220 :param key: The variable name to look up.221 """222 rv = self.resolve_or_missing(key)223 224 if rv is missing:225 return self.environment.undefined(name=key)226 227 return rv228 229 def resolve_or_missing(self, key: str) -> t.Any:230 """Look up a variable by name, or return a ``missing`` sentinel231 if the key is not found.232 233 Override this method to add custom lookup behavior.234 :meth:`resolve`, :meth:`get`, and :meth:`__getitem__` use this235 method. Don't call this method directly.236 237 :param key: The variable name to look up.238 """239 if key in self.vars:240 return self.vars[key]241 242 if key in self.parent:243 return self.parent[key]244 245 return missing246 247 def get_exported(self) -> t.Dict[str, t.Any]:248 """Get a new dict with the exported variables."""249 return {k: self.vars[k] for k in self.exported_vars}250 251 def get_all(self) -> t.Dict[str, t.Any]:252 """Return the complete context as dict including the exported253 variables. For optimizations reasons this might not return an254 actual copy so be careful with using it.255 """256 if not self.vars:257 return self.parent258 if not self.parent:259 return self.vars260 return dict(self.parent, **self.vars)261 262 @internalcode263 def call(264 __self,265 __obj: t.Callable[..., t.Any],266 *args: t.Any,267 **kwargs: t.Any, # noqa: B902268 ) -> t.Union[t.Any, "Undefined"]:269 """Call the callable with the arguments and keyword arguments270 provided but inject the active context or environment as first271 argument if the callable has :func:`pass_context` or272 :func:`pass_environment`.273 """274 if __debug__:275 __traceback_hide__ = True # noqa276 277 # Allow callable classes to take a context278 if (279 hasattr(__obj, "__call__") # noqa: B004280 and _PassArg.from_obj(__obj.__call__) is not None281 ):282 __obj = __obj.__call__283 284 pass_arg = _PassArg.from_obj(__obj)285 286 if pass_arg is _PassArg.context:287 # the active context should have access to variables set in288 # loops and blocks without mutating the context itself289 if kwargs.get("_loop_vars"):290 __self = __self.derived(kwargs["_loop_vars"])291 if kwargs.get("_block_vars"):292 __self = __self.derived(kwargs["_block_vars"])293 args = (__self,) + args294 elif pass_arg is _PassArg.eval_context:295 args = (__self.eval_ctx,) + args296 elif pass_arg is _PassArg.environment:297 args = (__self.environment,) + args298 299 kwargs.pop("_block_vars", None)300 kwargs.pop("_loop_vars", None)301 302 try:303 return __obj(*args, **kwargs)304 except StopIteration:305 return __self.environment.undefined(306 "value was undefined because a callable raised a"307 " StopIteration exception"308 )309 310 def derived(self, locals: t.Optional[t.Dict[str, t.Any]] = None) -> "Context":311 """Internal helper function to create a derived context. This is312 used in situations where the system needs a new context in the same313 template that is independent.314 """315 context = new_context(316 self.environment, self.name, {}, self.get_all(), True, None, locals317 )318 context.eval_ctx = self.eval_ctx319 context.blocks.update((k, list(v)) for k, v in self.blocks.items())320 return context321 322 keys = _dict_method_all(dict.keys)323 values = _dict_method_all(dict.values)324 items = _dict_method_all(dict.items)325 326 def __contains__(self, name: str) -> bool:327 return name in self.vars or name in self.parent328 329 def __getitem__(self, key: str) -> t.Any:330 """Look up a variable by name with ``[]`` syntax, or raise a331 ``KeyError`` if the key is not found.332 """333 item = self.resolve_or_missing(key)334 335 if item is missing:336 raise KeyError(key)337 338 return item339 340 def __repr__(self) -> str:341 return f"<{type(self).__name__} {self.get_all()!r} of {self.name!r}>"342 343 344class BlockReference:345 """One block on a template reference."""346 347 def __init__(348 self,349 name: str,350 context: "Context",351 stack: t.List[t.Callable[["Context"], t.Iterator[str]]],352 depth: int,353 ) -> None:354 self.name = name355 self._context = context356 self._stack = stack357 self._depth = depth358 359 @property360 def super(self) -> t.Union["BlockReference", "Undefined"]:361 """Super the block."""362 if self._depth + 1 >= len(self._stack):363 return self._context.environment.undefined(364 f"there is no parent block called {self.name!r}.", name="super"365 )366 return BlockReference(self.name, self._context, self._stack, self._depth + 1)367 368 @internalcode369 async def _async_call(self) -> str:370 rv = concat(371 [x async for x in self._stack[self._depth](self._context)] # type: ignore372 )373 374 if self._context.eval_ctx.autoescape:375 return Markup(rv)376 377 return rv378 379 @internalcode380 def __call__(self) -> str:381 if self._context.environment.is_async:382 return self._async_call() # type: ignore383 384 rv = concat(self._stack[self._depth](self._context))385 386 if self._context.eval_ctx.autoescape:387 return Markup(rv)388 389 return rv390 391 392class LoopContext:393 """A wrapper iterable for dynamic ``for`` loops, with information394 about the loop and iteration.395 """396 397 #: Current iteration of the loop, starting at 0.398 index0 = -1399 400 _length: t.Optional[int] = None401 _after: t.Any = missing402 _current: t.Any = missing403 _before: t.Any = missing404 _last_changed_value: t.Any = missing405 406 def __init__(407 self,408 iterable: t.Iterable[V],409 undefined: t.Type["Undefined"],410 recurse: t.Optional["LoopRenderFunc"] = None,411 depth0: int = 0,412 ) -> None:413 """414 :param iterable: Iterable to wrap.415 :param undefined: :class:`Undefined` class to use for next and416 previous items.417 :param recurse: The function to render the loop body when the418 loop is marked recursive.419 :param depth0: Incremented when looping recursively.420 """421 self._iterable = iterable422 self._iterator = self._to_iterator(iterable)423 self._undefined = undefined424 self._recurse = recurse425 #: How many levels deep a recursive loop currently is, starting at 0.426 self.depth0 = depth0427 428 @staticmethod429 def _to_iterator(iterable: t.Iterable[V]) -> t.Iterator[V]:430 return iter(iterable)431 432 @property433 def length(self) -> int:434 """Length of the iterable.435 436 If the iterable is a generator or otherwise does not have a437 size, it is eagerly evaluated to get a size.438 """439 if self._length is not None:440 return self._length441 442 try:443 self._length = len(self._iterable) # type: ignore444 except TypeError:445 iterable = list(self._iterator)446 self._iterator = self._to_iterator(iterable)447 self._length = len(iterable) + self.index + (self._after is not missing)448 449 return self._length450 451 def __len__(self) -> int:452 return self.length453 454 @property455 def depth(self) -> int:456 """How many levels deep a recursive loop currently is, starting at 1."""457 return self.depth0 + 1458 459 @property460 def index(self) -> int:461 """Current iteration of the loop, starting at 1."""462 return self.index0 + 1463 464 @property465 def revindex0(self) -> int:466 """Number of iterations from the end of the loop, ending at 0.467 468 Requires calculating :attr:`length`.469 """470 return self.length - self.index471 472 @property473 def revindex(self) -> int:474 """Number of iterations from the end of the loop, ending at 1.475 476 Requires calculating :attr:`length`.477 """478 return self.length - self.index0479 480 @property481 def first(self) -> bool:482 """Whether this is the first iteration of the loop."""483 return self.index0 == 0484 485 def _peek_next(self) -> t.Any:486 """Return the next element in the iterable, or :data:`missing`487 if the iterable is exhausted. Only peeks one item ahead, caching488 the result in :attr:`_last` for use in subsequent checks. The489 cache is reset when :meth:`__next__` is called.490 """491 if self._after is not missing:492 return self._after493 494 self._after = next(self._iterator, missing)495 return self._after496 497 @property498 def last(self) -> bool:499 """Whether this is the last iteration of the loop.500 501 Causes the iterable to advance early. See502 :func:`itertools.groupby` for issues this can cause.503 The :func:`groupby` filter avoids that issue.504 """505 return self._peek_next() is missing506 507 @property508 def previtem(self) -> t.Union[t.Any, "Undefined"]:509 """The item in the previous iteration. Undefined during the510 first iteration.511 """512 if self.first:513 return self._undefined("there is no previous item")514 515 return self._before516 517 @property518 def nextitem(self) -> t.Union[t.Any, "Undefined"]:519 """The item in the next iteration. Undefined during the last520 iteration.521 522 Causes the iterable to advance early. See523 :func:`itertools.groupby` for issues this can cause.524 The :func:`jinja-filters.groupby` filter avoids that issue.525 """526 rv = self._peek_next()527 528 if rv is missing:529 return self._undefined("there is no next item")530 531 return rv532 533 def cycle(self, *args: V) -> V:534 """Return a value from the given args, cycling through based on535 the current :attr:`index0`.536 537 :param args: One or more values to cycle through.538 """539 if not args:540 raise TypeError("no items for cycling given")541 542 return args[self.index0 % len(args)]543 544 def changed(self, *value: t.Any) -> bool:545 """Return ``True`` if previously called with a different value546 (including when called for the first time).547 548 :param value: One or more values to compare to the last call.549 """550 if self._last_changed_value != value:551 self._last_changed_value = value552 return True553 554 return False555 556 def __iter__(self) -> "LoopContext":557 return self558 559 def __next__(self) -> t.Tuple[t.Any, "LoopContext"]:560 if self._after is not missing:561 rv = self._after562 self._after = missing563 else:564 rv = next(self._iterator)565 566 self.index0 += 1567 self._before = self._current568 self._current = rv569 return rv, self570 571 @internalcode572 def __call__(self, iterable: t.Iterable[V]) -> str:573 """When iterating over nested data, render the body of the loop574 recursively with the given inner iterable data.575 576 The loop must have the ``recursive`` marker for this to work.577 """578 if self._recurse is None:579 raise TypeError(580 "The loop must have the 'recursive' marker to be called recursively."581 )582 583 return self._recurse(iterable, self._recurse, depth=self.depth)584 585 def __repr__(self) -> str:586 return f"<{type(self).__name__} {self.index}/{self.length}>"587 588 589class AsyncLoopContext(LoopContext):590 _iterator: t.AsyncIterator[t.Any] # type: ignore591 592 @staticmethod593 def _to_iterator( # type: ignore594 iterable: t.Union[t.Iterable[V], t.AsyncIterable[V]],595 ) -> t.AsyncIterator[V]:596 return auto_aiter(iterable)597 598 @property599 async def length(self) -> int: # type: ignore600 if self._length is not None:601 return self._length602 603 try:604 self._length = len(self._iterable) # type: ignore605 except TypeError:606 iterable = [x async for x in self._iterator]607 self._iterator = self._to_iterator(iterable)608 self._length = len(iterable) + self.index + (self._after is not missing)609 610 return self._length611 612 @property613 async def revindex0(self) -> int: # type: ignore614 return await self.length - self.index615 616 @property617 async def revindex(self) -> int: # type: ignore618 return await self.length - self.index0619 620 async def _peek_next(self) -> t.Any:621 if self._after is not missing:622 return self._after623 624 try:625 self._after = await self._iterator.__anext__()626 except StopAsyncIteration:627 self._after = missing628 629 return self._after630 631 @property632 async def last(self) -> bool: # type: ignore633 return await self._peek_next() is missing634 635 @property636 async def nextitem(self) -> t.Union[t.Any, "Undefined"]:637 rv = await self._peek_next()638 639 if rv is missing:640 return self._undefined("there is no next item")641 642 return rv643 644 def __aiter__(self) -> "AsyncLoopContext":645 return self646 647 async def __anext__(self) -> t.Tuple[t.Any, "AsyncLoopContext"]:648 if self._after is not missing:649 rv = self._after650 self._after = missing651 else:652 rv = await self._iterator.__anext__()653 654 self.index0 += 1655 self._before = self._current656 self._current = rv657 return rv, self658 659 660class Macro:661 """Wraps a macro function."""662 663 def __init__(664 self,665 environment: "Environment",666 func: t.Callable[..., str],667 name: str,668 arguments: t.List[str],669 catch_kwargs: bool,670 catch_varargs: bool,671 caller: bool,672 default_autoescape: t.Optional[bool] = None,673 ):674 self._environment = environment675 self._func = func676 self._argument_count = len(arguments)677 self.name = name678 self.arguments = arguments679 self.catch_kwargs = catch_kwargs680 self.catch_varargs = catch_varargs681 self.caller = caller682 self.explicit_caller = "caller" in arguments683 684 if default_autoescape is None:685 if callable(environment.autoescape):686 default_autoescape = environment.autoescape(None)687 else:688 default_autoescape = environment.autoescape689 690 self._default_autoescape = default_autoescape691 692 @internalcode693 @pass_eval_context694 def __call__(self, *args: t.Any, **kwargs: t.Any) -> str:695 # This requires a bit of explanation, In the past we used to696 # decide largely based on compile-time information if a macro is697 # safe or unsafe. While there was a volatile mode it was largely698 # unused for deciding on escaping. This turns out to be699 # problematic for macros because whether a macro is safe depends not700 # on the escape mode when it was defined, but rather when it was used.701 #702 # Because however we export macros from the module system and703 # there are historic callers that do not pass an eval context (and704 # will continue to not pass one), we need to perform an instance705 # check here.706 #707 # This is considered safe because an eval context is not a valid708 # argument to callables otherwise anyway. Worst case here is709 # that if no eval context is passed we fall back to the compile710 # time autoescape flag.711 if args and isinstance(args[0], EvalContext):712 autoescape = args[0].autoescape713 args = args[1:]714 else:715 autoescape = self._default_autoescape716 717 # try to consume the positional arguments718 arguments = list(args[: self._argument_count])719 off = len(arguments)720 721 # For information why this is necessary refer to the handling722 # of caller in the `macro_body` handler in the compiler.723 found_caller = False724 725 # if the number of arguments consumed is not the number of726 # arguments expected we start filling in keyword arguments727 # and defaults.728 if off != self._argument_count:729 for name in self.arguments[len(arguments) :]:730 try:731 value = kwargs.pop(name)732 except KeyError:733 value = missing734 if name == "caller":735 found_caller = True736 arguments.append(value)737 else:738 found_caller = self.explicit_caller739 740 # it's important that the order of these arguments does not change741 # if not also changed in the compiler's `function_scoping` method.742 # the order is caller, keyword arguments, positional arguments!743 if self.caller and not found_caller:744 caller = kwargs.pop("caller", None)745 if caller is None:746 caller = self._environment.undefined("No caller defined", name="caller")747 arguments.append(caller)748 749 if self.catch_kwargs:750 arguments.append(kwargs)751 elif kwargs:752 if "caller" in kwargs:753 raise TypeError(754 f"macro {self.name!r} was invoked with two values for the special"755 " caller argument. This is most likely a bug."756 )757 raise TypeError(758 f"macro {self.name!r} takes no keyword argument {next(iter(kwargs))!r}"759 )760 if self.catch_varargs:761 arguments.append(args[self._argument_count :])762 elif len(args) > self._argument_count:763 raise TypeError(764 f"macro {self.name!r} takes not more than"765 f" {len(self.arguments)} argument(s)"766 )767 768 return self._invoke(arguments, autoescape)769 770 async def _async_invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str:771 rv = await self._func(*arguments) # type: ignore772 773 if autoescape:774 return Markup(rv)775 776 return rv # type: ignore777 778 def _invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str:779 if self._environment.is_async:780 return self._async_invoke(arguments, autoescape) # type: ignore781 782 rv = self._func(*arguments)783 784 if autoescape:785 rv = Markup(rv)786 787 return rv788 789 def __repr__(self) -> str:790 name = "anonymous" if self.name is None else repr(self.name)791 return f"<{type(self).__name__} {name}>"792 793 794class Undefined:795 """The default undefined type. This undefined type can be printed and796 iterated over, but every other access will raise an :exc:`UndefinedError`:797 798 >>> foo = Undefined(name='foo')799 >>> str(foo)800 ''801 >>> not foo802 True803 >>> foo + 42804 Traceback (most recent call last):805 ...806 jinja2.exceptions.UndefinedError: 'foo' is undefined807 """808 809 __slots__ = (810 "_undefined_hint",811 "_undefined_obj",812 "_undefined_name",813 "_undefined_exception",814 )815 816 def __init__(817 self,818 hint: t.Optional[str] = None,819 obj: t.Any = missing,820 name: t.Optional[str] = None,821 exc: t.Type[TemplateRuntimeError] = UndefinedError,822 ) -> None:823 self._undefined_hint = hint824 self._undefined_obj = obj825 self._undefined_name = name826 self._undefined_exception = exc827 828 @property829 def _undefined_message(self) -> str:830 """Build a message about the undefined value based on how it was831 accessed.832 """833 if self._undefined_hint:834 return self._undefined_hint835 836 if self._undefined_obj is missing:837 return f"{self._undefined_name!r} is undefined"838 839 if not isinstance(self._undefined_name, str):840 return (841 f"{object_type_repr(self._undefined_obj)} has no"842 f" element {self._undefined_name!r}"843 )844 845 return (846 f"{object_type_repr(self._undefined_obj)!r} has no"847 f" attribute {self._undefined_name!r}"848 )849 850 @internalcode851 def _fail_with_undefined_error(852 self, *args: t.Any, **kwargs: t.Any853 ) -> "te.NoReturn":854 """Raise an :exc:`UndefinedError` when operations are performed855 on the undefined value.856 """857 raise self._undefined_exception(self._undefined_message)858 859 @internalcode860 def __getattr__(self, name: str) -> t.Any:861 if name[:2] == "__":862 raise AttributeError(name)863 864 return self._fail_with_undefined_error()865 866 __add__ = __radd__ = __sub__ = __rsub__ = _fail_with_undefined_error867 __mul__ = __rmul__ = __div__ = __rdiv__ = _fail_with_undefined_error868 __truediv__ = __rtruediv__ = _fail_with_undefined_error869 __floordiv__ = __rfloordiv__ = _fail_with_undefined_error870 __mod__ = __rmod__ = _fail_with_undefined_error871 __pos__ = __neg__ = _fail_with_undefined_error872 __call__ = __getitem__ = _fail_with_undefined_error873 __lt__ = __le__ = __gt__ = __ge__ = _fail_with_undefined_error874 __int__ = __float__ = __complex__ = _fail_with_undefined_error875 __pow__ = __rpow__ = _fail_with_undefined_error876 877 def __eq__(self, other: t.Any) -> bool:878 return type(self) is type(other)879 880 def __ne__(self, other: t.Any) -> bool:881 return not self.__eq__(other)882 883 def __hash__(self) -> int:884 return id(type(self))885 886 def __str__(self) -> str:887 return ""888 889 def __len__(self) -> int:890 return 0891 892 def __iter__(self) -> t.Iterator[t.Any]:893 yield from ()894 895 async def __aiter__(self) -> t.AsyncIterator[t.Any]:896 for _ in ():897 yield898 899 def __bool__(self) -> bool:900 return False901 902 def __repr__(self) -> str:903 return "Undefined"904 905 906def make_logging_undefined(907 logger: t.Optional["logging.Logger"] = None, base: t.Type[Undefined] = Undefined908) -> t.Type[Undefined]:909 """Given a logger object this returns a new undefined class that will910 log certain failures. It will log iterations and printing. If no911 logger is given a default logger is created.912 913 Example::914 915 logger = logging.getLogger(__name__)916 LoggingUndefined = make_logging_undefined(917 logger=logger,918 base=Undefined919 )920 921 .. versionadded:: 2.8922 923 :param logger: the logger to use. If not provided, a default logger924 is created.925 :param base: the base class to add logging functionality to. This926 defaults to :class:`Undefined`.927 """928 if logger is None:929 import logging930 931 logger = logging.getLogger(__name__)932 logger.addHandler(logging.StreamHandler(sys.stderr))933 934 def _log_message(undef: Undefined) -> None:935 logger.warning("Template variable warning: %s", undef._undefined_message)936 937 class LoggingUndefined(base): # type: ignore938 __slots__ = ()939 940 def _fail_with_undefined_error( # type: ignore941 self, *args: t.Any, **kwargs: t.Any942 ) -> "te.NoReturn":943 try:944 super()._fail_with_undefined_error(*args, **kwargs)945 except self._undefined_exception as e:946 logger.error("Template variable error: %s", e) # type: ignore947 raise e948 949 def __str__(self) -> str:950 _log_message(self)951 return super().__str__() # type: ignore952 953 def __iter__(self) -> t.Iterator[t.Any]:954 _log_message(self)955 return super().__iter__() # type: ignore956 957 def __bool__(self) -> bool:958 _log_message(self)959 return super().__bool__() # type: ignore960 961 return LoggingUndefined962 963 964class ChainableUndefined(Undefined):965 """An undefined that is chainable, where both ``__getattr__`` and966 ``__getitem__`` return itself rather than raising an967 :exc:`UndefinedError`.968 969 >>> foo = ChainableUndefined(name='foo')970 >>> str(foo.bar['baz'])971 ''972 >>> foo.bar['baz'] + 42973 Traceback (most recent call last):974 ...975 jinja2.exceptions.UndefinedError: 'foo' is undefined976 977 .. versionadded:: 2.11.0978 """979 980 __slots__ = ()981 982 def __html__(self) -> str:983 return str(self)984 985 def __getattr__(self, _: str) -> "ChainableUndefined":986 return self987 988 __getitem__ = __getattr__ # type: ignore989 990 991class DebugUndefined(Undefined):992 """An undefined that returns the debug info when printed.993 994 >>> foo = DebugUndefined(name='foo')995 >>> str(foo)996 '{{ foo }}'997 >>> not foo998 True999 >>> foo + 421000 Traceback (most recent call last):1001 ...1002 jinja2.exceptions.UndefinedError: 'foo' is undefined1003 """1004 1005 __slots__ = ()1006 1007 def __str__(self) -> str:1008 if self._undefined_hint:1009 message = f"undefined value printed: {self._undefined_hint}"1010 1011 elif self._undefined_obj is missing:1012 message = self._undefined_name # type: ignore1013 1014 else:1015 message = (1016 f"no such element: {object_type_repr(self._undefined_obj)}"1017 f"[{self._undefined_name!r}]"1018 )1019 1020 return f"{{{{ {message} }}}}"1021 1022 1023class StrictUndefined(Undefined):1024 """An undefined that barks on print and iteration as well as boolean1025 tests and all kinds of comparisons. In other words: you can do nothing1026 with it except checking if it's defined using the `defined` test.1027 1028 >>> foo = StrictUndefined(name='foo')1029 >>> str(foo)1030 Traceback (most recent call last):1031 ...1032 jinja2.exceptions.UndefinedError: 'foo' is undefined1033 >>> not foo1034 Traceback (most recent call last):1035 ...1036 jinja2.exceptions.UndefinedError: 'foo' is undefined1037 >>> foo + 421038 Traceback (most recent call last):1039 ...1040 jinja2.exceptions.UndefinedError: 'foo' is undefined1041 """1042 1043 __slots__ = ()1044 __iter__ = __str__ = __len__ = Undefined._fail_with_undefined_error1045 __eq__ = __ne__ = __bool__ = __hash__ = Undefined._fail_with_undefined_error1046 __contains__ = Undefined._fail_with_undefined_error1047 1048 1049# Remove slots attributes, after the metaclass is applied they are1050# unneeded and contain wrong data for subclasses.1051del (1052 Undefined.__slots__,1053 ChainableUndefined.__slots__,1054 DebugUndefined.__slots__,1055 StrictUndefined.__slots__,1056)1057 