-----------
(codename to be selected, release around July 2008)
-- initial release of Jinja2
+- the subscribing of objects (looking up attributes and items) changed from
+ slightly. It's now possible to give attributes or items a higher priority
+ by either using dot-notation lookup or the bracket syntax. This also
+ changed the AST slightly. `Subscript` is gone and was replaced with
+ :class:`~jinja2.nodes.Getitem` and :class:`~jinja2.nodes.Getattr`.
+
+ For more information see :ref:`the implementation details <notes-on-subscribing>`.
+
+Version 2.0rc1
+--------------
+(no codename, released on July 9th 2008)
+
+- first release of Jinja2
font-size: 15px;
}
+div.admonition p.admonition-title a {
+ color: white!important;
+}
+
div.admonition-note {
background: url(note.png) no-repeat 10px 40px;
}
configuration, the default behavior is that it evaluates to an empty string
if printed and that you can iterate over it, but every other operation fails.
+.. _notes-on-subscribing:
+
+.. admonition:: Implementation
+
+ The process of looking up attributes and items of objects is called
+ "subscribing" an object. For convenience sake ``foo.bar`` in Jinja2
+ does the following things on the Python layer:
+
+ - check if there is an attribute called `bar` on `foo`.
+ - if there is not, check if there is an item ``'bar'`` in `foo`.
+ - if there is not, return an undefined object.
+
+ ``foo['bar']`` on the other hand works mostly the same with the a small
+ difference in the order:
+
+ - check if there is an item ``'bar'`` in `foo`.
+ - if there is not, check if there is an attribute called `bar` on `foo`.
+ - if there is not, return an undefined object.
+
+ This is important if an object has an item or attribute with the same
+ name. Additionally there is the :func:`attr` filter that just looks up
+ attributes.
+
.. _filters:
Filters
self.write(' %s ' % operators[node.op])
self.visit(node.expr, frame)
- def visit_Subscript(self, node, frame):
+ def visit_Getattr(self, node, frame):
+ self.write('environment.getattr(')
+ self.visit(node.node, frame)
+ self.write(', %r)' % node.attr)
+
+ def visit_Getitem(self, node, frame):
# slices or integer subscriptions bypass the subscribe
# method if we can determine that at compile time.
if isinstance(node.arg, nodes.Slice) or \
self.visit(node.arg, frame)
self.write(']')
else:
- self.write('environment.subscribe(')
+ self.write('environment.getitem(')
self.visit(node.node, frame)
self.write(', ')
self.visit(node.arg, frame)
"""Return a fresh lexer for the environment."""
return Lexer(self)
- def subscribe(self, obj, argument):
- """Get an item or attribute of an object."""
+ def getitem(self, obj, argument):
+ """Get an item or attribute of an object but prefer the item."""
try:
return obj[argument]
except (TypeError, LookupError):
pass
return self.undefined(obj=obj, name=argument)
+ def getattr(self, obj, attribute):
+ """Get an item or attribute of an object but prefer the attribute.
+ Unlike :meth:`getitem` the attribute *must* be a bytestring.
+ """
+ try:
+ return getattr(obj, attribute)
+ except AttributeError:
+ pass
+ try:
+ return obj[attribute]
+ except (TypeError, LookupError):
+ return self.undefined(obj=obj, name=attribute)
+
def parse(self, source, name=None, filename=None):
"""Parse the sourcecode and return the abstract syntax tree. This
tree of nodes is used by the compiler to convert the template into
attribute and the `list` contains all the objects that have this grouper
in common.
"""
- expr = lambda x: environment.subscribe(x, attribute)
+ expr = lambda x: environment.getitem(x, attribute)
return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
@environmentfilter
def do_attr(environment, obj, name):
"""Get an attribute of an object. ``foo|attr("bar")`` works like
- ``foo["bar"]`` just that always an attribute is returned. This is useful
- if data structures are passed to the template that have an item that hides
- an attribute with the same name. For example a dict ``{'items': []}``
- that obviously hides the item method of a dict.
+ ``foo["bar"]`` just that always an attribute is returned and items are not
+ looked up.
+
+ See :ref:`Notes on subscribing <notes-on-subscribing>` for more details.
"""
try:
value = getattr(obj, name)
return environment.undefined(obj=obj, name=name)
if environment.sandboxed and not \
environment.is_safe_attribute(obj, name, value):
- return environment.undefined('access to attribute %r of %r '
- 'object is unsafe.' % (
- name, obj.__class__.__name__
- ), name=name, obj=obj, exc=SecurityError)
+ return environment.unsafe_undefined(obj, name)
return value
raise Impossible()
-class Subscript(Expr):
+class Getitem(Expr):
"""Subscribe an expression by an argument. This node performs a dict
and an attribute lookup on the object whatever succeeds.
"""
if self.ctx != 'load':
raise Impossible()
try:
- return self.environment.subscribe(self.node.as_const(),
- self.arg.as_const())
+ return self.environment.getitem(self.node.as_const(),
+ self.arg.as_const())
+ except:
+ raise Impossible()
+
+ def can_assign(self):
+ return False
+
+
+class Getattr(Expr):
+ """Subscribe an attribute."""
+ fields = ('node', 'attr', 'ctx')
+
+ def as_const(self):
+ if self.ctx != 'load':
+ raise Impossible()
+ try:
+ return self.environment.getattr(self.node.as_const(), arg)
except:
raise Impossible()
visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \
visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \
- visit_Not = visit_Compare = visit_Subscript = visit_Call = \
+ visit_Not = visit_Compare = visit_Getitem = visit_Getattr = visit_Call = \
visit_Filter = visit_Test = visit_CondExpr = fold
del fold
token = self.stream.next()
if token.type is 'dot':
attr_token = self.stream.current
- if attr_token.type not in ('name', 'integer'):
+ self.stream.next()
+ if attr_token.type is 'name':
+ return nodes.Getattr(node, attr_token.value, 'load',
+ lineno=token.lineno)
+ elif attr_token.type is not 'integer':
self.fail('expected name or number', attr_token.lineno)
arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
- self.stream.next()
- elif token.type is 'lbracket':
+ return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
+ if token.type is 'lbracket':
+ priority_on_attribute = False
args = []
while self.stream.current.type is not 'rbracket':
if args:
arg = args[0]
else:
arg = nodes.Tuple(args, self.lineno, self.filename)
- else:
- self.fail('expected subscript expression', self.lineno)
- return nodes.Subscript(node, arg, 'load', lineno=token.lineno)
+ return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
+ self.fail('expected subscript expression', self.lineno)
def parse_subscribed(self):
lineno = self.stream.current.lineno
return not (getattr(obj, 'unsafe_callable', False) or \
getattr(obj, 'alters_data', False))
- def subscribe(self, obj, argument):
+ def getitem(self, obj, argument):
"""Subscribe an object from sandboxed code."""
try:
return obj[argument]
else:
if self.is_safe_attribute(obj, argument, value):
return value
- return self.undefined('access to attribute %r of %r '
- 'object is unsafe.' % (
- argument,
- obj.__class__.__name__
- ), name=argument, obj=obj, exc=SecurityError)
+ return self.unsafe_undefined(obj, argument)
return self.undefined(obj=obj, name=argument)
+ def getattr(self, obj, attribute):
+ """Subscribe an object from sandboxed code and prefer the
+ attribute. The attribute passed *must* be a bytestring.
+ """
+ try:
+ value = getattr(obj, attribute)
+ except AttributeError:
+ try:
+ return obj[argument]
+ except (TypeError, LookupError):
+ pass
+ else:
+ if self.is_safe_attribute(obj, attribute, value):
+ return value
+ return self.unsafe_undefined(obj, attribute)
+ return self.undefined(obj=obj, name=argument)
+
+ def unsafe_undefined(self, obj, attribute):
+ """Return an undefined object for unsafe attributes."""
+ return self.undefined('access to attribute %r of %r '
+ 'object is unsafe.' % (
+ attribute,
+ obj.__class__.__name__
+ ), name=attribute, obj=obj, exc=SecurityError)
+
def call(__self, __context, __obj, *args, **kwargs):
"""Call an object from sandboxed code."""
# the double prefixes are to avoid double keyword argument
assert len(counts) == 1, 'ouch, c extension seems to leak objects'
-def test_item_before_attribute():
+def test_item_and_attribute():
from jinja2 import Environment
from jinja2.sandbox import SandboxedEnvironment
for env in Environment(), SandboxedEnvironment():
tmpl = env.from_string('{{ foo.items() }}')
- assert tmpl.render(foo={'items': lambda: 42}) == '42'
- assert tmpl.render(foo={}) == '[]'
- tmpl = env.from_string('{{ foo|attr("items")() }}')
- assert tmpl.render(foo={'items': None}) == "[('items', None)]"
+ assert tmpl.render(foo={'items': 42}) == "[('items', 42)]"
+ tmpl = env.from_string('{{ foo|attr("items") }}')
+ assert tmpl.render(foo={'items': 42}) == "[('items', 42)]"
+ tmpl = env.from_string('{{ foo["items"] }}')
+ assert tmpl.render(foo={'items': 42}) == '42'