无法将Jinja2模板包含在Pyinstaller发行版中

cah*_*hoy 5 python templates pyinstaller jinja2

我有一个使用Jinja2模板的python脚本,我正在尝试使用Pyinstaller创建一个文件夹分发.

在Jinja,我让程序通过使用PackageLoader类来理解模板的位置.下面的代码显示它指向Python包templates下的我的文件夹pycorr.

env = Environment(loader=PackageLoader('pycorr', 'templates'))
template = env.get_template('child_template.html')
Run Code Online (Sandbox Code Playgroud)

这是我的文件夹结构:

pycorr
| |
| + templates
|    |
|    + base.html
|    + child.html
Run Code Online (Sandbox Code Playgroud)

当我使用Pyinstaller将包编译成单个文件夹时,我没有看到任何与Jinja2相关的警告/错误,我可以启动.exe文件.但是,当程序开始查找Jinja2模板时,它会失败,并在控制台窗口中显示此错误消息:

Traceback (most recent call last):
  ...
  File "C:\Users\ ... \out00-PYZ.pyz\pycorr.WriterToHTML", line 96, in htmlout_table
  File "C:\Users\ ... \out00-PYZ.pyz\pycorr.WriterToHTML", line 13, in __init__
  File "C:\Users\ ... \out00-PYZ.pyz\pycorr.WriterToHTML", line 48, in __set_template
  File "C:\Users\ ... \out00-PYZ.pyz\jinja2.environment", line 791, in get_template
  File "C:\Users\ ... \out00-PYZ.pyz\jinja2.environment", line 765, in _load_template
  File "C:\Users\ ... \out00-PYZ.pyz\jinja2.loaders", line 113, in load
  File "C:\Users\ ... \out00-PYZ.pyz\jinja2.loaders", line 224, in get_source
  File "C:\Users\ ... \dist\OCA_CO~1\eggs\setuptools-14.3-py2.7.egg\pkg_resources\__init__.py", line 1572, in has_resource 
    return self._has(self._fn(self.module_path, resource_name))
  File "C:\Users\ ... \dist\OCA_CO~1\eggs\setuptools-14.3-py2.7.egg\pkg_resources\__init__.py", line 1627, in _has
    "Can't perform this operation for unregistered loader type"
  NotImplementedError: Can't perform this operation for unregistered loader type
Run Code Online (Sandbox Code Playgroud)

我真的不明白错误信息,但我的猜测是Pyinstaller需要找到该templates文件夹.所以我在Pyinstaller .spec文件中添加了这些行:

a.datas += [('BASE', './pycorr/templates/base.html', 'DATA')]
a.datas += [('TABLE', './pycorr/templates/table_child.html', 'DATA')]
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=None,
               upx=False,
               name='OCA_correlation')
Run Code Online (Sandbox Code Playgroud)

但它似乎没有解决问题.

有人可以帮忙吗?我多次阅读Pyinstaller手册,但我无法理解.

Chr*_*isL 10

我在尝试使用代码从 PyInstaller 发行版中将 Pandas DataFrame 渲染为 html 时遇到了类似的 Jinja2 错误,

html = df.style.render()
Run Code Online (Sandbox Code Playgroud)

我通过修改包加载器指令解决了这个问题。

在 Pandas 样式文件中:site-packages\pandas\io\formats\style.py

我换了,

loader = PackageLoader("pandas", "io/formats/templates")   
Run Code Online (Sandbox Code Playgroud)

和,

if getattr(sys, 'frozen', False):
    # we are running in a bundle
    bundle_dir = sys._MEIPASS
    loader = FileSystemLoader(bundle_dir)
else:
    loader = PackageLoader("pandas", "io/formats/templates")
Run Code Online (Sandbox Code Playgroud)

并在文件顶部进行相应的导入,

import sys
Run Code Online (Sandbox Code Playgroud)

现在,如果程序被“冻结”,那么加载器将在包目录中查找模板。在这种情况下,最后一步是将模板添加到包中。为此,我使用 --add-data 命令从命令行运行 PyInstaller。比如类似下面的命令添加默认模板html.tpl,

pyinstaller --add-data PATH1\site-packages\pandas\io\formats\templates\html.tpl;.  PATH2\Main.py
Run Code Online (Sandbox Code Playgroud)


Uyn*_*nix 2

我在使用 pyinstaller 构建 GUI 时遇到了这个问题。我使用 Jinja2 呈现报告,但模板未加载,相反,我还收到“未注册加载器类型”错误。网上阅读和测试了很多解决方案我终于找到了解决办法:必须使用FileSystemLoader而不是PackageLoader。还需要为 FileSystemLoader 提供文件路径。我的最终解决方案是结合此处此处的信息。

下面提供了此解决方案的完整示例。我的代码位于 testjinjia2 下,模板位于子目录 templates 中:

testjinja2
| |
| + templates
|    |
|    + base.html
|    + report.html
testreport.py
testreport.spec
Run Code Online (Sandbox Code Playgroud)

在测试报告.spec中:

# -*- mode: python -*-

block_cipher = None


a = Analysis(['E:\\testjinja2\\testreport.py'],
             pathex=['E:\\testjinja2'],
             binaries=[],
             datas=[('E:\\testjinja2\\templates\\base.html', '.'),
                    ('E:\\testjinja2\\templates\\report.css', '.'),
                    ('E:\\testjinja2\\templates\\report.html', '.')],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          name='testreport',
          debug=False,
          strip=False,
          upx=True,
      console=True )
Run Code Online (Sandbox Code Playgroud)

在测试报告.py中,

import os
import sys
from jinja2 import Environment, PackageLoader, FileSystemLoader


def resource_path(relative_path, file_name):
    """ Get absolute path to resource, works for both in IDE and for PyInstaller """
    # PyInstaller creates a temp folder and stores path in sys._MEIPASS
    # In IDE, the path is os.path.join(base_path, relative_path, file_name)
    # Search in Dev path first, then MEIPASS
    base_path = os.path.abspath(".")
    dev_file_path = os.path.join(base_path, relative_path, file_name)
    if os.path.exists(dev_file_path):
        return dev_file_path
    else:
        base_path = sys._MEIPASS
        file_path = os.path.join(base_path, file_name)
        if not os.path.exists(file_path):
            msg = "\nError finding resource in either {} or {}".format(dev_file_path, file_path)
            print(msg)
            return None
        return file_path

class Report:

    def main(self, output_html_file):
        # template_loader = PackageLoader("report", "templates")
        # --- PackageLoader returns unregistered loader problem,  use FileSystemLoader instead
        template_file_name = 'report.html'
        template_file_path = resource_path('templates', template_file_name)
        template_file_directory = os.path.dirname(template_file_path)
        template_loader = FileSystemLoader(searchpath=template_file_directory)
        env = Environment(loader=template_loader)  # Jinja2 template environment
        template = env.get_template(template_file_name)
        report_content_placeholder = "This is my report content placeholder"
        html = template.render(report_content= report_content_placeholder)
        with open(output_html_file, 'w') as f:
            f.write(html)

if __name__ == "__main__":
    my_report = Report()
    my_report.main("output.html")
Run Code Online (Sandbox Code Playgroud)

需要一个方法resource_path,因为jinja模板文件的文件路径在我的IDE和从exe文件中提取的文件中是不同的。

还有一些简单的模板文件供您尝试。
基本.html

<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>

    <style>
        .centered {
            text-align: center;
        }
        .centeredbr {
            text-align: center;
            page-break-before:always;
        }

        .underlined {
            text-decoration: underline;
        }

    </style>
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

报告 html

<!DOCTYPE html>
{% extends "base.html" %}

{% block body %}
<h1 class="centered underlined">Report Title</h1>

<h2 class="centeredbr">Chaper I</h2>

<p>{{ report_content }}</p>

{% endblock %}
Run Code Online (Sandbox Code Playgroud)

我正在使用 pyinstaller 3.2.1 和 Python 3.5.1 Anaconda Custom(64 位)