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)
jinja2.FunctionLoader(load_template)
is looking for a function to return the fullindex.html
template as a unicode string. Per the jinja2 docs:pkgutil.get_data('templates', name)
doesn't return a unicode string, instead it returns a bytes object. To fix this, you should usepkgutil.get_data('templates', name).decode('utf-8')
This means that part (2) will work fine since the code is printing
index.html
as a bytes object. Print can handle a bytes object and it will look almost the same as a string on the console. However, the code in part (1) will fail since it is fed tojinja2.FunctionLoader
which expects a string. Part (1) failed with aValueError
for me.I suspect that since your error message is a
FileNotFoundError
and calls outmyapp
as the file, that part of your post does not exactly match your application. I replicated the instructions exactly on both Windows 10 and Ubuntu Server 18.04 as well as with Python 3.6 and 3.7 and had no issues aside from needed to usedecode
. I did occasionally run intoPermissionErrors
on Ubuntu which required me to runsudo python3 myapp
.