Pyinstaller Jinja2 TemplateNotFound

Question:

I am using pyinstaller to build my flask application,
everything is working fine except I get problems with Jinja2 templates.

It gave me jinja2.exceptions.TemplateNotFound,

I tried to put from app import template which is the templates folder, but it didn’t work (I guess since they don’t contain any py file).

I also tried changing the .spec file to include the templates folder

added_files = [
         ( '..\CommerceApp\app\templates', 'templates' ),
         ( '..\CommerceApp\app\static', 'static' )
        ]

a = Analysis(['..\CommerceApp\run.py'],
             pathex=['D:\PythonProjects\CommerceAppExe'],
             binaries=None,
             datas=added_files,
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)

But it didn’t work either, same result as if I copy the folder manually by myself.

Is there any way to include Template bundled together with the .exe?


Edit

This is my spec file

# -*- mode: python -*-

block_cipher = None

a = Analysis(['..\CommerceApp_withPyInstaller\run.py'],
             pathex=['D:\PythonProjects\CommerceAppExe'],
             binaries=None,
             datas=[],
             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,
          exclude_binaries=True,
          name='SupplyTracker',
          debug=False,
          strip=False,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               name='SupplyTracker')

Edit 2

Accepted Answer changed to gmas80 because it fixes the problem.

Edit 3

Also I just realize, I can just make a new folder with my package name and fill in the static templates css, html, etc, and it is gonna work (similar result from what gmas80 script does)

Asked By: andiwin

||

Answers:

The Jinja2 package uses the pkg_resources API which is not supported by PyInstaller. The pkg_resources module is provided via the setuptools package.

From the FAQ page of pyinstaller:

pkg_resources is currently not supported by PyInstaller. This means
that an application using a library which uses the the pkg_resources
API will probably not work out of the box. The only situation in which
it works is when it’s being used on .egg files (see above). For
details follow issue #183.

Answered By: Forge

I don’t believe that the issue is what is described in https://stackoverflow.com/a/35816876/2741329. I have just been able to freeze an application with Jinja2.

In my spec file I use this approach to collect all the templates:

from PyInstaller.building.build_main import Analysis, PYZ, EXE, COLLECT, BUNDLE, TOC


def collect_pkg_data(package, include_py_files=False, subdir=None):
    import os
    from PyInstaller.utils.hooks import get_package_paths, remove_prefix, PY_IGNORE_EXTENSIONS

    # Accept only strings as packages.
    if type(package) is not str:
        raise ValueError

    pkg_base, pkg_dir = get_package_paths(package)
    if subdir:
        pkg_dir = os.path.join(pkg_dir, subdir)
    # Walk through all file in the given package, looking for data files.
    data_toc = TOC()
    for dir_path, dir_names, files in os.walk(pkg_dir):
        for f in files:
            extension = os.path.splitext(f)[1]
            if include_py_files or (extension not in PY_IGNORE_EXTENSIONS):
                source_file = os.path.join(dir_path, f)
                dest_folder = remove_prefix(dir_path, os.path.dirname(pkg_base) + os.sep)
                dest_file = os.path.join(dest_folder, f)
                data_toc.append((dest_file, source_file, 'DATA'))

    return data_toc

pkg_data = collect_pkg_data('<YOUR LIB HERE>')

Then add pkg_data to the COLLECT (1-folder) or to the EXE (1-file) .spec.

In the 1-folder solution, you should be able to find all your templates in the created sub-folder.


Edit

This might work (assuming that you have a package (i.e., you have an __init__.py) following these suggestions: http://flask.pocoo.org/docs/0.10/patterns/packages/):

# -*- mode: python -*-

# <<< START ADDED PART    
from PyInstaller.building.build_main import Analysis, PYZ, EXE, COLLECT, BUNDLE, TOC


def collect_pkg_data(package, include_py_files=False, subdir=None):
    import os
    from PyInstaller.utils.hooks import get_package_paths, remove_prefix, PY_IGNORE_EXTENSIONS

    # Accept only strings as packages.
    if type(package) is not str:
        raise ValueError

    pkg_base, pkg_dir = get_package_paths(package)
    if subdir:
        pkg_dir = os.path.join(pkg_dir, subdir)
    # Walk through all file in the given package, looking for data files.
    data_toc = TOC()
    for dir_path, dir_names, files in os.walk(pkg_dir):
        for f in files:
            extension = os.path.splitext(f)[1]
            if include_py_files or (extension not in PY_IGNORE_EXTENSIONS):
                source_file = os.path.join(dir_path, f)
                dest_folder = remove_prefix(dir_path, os.path.dirname(pkg_base) + os.sep)
                dest_file = os.path.join(dest_folder, f)
                data_toc.append((dest_file, source_file, 'DATA'))

    return data_toc

pkg_data = collect_pkg_data('<yourapplication>')  # <<< Put the name of your package here
# <<< END ADDED PART    

block_cipher = None

a = Analysis(['..\CommerceApp_withPyInstaller\run.py'],
             pathex=['D:\PythonProjects\CommerceAppExe'],
             binaries=None,
             datas=[],
             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,
          exclude_binaries=True,
          name='SupplyTracker',
          debug=False,
          strip=False,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               pkg_data,  # <<< Add here the collected files
               strip=False,
               upx=True,
               name='SupplyTracker')
Answered By: gmas80

You can embed jinja2 templates by tweaking generated .spec file.

  1. 1st run in automatic mode: pyinstaller yourscript.py –name yourapp –onefile

  2. edit yourscript.spec to add templates directory in Analysis

    datas=[(‘yourscript/templates/*.html’, ‘yourscript/templates/’)],

  3. Generate one file installer from spec file: pyinstaller yourscript.spec

Answered By: Yax