Search code examples
pythonjinja2

Script is being ran twice when rendering jinja2 template?


I have a python script that renders a jinja2 template. Something like this:

basic.py

#!/usr/bin/env python3
from jinja2 import Environment, PackageLoader, select_autoescape

env = Environment(
    loader=PackageLoader('basic'),
    autoescape=select_autoescape()
)

print(env.get_template('basic_template.md').render({'version': 'version123', 'date': 'some-date'}))

My directory structure looks like this:

.
|__ templates/
|   |
|   |__ basic_template.md
|
|__ basic.py

I'm running this in a CI pipeline. The job runs this command: python /path/to/basic.py.

Whenever it runs, the whole script runs twice. This is being caused by the env variable--specifically PackageLoader('basic'). I'm pretty sure when this is loaded it's running the basic.py script (again)--from within basic.py.

If I create an empty dummy script and use that in PackageLoader, my script works fine (only renders the template once):

.
|__ templates/
|   |
|   |__ basic_template.md
|
|__ basic.py
|
|__ other_script.py
#!/usr/bin/env python3
from jinja2 import Environment, PackageLoader, select_autoescape

env = Environment(
    loader=PackageLoader('other_script'),
    autoescape=select_autoescape()
)

print(env.get_template('basic_template.md').render({'version': 'version123', 'date': 'some-date'}))

If I pass a non-existent file to PackageLoader or if I omit loader=PackageLoader() all together, it throws an error.

So what's the right way to accomplish this? I don't want to have a random empty file lying around if I don't need it. How do I render the template without PackageLoader running my script a second time?


Solution

  • The reason for this is indeed the fact that the PackageLoader object is meant to import Python packages, so it will try to invoke either a file or a folder called as your package that are in the same folder as the current script.

    So, there is two ways you can solve this:

    1. Keep the PackageLoader object and create yourself a basic package, your file structure becomes
      .
      ├── basic
      │   ├── __init__.py
      │   └── templates
      │       └── basic_template.md
      └── basic.py
      
      The __init__.py file is mandatory, but could be just an empty file.
      Further reading the Python documentation regarding packages might help here.
    2. Use the FileSystemLoader instead of the PackageLoader.
      Here, your file structure stays the same, it is the code of basic.py that changes:
      #!/usr/bin/env python3
      from jinja2 import Environment, FileSystemLoader, select_autoescape
      
      env = Environment(
          loader=FileSystemLoader('templates'),
          autoescape=select_autoescape()
      )
      
      print(env.get_template('basic_template.md').render(
          {'version': 'version123', 'date': 'some-date'}
      ))
      

    Related issues in their issue tracker: