def visit_Assign(self, node: nodes.Assign, frame: Frame) -> None:
self.push_assign_tracking()
+
+ # NSRef can only ever be used during assignment so we need to check
+ # to make sure that it is only being used to assign using a Namespace.
+ # This check is done here because it is used an expression during the
+ # assignment and therefore cannot have this check done when the NSRef
+ # node is visited
+ for nsref in node.find_all(nodes.NSRef):
+ ref = frame.symbols.ref(nsref.name)
+ self.writeline(f"if not isinstance({ref}, Namespace):")
+ self.indent()
+ self.writeline(
+ "raise TemplateRuntimeError"
+ '("cannot assign attribute on non-namespace object")'
+ )
+ self.outdent()
+
self.newline(node)
self.visit(node.target, frame)
self.write(" = ")
# `foo.bar` notation they will be parsed as a normal attribute access
# when used anywhere but in a `set` context
ref = frame.symbols.ref(node.name)
- self.writeline(f"if not isinstance({ref}, Namespace):")
- self.indent()
- self.writeline(
- "raise TemplateRuntimeError"
- '("cannot assign attribute on non-namespace object")'
- )
- self.outdent()
self.writeline(f"{ref}[{node.attr!r}]")
def visit_Const(self, node: nodes.Const, frame: Frame) -> None:
"""
target: nodes.Expr
- if with_namespace and self.stream.look().type == "dot":
- token = self.stream.expect("name")
- next(self.stream) # dot
- attr = self.stream.expect("name")
- target = nodes.NSRef(token.value, attr.value, lineno=token.lineno)
- elif name_only:
+ if name_only:
token = self.stream.expect("name")
target = nodes.Name(token.value, "store", lineno=token.lineno)
else:
if with_tuple:
target = self.parse_tuple(
- simplified=True, extra_end_rules=extra_end_rules
+ simplified=True,
+ extra_end_rules=extra_end_rules,
+ with_namespace=with_namespace,
)
else:
- target = self.parse_primary()
+ target = self.parse_primary(with_namespace=with_namespace)
target.set_ctx("store")
node = self.parse_filter_expr(node)
return node
- def parse_primary(self) -> nodes.Expr:
+ def parse_primary(self, with_namespace: bool = False) -> nodes.Expr:
token = self.stream.current
node: nodes.Expr
if token.type == "name":
node = nodes.Const(token.value in ("true", "True"), lineno=token.lineno)
elif token.value in ("none", "None"):
node = nodes.Const(None, lineno=token.lineno)
+ elif with_namespace and self.stream.look().type == "dot":
+ next(self.stream) # token
+ next(self.stream) # dot
+ attr = self.stream.current
+ node = nodes.NSRef(token.value, attr.value, lineno=token.lineno)
else:
node = nodes.Name(token.value, "load", lineno=token.lineno)
next(self.stream)
with_condexpr: bool = True,
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
explicit_parentheses: bool = False,
+ with_namespace: bool = False,
) -> t.Union[nodes.Tuple, nodes.Expr]:
"""Works like `parse_expression` but if multiple expressions are
delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.
"""
lineno = self.stream.current.lineno
if simplified:
- parse = self.parse_primary
- elif with_condexpr:
- parse = self.parse_expression
+
+ def parse() -> nodes.Expr:
+ return self.parse_primary(with_namespace=with_namespace)
+
else:
def parse() -> nodes.Expr:
- return self.parse_expression(with_condexpr=False)
+ return self.parse_expression(with_condexpr=with_condexpr)
args: t.List[nodes.Expr] = []
is_tuple = False
)
assert tmpl.render() == "13|37"
+ def test_namespace_set_tuple(self, env_trim):
+ tmpl = env_trim.from_string(
+ "{% set ns = namespace(a=12, b=36) %}"
+ "{% set ns.a, ns.b = ns.a + 1, ns.b + 1 %}"
+ "{{ ns.a }}|{{ ns.b }}"
+ )
+ assert tmpl.render() == "13|37"
+
def test_block_escaping_filtered(self):
env = Environment(autoescape=True)
tmpl = env.from_string(