summaryrefslogtreecommitdiffhomepage
path: root/scripts/jinja2/nodes.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/jinja2/nodes.py')
-rw-r--r--scripts/jinja2/nodes.py879
1 files changed, 582 insertions, 297 deletions
diff --git a/scripts/jinja2/nodes.py b/scripts/jinja2/nodes.py
index d32046c..b2f88d9 100644
--- a/scripts/jinja2/nodes.py
+++ b/scripts/jinja2/nodes.py
@@ -1,54 +1,47 @@
-# -*- coding: utf-8 -*-
+"""AST nodes generated by the parser for the compiler. Also provides
+some node tree helper functions used by the parser and compiler in order
+to normalize nodes.
"""
- jinja2.nodes
- ~~~~~~~~~~~~
-
- This module implements additional nodes derived from the ast base node.
-
- It also provides some node tree helper functions like `in_lineno` and
- `get_nodes` used by the parser and translator in order to normalize
- python and jinja nodes.
-
- :copyright: (c) 2010 by the Jinja Team.
- :license: BSD, see LICENSE for more details.
-"""
-import types
+import inspect
import operator
-
+import typing as t
from collections import deque
-from jinja2.utils import Markup
-from jinja2._compat import izip, with_metaclass, text_type
+from markupsafe import Markup
-#: the types we support for context functions
-_context_function_types = (types.FunctionType, types.MethodType)
+from .utils import _PassArg
+if t.TYPE_CHECKING:
+ import typing_extensions as te
+ from .environment import Environment
-_binop_to_func = {
- '*': operator.mul,
- '/': operator.truediv,
- '//': operator.floordiv,
- '**': operator.pow,
- '%': operator.mod,
- '+': operator.add,
- '-': operator.sub
+_NodeBound = t.TypeVar("_NodeBound", bound="Node")
+
+_binop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
+ "*": operator.mul,
+ "/": operator.truediv,
+ "//": operator.floordiv,
+ "**": operator.pow,
+ "%": operator.mod,
+ "+": operator.add,
+ "-": operator.sub,
}
-_uaop_to_func = {
- 'not': operator.not_,
- '+': operator.pos,
- '-': operator.neg
+_uaop_to_func: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
+ "not": operator.not_,
+ "+": operator.pos,
+ "-": operator.neg,
}
-_cmpop_to_func = {
- 'eq': operator.eq,
- 'ne': operator.ne,
- 'gt': operator.gt,
- 'gteq': operator.ge,
- 'lt': operator.lt,
- 'lteq': operator.le,
- 'in': lambda a, b: a in b,
- 'notin': lambda a, b: a not in b
+_cmpop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
+ "eq": operator.eq,
+ "ne": operator.ne,
+ "gt": operator.gt,
+ "gteq": operator.ge,
+ "lt": operator.lt,
+ "lteq": operator.le,
+ "in": lambda a, b: a in b,
+ "notin": lambda a, b: a not in b,
}
@@ -61,24 +54,26 @@ class NodeType(type):
inheritance. fields and attributes from the parent class are
automatically forwarded to the child."""
- def __new__(cls, name, bases, d):
- for attr in 'fields', 'attributes':
+ def __new__(mcs, name, bases, d): # type: ignore
+ for attr in "fields", "attributes":
storage = []
- storage.extend(getattr(bases[0], attr, ()))
+ storage.extend(getattr(bases[0] if bases else object, attr, ()))
storage.extend(d.get(attr, ()))
- assert len(bases) == 1, 'multiple inheritance not allowed'
- assert len(storage) == len(set(storage)), 'layout conflict'
+ assert len(bases) <= 1, "multiple inheritance not allowed"
+ assert len(storage) == len(set(storage)), "layout conflict"
d[attr] = tuple(storage)
- d.setdefault('abstract', False)
- return type.__new__(cls, name, bases, d)
+ d.setdefault("abstract", False)
+ return type.__new__(mcs, name, bases, d)
-class EvalContext(object):
+class EvalContext:
"""Holds evaluation time information. Custom attributes can be attached
to it in extensions.
"""
- def __init__(self, environment, template_name=None):
+ def __init__(
+ self, environment: "Environment", template_name: t.Optional[str] = None
+ ) -> None:
self.environment = environment
if callable(environment.autoescape):
self.autoescape = environment.autoescape(template_name)
@@ -86,26 +81,27 @@ class EvalContext(object):
self.autoescape = environment.autoescape
self.volatile = False
- def save(self):
+ def save(self) -> t.Mapping[str, t.Any]:
return self.__dict__.copy()
- def revert(self, old):
+ def revert(self, old: t.Mapping[str, t.Any]) -> None:
self.__dict__.clear()
self.__dict__.update(old)
-def get_eval_context(node, ctx):
+def get_eval_context(node: "Node", ctx: t.Optional[EvalContext]) -> EvalContext:
if ctx is None:
if node.environment is None:
- raise RuntimeError('if no eval context is passed, the '
- 'node must have an attached '
- 'environment.')
+ raise RuntimeError(
+ "if no eval context is passed, the node must have an"
+ " attached environment."
+ )
return EvalContext(node.environment)
return ctx
-class Node(with_metaclass(NodeType, object)):
- """Baseclass for all Jinja2 nodes. There are a number of nodes available
+class Node(metaclass=NodeType):
+ """Baseclass for all Jinja nodes. There are a number of nodes available
of different types. There are four major types:
- :class:`Stmt`: statements
@@ -120,32 +116,37 @@ class Node(with_metaclass(NodeType, object)):
The `environment` attribute is set at the end of the parsing process for
all nodes automatically.
"""
- fields = ()
- attributes = ('lineno', 'environment')
+
+ fields: t.Tuple[str, ...] = ()
+ attributes: t.Tuple[str, ...] = ("lineno", "environment")
abstract = True
- def __init__(self, *fields, **attributes):
+ lineno: int
+ environment: t.Optional["Environment"]
+
+ def __init__(self, *fields: t.Any, **attributes: t.Any) -> None:
if self.abstract:
- raise TypeError('abstract nodes are not instanciable')
+ raise TypeError("abstract nodes are not instantiable")
if fields:
if len(fields) != len(self.fields):
if not self.fields:
- raise TypeError('%r takes 0 arguments' %
- self.__class__.__name__)
- raise TypeError('%r takes 0 or %d argument%s' % (
- self.__class__.__name__,
- len(self.fields),
- len(self.fields) != 1 and 's' or ''
- ))
- for name, arg in izip(self.fields, fields):
+ raise TypeError(f"{type(self).__name__!r} takes 0 arguments")
+ raise TypeError(
+ f"{type(self).__name__!r} takes 0 or {len(self.fields)}"
+ f" argument{'s' if len(self.fields) != 1 else ''}"
+ )
+ for name, arg in zip(self.fields, fields):
setattr(self, name, arg)
for attr in self.attributes:
setattr(self, attr, attributes.pop(attr, None))
if attributes:
- raise TypeError('unknown attribute %r' %
- next(iter(attributes)))
+ raise TypeError(f"unknown attribute {next(iter(attributes))!r}")
- def iter_fields(self, exclude=None, only=None):
+ def iter_fields(
+ self,
+ exclude: t.Optional[t.Container[str]] = None,
+ only: t.Optional[t.Container[str]] = None,
+ ) -> t.Iterator[t.Tuple[str, t.Any]]:
"""This method iterates over all fields that are defined and yields
``(key, value)`` tuples. Per default all fields are returned, but
it's possible to limit that to some fields by providing the `only`
@@ -153,20 +154,26 @@ class Node(with_metaclass(NodeType, object)):
should be sets or tuples of field names.
"""
for name in self.fields:
- if (exclude is only is None) or \
- (exclude is not None and name not in exclude) or \
- (only is not None and name in only):
+ if (
+ (exclude is None and only is None)
+ or (exclude is not None and name not in exclude)
+ or (only is not None and name in only)
+ ):
try:
yield name, getattr(self, name)
except AttributeError:
pass
- def iter_child_nodes(self, exclude=None, only=None):
+ def iter_child_nodes(
+ self,
+ exclude: t.Optional[t.Container[str]] = None,
+ only: t.Optional[t.Container[str]] = None,
+ ) -> t.Iterator["Node"]:
"""Iterates over all direct child nodes of the node. This iterates
over all fields and yields the values of they are nodes. If the value
of a field is a list all the nodes in that list are returned.
"""
- for field, item in self.iter_fields(exclude, only):
+ for _, item in self.iter_fields(exclude, only):
if isinstance(item, list):
for n in item:
if isinstance(n, Node):
@@ -174,24 +181,27 @@ class Node(with_metaclass(NodeType, object)):
elif isinstance(item, Node):
yield item
- def find(self, node_type):
+ def find(self, node_type: t.Type[_NodeBound]) -> t.Optional[_NodeBound]:
"""Find the first node of a given type. If no such node exists the
return value is `None`.
"""
for result in self.find_all(node_type):
return result
- def find_all(self, node_type):
+ return None
+
+ def find_all(
+ self, node_type: t.Union[t.Type[_NodeBound], t.Tuple[t.Type[_NodeBound], ...]]
+ ) -> t.Iterator[_NodeBound]:
"""Find all the nodes of a given type. If the type is a tuple,
the check is performed for any of the tuple items.
"""
for child in self.iter_child_nodes():
if isinstance(child, node_type):
- yield child
- for result in child.find_all(node_type):
- yield result
+ yield child # type: ignore
+ yield from child.find_all(node_type)
- def set_ctx(self, ctx):
+ def set_ctx(self, ctx: str) -> "Node":
"""Reset the context of a node and all child nodes. Per default the
parser will all generate nodes that have a 'load' context as it's the
most common one. This method is used in the parser to set assignment
@@ -200,23 +210,23 @@ class Node(with_metaclass(NodeType, object)):
todo = deque([self])
while todo:
node = todo.popleft()
- if 'ctx' in node.fields:
- node.ctx = ctx
+ if "ctx" in node.fields:
+ node.ctx = ctx # type: ignore
todo.extend(node.iter_child_nodes())
return self
- def set_lineno(self, lineno, override=False):
+ def set_lineno(self, lineno: int, override: bool = False) -> "Node":
"""Set the line numbers of the node and children."""
todo = deque([self])
while todo:
node = todo.popleft()
- if 'lineno' in node.attributes:
+ if "lineno" in node.attributes:
if node.lineno is None or override:
node.lineno = lineno
todo.extend(node.iter_child_nodes())
return self
- def set_environment(self, environment):
+ def set_environment(self, environment: "Environment") -> "Node":
"""Set the environment for all nodes."""
todo = deque([self])
while todo:
@@ -225,31 +235,57 @@ class Node(with_metaclass(NodeType, object)):
todo.extend(node.iter_child_nodes())
return self
- def __eq__(self, other):
- return type(self) is type(other) and \
- tuple(self.iter_fields()) == tuple(other.iter_fields())
+ def __eq__(self, other: t.Any) -> bool:
+ if type(self) is not type(other):
+ return NotImplemented
- def __ne__(self, other):
- return not self.__eq__(other)
+ return tuple(self.iter_fields()) == tuple(other.iter_fields())
- # Restore Python 2 hashing behavior on Python 3
__hash__ = object.__hash__
- def __repr__(self):
- return '%s(%s)' % (
- self.__class__.__name__,
- ', '.join('%s=%r' % (arg, getattr(self, arg, None)) for
- arg in self.fields)
- )
+ def __repr__(self) -> str:
+ args_str = ", ".join(f"{a}={getattr(self, a, None)!r}" for a in self.fields)
+ return f"{type(self).__name__}({args_str})"
+
+ def dump(self) -> str:
+ def _dump(node: t.Union[Node, t.Any]) -> None:
+ if not isinstance(node, Node):
+ buf.append(repr(node))
+ return
+
+ buf.append(f"nodes.{type(node).__name__}(")
+ if not node.fields:
+ buf.append(")")
+ return
+ for idx, field in enumerate(node.fields):
+ if idx:
+ buf.append(", ")
+ value = getattr(node, field)
+ if isinstance(value, list):
+ buf.append("[")
+ for idx, item in enumerate(value):
+ if idx:
+ buf.append(", ")
+ _dump(item)
+ buf.append("]")
+ else:
+ _dump(value)
+ buf.append(")")
+
+ buf: t.List[str] = []
+ _dump(self)
+ return "".join(buf)
class Stmt(Node):
"""Base node for all statements."""
+
abstract = True
class Helper(Node):
"""Nodes that exist in a specific context only."""
+
abstract = True
@@ -257,19 +293,25 @@ class Template(Node):
"""Node that represents a template. This must be the outermost node that
is passed to the compiler.
"""
- fields = ('body',)
+
+ fields = ("body",)
+ body: t.List[Node]
class Output(Stmt):
"""A node that holds multiple expressions which are then printed out.
This is used both for the `print` statement and the regular template data.
"""
- fields = ('nodes',)
+
+ fields = ("nodes",)
+ nodes: t.List["Expr"]
class Extends(Stmt):
"""Represents an extends statement."""
- fields = ('template',)
+
+ fields = ("template",)
+ template: "Expr"
class For(Stmt):
@@ -280,12 +322,24 @@ class For(Stmt):
For filtered nodes an expression can be stored as `test`, otherwise `None`.
"""
- fields = ('target', 'iter', 'body', 'else_', 'test', 'recursive')
+
+ fields = ("target", "iter", "body", "else_", "test", "recursive")
+ target: Node
+ iter: Node
+ body: t.List[Node]
+ else_: t.List[Node]
+ test: t.Optional[Node]
+ recursive: bool
class If(Stmt):
"""If `test` is true, `body` is rendered, else `else_`."""
- fields = ('test', 'body', 'else_')
+
+ fields = ("test", "body", "elif_", "else_")
+ test: Node
+ body: t.List[Node]
+ elif_: t.List["If"]
+ else_: t.List[Node]
class Macro(Stmt):
@@ -293,34 +347,77 @@ class Macro(Stmt):
arguments and `defaults` a list of defaults if there are any. `body` is
a list of nodes for the macro body.
"""
- fields = ('name', 'args', 'defaults', 'body')
+
+ fields = ("name", "args", "defaults", "body")
+ name: str
+ args: t.List["Name"]
+ defaults: t.List["Expr"]
+ body: t.List[Node]
class CallBlock(Stmt):
"""Like a macro without a name but a call instead. `call` is called with
the unnamed macro as `caller` argument this node holds.
"""
- fields = ('call', 'args', 'defaults', 'body')
+
+ fields = ("call", "args", "defaults", "body")
+ call: "Call"
+ args: t.List["Name"]
+ defaults: t.List["Expr"]
+ body: t.List[Node]
class FilterBlock(Stmt):
"""Node for filter sections."""
- fields = ('body', 'filter')
+
+ fields = ("body", "filter")
+ body: t.List[Node]
+ filter: "Filter"
+
+
+class With(Stmt):
+ """Specific node for with statements. In older versions of Jinja the
+ with statement was implemented on the base of the `Scope` node instead.
+
+ .. versionadded:: 2.9.3
+ """
+
+ fields = ("targets", "values", "body")
+ targets: t.List["Expr"]
+ values: t.List["Expr"]
+ body: t.List[Node]
class Block(Stmt):
- """A node that represents a block."""
- fields = ('name', 'body', 'scoped')
+ """A node that represents a block.
+
+ .. versionchanged:: 3.0.0
+ the `required` field was added.
+ """
+
+ fields = ("name", "body", "scoped", "required")
+ name: str
+ body: t.List[Node]
+ scoped: bool
+ required: bool
class Include(Stmt):
"""A node that represents the include tag."""
- fields = ('template', 'with_context', 'ignore_missing')
+
+ fields = ("template", "with_context", "ignore_missing")
+ template: "Expr"
+ with_context: bool
+ ignore_missing: bool
class Import(Stmt):
"""A node that represents the import tag."""
- fields = ('template', 'target', 'with_context')
+
+ fields = ("template", "target", "with_context")
+ template: "Expr"
+ target: str
+ with_context: bool
class FromImport(Stmt):
@@ -334,29 +431,43 @@ class FromImport(Stmt):
The list of names may contain tuples if aliases are wanted.
"""
- fields = ('template', 'names', 'with_context')
+
+ fields = ("template", "names", "with_context")
+ template: "Expr"
+ names: t.List[t.Union[str, t.Tuple[str, str]]]
+ with_context: bool
class ExprStmt(Stmt):
"""A statement that evaluates an expression and discards the result."""
- fields = ('node',)
+
+ fields = ("node",)
+ node: Node
class Assign(Stmt):
"""Assigns an expression to a target."""
- fields = ('target', 'node')
+
+ fields = ("target", "node")
+ target: "Expr"
+ node: Node
class AssignBlock(Stmt):
"""Assigns a block to a target."""
- fields = ('target', 'body')
+
+ fields = ("target", "filter", "body")
+ target: "Expr"
+ filter: t.Optional["Filter"]
+ body: t.List[Node]
class Expr(Node):
"""Baseclass for all expressions."""
+
abstract = True
- def as_const(self, eval_ctx=None):
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
"""Return the value of the expression as constant or raise
:exc:`Impossible` if this was not possible.
@@ -369,47 +480,58 @@ class Expr(Node):
"""
raise Impossible()
- def can_assign(self):
+ def can_assign(self) -> bool:
"""Check if it's possible to assign something to this node."""
return False
class BinExpr(Expr):
"""Baseclass for all binary expressions."""
- fields = ('left', 'right')
- operator = None
+
+ fields = ("left", "right")
+ left: Expr
+ right: Expr
+ operator: str
abstract = True
- def as_const(self, eval_ctx=None):
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
eval_ctx = get_eval_context(self, eval_ctx)
+
# intercepted operators cannot be folded at compile time
- if self.environment.sandboxed and \
- self.operator in self.environment.intercepted_binops:
+ if (
+ eval_ctx.environment.sandboxed
+ and self.operator in eval_ctx.environment.intercepted_binops # type: ignore
+ ):
raise Impossible()
f = _binop_to_func[self.operator]
try:
return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
- except Exception:
- raise Impossible()
+ except Exception as e:
+ raise Impossible() from e
class UnaryExpr(Expr):
"""Baseclass for all unary expressions."""
- fields = ('node',)
- operator = None
+
+ fields = ("node",)
+ node: Expr
+ operator: str
abstract = True
- def as_const(self, eval_ctx=None):
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
eval_ctx = get_eval_context(self, eval_ctx)
+
# intercepted operators cannot be folded at compile time
- if self.environment.sandboxed and \
- self.operator in self.environment.intercepted_unops:
+ if (
+ eval_ctx.environment.sandboxed
+ and self.operator in eval_ctx.environment.intercepted_unops # type: ignore
+ ):
raise Impossible()
f = _uaop_to_func[self.operator]
try:
return f(self.node.as_const(eval_ctx))
- except Exception:
- raise Impossible()
+ except Exception as e:
+ raise Impossible() from e
class Name(Expr):
@@ -420,15 +542,33 @@ class Name(Expr):
- `load`: load that name
- `param`: like `store` but if the name was defined as function parameter.
"""
- fields = ('name', 'ctx')
- def can_assign(self):
- return self.name not in ('true', 'false', 'none',
- 'True', 'False', 'None')
+ fields = ("name", "ctx")
+ name: str
+ ctx: str
+
+ def can_assign(self) -> bool:
+ return self.name not in {"true", "false", "none", "True", "False", "None"}
+
+
+class NSRef(Expr):
+ """Reference to a namespace value assignment"""
+
+ fields = ("name", "attr")
+ name: str
+ attr: str
+
+ def can_assign(self) -> bool:
+ # We don't need any special checks here; NSRef assignments have a
+ # runtime check to ensure the target is a namespace object which will
+ # have been checked already as it is created using a normal assignment
+ # which goes through a `Name` node.
+ return True
class Literal(Expr):
"""Baseclass for literals."""
+
abstract = True
@@ -438,18 +578,26 @@ class Const(Literal):
complex values such as lists too. Only constants with a safe
representation (objects where ``eval(repr(x)) == x`` is true).
"""
- fields = ('value',)
- def as_const(self, eval_ctx=None):
+ fields = ("value",)
+ value: t.Any
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
return self.value
@classmethod
- def from_untrusted(cls, value, lineno=None, environment=None):
+ def from_untrusted(
+ cls,
+ value: t.Any,
+ lineno: t.Optional[int] = None,
+ environment: "t.Optional[Environment]" = None,
+ ) -> "Const":
"""Return a const object if the value is representable as
constant value in the generated code, otherwise it will raise
an `Impossible` exception.
"""
from .compiler import has_safe_repr
+
if not has_safe_repr(value):
raise Impossible()
return cls(value, lineno=lineno, environment=environment)
@@ -457,9 +605,11 @@ class Const(Literal):
class TemplateData(Literal):
"""A constant template string."""
- fields = ('data',)
- def as_const(self, eval_ctx=None):
+ fields = ("data",)
+ data: str
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str:
eval_ctx = get_eval_context(self, eval_ctx)
if eval_ctx.volatile:
raise Impossible()
@@ -473,13 +623,16 @@ class Tuple(Literal):
for subscripts. Like for :class:`Name` `ctx` specifies if the tuple
is used for loading the names or storing.
"""
- fields = ('items', 'ctx')
- def as_const(self, eval_ctx=None):
+ fields = ("items", "ctx")
+ items: t.List[Expr]
+ ctx: str
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[t.Any, ...]:
eval_ctx = get_eval_context(self, eval_ctx)
return tuple(x.as_const(eval_ctx) for x in self.items)
- def can_assign(self):
+ def can_assign(self) -> bool:
for item in self.items:
if not item.can_assign():
return False
@@ -488,9 +641,11 @@ class Tuple(Literal):
class List(Literal):
"""Any list literal such as ``[1, 2, 3]``"""
- fields = ('items',)
- def as_const(self, eval_ctx=None):
+ fields = ("items",)
+ items: t.List[Expr]
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.List[t.Any]:
eval_ctx = get_eval_context(self, eval_ctx)
return [x.as_const(eval_ctx) for x in self.items]
@@ -499,27 +654,39 @@ class Dict(Literal):
"""Any dict literal such as ``{1: 2, 3: 4}``. The items must be a list of
:class:`Pair` nodes.
"""
- fields = ('items',)
- def as_const(self, eval_ctx=None):
+ fields = ("items",)
+ items: t.List["Pair"]
+
+ def as_const(
+ self, eval_ctx: t.Optional[EvalContext] = None
+ ) -> t.Dict[t.Any, t.Any]:
eval_ctx = get_eval_context(self, eval_ctx)
return dict(x.as_const(eval_ctx) for x in self.items)
class Pair(Helper):
"""A key, value pair for dicts."""
- fields = ('key', 'value')
- def as_const(self, eval_ctx=None):
+ fields = ("key", "value")
+ key: Expr
+ value: Expr
+
+ def as_const(
+ self, eval_ctx: t.Optional[EvalContext] = None
+ ) -> t.Tuple[t.Any, t.Any]:
eval_ctx = get_eval_context(self, eval_ctx)
return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx)
class Keyword(Helper):
"""A key, value pair for keyword arguments where key is a string."""
- fields = ('key', 'value')
- def as_const(self, eval_ctx=None):
+ fields = ("key", "value")
+ key: str
+ value: Expr
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[str, t.Any]:
eval_ctx = get_eval_context(self, eval_ctx)
return self.key, self.value.as_const(eval_ctx)
@@ -528,9 +695,13 @@ class CondExpr(Expr):
"""A conditional expression (inline if expression). (``{{
foo if bar else baz }}``)
"""
- fields = ('test', 'expr1', 'expr2')
- def as_const(self, eval_ctx=None):
+ fields = ("test", "expr1", "expr2")
+ test: Expr
+ expr1: Expr
+ expr2: t.Optional[Expr]
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
eval_ctx = get_eval_context(self, eval_ctx)
if self.test.as_const(eval_ctx):
return self.expr1.as_const(eval_ctx)
@@ -542,55 +713,103 @@ class CondExpr(Expr):
return self.expr2.as_const(eval_ctx)
-class Filter(Expr):
- """This node applies a filter on an expression. `name` is the name of
- the filter, the rest of the fields are the same as for :class:`Call`.
+def args_as_const(
+ node: t.Union["_FilterTestCommon", "Call"], eval_ctx: t.Optional[EvalContext]
+) -> t.Tuple[t.List[t.Any], t.Dict[t.Any, t.Any]]:
+ args = [x.as_const(eval_ctx) for x in node.args]
+ kwargs = dict(x.as_const(eval_ctx) for x in node.kwargs)
- If the `node` of a filter is `None` the contents of the last buffer are
- filtered. Buffers are created by macros and filter blocks.
- """
- fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
+ if node.dyn_args is not None:
+ try:
+ args.extend(node.dyn_args.as_const(eval_ctx))
+ except Exception as e:
+ raise Impossible() from e
+
+ if node.dyn_kwargs is not None:
+ try:
+ kwargs.update(node.dyn_kwargs.as_const(eval_ctx))
+ except Exception as e:
+ raise Impossible() from e
+
+ return args, kwargs
- def as_const(self, eval_ctx=None):
+
+class _FilterTestCommon(Expr):
+ fields = ("node", "name", "args", "kwargs", "dyn_args", "dyn_kwargs")
+ node: Expr
+ name: str
+ args: t.List[Expr]
+ kwargs: t.List[Pair]
+ dyn_args: t.Optional[Expr]
+ dyn_kwargs: t.Optional[Expr]
+ abstract = True
+ _is_filter = True
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
eval_ctx = get_eval_context(self, eval_ctx)
- if eval_ctx.volatile or self.node is None:
+
+ if eval_ctx.volatile:
raise Impossible()
- # we have to be careful here because we call filter_ below.
- # if this variable would be called filter, 2to3 would wrap the
- # call in a list beause it is assuming we are talking about the
- # builtin filter function here which no longer returns a list in
- # python 3. because of that, do not rename filter_ to filter!
- filter_ = self.environment.filters.get(self.name)
- if filter_ is None or getattr(filter_, 'contextfilter', False):
+
+ if self._is_filter:
+ env_map = eval_ctx.environment.filters
+ else:
+ env_map = eval_ctx.environment.tests
+
+ func = env_map.get(self.name)
+ pass_arg = _PassArg.from_obj(func) # type: ignore
+
+ if func is None or pass_arg is _PassArg.context:
raise Impossible()
- obj = self.node.as_const(eval_ctx)
- args = [x.as_const(eval_ctx) for x in self.args]
- if getattr(filter_, 'evalcontextfilter', False):
+
+ if eval_ctx.environment.is_async and (
+ getattr(func, "jinja_async_variant", False) is True
+ or inspect.iscoroutinefunction(func)
+ ):
+ raise Impossible()
+
+ args, kwargs = args_as_const(self, eval_ctx)
+ args.insert(0, self.node.as_const(eval_ctx))
+
+ if pass_arg is _PassArg.eval_context:
args.insert(0, eval_ctx)
- elif getattr(filter_, 'environmentfilter', False):
- args.insert(0, self.environment)
- kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
- if self.dyn_args is not None:
- try:
- args.extend(self.dyn_args.as_const(eval_ctx))
- except Exception:
- raise Impossible()
- if self.dyn_kwargs is not None:
- try:
- kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
- except Exception:
- raise Impossible()
+ elif pass_arg is _PassArg.environment:
+ args.insert(0, eval_ctx.environment)
+
try:
- return filter_(obj, *args, **kwargs)
- except Exception:
+ return func(*args, **kwargs)
+ except Exception as e:
+ raise Impossible() from e
+
+
+class Filter(_FilterTestCommon):
+ """Apply a filter to an expression. ``name`` is the name of the
+ filter, the other fields are the same as :class:`Call`.
+
+ If ``node`` is ``None``, the filter is being used in a filter block
+ and is applied to the content of the block.
+ """
+
+ node: t.Optional[Expr] # type: ignore
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
+ if self.node is None:
raise Impossible()
+ return super().as_const(eval_ctx=eval_ctx)
+
+
+class Test(_FilterTestCommon):
+ """Apply a test to an expression. ``name`` is the name of the test,
+ the other field are the same as :class:`Call`.
-class Test(Expr):
- """Applies a test on an expression. `name` is the name of the test, the
- rest of the fields are the same as for :class:`Call`.
+ .. versionchanged:: 3.0
+ ``as_const`` shares the same logic for filters and tests. Tests
+ check for volatile, async, and ``@pass_context`` etc.
+ decorators.
"""
- fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
+
+ _is_filter = False
class Call(Expr):
@@ -600,202 +819,209 @@ class Call(Expr):
node for dynamic positional (``*args``) or keyword (``**kwargs``)
arguments.
"""
- fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- if eval_ctx.volatile:
- raise Impossible()
- obj = self.node.as_const(eval_ctx)
-
- # don't evaluate context functions
- args = [x.as_const(eval_ctx) for x in self.args]
- if isinstance(obj, _context_function_types):
- if getattr(obj, 'contextfunction', False):
- raise Impossible()
- elif getattr(obj, 'evalcontextfunction', False):
- args.insert(0, eval_ctx)
- elif getattr(obj, 'environmentfunction', False):
- args.insert(0, self.environment)
-
- kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
- if self.dyn_args is not None:
- try:
- args.extend(self.dyn_args.as_const(eval_ctx))
- except Exception:
- raise Impossible()
- if self.dyn_kwargs is not None:
- try:
- kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
- except Exception:
- raise Impossible()
- try:
- return obj(*args, **kwargs)
- except Exception:
- raise Impossible()
+ fields = ("node", "args", "kwargs", "dyn_args", "dyn_kwargs")
+ node: Expr
+ args: t.List[Expr]
+ kwargs: t.List[Keyword]
+ dyn_args: t.Optional[Expr]
+ dyn_kwargs: t.Optional[Expr]
class Getitem(Expr):
"""Get an attribute or item from an expression and prefer the item."""
- fields = ('node', 'arg', 'ctx')
- def as_const(self, eval_ctx=None):
- eval_ctx = get_eval_context(self, eval_ctx)
- if self.ctx != 'load':
- raise Impossible()
- try:
- return self.environment.getitem(self.node.as_const(eval_ctx),
- self.arg.as_const(eval_ctx))
- except Exception:
+ fields = ("node", "arg", "ctx")
+ node: Expr
+ arg: Expr
+ ctx: str
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
+ if self.ctx != "load":
raise Impossible()
- def can_assign(self):
- return False
+ eval_ctx = get_eval_context(self, eval_ctx)
+
+ try:
+ return eval_ctx.environment.getitem(
+ self.node.as_const(eval_ctx), self.arg.as_const(eval_ctx)
+ )
+ except Exception as e:
+ raise Impossible() from e
class Getattr(Expr):
"""Get an attribute or item from an expression that is a ascii-only
bytestring and prefer the attribute.
"""
- fields = ('node', 'attr', 'ctx')
- def as_const(self, eval_ctx=None):
- if self.ctx != 'load':
- raise Impossible()
- try:
- eval_ctx = get_eval_context(self, eval_ctx)
- return self.environment.getattr(self.node.as_const(eval_ctx),
- self.attr)
- except Exception:
+ fields = ("node", "attr", "ctx")
+ node: Expr
+ attr: str
+ ctx: str
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
+ if self.ctx != "load":
raise Impossible()
- def can_assign(self):
- return False
+ eval_ctx = get_eval_context(self, eval_ctx)
+
+ try:
+ return eval_ctx.environment.getattr(self.node.as_const(eval_ctx), self.attr)
+ except Exception as e:
+ raise Impossible() from e
class Slice(Expr):
"""Represents a slice object. This must only be used as argument for
:class:`Subscript`.
"""
- fields = ('start', 'stop', 'step')
- def as_const(self, eval_ctx=None):
+ fields = ("start", "stop", "step")
+ start: t.Optional[Expr]
+ stop: t.Optional[Expr]
+ step: t.Optional[Expr]
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> slice:
eval_ctx = get_eval_context(self, eval_ctx)
- def const(obj):
+
+ def const(obj: t.Optional[Expr]) -> t.Optional[t.Any]:
if obj is None:
return None
return obj.as_const(eval_ctx)
+
return slice(const(self.start), const(self.stop), const(self.step))
class Concat(Expr):
- """Concatenates the list of expressions provided after converting them to
- unicode.
+ """Concatenates the list of expressions provided after converting
+ them to strings.
"""
- fields = ('nodes',)
- def as_const(self, eval_ctx=None):
+ fields = ("nodes",)
+ nodes: t.List[Expr]
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str:
eval_ctx = get_eval_context(self, eval_ctx)
- return ''.join(text_type(x.as_const(eval_ctx)) for x in self.nodes)
+ return "".join(str(x.as_const(eval_ctx)) for x in self.nodes)
class Compare(Expr):
"""Compares an expression with some other expressions. `ops` must be a
- list of :class:`Operand`\s.
+ list of :class:`Operand`\\s.
"""
- fields = ('expr', 'ops')
- def as_const(self, eval_ctx=None):
+ fields = ("expr", "ops")
+ expr: Expr
+ ops: t.List["Operand"]
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
eval_ctx = get_eval_context(self, eval_ctx)
result = value = self.expr.as_const(eval_ctx)
+
try:
for op in self.ops:
new_value = op.expr.as_const(eval_ctx)
result = _cmpop_to_func[op.op](value, new_value)
+
+ if not result:
+ return False
+
value = new_value
- except Exception:
- raise Impossible()
+ except Exception as e:
+ raise Impossible() from e
+
return result
class Operand(Helper):
"""Holds an operator and an expression."""
- fields = ('op', 'expr')
-if __debug__:
- Operand.__doc__ += '\nThe following operators are available: ' + \
- ', '.join(sorted('``%s``' % x for x in set(_binop_to_func) |
- set(_uaop_to_func) | set(_cmpop_to_func)))
+ fields = ("op", "expr")
+ op: str
+ expr: Expr
class Mul(BinExpr):
"""Multiplies the left with the right node."""
- operator = '*'
+
+ operator = "*"
class Div(BinExpr):
"""Divides the left by the right node."""
- operator = '/'
+
+ operator = "/"
class FloorDiv(BinExpr):
- """Divides the left by the right node and truncates conver the
+ """Divides the left by the right node and converts the
result into an integer by truncating.
"""
- operator = '//'
+
+ operator = "//"
class Add(BinExpr):
"""Add the left to the right node."""
- operator = '+'
+
+ operator = "+"
class Sub(BinExpr):
"""Subtract the right from the left node."""
- operator = '-'
+
+ operator = "-"
class Mod(BinExpr):
"""Left modulo right."""
- operator = '%'
+
+ operator = "%"
class Pow(BinExpr):
"""Left to the power of right."""
- operator = '**'
+
+ operator = "**"
class And(BinExpr):
"""Short circuited AND."""
- operator = 'and'
- def as_const(self, eval_ctx=None):
+ operator = "and"
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
eval_ctx = get_eval_context(self, eval_ctx)
return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx)
class Or(BinExpr):
"""Short circuited OR."""
- operator = 'or'
- def as_const(self, eval_ctx=None):
+ operator = "or"
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
eval_ctx = get_eval_context(self, eval_ctx)
return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx)
class Not(UnaryExpr):
"""Negate the expression."""
- operator = 'not'
+
+ operator = "not"
class Neg(UnaryExpr):
"""Make the expression negative."""
- operator = '-'
+
+ operator = "-"
class Pos(UnaryExpr):
"""Make the expression positive (noop for most expressions)"""
- operator = '+'
+
+ operator = "+"
# Helpers for extensions
@@ -805,7 +1031,9 @@ class EnvironmentAttribute(Expr):
"""Loads an attribute from the environment object. This is useful for
extensions that want to call a callback stored on the environment.
"""
- fields = ('name',)
+
+ fields = ("name",)
+ name: str
class ExtensionAttribute(Expr):
@@ -815,7 +1043,10 @@ class ExtensionAttribute(Expr):
This node is usually constructed by calling the
:meth:`~jinja2.ext.Extension.attr` method on an extension.
"""
- fields = ('identifier', 'name')
+
+ fields = ("identifier", "name")
+ identifier: str
+ name: str
class ImportedName(Expr):
@@ -824,7 +1055,9 @@ class ImportedName(Expr):
function from the cgi module on evaluation. Imports are optimized by the
compiler so there is no need to assign them to local variables.
"""
- fields = ('importname',)
+
+ fields = ("importname",)
+ importname: str
class InternalName(Expr):
@@ -832,20 +1065,26 @@ class InternalName(Expr):
yourself but the parser provides a
:meth:`~jinja2.parser.Parser.free_identifier` method that creates
a new identifier for you. This identifier is not available from the
- template and is not threated specially by the compiler.
+ template and is not treated specially by the compiler.
"""
- fields = ('name',)
- def __init__(self):
- raise TypeError('Can\'t create internal names. Use the '
- '`free_identifier` method on a parser.')
+ fields = ("name",)
+ name: str
+
+ def __init__(self) -> None:
+ raise TypeError(
+ "Can't create internal names. Use the "
+ "`free_identifier` method on a parser."
+ )
class MarkSafe(Expr):
"""Mark the wrapped expression as safe (wrap it as `Markup`)."""
- fields = ('expr',)
- def as_const(self, eval_ctx=None):
+ fields = ("expr",)
+ expr: Expr
+
+ def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> Markup:
eval_ctx = get_eval_context(self, eval_ctx)
return Markup(self.expr.as_const(eval_ctx))
@@ -856,9 +1095,13 @@ class MarkSafeIfAutoescape(Expr):
.. versionadded:: 2.5
"""
- fields = ('expr',)
- def as_const(self, eval_ctx=None):
+ fields = ("expr",)
+ expr: Expr
+
+ def as_const(
+ self, eval_ctx: t.Optional[EvalContext] = None
+ ) -> t.Union[Markup, t.Any]:
eval_ctx = get_eval_context(self, eval_ctx)
if eval_ctx.volatile:
raise Impossible()
@@ -878,6 +1121,20 @@ class ContextReference(Expr):
Assign(Name('foo', ctx='store'),
Getattr(ContextReference(), 'name'))
+
+ This is basically equivalent to using the
+ :func:`~jinja2.pass_context` decorator when using the high-level
+ API, which causes a reference to the context to be passed as the
+ first argument to a function.
+ """
+
+
+class DerivedContextReference(Expr):
+ """Return the current template context including locals. Behaves
+ exactly like :class:`ContextReference`, but includes local
+ variables, such as from a ``for`` loop.
+
+ .. versionadded:: 2.11
"""
@@ -891,7 +1148,28 @@ class Break(Stmt):
class Scope(Stmt):
"""An artificial scope."""
- fields = ('body',)
+
+ fields = ("body",)
+ body: t.List[Node]
+
+
+class OverlayScope(Stmt):
+ """An overlay scope for extensions. This is a largely unoptimized scope
+ that however can be used to introduce completely arbitrary variables into
+ a sub scope from a dictionary or dictionary like object. The `context`
+ field has to evaluate to a dictionary object.
+
+ Example usage::
+
+ OverlayScope(context=self.call_method('get_context'),
+ body=[...])
+
+ .. versionadded:: 2.10
+ """
+
+ fields = ("context", "body")
+ context: Expr
+ body: t.List[Node]
class EvalContextModifier(Stmt):
@@ -902,7 +1180,9 @@ class EvalContextModifier(Stmt):
EvalContextModifier(options=[Keyword('autoescape', Const(True))])
"""
- fields = ('options',)
+
+ fields = ("options",)
+ options: t.List[Keyword]
class ScopedEvalContextModifier(EvalContextModifier):
@@ -910,10 +1190,15 @@ class ScopedEvalContextModifier(EvalContextModifier):
:class:`EvalContextModifier` but will only modify the
:class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`.
"""
- fields = ('body',)
+
+ fields = ("body",)
+ body: t.List[Node]
# make sure nobody creates custom nodes
-def _failing_new(*args, **kwargs):
- raise TypeError('can\'t create custom node types')
-NodeType.__new__ = staticmethod(_failing_new); del _failing_new
+def _failing_new(*args: t.Any, **kwargs: t.Any) -> "te.NoReturn":
+ raise TypeError("can't create custom node types")
+
+
+NodeType.__new__ = staticmethod(_failing_new) # type: ignore
+del _failing_new