| # -*- coding: utf-8 -*- |
| """ |
| jinja2.runtime |
| ~~~~~~~~~~~~~~ |
| |
| Runtime helpers. |
| |
| :copyright: (c) 2017 by the Jinja Team. |
| :license: BSD. |
| """ |
| import sys |
| |
| from itertools import chain |
| from types import MethodType |
| |
| from jinja2.nodes import EvalContext, _context_function_types |
| from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \ |
| internalcode, object_type_repr, evalcontextfunction, Namespace |
| from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \ |
| TemplateNotFound |
| from jinja2._compat import imap, text_type, iteritems, \ |
| implements_iterator, implements_to_string, string_types, PY2, \ |
| with_metaclass |
| |
| |
| # these variables are exported to the template runtime |
| __all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup', |
| 'TemplateRuntimeError', 'missing', 'concat', 'escape', |
| 'markup_join', 'unicode_join', 'to_string', 'identity', |
| 'TemplateNotFound', 'Namespace'] |
| |
| #: the name of the function that is used to convert something into |
| #: a string. We can just use the text type here. |
| to_string = text_type |
| |
| #: the identity function. Useful for certain things in the environment |
| identity = lambda x: x |
| |
| _first_iteration = object() |
| _last_iteration = object() |
| |
| |
| def markup_join(seq): |
| """Concatenation that escapes if necessary and converts to unicode.""" |
| buf = [] |
| iterator = imap(soft_unicode, seq) |
| for arg in iterator: |
| buf.append(arg) |
| if hasattr(arg, '__html__'): |
| return Markup(u'').join(chain(buf, iterator)) |
| return concat(buf) |
| |
| |
| def unicode_join(seq): |
| """Simple args to unicode conversion and concatenation.""" |
| return concat(imap(text_type, seq)) |
| |
| |
| def new_context(environment, template_name, blocks, vars=None, |
| shared=None, globals=None, locals=None): |
| """Internal helper to 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 iteritems(locals): |
| if value is not missing: |
| parent[key] = value |
| return environment.context_class(environment, parent, template_name, |
| blocks) |
| |
| |
| class TemplateReference(object): |
| """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): |
| return '<%s %r>' % ( |
| self.__class__.__name__, |
| self.__context.name |
| ) |
| |
| |
| def _get_func(x): |
| return getattr(x, '__func__', x) |
| |
| |
| class ContextMeta(type): |
| |
| def __new__(cls, name, bases, d): |
| rv = type.__new__(cls, name, bases, d) |
| 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. |
| if resolve is not default_resolve and \ |
| resolve_or_missing is default_resolve_or_missing: |
| rv._legacy_resolve_mode = True |
| elif resolve is default_resolve and \ |
| resolve_or_missing is default_resolve_or_missing: |
| 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 |
| |
| |
| class Context(with_metaclass(ContextMeta)): |
| """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:`contextfunction`\\s 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. |
| """ |
| # XXX: we want to eventually make this be a deprecation warning and |
| # remove it. |
| _legacy_resolve_mode = False |
| _fast_resolve_mode = False |
| |
| def __init__(self, environment, parent, name, blocks): |
| self.parent = parent |
| self.vars = {} |
| self.environment = environment |
| self.eval_ctx = EvalContext(self.environment, name) |
| self.exported_vars = set() |
| self.name = name |
| |
| # 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 = dict((k, [v]) for k, v in iteritems(blocks)) |
| |
| # 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: |
| self.resolve_or_missing = MethodType(resolve_or_missing, self) |
| |
| def super(self, name, current): |
| """Render a parent block.""" |
| try: |
| blocks = self.blocks[name] |
| index = blocks.index(current) + 1 |
| blocks[index] |
| except LookupError: |
| return self.environment.undefined('there is no parent block ' |
| 'called %r.' % name, |
| name='super') |
| return BlockReference(name, self, blocks, index) |
| |
| def get(self, key, default=None): |
| """Returns an item from the template context, if it doesn't exist |
| `default` is returned. |
| """ |
| 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. |
| """ |
| if self._legacy_resolve_mode: |
| rv = resolve_or_missing(self, key) |
| else: |
| rv = self.resolve_or_missing(key) |
| 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. |
| """ |
| if self._legacy_resolve_mode: |
| rv = self.resolve(key) |
| if isinstance(rv, Undefined): |
| rv = missing |
| return rv |
| return resolve_or_missing(self, key) |
| |
| def get_exported(self): |
| """Get a new dict with the exported variables.""" |
| return dict((k, self.vars[k]) for k in self.exported_vars) |
| |
| def get_all(self): |
| """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, *args, **kwargs): |
| """Call the callable with the arguments and keyword arguments |
| provided but inject the active context or environment as first |
| argument if the callable is a :func:`contextfunction` or |
| :func:`environmentfunction`. |
| """ |
| if __debug__: |
| __traceback_hide__ = True # noqa |
| |
| # Allow callable classes to take a context |
| if hasattr(__obj, '__call__'): |
| fn = __obj.__call__ |
| for fn_type in ('contextfunction', |
| 'evalcontextfunction', |
| 'environmentfunction'): |
| if hasattr(fn, fn_type): |
| __obj = fn |
| break |
| |
| if isinstance(__obj, _context_function_types): |
| if getattr(__obj, 'contextfunction', 0): |
| args = (__self,) + args |
| elif getattr(__obj, 'evalcontextfunction', 0): |
| args = (__self.eval_ctx,) + args |
| elif getattr(__obj, 'environmentfunction', 0): |
| args = (__self.environment,) + args |
| 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=None): |
| """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 iteritems(self.blocks)) |
| return context |
| |
| def _all(meth): |
| proxy = lambda self: getattr(self.get_all(), meth)() |
| proxy.__doc__ = getattr(dict, meth).__doc__ |
| proxy.__name__ = meth |
| return proxy |
| |
| keys = _all('keys') |
| values = _all('values') |
| items = _all('items') |
| |
| # not available on python 3 |
| if PY2: |
| iterkeys = _all('iterkeys') |
| itervalues = _all('itervalues') |
| iteritems = _all('iteritems') |
| del _all |
| |
| def __contains__(self, name): |
| return name in self.vars or name in self.parent |
| |
| def __getitem__(self, key): |
| """Lookup a variable or raise `KeyError` if the variable is |
| undefined. |
| """ |
| item = self.resolve_or_missing(key) |
| if item is missing: |
| raise KeyError(key) |
| return item |
| |
| def __repr__(self): |
| return '<%s %s of %r>' % ( |
| self.__class__.__name__, |
| repr(self.get_all()), |
| self.name |
| ) |
| |
| |
| # register the context as mapping if possible |
| try: |
| from collections import Mapping |
| Mapping.register(Context) |
| except ImportError: |
| pass |
| |
| |
| class BlockReference(object): |
| """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): |
| return self._context.environment. \ |
| undefined('there is no parent block called %r.' % |
| self.name, name='super') |
| return BlockReference(self.name, self._context, self._stack, |
| self._depth + 1) |
| |
| @internalcode |
| def __call__(self): |
| rv = concat(self._stack[self._depth](self._context)) |
| if self._context.eval_ctx.autoescape: |
| rv = Markup(rv) |
| return rv |
| |
| |
| class LoopContextBase(object): |
| """A loop context for dynamic iteration.""" |
| |
| _before = _first_iteration |
| _current = _first_iteration |
| _after = _last_iteration |
| _length = None |
| |
| def __init__(self, undefined, recurse=None, depth0=0): |
| self._undefined = undefined |
| self._recurse = recurse |
| self.index0 = -1 |
| self.depth0 = depth0 |
| self._last_checked_value = missing |
| |
| def cycle(self, *args): |
| """Cycles among the arguments with the current loop index.""" |
| if not args: |
| raise TypeError('no items for cycling given') |
| return args[self.index0 % len(args)] |
| |
| def changed(self, *value): |
| """Checks whether the value has changed since the last call.""" |
| if self._last_checked_value != value: |
| self._last_checked_value = value |
| return True |
| return False |
| |
| first = property(lambda x: x.index0 == 0) |
| last = property(lambda x: x._after is _last_iteration) |
| index = property(lambda x: x.index0 + 1) |
| revindex = property(lambda x: x.length - x.index0) |
| revindex0 = property(lambda x: x.length - x.index) |
| depth = property(lambda x: x.depth0 + 1) |
| |
| @property |
| def previtem(self): |
| if self._before is _first_iteration: |
| return self._undefined('there is no previous item') |
| return self._before |
| |
| @property |
| def nextitem(self): |
| if self._after is _last_iteration: |
| return self._undefined('there is no next item') |
| return self._after |
| |
| def __len__(self): |
| return self.length |
| |
| @internalcode |
| def loop(self, iterable): |
| if self._recurse is None: |
| raise TypeError('Tried to call non recursive loop. Maybe you ' |
| "forgot the 'recursive' modifier.") |
| return self._recurse(iterable, self._recurse, self.depth0 + 1) |
| |
| # a nifty trick to enhance the error message if someone tried to call |
| # the the loop without or with too many arguments. |
| __call__ = loop |
| del loop |
| |
| def __repr__(self): |
| return '<%s %r/%r>' % ( |
| self.__class__.__name__, |
| self.index, |
| self.length |
| ) |
| |
| |
| class LoopContext(LoopContextBase): |
| |
| def __init__(self, iterable, undefined, recurse=None, depth0=0): |
| LoopContextBase.__init__(self, undefined, recurse, depth0) |
| self._iterator = iter(iterable) |
| |
| # try to get the length of the iterable early. This must be done |
| # here because there are some broken iterators around where there |
| # __len__ is the number of iterations left (i'm looking at your |
| # listreverseiterator!). |
| try: |
| self._length = len(iterable) |
| except (TypeError, AttributeError): |
| self._length = None |
| self._after = self._safe_next() |
| |
| @property |
| def length(self): |
| if self._length is None: |
| # if was not possible to get the length of the iterator when |
| # the loop context was created (ie: iterating over a generator) |
| # we have to convert the iterable into a sequence and use the |
| # length of that + the number of iterations so far. |
| iterable = tuple(self._iterator) |
| self._iterator = iter(iterable) |
| iterations_done = self.index0 + 2 |
| self._length = len(iterable) + iterations_done |
| return self._length |
| |
| def __iter__(self): |
| return LoopContextIterator(self) |
| |
| def _safe_next(self): |
| try: |
| return next(self._iterator) |
| except StopIteration: |
| return _last_iteration |
| |
| |
| @implements_iterator |
| class LoopContextIterator(object): |
| """The iterator for a loop context.""" |
| __slots__ = ('context',) |
| |
| def __init__(self, context): |
| self.context = context |
| |
| def __iter__(self): |
| return self |
| |
| def __next__(self): |
| ctx = self.context |
| ctx.index0 += 1 |
| if ctx._after is _last_iteration: |
| raise StopIteration() |
| ctx._before = ctx._current |
| ctx._current = ctx._after |
| ctx._after = ctx._safe_next() |
| return ctx._current, ctx |
| |
| |
| class Macro(object): |
| """Wraps a macro function.""" |
| |
| def __init__(self, environment, func, name, arguments, |
| catch_kwargs, catch_varargs, caller, |
| default_autoescape=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: |
| default_autoescape = environment.autoescape |
| self._default_autoescape = default_autoescape |
| |
| @internalcode |
| @evalcontextfunction |
| def __call__(self, *args, **kwargs): |
| # 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 |
| # problemtic for macros because if a macro is safe or not not so |
| # much depends on the escape mode when it was defined but 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 anwyays. 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 idx, name in enumerate(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('macro %r was invoked with two values for ' |
| 'the special caller argument. This is ' |
| 'most likely a bug.' % self.name) |
| raise TypeError('macro %r takes no keyword argument %r' % |
| (self.name, next(iter(kwargs)))) |
| if self.catch_varargs: |
| arguments.append(args[self._argument_count:]) |
| elif len(args) > self._argument_count: |
| raise TypeError('macro %r takes not more than %d argument(s)' % |
| (self.name, len(self.arguments))) |
| |
| return self._invoke(arguments, autoescape) |
| |
| def _invoke(self, arguments, autoescape): |
| """This method is being swapped out by the async implementation.""" |
| rv = self._func(*arguments) |
| if autoescape: |
| rv = Markup(rv) |
| return rv |
| |
| def __repr__(self): |
| return '<%s %s>' % ( |
| self.__class__.__name__, |
| self.name is None and 'anonymous' or repr(self.name) |
| ) |
| |
| |
| @implements_to_string |
| class Undefined(object): |
| """The default undefined type. This undefined type can be printed and |
| iterated over, but every other access will raise an :exc:`jinja2.exceptions.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=None, obj=missing, name=None, exc=UndefinedError): |
| self._undefined_hint = hint |
| self._undefined_obj = obj |
| self._undefined_name = name |
| self._undefined_exception = exc |
| |
| @internalcode |
| def _fail_with_undefined_error(self, *args, **kwargs): |
| """Regular callback function for undefined objects that raises an |
| `jinja2.exceptions.UndefinedError` on call. |
| """ |
| if self._undefined_hint is None: |
| if self._undefined_obj is missing: |
| hint = '%r is undefined' % self._undefined_name |
| elif not isinstance(self._undefined_name, string_types): |
| hint = '%s has no element %r' % ( |
| object_type_repr(self._undefined_obj), |
| self._undefined_name |
| ) |
| else: |
| hint = '%r has no attribute %r' % ( |
| object_type_repr(self._undefined_obj), |
| self._undefined_name |
| ) |
| else: |
| hint = self._undefined_hint |
| raise self._undefined_exception(hint) |
| |
| @internalcode |
| def __getattr__(self, name): |
| if name[:2] == '__': |
| raise AttributeError(name) |
| return self._fail_with_undefined_error() |
| |
| __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \ |
| __truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \ |
| __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \ |
| __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \ |
| __float__ = __complex__ = __pow__ = __rpow__ = __sub__ = \ |
| __rsub__ = _fail_with_undefined_error |
| |
| 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)) |
| |
| def __str__(self): |
| return u'' |
| |
| def __len__(self): |
| return 0 |
| |
| def __iter__(self): |
| if 0: |
| yield None |
| |
| def __nonzero__(self): |
| return False |
| __bool__ = __nonzero__ |
| |
| def __repr__(self): |
| return 'Undefined' |
| |
| |
| def make_logging_undefined(logger=None, base=None): |
| """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)) |
| if base is None: |
| base = Undefined |
| |
| def _log_message(undef): |
| if undef._undefined_hint is None: |
| if undef._undefined_obj is missing: |
| hint = '%s is undefined' % undef._undefined_name |
| elif not isinstance(undef._undefined_name, string_types): |
| hint = '%s has no element %s' % ( |
| object_type_repr(undef._undefined_obj), |
| undef._undefined_name) |
| else: |
| hint = '%s has no attribute %s' % ( |
| object_type_repr(undef._undefined_obj), |
| undef._undefined_name) |
| else: |
| hint = undef._undefined_hint |
| logger.warning('Template variable warning: %s', hint) |
| |
| class LoggingUndefined(base): |
| |
| def _fail_with_undefined_error(self, *args, **kwargs): |
| try: |
| return base._fail_with_undefined_error(self, *args, **kwargs) |
| except self._undefined_exception as e: |
| logger.error('Template variable error: %s', str(e)) |
| raise e |
| |
| def __str__(self): |
| rv = base.__str__(self) |
| _log_message(self) |
| return rv |
| |
| def __iter__(self): |
| rv = base.__iter__(self) |
| _log_message(self) |
| return rv |
| |
| if PY2: |
| def __nonzero__(self): |
| rv = base.__nonzero__(self) |
| _log_message(self) |
| return rv |
| |
| def __unicode__(self): |
| rv = base.__unicode__(self) |
| _log_message(self) |
| return rv |
| else: |
| def __bool__(self): |
| rv = base.__bool__(self) |
| _log_message(self) |
| return rv |
| |
| return LoggingUndefined |
| |
| |
| @implements_to_string |
| 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): |
| if self._undefined_hint is None: |
| if self._undefined_obj is missing: |
| return u'{{ %s }}' % self._undefined_name |
| return '{{ no such element: %s[%r] }}' % ( |
| object_type_repr(self._undefined_obj), |
| self._undefined_name |
| ) |
| return u'{{ undefined value printed: %s }}' % self._undefined_hint |
| |
| |
| @implements_to_string |
| 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__ = __nonzero__ = __eq__ = \ |
| __ne__ = __bool__ = __hash__ = \ |
| Undefined._fail_with_undefined_error |
| |
| |
| # remove remaining slots attributes, after the metaclass did the magic they |
| # are unneeded and irritating as they contain wrong data for the subclasses. |
| del Undefined.__slots__, DebugUndefined.__slots__, StrictUndefined.__slots__ |