[Python] How to programmatically get a list of all first-level keys stored in a namespace?

Discussão em 'Python' iniciado por Stack, Março 26, 2025.

  1. Stack

    Stack Membro Participativo

    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:

    • The extension source code is located in ./j2_ext/inspect.py, along with the customary __init__.py which contains a single statement from .inspect import inspect.
    • Jinja2 engine is called through 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:


    Continue reading...

Compartilhe esta Página