Biggest change to Jinja since the 1.x migration: added evaluation contexts
which make it possible to keep the ahead of time optimizations and provide
dynamic activation and deactivation of autoescaping and other context
specific features.
--HG--
branch : trunk
diff --git a/jinja2/nodes.py b/jinja2/nodes.py
index 424c1cd..afc7355 100644
--- a/jinja2/nodes.py
+++ b/jinja2/nodes.py
@@ -67,6 +67,31 @@
return type.__new__(cls, name, bases, d)
+class EvalContext(object):
+ """Holds evaluation time information"""
+
+ def __init__(self, environment):
+ self.autoescape = environment.autoescape
+ self.volatile = False
+
+ def save(self):
+ return self.__dict__.copy()
+
+ def revert(self, old):
+ self.__dict__.clear()
+ self.__dict__.update(old)
+
+
+def get_eval_context(node, ctx):
+ if ctx is None:
+ if node.environment is None:
+ raise RuntimeError('if no eval context is passed, the '
+ 'node must have an attached '
+ 'environment.')
+ return EvalContext(node.environment)
+ return ctx
+
+
class Node(object):
"""Baseclass for all Jinja2 nodes. There are a number of nodes available
of different types. There are three major types:
@@ -312,19 +337,16 @@
"""Baseclass for all expressions."""
abstract = True
- def as_const(self):
+ def as_const(self, eval_ctx=None):
"""Return the value of the expression as constant or raise
- :exc:`Impossible` if this was not possible:
+ :exc:`Impossible` if this was not possible.
- >>> Add(Const(23), Const(42)).as_const()
- 65
- >>> Add(Const(23), Name('var', 'load')).as_const()
- Traceback (most recent call last):
- ...
- Impossible
+ An :class:`EvalContext` can be provided, if none is given
+ a default context is created which requires the nodes to have
+ an attached environment.
- This requires the `environment` attribute of all nodes to be
- set to the environment that created the nodes.
+ .. versionchanged:: 2.4
+ the `eval_ctx` parameter was added.
"""
raise Impossible()
@@ -339,10 +361,11 @@
operator = None
abstract = True
- def as_const(self):
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
f = _binop_to_func[self.operator]
try:
- return f(self.left.as_const(), self.right.as_const())
+ return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
except:
raise Impossible()
@@ -353,10 +376,11 @@
operator = None
abstract = True
- def as_const(self):
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
f = _uaop_to_func[self.operator]
try:
- return f(self.node.as_const())
+ return f(self.node.as_const(eval_ctx))
except:
raise Impossible()
@@ -389,7 +413,7 @@
"""
fields = ('value',)
- def as_const(self):
+ def as_const(self, eval_ctx=None):
return self.value
@classmethod
@@ -408,8 +432,8 @@
"""A constant template string."""
fields = ('data',)
- def as_const(self):
- if self.environment.autoescape:
+ def as_const(self, eval_ctx=None):
+ if get_eval_context(self, eval_ctx).autoescape:
return Markup(self.data)
return self.data
@@ -421,8 +445,9 @@
"""
fields = ('items', 'ctx')
- def as_const(self):
- return tuple(x.as_const() for x in self.items)
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return tuple(x.as_const(eval_ctx) for x in self.items)
def can_assign(self):
for item in self.items:
@@ -435,8 +460,9 @@
"""Any list literal such as ``[1, 2, 3]``"""
fields = ('items',)
- def as_const(self):
- return [x.as_const() for x in self.items]
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return [x.as_const(eval_ctx) for x in self.items]
class Dict(Literal):
@@ -445,24 +471,27 @@
"""
fields = ('items',)
- def as_const(self):
- return dict(x.as_const() for x in self.items)
+ def as_const(self, eval_ctx=None):
+ 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):
- return self.key.as_const(), self.value.as_const()
+ def as_const(self, eval_ctx=None):
+ 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):
- return self.key, self.value.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.key, self.value.as_const(eval_ctx)
class CondExpr(Expr):
@@ -471,15 +500,16 @@
"""
fields = ('test', 'expr1', 'expr2')
- def as_const(self):
- if self.test.as_const():
- return self.expr1.as_const()
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if self.test.as_const(eval_ctx):
+ return self.expr1.as_const(eval_ctx)
# if we evaluate to an undefined object, we better do that at runtime
if self.expr2 is None:
raise Impossible()
- return self.expr2.as_const()
+ return self.expr2.as_const(eval_ctx)
class Filter(Expr):
@@ -491,8 +521,9 @@
"""
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
- def as_const(self, obj=None):
- if self.node is obj is None:
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ if eval_ctx.volatile or self.node is None:
raise Impossible()
# we have to be careful here because we call filter_ below.
# if this variable would be called filter, 2to3 would wrap the
@@ -502,20 +533,21 @@
filter_ = self.environment.filters.get(self.name)
if filter_ is None or getattr(filter_, 'contextfilter', False):
raise Impossible()
- if obj is None:
- obj = self.node.as_const()
- args = [x.as_const() for x in self.args]
- if getattr(filter_, 'environmentfilter', False):
+ obj = self.node.as_const(eval_ctx)
+ args = [x.as_const(eval_ctx) for x in self.args]
+ if getattr(filter_, 'evalcontextfilter', False):
+ args.insert(0, eval_ctx)
+ elif getattr(filter_, 'environmentfilter', False):
args.insert(0, self.environment)
- kwargs = dict(x.as_const() for x in self.kwargs)
+ 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())
+ args.extend(self.dyn_args.as_const(eval_ctx))
except:
raise Impossible()
if self.dyn_kwargs is not None:
try:
- kwargs.update(self.dyn_kwargs.as_const())
+ kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
except:
raise Impossible()
try:
@@ -540,25 +572,30 @@
"""
fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
- def as_const(self):
- obj = self.node.as_const()
+ 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() for x in self.args]
+ args = [x.as_const(eval_ctx) for x in self.args]
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() for x in self.kwargs)
+ 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())
+ args.extend(self.dyn_args.as_const(eval_ctx))
except:
raise Impossible()
if self.dyn_kwargs is not None:
try:
- kwargs.update(self.dyn_kwargs.as_const())
+ kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
except:
raise Impossible()
try:
@@ -571,12 +608,13 @@
"""Get an attribute or item from an expression and prefer the item."""
fields = ('node', 'arg', 'ctx')
- def as_const(self):
+ 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(),
- self.arg.as_const())
+ return self.environment.getitem(self.node.as_const(eval_ctx),
+ self.arg.as_const(eval_ctx))
except:
raise Impossible()
@@ -590,11 +628,12 @@
"""
fields = ('node', 'attr', 'ctx')
- def as_const(self):
+ def as_const(self, eval_ctx=None):
if self.ctx != 'load':
raise Impossible()
try:
- return self.environment.getattr(self.node.as_const(), arg)
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return self.environment.getattr(self.node.as_const(eval_ctx), arg)
except:
raise Impossible()
@@ -608,11 +647,12 @@
"""
fields = ('start', 'stop', 'step')
- def as_const(self):
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
def const(obj):
if obj is None:
- return obj
- return obj.as_const()
+ return None
+ return obj.as_const(eval_ctx)
return slice(const(self.start), const(self.stop), const(self.step))
@@ -622,8 +662,9 @@
"""
fields = ('nodes',)
- def as_const(self):
- return ''.join(unicode(x.as_const()) for x in self.nodes)
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return ''.join(unicode(x.as_const(eval_ctx)) for x in self.nodes)
class Compare(Expr):
@@ -632,11 +673,12 @@
"""
fields = ('expr', 'ops')
- def as_const(self):
- result = value = self.expr.as_const()
+ def as_const(self, eval_ctx=None):
+ 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()
+ new_value = op.expr.as_const(eval_ctx)
result = _cmpop_to_func[op.op](value, new_value)
value = new_value
except:
@@ -695,16 +737,18 @@
"""Short circuited AND."""
operator = 'and'
- def as_const(self):
- return self.left.as_const() and self.right.as_const()
+ def as_const(self, eval_ctx=None):
+ 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):
- return self.left.as_const() or self.right.as_const()
+ def as_const(self, eval_ctx=None):
+ 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):
@@ -769,8 +813,9 @@
"""Mark the wrapped expression as safe (wrap it as `Markup`)."""
fields = ('expr',)
- def as_const(self):
- return Markup(self.expr.as_const())
+ def as_const(self, eval_ctx=None):
+ eval_ctx = get_eval_context(self, eval_ctx)
+ return Markup(self.expr.as_const(eval_ctx))
class ContextReference(Expr):
@@ -790,6 +835,16 @@
fields = ('body',)
+class EvalContextModifier(Stmt):
+ """Modifies the eval context"""
+ fields = ('options',)
+
+
+class ScopedEvalContextModifier(EvalContextModifier):
+ """Modifies the eval context and reverts it later."""
+ fields = ('body',)
+
+
# make sure nobody creates custom nodes
def _failing_new(*args, **kwargs):
raise TypeError('can\'t create custom node types')