Python 如何使Flask/Jinja2在可执行的zip存档中加载捆绑模板?
我已经将我的Flask web应用程序打包到一个可执行的Python压缩存档()中。我在加载模板时遇到问题。Flask/Jinja2无法找到模板 为了加载模板,我使用了Python 如何使Flask/Jinja2在可执行的zip存档中加载捆绑模板?,python,flask,jinja2,zipapp,Python,Flask,Jinja2,Zipapp,我已经将我的Flask web应用程序打包到一个可执行的Python压缩存档()中。我在加载模板时遇到问题。Flask/Jinja2无法找到模板 为了加载模板,我使用了jinja2.FunctionLoader和加载函数,该函数应该能够从可执行zip存档(参考:)中读取捆绑文件(在本例中为Jinja模板)。但是,加载函数无法找到模板(请参阅代码中的:(1)),即使可以从加载函数外部读取模板(请参阅代码中的:(2)) 以下是目录结构: └── src ├── __main__.py
jinja2.FunctionLoader
和加载函数,该函数应该能够从可执行zip存档(参考:)中读取捆绑文件(在本例中为Jinja模板)。但是,加载函数无法找到模板(请参阅代码中的:(1)
),即使可以从加载函数外部读取模板(请参阅代码中的:(2)
)
以下是目录结构:
└── src
├── __main__.py
└── templates
├── index.html
└── __init__.py # Empty file.
src/\uuuuuu main\uuuuuuuuu.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)
错误是由代码中的(1)
引起的。作为调试工作的一部分,我添加了(2)
,以检查是否可以在文件的全局范围内找到模板。令人惊讶的是,它成功地找到并读取了模板文件
是什么解释了(1)
和(2)
之间的行为差异?更重要的是,我如何让Flask在可执行的Python zip存档中找到与Flask应用程序捆绑在一起的Jinja模板
(linux上的Python版本:3.6.8;Flask版本:1.1.1)
jinja2.FunctionLoader(load_template)
正在寻找一个函数以unicode字符串形式返回完整的index.html
模板。根据:
一种加载程序,它被传递一个进行加载的函数。函数接收模板的名称,并且必须返回带有模板源的unicode字符串、格式为(源、文件名、uptodatefunc)的元组,或者如果模板不存在,则返回None
pkgutil.get_data('templates',name)
不返回unicode字符串,而是返回bytes对象。要解决这个问题,您应该使用pkgutil.get_data('templates',name.).decode('utf-8')
def load_模板(名称):
"""
从templates文件夹加载文件并以字符串形式返回文件内容。
请参阅jinja2.FunctionLoader文档。
"""
返回pkgutil.get_数据('templates',name)。解码('utf-8')
这意味着第(2)部分可以正常工作,因为代码正在将index.html
打印为字节对象。Print可以处理bytes对象,它看起来几乎与控制台上的字符串相同。但是,第(1)部分中的代码将失败,因为它被馈送到需要字符串的jinja2.FunctionLoader
。第(1)部分失败,我的ValueError
我怀疑,由于您的错误消息是一个
FileNotFoundError
,并将myapp
作为文件调用,因此您文章的这一部分与您的应用程序不完全匹配。我在Windows 10和Ubuntu Server 18.04以及Python 3.6和3.7上完全复制了这些说明,除了需要使用解码
之外,没有其他问题。我确实偶尔在Ubuntu上遇到权限错误
,这要求我运行sudo python3 myapp
一旦你更新了你的代码,你应该更新你的myapp
。
然后你会发现
pkgutil.get_data('templates',name)
始终返回上下文数据
当我尝试您的原始代码时,它失败了,因为ValueError:太多值无法解压缩(预期为3)
,这是由app.jinja\u loader=jinja2.FunctionLoader(加载模板)生成的
在我看来,最好的方法是自定义自己的渲染模板
,如下所示:
import pkgutil
import jinja2
from flask import Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = 'my-secret-key'
template = jinja2.Environment()
def load_template(name):
data = pkgutil.get_data('templates', name)
return data
def render_template(name, **kwargs):
data = load_template(name).decode()
tpl = template.from_string(data)
return tpl.render(**kwargs)
@app.route('/')
def index():
return render_template('index.html')
app.run(host='127.0.0.1', port=3000)
此外,以下是建议:
\uuuu main\uuuuuuu.py
,然后重新执行命令以创建新的Zipappkgutil.get\u data('templates',name)
之前,不要尝试print(sys.modules[“templates”]。\uuuuu file\uuuu)
打印(sys.modules[“templates”]。importlib.util.find\u spec
pkgutil.get_data('templates',name)
之后,您可能会调试并获取sys.modules[“templates”]。\uuuuuu文件\uuuuuuuuu==“myapp/templates/\uuuuuu init\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
myapp/templates/
不是目录,因此如果您试图设置app.template\u folder=os.path.dirname(sys.modules[“templates”]。\u文件\u)
非常感谢。你知道问题代码中所示的
(1)
和(2)
之间的区别是什么吗?我做了一些测试,意识到我对pkgutil的理解是错误的。它应该与Zipap兼容。我还测试了第(1)部分和第(2)部分,并在添加了解码('utf-8')
后成功运行了这两个部分。没有解码
,我收到了(1)的错误消息,但它与您的不同。有趣。在我的例子中,(1)
失败,出现FileNotFoundError:[Errno 2]没有这样的文件或目录:'myapp'
,而(2)
成功运行。您使用的是哪个操作系统、Python版本、Flask版本?Python 3.6、Flask 1.1.1、Windows 10。我在没有使用decode的情况下得到了一个ValueError
。如果我试图呈现的不是index.html
,我只会得到一个FileNotFoundError
。在您的代码中,是否有render_模板('index.html')
verbatim?或者您是否可以将myapp
输入到render\u template
?我尝试了您的代码,但我的原始错误仍然出现:FileNotFoundError:[Errno 2]没有这样的文件或目录:“myapp”
。回溯表明它是由data=pkgutil.get_data('templates',name)
引起的。您是否重新运行了python3-m zipapp src/-o myapp
?是的rm myapp
,python3-m zipapp src/-o myapp
。您是否保留了“templates/\uu init\uuuuuu.py”是的templates/_init__.py
和templates/index.html
都存在。
import pkgutil
import jinja2
from flask import Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = 'my-secret-key'
template = jinja2.Environment()
def load_template(name):
data = pkgutil.get_data('templates', name)
return data
def render_template(name, **kwargs):
data = load_template(name).decode()
tpl = template.from_string(data)
return tpl.render(**kwargs)
@app.route('/')
def index():
return render_template('index.html')
app.run(host='127.0.0.1', port=3000)