pallets / jinja

A very fast and expressive template engine.
https://jinja.palletsprojects.com
BSD 3-Clause "New" or "Revised" License
10.23k stars 1.6k forks source link

Template globals not visible in {% import %}'ed file when it's {% include %}'ed by another file #1939

Closed clarkwang closed 3 months ago

clarkwang commented 6 months ago

I've come up with the following minimal repro steps.

Python script:

$ cat test.py
#!/usr/bin/env python3

import jinja2 as j2
import os, sys

print(f'jinja2 version: {j2.__version__}')

env = j2.Environment(loader=j2.FileSystemLoader('.') )
tmpl = env.get_template(sys.argv[1], globals={ 'ENV': os.getenv })
print(tmpl.render() )

Data files:

$ cat common
{% set var1 = ENV('HOME') %}
$ cat file1
{% include 'file2' %}
$ cat file2
{% import 'common' as COMMON %}
{{ COMMON.var1 }}

To repro the issue:

$ python3 test.py file2
jinja2 version: 3.1.3

/root
$ python3 test.py file1
jinja2 version: 3.1.3
Traceback (most recent call last):
  File "/root/void/python/jinja/t1/test.py", line 10, in <module>
    print(tmpl.render() )
          ^^^^^^^^^^^^^
  File "/usr/local/py3/lib/python3.11/site-packages/jinja2/environment.py", line 1301, in render
    self.environment.handle_exception()
  File "/usr/local/py3/lib/python3.11/site-packages/jinja2/environment.py", line 936, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "file1", line 1, in top-level template code
    {% include 'file2' %}
    ^^^^^^^^^^^^^^^^^^^^^^
  File "file2", line 1, in top-level template code
    {% import 'common' as COMMON %}
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/py3/lib/python3.11/site-packages/jinja2/environment.py", line 1405, in make_module
    return TemplateModule(self, ctx)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/py3/lib/python3.11/site-packages/jinja2/environment.py", line 1535, in __init__
    body_stream = list(template.root_render_func(context))
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "common", line 1, in top-level template code
    {% set var1 = ENV('HOME') %}
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/py3/lib/python3.11/site-packages/jinja2/utils.py", line 83, in from_obj
    if hasattr(obj, "jinja_pass_arg"):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
jinja2.exceptions.UndefinedError: 'ENV' is undefined

As we can see, python3 test.py file2 works but python3 test.py file1 fails.

j7an commented 3 months ago

@clarkwang The error occurs because globals is being incorrectly passed to get_template. The correct way to add globals to the Jinja2 environment is to set them on the Environment object itself.

#!/usr/bin/env python3

import jinja2 as j2
import os
import sys

print(f'jinja2 version: {j2.__version__}')

# Set up Jinja2 environment and add the global function
env = j2.Environment(loader=j2.FileSystemLoader('.'))
env.globals['ENV'] = os.getenv

# Load the template specified by the first command line argument
tmpl = env.get_template(sys.argv[1])

# Render the template
print(tmpl.render())
davidism commented 3 months ago

@j7an adding globals to a template is supported: https://jinja.palletsprojects.com/en/3.1.x/api/#the-global-namespace. That said, you're correct that they're not setting the globals in the correct place. In their example, they set the globals on file1, but it's common that needs access. They should either set the globals on common, or on the environment as you showed.