I have packaged my Flask web application into an executable Python zipped archive (zipapp). I am having problems with the loading of templates. Flask/Jinja2 is unable to find the templates.
To load templates, I used jinja2.FunctionLoader
with a loading function that should have been able to read bundled files (in this case, Jinja templates) from inside the executable zip archive (reference: python: can executable zip files include data files?). However, the loading function is unable to find the template (see: (1)
in the code), even though the template is readable from outside the loading function (see: (2)
in the code).
Here's the directory structure:
└── src
├── __main__.py
└── templates
├── index.html
└── __init__.py # Empty file.
src/__main__.py
:
import pkgutil
import jinja2
from flask import Flask, render_template
def load_template(name):
# (1) ATTENTION: this produces an error. Why?
# Error message:
# FileNotFoundError: [Errno 2] No such file or directory: 'myapp'
data = pkgutil.get_data('templates', name)
return data
# (2) ATTENTION: Unlike (1), this successfully found and read the template file. Why?
data = pkgutil.get_data('templates', 'index.html')
print(data)
# This also works:
data = load_template('index.html')
print(data)
# Why?
app = Flask(__name__)
app.config['SECRET_KEY'] = 'my-secret-key'
app.jinja_loader = jinja2.FunctionLoader(load_template) # <-
@app.route('/')
def index():
return render_template('index.html')
app.run(host='127.0.0.1', port=3000)
To produce the executable archive, I installed all dependencies into src/
(using pip3 install wheel flask --target src/
), then I ran python3 -m zipapp src/ -o myapp
to produce the executable archive itself. I then ran the executable archive using python3 myapp
. Unfortunately, trying to access the the index page through a web browser results in an error:
# ...
File "myapp/__main__.py", line 10, in load_template
File "/usr/lib/python3.6/pkgutil.py", line 634, in get_data
return loader.get_data(resource_name)
FileNotFoundError: [Errno 2] No such file or directory: 'myapp'
The error is caused by (1)
in the code. As part of my debugging effort, I added (2)
to check whether or not the template could be found in the global scope of the file. Surprisingly, it successfully finds and reads the template file.
What accounts for the difference in behavior between (1)
and (2)
? More importantly, how can I make Flask find Jinja templates that are bundled together with a Flask app inside an executable Python zip archive?
(Python version: 3.6.8 on linux; Flask version: 1.1.1)