2020-01-10 21:42:02 +00:00
|
|
|
"""The runtime functions and state used by compiled templates."""
|
2014-06-06 16:00:04 +00:00
|
|
|
import sys
|
2021-02-24 17:47:57 +00:00
|
|
|
import typing as t
|
2020-01-28 06:05:00 +00:00
|
|
|
from collections import abc
|
2013-05-17 22:06:22 +00:00
|
|
|
from itertools import chain
|
2017-03-15 18:19:04 +00:00
|
|
|
from types import MethodType
|
|
|
|
|
2020-01-27 06:23:36 +00:00
|
|
|
from markupsafe import escape # noqa: F401
|
2020-01-10 18:40:52 +00:00
|
|
|
from markupsafe import Markup
|
2020-01-28 06:05:00 +00:00
|
|
|
from markupsafe import soft_str
|
|
|
|
|
2021-04-10 23:12:25 +00:00
|
|
|
from .async_utils import auto_aiter
|
|
|
|
from .async_utils import auto_await # noqa: F401
|
2020-01-27 06:23:36 +00:00
|
|
|
from .exceptions import TemplateNotFound # noqa: F401
|
|
|
|
from .exceptions import TemplateRuntimeError # noqa: F401
|
2020-01-09 20:03:07 +00:00
|
|
|
from .exceptions import UndefinedError
|
|
|
|
from .nodes import EvalContext
|
2021-04-10 15:58:16 +00:00
|
|
|
from .utils import _PassArg
|
2020-01-09 20:03:07 +00:00
|
|
|
from .utils import concat
|
|
|
|
from .utils import internalcode
|
|
|
|
from .utils import missing
|
2020-01-27 06:23:36 +00:00
|
|
|
from .utils import Namespace # noqa: F401
|
2020-01-09 20:03:07 +00:00
|
|
|
from .utils import object_type_repr
|
2021-04-10 15:58:16 +00:00
|
|
|
from .utils import pass_eval_context
|
2008-04-07 16:39:54 +00:00
|
|
|
|
2021-04-05 16:25:26 +00:00
|
|
|
if t.TYPE_CHECKING:
|
|
|
|
from .environment import Environment
|
|
|
|
|
2008-04-26 14:26:52 +00:00
|
|
|
# these variables are exported to the template runtime
|
2020-01-27 06:23:36 +00:00
|
|
|
exported = [
|
2020-01-10 15:46:18 +00:00
|
|
|
"LoopContext",
|
|
|
|
"TemplateReference",
|
|
|
|
"Macro",
|
|
|
|
"Markup",
|
|
|
|
"TemplateRuntimeError",
|
|
|
|
"missing",
|
|
|
|
"concat",
|
|
|
|
"escape",
|
|
|
|
"markup_join",
|
2020-01-28 06:05:00 +00:00
|
|
|
"str_join",
|
2020-01-10 15:46:18 +00:00
|
|
|
"identity",
|
|
|
|
"TemplateNotFound",
|
|
|
|
"Namespace",
|
|
|
|
"Undefined",
|
2020-06-26 19:26:04 +00:00
|
|
|
"internalcode",
|
2020-01-10 15:46:18 +00:00
|
|
|
]
|
2021-04-10 23:12:25 +00:00
|
|
|
async_exported = [
|
|
|
|
"AsyncLoopContext",
|
|
|
|
"auto_aiter",
|
|
|
|
"auto_await",
|
|
|
|
]
|
2008-04-25 21:44:14 +00:00
|
|
|
|
2010-05-29 15:35:10 +00:00
|
|
|
|
2020-01-10 18:40:52 +00:00
|
|
|
def identity(x):
|
|
|
|
"""Returns its argument. Useful for certain things in the
|
|
|
|
environment.
|
|
|
|
"""
|
|
|
|
return x
|
2012-01-24 23:42:54 +00:00
|
|
|
|
2008-04-25 21:44:14 +00:00
|
|
|
|
2008-05-15 20:47:27 +00:00
|
|
|
def markup_join(seq):
|
2020-01-27 22:12:52 +00:00
|
|
|
"""Concatenation that escapes if necessary and converts to string."""
|
2008-04-28 10:20:12 +00:00
|
|
|
buf = []
|
2020-01-28 06:05:00 +00:00
|
|
|
iterator = map(soft_str, seq)
|
2008-04-28 10:20:12 +00:00
|
|
|
for arg in iterator:
|
|
|
|
buf.append(arg)
|
2020-01-10 15:46:18 +00:00
|
|
|
if hasattr(arg, "__html__"):
|
2020-01-29 04:16:59 +00:00
|
|
|
return Markup("").join(chain(buf, iterator))
|
2008-04-28 10:20:12 +00:00
|
|
|
return concat(buf)
|
|
|
|
|
|
|
|
|
2020-01-28 06:05:00 +00:00
|
|
|
def str_join(seq):
|
2020-01-27 22:12:52 +00:00
|
|
|
"""Simple args to string conversion and concatenation."""
|
2020-01-28 06:05:00 +00:00
|
|
|
return concat(map(str, seq))
|
|
|
|
|
|
|
|
|
|
|
|
def unicode_join(seq):
|
|
|
|
import warnings
|
|
|
|
|
|
|
|
warnings.warn(
|
|
|
|
"This template must be recompiled with at least Jinja 3.0, or"
|
|
|
|
" it will fail in 3.1.",
|
|
|
|
DeprecationWarning,
|
|
|
|
stacklevel=2,
|
|
|
|
)
|
|
|
|
return str_join(seq)
|
2008-04-28 10:20:12 +00:00
|
|
|
|
|
|
|
|
2020-01-10 15:46:18 +00:00
|
|
|
def new_context(
|
|
|
|
environment,
|
|
|
|
template_name,
|
|
|
|
blocks,
|
|
|
|
vars=None,
|
|
|
|
shared=None,
|
|
|
|
globals=None,
|
|
|
|
locals=None,
|
|
|
|
):
|
2019-07-25 13:28:26 +00:00
|
|
|
"""Internal helper for context creation."""
|
2009-02-19 14:56:53 +00:00
|
|
|
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)
|
2020-01-28 06:05:00 +00:00
|
|
|
for key, value in locals.items():
|
2017-01-06 19:57:30 +00:00
|
|
|
if value is not missing:
|
|
|
|
parent[key] = value
|
2020-06-22 13:54:08 +00:00
|
|
|
return environment.context_class(
|
|
|
|
environment, parent, template_name, blocks, globals=globals
|
|
|
|
)
|
2009-02-19 14:56:53 +00:00
|
|
|
|
|
|
|
|
2020-01-29 04:16:59 +00:00
|
|
|
class TemplateReference:
|
2009-02-19 14:56:53 +00:00
|
|
|
"""The `self` in templates."""
|
|
|
|
|
|
|
|
def __init__(self, context):
|
|
|
|
self.__context = context
|
|
|
|
|
|
|
|
def __getitem__(self, name):
|
|
|
|
blocks = self.__context.blocks[name]
|
|
|
|
return BlockReference(name, self.__context, blocks, 0)
|
|
|
|
|
|
|
|
def __repr__(self):
|
2020-01-29 04:16:59 +00:00
|
|
|
return f"<{self.__class__.__name__} {self.__context.name!r}>"
|
2009-02-19 14:56:53 +00:00
|
|
|
|
|
|
|
|
2017-01-12 19:10:58 +00:00
|
|
|
def _get_func(x):
|
2020-01-10 15:46:18 +00:00
|
|
|
return getattr(x, "__func__", x)
|
2017-01-12 19:10:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ContextMeta(type):
|
2020-01-10 18:40:52 +00:00
|
|
|
def __new__(mcs, name, bases, d):
|
|
|
|
rv = type.__new__(mcs, name, bases, d)
|
2017-01-12 19:10:58 +00:00
|
|
|
if bases == ():
|
|
|
|
return rv
|
|
|
|
|
|
|
|
resolve = _get_func(rv.resolve)
|
|
|
|
default_resolve = _get_func(Context.resolve)
|
|
|
|
resolve_or_missing = _get_func(rv.resolve_or_missing)
|
|
|
|
default_resolve_or_missing = _get_func(Context.resolve_or_missing)
|
|
|
|
|
|
|
|
# If we have a changed resolve but no changed default or missing
|
|
|
|
# resolve we invert the call logic.
|
2020-01-10 15:46:18 +00:00
|
|
|
if (
|
|
|
|
resolve is not default_resolve
|
|
|
|
and resolve_or_missing is default_resolve_or_missing
|
|
|
|
):
|
2017-01-12 19:10:58 +00:00
|
|
|
rv._legacy_resolve_mode = True
|
2020-01-10 15:46:18 +00:00
|
|
|
elif (
|
|
|
|
resolve is default_resolve
|
|
|
|
and resolve_or_missing is default_resolve_or_missing
|
|
|
|
):
|
2017-01-12 19:10:58 +00:00
|
|
|
rv._fast_resolve_mode = True
|
|
|
|
|
|
|
|
return rv
|
|
|
|
|
|
|
|
|
|
|
|
def resolve_or_missing(context, key, missing=missing):
|
|
|
|
if key in context.vars:
|
|
|
|
return context.vars[key]
|
|
|
|
if key in context.parent:
|
|
|
|
return context.parent[key]
|
|
|
|
return missing
|
|
|
|
|
|
|
|
|
2020-01-28 06:05:00 +00:00
|
|
|
@abc.Mapping.register
|
|
|
|
class Context(metaclass=ContextMeta):
|
2008-04-30 11:03:59 +00:00
|
|
|
"""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
|
2021-04-10 15:58:16 +00:00
|
|
|
:func:`pass_context` get the active context passed as first argument
|
2008-04-30 11:03:59 +00:00
|
|
|
and are allowed to access the context read-only.
|
|
|
|
|
|
|
|
The template context supports read only dict operations (`get`,
|
2008-05-06 14:04:10 +00:00
|
|
|
`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.
|
2008-04-08 16:49:56 +00:00
|
|
|
"""
|
2020-01-10 15:46:18 +00:00
|
|
|
|
2017-01-12 19:10:58 +00:00
|
|
|
# XXX: we want to eventually make this be a deprecation warning and
|
|
|
|
# remove it.
|
|
|
|
_legacy_resolve_mode = False
|
|
|
|
_fast_resolve_mode = False
|
2008-04-07 16:39:54 +00:00
|
|
|
|
2020-06-22 13:54:08 +00:00
|
|
|
def __init__(self, environment, parent, name, blocks, globals=None):
|
2008-04-24 19:54:44 +00:00
|
|
|
self.parent = parent
|
2009-10-26 10:53:27 +00:00
|
|
|
self.vars = {}
|
2021-04-05 16:25:26 +00:00
|
|
|
self.environment: "Environment" = environment
|
2010-04-05 16:11:18 +00:00
|
|
|
self.eval_ctx = EvalContext(self.environment, name)
|
2008-04-24 19:54:44 +00:00
|
|
|
self.exported_vars = set()
|
2008-04-17 09:50:39 +00:00
|
|
|
self.name = name
|
2020-06-22 13:54:08 +00:00
|
|
|
self.globals_keys = set() if globals is None else set(globals)
|
2008-04-24 19:54:44 +00:00
|
|
|
|
|
|
|
# create the initial mapping of blocks. Whenever template inheritance
|
|
|
|
# takes place the runtime will update this mapping with the new blocks
|
|
|
|
# from the template.
|
2020-01-28 06:05:00 +00:00
|
|
|
self.blocks = {k: [v] for k, v in blocks.items()}
|
2008-04-11 20:21:00 +00:00
|
|
|
|
2017-01-12 19:10:58 +00:00
|
|
|
# In case we detect the fast resolve mode we can set up an alias
|
|
|
|
# here that bypasses the legacy code logic.
|
|
|
|
if self._fast_resolve_mode:
|
2017-03-15 18:19:04 +00:00
|
|
|
self.resolve_or_missing = MethodType(resolve_or_missing, self)
|
2017-01-12 19:10:58 +00:00
|
|
|
|
2008-04-24 19:54:44 +00:00
|
|
|
def super(self, name, current):
|
2008-04-13 21:18:05 +00:00
|
|
|
"""Render a parent block."""
|
2008-04-27 19:28:03 +00:00
|
|
|
try:
|
|
|
|
blocks = self.blocks[name]
|
2008-09-20 10:04:53 +00:00
|
|
|
index = blocks.index(current) + 1
|
|
|
|
blocks[index]
|
2008-04-27 19:28:03 +00:00
|
|
|
except LookupError:
|
2020-01-10 15:46:18 +00:00
|
|
|
return self.environment.undefined(
|
2020-01-29 04:16:59 +00:00
|
|
|
f"there is no parent block called {name!r}.", name="super"
|
2020-01-10 15:46:18 +00:00
|
|
|
)
|
2008-09-20 10:04:53 +00:00
|
|
|
return BlockReference(name, self, blocks, index)
|
2008-04-07 16:39:54 +00:00
|
|
|
|
2008-04-26 16:30:19 +00:00
|
|
|
def get(self, key, default=None):
|
2008-04-30 11:03:59 +00:00
|
|
|
"""Returns an item from the template context, if it doesn't exist
|
|
|
|
`default` is returned.
|
|
|
|
"""
|
2008-05-06 14:04:10 +00:00
|
|
|
try:
|
|
|
|
return self[key]
|
|
|
|
except KeyError:
|
|
|
|
return default
|
|
|
|
|
|
|
|
def resolve(self, key):
|
|
|
|
"""Looks up a variable like `__getitem__` or `get` but returns an
|
|
|
|
:class:`Undefined` object with the name of the name looked up.
|
|
|
|
"""
|
2017-01-12 19:10:58 +00:00
|
|
|
if self._legacy_resolve_mode:
|
|
|
|
rv = resolve_or_missing(self, key)
|
|
|
|
else:
|
|
|
|
rv = self.resolve_or_missing(key)
|
2017-01-02 11:09:52 +00:00
|
|
|
if rv is missing:
|
|
|
|
return self.environment.undefined(name=key)
|
|
|
|
return rv
|
|
|
|
|
|
|
|
def resolve_or_missing(self, key):
|
|
|
|
"""Resolves a variable like :meth:`resolve` but returns the
|
|
|
|
special `missing` value if it cannot be found.
|
|
|
|
"""
|
2017-01-12 19:10:58 +00:00
|
|
|
if self._legacy_resolve_mode:
|
|
|
|
rv = self.resolve(key)
|
|
|
|
if isinstance(rv, Undefined):
|
|
|
|
rv = missing
|
|
|
|
return rv
|
|
|
|
return resolve_or_missing(self, key)
|
2008-04-24 22:36:14 +00:00
|
|
|
|
2008-04-08 16:49:56 +00:00
|
|
|
def get_exported(self):
|
2008-04-24 19:54:44 +00:00
|
|
|
"""Get a new dict with the exported variables."""
|
2020-01-29 04:16:59 +00:00
|
|
|
return {k: self.vars[k] for k in self.exported_vars}
|
2008-04-24 19:54:44 +00:00
|
|
|
|
|
|
|
def get_all(self):
|
2017-01-07 14:35:21 +00:00
|
|
|
"""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.
|
2008-04-30 11:03:59 +00:00
|
|
|
"""
|
2017-01-07 14:35:21 +00:00
|
|
|
if not self.vars:
|
|
|
|
return self.parent
|
|
|
|
if not self.parent:
|
|
|
|
return self.vars
|
2008-04-24 19:54:44 +00:00
|
|
|
return dict(self.parent, **self.vars)
|
|
|
|
|
2009-02-24 21:58:00 +00:00
|
|
|
@internalcode
|
2020-01-10 18:40:52 +00:00
|
|
|
def call(__self, __obj, *args, **kwargs): # noqa: B902
|
2008-05-28 09:26:59 +00:00
|
|
|
"""Call the callable with the arguments and keyword arguments
|
|
|
|
provided but inject the active context or environment as first
|
2021-04-10 15:58:16 +00:00
|
|
|
argument if the callable has :func:`pass_context` or
|
|
|
|
:func:`pass_environment`.
|
2008-05-28 09:26:59 +00:00
|
|
|
"""
|
2008-05-26 11:35:58 +00:00
|
|
|
if __debug__:
|
2014-06-06 16:00:04 +00:00
|
|
|
__traceback_hide__ = True # noqa
|
2013-05-18 10:58:12 +00:00
|
|
|
|
2013-04-16 05:49:02 +00:00
|
|
|
# Allow callable classes to take a context
|
2021-04-10 15:58:16 +00:00
|
|
|
if (
|
|
|
|
hasattr(__obj, "__call__") # noqa: B004
|
|
|
|
and _PassArg.from_obj(__obj.__call__) is not None
|
|
|
|
):
|
|
|
|
__obj = __obj.__call__
|
2013-05-18 10:58:12 +00:00
|
|
|
|
2019-11-19 14:26:39 +00:00
|
|
|
if callable(__obj):
|
2021-04-10 15:58:16 +00:00
|
|
|
pass_arg = _PassArg.from_obj(__obj)
|
|
|
|
|
|
|
|
if pass_arg is _PassArg.context:
|
2020-06-23 14:53:59 +00:00
|
|
|
# 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"])
|
2008-05-26 11:35:58 +00:00
|
|
|
args = (__self,) + args
|
2021-04-10 15:58:16 +00:00
|
|
|
elif pass_arg is _PassArg.eval_context:
|
2010-03-14 18:43:47 +00:00
|
|
|
args = (__self.eval_ctx,) + args
|
2021-04-10 15:58:16 +00:00
|
|
|
elif pass_arg is _PassArg.environment:
|
2008-05-26 11:35:58 +00:00
|
|
|
args = (__self.environment,) + args
|
2020-06-23 14:53:59 +00:00
|
|
|
|
|
|
|
kwargs.pop("_block_vars", None)
|
|
|
|
kwargs.pop("_loop_vars", None)
|
2010-06-05 12:32:06 +00:00
|
|
|
try:
|
|
|
|
return __obj(*args, **kwargs)
|
|
|
|
except StopIteration:
|
2020-01-10 15:46:18 +00:00
|
|
|
return __self.environment.undefined(
|
2020-01-29 04:16:59 +00:00
|
|
|
"value was undefined because a callable raised a"
|
|
|
|
" StopIteration exception"
|
2020-01-10 15:46:18 +00:00
|
|
|
)
|
2008-05-24 22:16:51 +00:00
|
|
|
|
2009-02-19 14:56:53 +00:00
|
|
|
def derived(self, locals=None):
|
2017-01-08 10:21:32 +00:00
|
|
|
"""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.
|
|
|
|
"""
|
2020-01-10 15:46:18 +00:00
|
|
|
context = new_context(
|
|
|
|
self.environment, self.name, {}, self.get_all(), True, None, locals
|
|
|
|
)
|
2010-03-14 18:43:47 +00:00
|
|
|
context.eval_ctx = self.eval_ctx
|
2020-01-28 06:05:00 +00:00
|
|
|
context.blocks.update((k, list(v)) for k, v in self.blocks.items())
|
2009-02-19 15:11:11 +00:00
|
|
|
return context
|
2009-02-19 14:56:53 +00:00
|
|
|
|
2020-06-26 14:22:21 +00:00
|
|
|
# ignore: true
|
2020-01-10 18:40:52 +00:00
|
|
|
def _all(meth): # noqa: B902
|
|
|
|
def proxy(self):
|
|
|
|
return getattr(self.get_all(), meth)()
|
|
|
|
|
2008-05-06 14:04:10 +00:00
|
|
|
proxy.__doc__ = getattr(dict, meth).__doc__
|
|
|
|
proxy.__name__ = meth
|
|
|
|
return proxy
|
|
|
|
|
2020-06-26 14:22:21 +00:00
|
|
|
keys = _all("keys") # type:ignore
|
|
|
|
values = _all("values") # type:ignore
|
|
|
|
items = _all("items") # type:ignore
|
2008-05-06 14:04:10 +00:00
|
|
|
del _all
|
|
|
|
|
2008-04-24 22:36:14 +00:00
|
|
|
def __contains__(self, name):
|
|
|
|
return name in self.vars or name in self.parent
|
|
|
|
|
2008-04-24 19:54:44 +00:00
|
|
|
def __getitem__(self, key):
|
2008-05-18 22:23:37 +00:00
|
|
|
"""Lookup a variable or raise `KeyError` if the variable is
|
|
|
|
undefined.
|
|
|
|
"""
|
2017-01-02 11:09:52 +00:00
|
|
|
item = self.resolve_or_missing(key)
|
|
|
|
if item is missing:
|
2008-05-18 22:23:37 +00:00
|
|
|
raise KeyError(key)
|
|
|
|
return item
|
2008-04-08 16:09:13 +00:00
|
|
|
|
2008-04-11 20:21:00 +00:00
|
|
|
def __repr__(self):
|
2020-01-29 04:16:59 +00:00
|
|
|
return f"<{self.__class__.__name__} {self.get_all()!r} of {self.name!r}>"
|
2008-04-11 20:21:00 +00:00
|
|
|
|
|
|
|
|
2020-01-29 04:16:59 +00:00
|
|
|
class BlockReference:
|
2008-09-20 10:04:53 +00:00
|
|
|
"""One block on a template reference."""
|
|
|
|
|
|
|
|
def __init__(self, name, context, stack, depth):
|
|
|
|
self.name = name
|
|
|
|
self._context = context
|
|
|
|
self._stack = stack
|
|
|
|
self._depth = depth
|
|
|
|
|
|
|
|
@property
|
|
|
|
def super(self):
|
|
|
|
"""Super the block."""
|
|
|
|
if self._depth + 1 >= len(self._stack):
|
2020-01-10 15:46:18 +00:00
|
|
|
return self._context.environment.undefined(
|
2020-01-29 04:16:59 +00:00
|
|
|
f"there is no parent block called {self.name!r}.", name="super"
|
2020-01-10 15:46:18 +00:00
|
|
|
)
|
|
|
|
return BlockReference(self.name, self._context, self._stack, self._depth + 1)
|
2008-09-20 10:04:53 +00:00
|
|
|
|
2021-04-10 23:12:25 +00:00
|
|
|
@internalcode
|
|
|
|
async def _async_call(self):
|
|
|
|
rv = concat([x async for x in self._stack[self._depth](self._context)])
|
|
|
|
|
|
|
|
if self._context.eval_ctx.autoescape:
|
|
|
|
return Markup(rv)
|
|
|
|
|
|
|
|
return rv
|
|
|
|
|
2009-02-24 21:58:00 +00:00
|
|
|
@internalcode
|
2008-09-20 10:04:53 +00:00
|
|
|
def __call__(self):
|
2021-04-10 23:12:25 +00:00
|
|
|
if self._context.environment.is_async:
|
|
|
|
return self._async_call()
|
|
|
|
|
2008-09-20 10:04:53 +00:00
|
|
|
rv = concat(self._stack[self._depth](self._context))
|
2021-04-10 23:12:25 +00:00
|
|
|
|
2010-04-05 16:11:18 +00:00
|
|
|
if self._context.eval_ctx.autoescape:
|
2021-04-10 23:12:25 +00:00
|
|
|
return Markup(rv)
|
|
|
|
|
2008-09-20 10:04:53 +00:00
|
|
|
return rv
|
|
|
|
|
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
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
|
2008-04-09 10:14:24 +00:00
|
|
|
|
2021-02-24 17:47:57 +00:00
|
|
|
_length: t.Optional[int] = None
|
2019-11-07 21:35:57 +00:00
|
|
|
_after = missing
|
|
|
|
_current = missing
|
|
|
|
_before = missing
|
|
|
|
_last_changed_value = missing
|
2016-12-28 19:06:34 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
def __init__(self, iterable, undefined, recurse=None, depth0=0):
|
|
|
|
"""
|
|
|
|
: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)
|
2017-02-01 20:05:03 +00:00
|
|
|
self._undefined = undefined
|
2008-05-11 21:21:16 +00:00
|
|
|
self._recurse = recurse
|
2019-11-07 21:35:57 +00:00
|
|
|
#: How many levels deep a recursive loop currently is, starting at 0.
|
2013-05-20 08:26:57 +00:00
|
|
|
self.depth0 = depth0
|
2008-07-04 14:35:10 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
@staticmethod
|
|
|
|
def _to_iterator(iterable):
|
|
|
|
return iter(iterable)
|
2008-04-16 12:21:57 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
@property
|
|
|
|
def length(self):
|
|
|
|
"""Length of the iterable.
|
2017-02-01 20:47:17 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
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
|
2008-04-16 12:21:57 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
try:
|
|
|
|
self._length = len(self._iterable)
|
|
|
|
except TypeError:
|
|
|
|
iterable = list(self._iterator)
|
|
|
|
self._iterator = self._to_iterator(iterable)
|
|
|
|
self._length = len(iterable) + self.index + (self._after is not missing)
|
2017-02-01 20:05:03 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
return self._length
|
2017-02-01 20:05:03 +00:00
|
|
|
|
2008-04-16 12:21:57 +00:00
|
|
|
def __len__(self):
|
|
|
|
return self.length
|
2008-04-09 12:02:55 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
@property
|
|
|
|
def depth(self):
|
|
|
|
"""How many levels deep a recursive loop currently is, starting at 1."""
|
|
|
|
return self.depth0 + 1
|
2008-05-11 21:21:16 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
@property
|
|
|
|
def index(self):
|
|
|
|
"""Current iteration of the loop, starting at 1."""
|
|
|
|
return self.index0 + 1
|
2008-05-11 21:42:19 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
@property
|
|
|
|
def revindex0(self):
|
|
|
|
"""Number of iterations from the end of the loop, ending at 0.
|
2008-04-16 12:21:57 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
Requires calculating :attr:`length`.
|
|
|
|
"""
|
|
|
|
return self.length - self.index
|
2008-04-09 10:14:24 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
@property
|
|
|
|
def revindex(self):
|
|
|
|
"""Number of iterations from the end of the loop, ending at 1.
|
2016-12-28 19:06:34 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
Requires calculating :attr:`length`.
|
|
|
|
"""
|
|
|
|
return self.length - self.index0
|
2016-12-28 19:06:34 +00:00
|
|
|
|
2016-12-28 20:49:00 +00:00
|
|
|
@property
|
2019-11-07 21:35:57 +00:00
|
|
|
def first(self):
|
|
|
|
"""Whether this is the first iteration of the loop."""
|
|
|
|
return self.index0 == 0
|
|
|
|
|
|
|
|
def _peek_next(self):
|
|
|
|
"""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.
|
2019-05-06 21:17:43 +00:00
|
|
|
"""
|
2019-11-07 21:35:57 +00:00
|
|
|
if self._after is not missing:
|
|
|
|
return self._after
|
|
|
|
|
|
|
|
self._after = next(self._iterator, missing)
|
|
|
|
return self._after
|
|
|
|
|
|
|
|
@property
|
|
|
|
def last(self):
|
|
|
|
"""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.
|
2019-05-06 21:17:43 +00:00
|
|
|
"""
|
2019-11-07 21:35:57 +00:00
|
|
|
return self._peek_next() is missing
|
2016-12-28 20:49:00 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
@property
|
|
|
|
def previtem(self):
|
|
|
|
"""The item in the previous iteration. Undefined during the
|
|
|
|
first iteration.
|
|
|
|
"""
|
|
|
|
if self.first:
|
|
|
|
return self._undefined("there is no previous item")
|
2016-12-28 19:06:34 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
return self._before
|
|
|
|
|
|
|
|
@property
|
|
|
|
def nextitem(self):
|
|
|
|
"""The item in the next iteration. Undefined during the last
|
|
|
|
iteration.
|
2016-12-28 19:06:34 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
Causes the iterable to advance early. See
|
|
|
|
:func:`itertools.groupby` for issues this can cause.
|
|
|
|
The :func:`groupby` filter avoids that issue.
|
|
|
|
"""
|
|
|
|
rv = self._peek_next()
|
2016-12-28 19:06:34 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
if rv is missing:
|
|
|
|
return self._undefined("there is no next item")
|
2008-05-18 18:25:28 +00:00
|
|
|
|
2019-11-07 21:35:57 +00:00
|
|
|
return rv
|
|
|
|
|
|
|
|
def cycle(self, *args):
|
|
|
|
"""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):
|
|
|
|
"""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
|
2008-05-18 18:25:28 +00:00
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
return self
|
|
|
|
|
2013-05-17 22:06:22 +00:00
|
|
|
def __next__(self):
|
2019-11-07 21:35:57 +00:00
|
|
|
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
|
|
|
|
|
2019-11-20 20:38:16 +00:00
|
|
|
@internalcode
|
2019-11-07 21:35:57 +00:00
|
|
|
def __call__(self, iterable):
|
|
|
|
"""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(
|
2020-01-10 15:46:18 +00:00
|
|
|
"The loop must have the 'recursive' marker to be called recursively."
|
2019-11-07 21:35:57 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
return self._recurse(iterable, self._recurse, depth=self.depth)
|
|
|
|
|
|
|
|
def __repr__(self):
|
2020-01-29 04:16:59 +00:00
|
|
|
return f"<{self.__class__.__name__} {self.index}/{self.length}>"
|
2008-05-18 18:25:28 +00:00
|
|
|
|
|
|
|
|
2021-04-10 23:12:25 +00:00
|
|
|
class AsyncLoopContext(LoopContext):
|
|
|
|
@staticmethod
|
|
|
|
def _to_iterator(iterable):
|
|
|
|
return auto_aiter(iterable)
|
|
|
|
|
|
|
|
@property
|
|
|
|
async def length(self):
|
|
|
|
if self._length is not None:
|
|
|
|
return self._length
|
|
|
|
|
|
|
|
try:
|
|
|
|
self._length = len(self._iterable)
|
|
|
|
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):
|
|
|
|
return await self.length - self.index
|
|
|
|
|
|
|
|
@property
|
|
|
|
async def revindex(self):
|
|
|
|
return await self.length - self.index0
|
|
|
|
|
|
|
|
async def _peek_next(self):
|
|
|
|
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):
|
|
|
|
return await self._peek_next() is missing
|
|
|
|
|
|
|
|
@property
|
|
|
|
async def nextitem(self):
|
|
|
|
rv = await self._peek_next()
|
|
|
|
|
|
|
|
if rv is missing:
|
|
|
|
return self._undefined("there is no next item")
|
|
|
|
|
|
|
|
return rv
|
|
|
|
|
|
|
|
def __aiter__(self):
|
|
|
|
return self
|
|
|
|
|
|
|
|
async def __anext__(self):
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2020-01-29 04:16:59 +00:00
|
|
|
class Macro:
|
2010-06-05 12:32:06 +00:00
|
|
|
"""Wraps a macro function."""
|
2008-04-08 16:09:13 +00:00
|
|
|
|
2020-01-10 15:46:18 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
environment,
|
|
|
|
func,
|
|
|
|
name,
|
|
|
|
arguments,
|
|
|
|
catch_kwargs,
|
|
|
|
catch_varargs,
|
|
|
|
caller,
|
|
|
|
default_autoescape=None,
|
|
|
|
):
|
2008-04-14 20:53:58 +00:00
|
|
|
self._environment = environment
|
2008-04-12 12:19:36 +00:00
|
|
|
self._func = func
|
2008-04-29 11:43:16 +00:00
|
|
|
self._argument_count = len(arguments)
|
2008-04-08 16:09:13 +00:00
|
|
|
self.name = name
|
|
|
|
self.arguments = arguments
|
2008-04-25 09:44:59 +00:00
|
|
|
self.catch_kwargs = catch_kwargs
|
|
|
|
self.catch_varargs = catch_varargs
|
2008-04-12 12:19:36 +00:00
|
|
|
self.caller = caller
|
2020-01-10 15:46:18 +00:00
|
|
|
self.explicit_caller = "caller" in arguments
|
2017-01-06 13:29:23 +00:00
|
|
|
if default_autoescape is None:
|
|
|
|
default_autoescape = environment.autoescape
|
|
|
|
self._default_autoescape = default_autoescape
|
2008-04-08 16:09:13 +00:00
|
|
|
|
2009-02-24 21:58:00 +00:00
|
|
|
@internalcode
|
2021-04-10 15:58:16 +00:00
|
|
|
@pass_eval_context
|
2008-04-08 16:09:13 +00:00
|
|
|
def __call__(self, *args, **kwargs):
|
2017-01-06 13:29:23 +00:00
|
|
|
# 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
|
2019-09-04 22:12:07 +00:00
|
|
|
# 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.
|
2017-01-06 13:29:23 +00:00
|
|
|
#
|
|
|
|
# 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
|
2019-07-07 13:07:29 +00:00
|
|
|
# argument to callables otherwise anyway. Worst case here is
|
2017-01-06 13:29:23 +00:00
|
|
|
# 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
|
|
|
|
|
2010-06-05 12:32:06 +00:00
|
|
|
# try to consume the positional arguments
|
2020-01-10 15:46:18 +00:00
|
|
|
arguments = list(args[: self._argument_count])
|
2010-06-05 12:32:06 +00:00
|
|
|
off = len(arguments)
|
|
|
|
|
2017-01-08 01:16:41 +00:00
|
|
|
# For information why this is necessary refer to the handling
|
|
|
|
# of caller in the `macro_body` handler in the compiler.
|
|
|
|
found_caller = False
|
|
|
|
|
2010-06-05 12:32:06 +00:00
|
|
|
# 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:
|
2020-01-10 18:40:52 +00:00
|
|
|
for name in self.arguments[len(arguments) :]:
|
2008-04-08 16:49:56 +00:00
|
|
|
try:
|
|
|
|
value = kwargs.pop(name)
|
2010-06-05 12:32:06 +00:00
|
|
|
except KeyError:
|
2017-01-03 17:19:31 +00:00
|
|
|
value = missing
|
2020-01-10 15:46:18 +00:00
|
|
|
if name == "caller":
|
2017-01-08 01:16:41 +00:00
|
|
|
found_caller = True
|
2010-06-05 12:32:06 +00:00
|
|
|
arguments.append(value)
|
2017-01-08 01:16:41 +00:00
|
|
|
else:
|
|
|
|
found_caller = self.explicit_caller
|
2008-04-26 14:26:52 +00:00
|
|
|
|
|
|
|
# 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!
|
2017-01-08 01:16:41 +00:00
|
|
|
if self.caller and not found_caller:
|
2020-01-10 15:46:18 +00:00
|
|
|
caller = kwargs.pop("caller", None)
|
2008-04-12 12:19:36 +00:00
|
|
|
if caller is None:
|
2020-01-10 15:46:18 +00:00
|
|
|
caller = self._environment.undefined("No caller defined", name="caller")
|
2008-04-26 14:26:52 +00:00
|
|
|
arguments.append(caller)
|
2017-01-08 01:16:41 +00:00
|
|
|
|
2008-04-25 09:44:59 +00:00
|
|
|
if self.catch_kwargs:
|
2008-04-26 14:26:52 +00:00
|
|
|
arguments.append(kwargs)
|
2008-04-25 09:44:59 +00:00
|
|
|
elif kwargs:
|
2020-01-10 15:46:18 +00:00
|
|
|
if "caller" in kwargs:
|
|
|
|
raise TypeError(
|
2020-01-29 04:16:59 +00:00
|
|
|
f"macro {self.name!r} was invoked with two values for the special"
|
|
|
|
" caller argument. This is most likely a bug."
|
2020-01-10 15:46:18 +00:00
|
|
|
)
|
|
|
|
raise TypeError(
|
2020-01-29 04:16:59 +00:00
|
|
|
f"macro {self.name!r} takes no keyword argument {next(iter(kwargs))!r}"
|
2020-01-10 15:46:18 +00:00
|
|
|
)
|
2008-04-25 09:44:59 +00:00
|
|
|
if self.catch_varargs:
|
2020-01-10 15:46:18 +00:00
|
|
|
arguments.append(args[self._argument_count :])
|
2008-05-01 10:49:53 +00:00
|
|
|
elif len(args) > self._argument_count:
|
2020-01-10 15:46:18 +00:00
|
|
|
raise TypeError(
|
2020-01-29 04:16:59 +00:00
|
|
|
f"macro {self.name!r} takes not more than"
|
|
|
|
f" {len(self.arguments)} argument(s)"
|
2020-01-10 15:46:18 +00:00
|
|
|
)
|
2017-01-06 13:29:23 +00:00
|
|
|
|
2017-01-28 14:33:09 +00:00
|
|
|
return self._invoke(arguments, autoescape)
|
|
|
|
|
2021-04-10 23:12:25 +00:00
|
|
|
async def _async_invoke(self, arguments, autoescape):
|
|
|
|
rv = await self._func(*arguments)
|
|
|
|
|
|
|
|
if autoescape:
|
|
|
|
return Markup(rv)
|
|
|
|
|
|
|
|
return rv
|
|
|
|
|
2017-01-28 14:33:09 +00:00
|
|
|
def _invoke(self, arguments, autoescape):
|
2021-04-10 23:12:25 +00:00
|
|
|
if self._environment.is_async:
|
|
|
|
return self._async_invoke(arguments, autoescape)
|
|
|
|
|
2017-01-06 13:29:23 +00:00
|
|
|
rv = self._func(*arguments)
|
2021-04-10 23:12:25 +00:00
|
|
|
|
2017-01-06 13:29:23 +00:00
|
|
|
if autoescape:
|
|
|
|
rv = Markup(rv)
|
2021-04-10 23:12:25 +00:00
|
|
|
|
2017-01-06 13:29:23 +00:00
|
|
|
return rv
|
2008-04-12 12:19:36 +00:00
|
|
|
|
|
|
|
def __repr__(self):
|
2020-01-29 04:16:59 +00:00
|
|
|
name = "anonymous" if self.name is None else repr(self.name)
|
|
|
|
return f"<{self.__class__.__name__} {name}>"
|
2008-04-09 12:02:55 +00:00
|
|
|
|
|
|
|
|
2020-01-29 04:16:59 +00:00
|
|
|
class Undefined:
|
2008-04-28 10:20:12 +00:00
|
|
|
"""The default undefined type. This undefined type can be printed and
|
2019-05-08 14:47:33 +00:00
|
|
|
iterated over, but every other access will raise an :exc:`UndefinedError`:
|
2008-04-28 10:20:12 +00:00
|
|
|
|
|
|
|
>>> foo = Undefined(name='foo')
|
|
|
|
>>> str(foo)
|
|
|
|
''
|
|
|
|
>>> not foo
|
|
|
|
True
|
|
|
|
>>> foo + 42
|
|
|
|
Traceback (most recent call last):
|
|
|
|
...
|
2020-01-27 05:12:52 +00:00
|
|
|
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
2008-04-14 20:53:58 +00:00
|
|
|
"""
|
2020-01-10 15:46:18 +00:00
|
|
|
|
|
|
|
__slots__ = (
|
|
|
|
"_undefined_hint",
|
|
|
|
"_undefined_obj",
|
|
|
|
"_undefined_name",
|
|
|
|
"_undefined_exception",
|
|
|
|
)
|
2008-04-09 12:02:55 +00:00
|
|
|
|
2010-04-12 11:51:33 +00:00
|
|
|
def __init__(self, hint=None, obj=missing, name=None, exc=UndefinedError):
|
2008-04-17 16:44:07 +00:00
|
|
|
self._undefined_hint = hint
|
|
|
|
self._undefined_obj = obj
|
|
|
|
self._undefined_name = name
|
2008-05-07 10:17:18 +00:00
|
|
|
self._undefined_exception = exc
|
2008-04-09 12:02:55 +00:00
|
|
|
|
2019-12-05 21:46:36 +00:00
|
|
|
@property
|
|
|
|
def _undefined_message(self):
|
|
|
|
"""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:
|
2020-01-29 04:16:59 +00:00
|
|
|
return f"{self._undefined_name!r} is undefined"
|
2019-12-05 21:46:36 +00:00
|
|
|
|
2020-01-28 06:05:00 +00:00
|
|
|
if not isinstance(self._undefined_name, str):
|
2020-01-29 04:16:59 +00:00
|
|
|
return (
|
|
|
|
f"{object_type_repr(self._undefined_obj)} has no"
|
|
|
|
f" element {self._undefined_name!r}"
|
2019-12-05 21:46:36 +00:00
|
|
|
)
|
|
|
|
|
2020-01-29 04:16:59 +00:00
|
|
|
return (
|
|
|
|
f"{object_type_repr(self._undefined_obj)!r} has no"
|
|
|
|
f" attribute {self._undefined_name!r}"
|
2019-12-05 21:46:36 +00:00
|
|
|
)
|
|
|
|
|
2009-02-24 21:58:00 +00:00
|
|
|
@internalcode
|
2008-05-28 09:26:59 +00:00
|
|
|
def _fail_with_undefined_error(self, *args, **kwargs):
|
2019-12-05 21:46:36 +00:00
|
|
|
"""Raise an :exc:`UndefinedError` when operations are performed
|
|
|
|
on the undefined value.
|
2008-05-28 09:26:59 +00:00
|
|
|
"""
|
2019-12-05 21:46:36 +00:00
|
|
|
raise self._undefined_exception(self._undefined_message)
|
2008-05-28 09:26:59 +00:00
|
|
|
|
2010-11-19 12:51:38 +00:00
|
|
|
@internalcode
|
|
|
|
def __getattr__(self, name):
|
2020-01-10 15:46:18 +00:00
|
|
|
if name[:2] == "__":
|
2010-11-19 12:51:38 +00:00
|
|
|
raise AttributeError(name)
|
|
|
|
return self._fail_with_undefined_error()
|
|
|
|
|
2020-01-28 06:05:00 +00:00
|
|
|
__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
|
2020-05-09 20:04:28 +00:00
|
|
|
__call__ = __getitem__ = __contains__ = _fail_with_undefined_error
|
2020-01-28 06:05:00 +00:00
|
|
|
__lt__ = __le__ = __gt__ = __ge__ = _fail_with_undefined_error
|
|
|
|
__int__ = __float__ = __complex__ = _fail_with_undefined_error
|
|
|
|
__pow__ = __rpow__ = _fail_with_undefined_error
|
2008-04-14 20:53:58 +00:00
|
|
|
|
2013-08-07 11:48:37 +00:00
|
|
|
def __eq__(self, other):
|
|
|
|
return type(self) is type(other)
|
|
|
|
|
|
|
|
def __ne__(self, other):
|
|
|
|
return not self.__eq__(other)
|
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
return id(type(self))
|
|
|
|
|
2013-05-20 00:51:26 +00:00
|
|
|
def __str__(self):
|
2020-01-29 04:16:59 +00:00
|
|
|
return ""
|
2008-04-24 19:54:44 +00:00
|
|
|
|
2008-04-09 12:02:55 +00:00
|
|
|
def __len__(self):
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def __iter__(self):
|
2021-02-24 17:47:57 +00:00
|
|
|
yield from ()
|
2008-04-14 20:53:58 +00:00
|
|
|
|
2020-09-23 10:34:58 +00:00
|
|
|
async def __aiter__(self):
|
|
|
|
for _ in ():
|
|
|
|
yield
|
|
|
|
|
2020-01-28 06:05:00 +00:00
|
|
|
def __bool__(self):
|
2008-04-14 20:53:58 +00:00
|
|
|
return False
|
2020-01-10 15:46:18 +00:00
|
|
|
|
2008-05-28 09:26:59 +00:00
|
|
|
def __repr__(self):
|
2020-01-10 15:46:18 +00:00
|
|
|
return "Undefined"
|
2008-05-28 09:26:59 +00:00
|
|
|
|
2008-04-14 20:53:58 +00:00
|
|
|
|
2014-06-06 16:00:04 +00:00
|
|
|
def make_logging_undefined(logger=None, base=None):
|
2014-06-06 16:14:45 +00:00
|
|
|
"""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`.
|
2014-06-06 16:00:04 +00:00
|
|
|
"""
|
|
|
|
if logger is None:
|
|
|
|
import logging
|
2020-01-10 15:46:18 +00:00
|
|
|
|
2014-06-06 16:00:04 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
logger.addHandler(logging.StreamHandler(sys.stderr))
|
|
|
|
if base is None:
|
|
|
|
base = Undefined
|
|
|
|
|
2014-06-06 16:14:45 +00:00
|
|
|
def _log_message(undef):
|
2020-01-29 04:16:59 +00:00
|
|
|
logger.warning("Template variable warning: %s", undef._undefined_message)
|
2014-06-06 16:14:45 +00:00
|
|
|
|
2014-06-06 16:00:04 +00:00
|
|
|
class LoggingUndefined(base):
|
2014-06-06 16:14:45 +00:00
|
|
|
def _fail_with_undefined_error(self, *args, **kwargs):
|
|
|
|
try:
|
2020-01-29 04:16:59 +00:00
|
|
|
return super()._fail_with_undefined_error(*args, **kwargs)
|
2014-06-06 16:14:45 +00:00
|
|
|
except self._undefined_exception as e:
|
2021-01-31 07:07:30 +00:00
|
|
|
logger.error("Template variable error: %s", e)
|
2014-06-06 16:14:45 +00:00
|
|
|
raise e
|
|
|
|
|
2014-06-06 16:00:04 +00:00
|
|
|
def __str__(self):
|
2014-06-06 16:14:45 +00:00
|
|
|
_log_message(self)
|
2020-01-29 04:16:59 +00:00
|
|
|
return super().__str__()
|
2014-06-06 16:14:45 +00:00
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
_log_message(self)
|
2020-01-29 04:16:59 +00:00
|
|
|
return super().__iter__()
|
2014-06-06 16:14:45 +00:00
|
|
|
|
2020-01-28 06:05:00 +00:00
|
|
|
def __bool__(self):
|
|
|
|
_log_message(self)
|
2020-01-29 04:16:59 +00:00
|
|
|
return super().__bool__()
|
2014-06-06 16:00:04 +00:00
|
|
|
|
|
|
|
return LoggingUndefined
|
|
|
|
|
|
|
|
|
2019-05-08 14:47:33 +00:00
|
|
|
class ChainableUndefined(Undefined):
|
2019-08-10 21:08:39 +00:00
|
|
|
"""An undefined that is chainable, where both ``__getattr__`` and
|
|
|
|
``__getitem__`` return itself rather than raising an
|
|
|
|
:exc:`UndefinedError`.
|
2019-05-08 14:47:33 +00:00
|
|
|
|
|
|
|
>>> foo = ChainableUndefined(name='foo')
|
|
|
|
>>> str(foo.bar['baz'])
|
|
|
|
''
|
|
|
|
>>> foo.bar['baz'] + 42
|
|
|
|
Traceback (most recent call last):
|
|
|
|
...
|
2020-01-27 05:12:52 +00:00
|
|
|
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
2019-05-08 14:47:33 +00:00
|
|
|
|
2019-08-10 21:08:39 +00:00
|
|
|
.. versionadded:: 2.11.0
|
2019-05-08 14:47:33 +00:00
|
|
|
"""
|
2020-01-10 15:46:18 +00:00
|
|
|
|
2019-05-08 14:47:33 +00:00
|
|
|
__slots__ = ()
|
|
|
|
|
2019-08-10 21:08:39 +00:00
|
|
|
def __html__(self):
|
|
|
|
return self.__str__()
|
|
|
|
|
2019-05-08 14:47:33 +00:00
|
|
|
def __getattr__(self, _):
|
|
|
|
return self
|
|
|
|
|
|
|
|
__getitem__ = __getattr__
|
|
|
|
|
|
|
|
|
2008-04-14 20:53:58 +00:00
|
|
|
class DebugUndefined(Undefined):
|
2008-04-28 10:20:12 +00:00
|
|
|
"""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):
|
|
|
|
...
|
2020-01-27 05:12:52 +00:00
|
|
|
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
2008-04-28 10:20:12 +00:00
|
|
|
"""
|
2020-01-10 15:46:18 +00:00
|
|
|
|
2008-04-26 16:30:19 +00:00
|
|
|
__slots__ = ()
|
2008-04-14 20:53:58 +00:00
|
|
|
|
2013-05-20 00:51:26 +00:00
|
|
|
def __str__(self):
|
2020-01-29 04:16:59 +00:00
|
|
|
if self._undefined_hint:
|
|
|
|
message = f"undefined value printed: {self._undefined_hint}"
|
|
|
|
|
|
|
|
elif self._undefined_obj is missing:
|
|
|
|
message = self._undefined_name
|
|
|
|
|
|
|
|
else:
|
|
|
|
message = (
|
|
|
|
f"no such element: {object_type_repr(self._undefined_obj)}"
|
|
|
|
f"[{self._undefined_name!r}]"
|
2008-04-17 16:44:07 +00:00
|
|
|
)
|
2020-01-29 04:16:59 +00:00
|
|
|
|
|
|
|
return f"{{{{ {message} }}}}"
|
2008-04-14 20:53:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
class StrictUndefined(Undefined):
|
2008-04-24 19:54:44 +00:00
|
|
|
"""An undefined that barks on print and iteration as well as boolean
|
2008-04-26 16:30:19 +00:00
|
|
|
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.
|
2008-04-28 10:20:12 +00:00
|
|
|
|
|
|
|
>>> foo = StrictUndefined(name='foo')
|
|
|
|
>>> str(foo)
|
|
|
|
Traceback (most recent call last):
|
|
|
|
...
|
2020-01-27 05:12:52 +00:00
|
|
|
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
2008-04-28 10:20:12 +00:00
|
|
|
>>> not foo
|
|
|
|
Traceback (most recent call last):
|
|
|
|
...
|
2020-01-27 05:12:52 +00:00
|
|
|
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
2008-04-28 10:20:12 +00:00
|
|
|
>>> foo + 42
|
|
|
|
Traceback (most recent call last):
|
|
|
|
...
|
2020-01-27 05:12:52 +00:00
|
|
|
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
2008-04-17 17:04:44 +00:00
|
|
|
"""
|
2020-01-10 15:46:18 +00:00
|
|
|
|
2008-04-26 16:30:19 +00:00
|
|
|
__slots__ = ()
|
2020-01-28 06:05:00 +00:00
|
|
|
__iter__ = __str__ = __len__ = Undefined._fail_with_undefined_error
|
|
|
|
__eq__ = __ne__ = __bool__ = __hash__ = Undefined._fail_with_undefined_error
|
2008-04-26 16:30:19 +00:00
|
|
|
|
2008-04-14 20:53:58 +00:00
|
|
|
|
2020-01-28 06:05:00 +00:00
|
|
|
# Remove slots attributes, after the metaclass is applied they are
|
|
|
|
# unneeded and contain wrong data for subclasses.
|
2020-01-10 15:46:18 +00:00
|
|
|
del (
|
|
|
|
Undefined.__slots__,
|
|
|
|
ChainableUndefined.__slots__,
|
|
|
|
DebugUndefined.__slots__,
|
|
|
|
StrictUndefined.__slots__,
|
|
|
|
)
|