In the process of developing a custom Jinja2 extension (a tag, not a filter), I am finding myself in the situation where I need to extract a list of first-level keys that refer to the runtime values saved in a namespace.
Individual first-level values with known keys are easy to extract from the namespace with a simple Getattr(Name('my_ns_name', 'load'), 'my_key', 'load')
node. But how can I get the list of all first-level keys when not known in advance?
I inspected Namespace
objects as returned at runtime by a Name('my_ns_name', 'load')
node and haven't found any relevant information there. I also feel that the NSRef
node type could possibly be a key to the solution, but I still fail to understand its purpose, specially considering how little documented it is. I also thought of using a For
node and iterate through some collection returned by one of the nodes.iter_*
functions, but that doesn't seem to apply to namespace attributes. So I'm a bit stuck, now...
Here's a condensed version of my attempts so far:
import pprint
from jinja2 import nodes
from jinja2.ext import Extension
class InspectExtension(Extension):
tags = {"inspect"}
def __init__(self, environment):
super().__init__(environment)
def parse(self, parser):
lineno = parser.stream.expect("name:inspect").lineno
expr = parser.parse_expression()
context = nodes.DerivedContextReference()
return nodes.Output([
nodes.Const(expr), # Inspect the AST
nodes.TemplateData('\n'), # For a nicer output
self.call_method("_render", [expr, context]) # Runtime inspection
]).set_lineno(lineno)
def _render(self, value, context):
result = {
"class": value.__class__.__name__,
"attrs": dir(value),
"context": list(context.get_all().keys())
}
return pprint.pformat(result, compact=False)
inspect = InspectExtension
With this template:
<!DOCTYPE html>
<html><body><pre>
{% set ns = namespace() %}
{% set ns.test = 'foobar' %}
{{ ns.test }}
{% inspect ns %}
</pre></body></html>
Outputs (rendered HTML):
foobar
Getattr(node=Name(name='ns', ctx='load'), attr='test', ctx='load')
{'attrs': ['__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__setitem__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__'],
'class': 'Namespace',
'context': ['range',
'dict',
'lipsum',
'cycler',
'joiner',
'namespace',
'environ',
'get_context',
'request',
'result',
'ctx',
'ns'],
'value': }
For completeness:
./j2_ext/inspect.py
, along with the customary __init__.py
which contains a single statement from .inspect import inspect
.jinja2-cli
, passing the extension name to the --extension
argument, as such:jinja2 --format=json --extension=.j2_ext.inspect /path/to/my_tpl.j2 < my_data.json
Some relevant documentation:
If you read the source code you'll find that the attributes of jinja2.utils.Namespace
are stored in the __attrs
attribute, a "private" variable prefixed with double underscores that is subject to name mangling, so you would have to access it with the _Namespace
prefix instead.
So to obtain a list of all first-level keys stored in the namespace value
, evaluate:
list(value._Namespace__attrs)