pythonjinja2

get global variable with dashes in jinja2


is there a way to render the variable b-c (456) by changing only template variable in the following code?

import jinja2

environment = jinja2.Environment()

data = {'a':123, 'b-c':456}
template = "hello {{ b-c }}"

result = environment.from_string(template, globals = data).render()

print('result:', result)

the expected result:

hello 456

current code throws error:

jinja2.exceptions.UndefinedError: 'b' is undefined

i can't change how template variables (data) defined or how template is rendered at the moment. but i can change template.


Solution

  • b-c is not a valid identifier, so you would need to hack into Jinja2's internals to retrieve its value.

    This can be done by identifying a Python-implemented class (as in a class that isn't implemented in C) so we can obtain the __builtins__ attribute of one of its methods and access the locals function from it.

    One such class in the Jinja2 environment is jinja2.runtime.Undefined, which is instantiated when an undefined name is evaluated. We'll use the name _ for the demo here. This object has an __init__ method which you can use to obtain the __builtins__ attribute.

    Once you obtain the local namespace through the built-in locals function, you can obtain the jinja2.runtime.Context object of the frame as the __self variable, though you would have to look it up by the name of _Context__self because it is prefixed with double underscores and is therefore subject to name mangling.

    The Context object supports name lookups through the __getitem__ method so you can then look up the key 'b-c' with square brackets:

    import jinja2
    
    environment = jinja2.Environment()
    
    data = {'a':123, 'b-c':456}
    template = "hello {{ _.__init__.__builtins__.locals()._Context__self['b-c'] }}"
    
    result = environment.from_string(template, globals = data).render()
    
    print('result:', result)
    

    This outputs:

    result: hello 456
    

    Demo here