12

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)

Community
  • 1
  • 1
andiwin
  • 1,552
  • 3
  • 14
  • 27
  • Create a 1-folder frozen application and check if all the template files have been collected! – gmas80 Mar 22 '16 at 17:11
  • Possible duplicate of [Flask application built using pyinstaller not rendering index.html](https://stackoverflow.com/questions/32149892/flask-application-built-using-pyinstaller-not-rendering-index-html) – Chai Ang Jun 26 '18 at 08:23

3 Answers3

9

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')
Community
  • 1
  • 1
gmas80
  • 1,218
  • 1
  • 14
  • 44
  • Hey sorry if I don't know, how do you put the `pkg_data` attribute into `COLLECT` in `spec` file? I tried putting the `pathname` of the script (../path/to/pkg_data.py) but it didn't work. Should I `import` the script in my main script? or put it somewhere in `Analysis` attributes? Thanks – andiwin Mar 22 '16 at 21:56
  • @kingathur61 : Why don't you put your whole spec file in the question body? So that I can edit it as I believe that may work. – gmas80 Mar 22 '16 at 22:02
  • @kingathur61: added a new part to my answer – gmas80 Mar 23 '16 at 12:38
  • 1
    Wow, thanks! `collect_pkg_data('flask_appbuilder')` did lots of magic collecting for me. – mapto Feb 20 '19 at 14:28
3

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.

Forge
  • 6,538
  • 6
  • 44
  • 64
  • Ah, I see, so until pyinstaller is updated, there is nothing I can do right? – andiwin Mar 05 '16 at 23:37
  • @Forge and @kingathur61: ``jinja2.exceptions.TemplateNotFound`` is not related to ``pkg_resources``. Otherwise, it would have been something different: https://github.com/pyinstaller/pyinstaller/issues/1898 – gmas80 Mar 22 '16 at 17:10
0

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

Yax
  • 1