7

I am getting below errors : script name = prepareIncidentCountMail.py

Traceback (most recent call last):
  File "Alexa\prepareIncidentCountMail.py", line 52, in <module>
  File "site-packages\pandas\core\frame.py", line 683, in style
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "c:\users\avikumar\documents\learn\alexa\venv\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 631, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\pandas\io\formats\style.py", line 50, in <module>
  File "site-packages\pandas\io\formats\style.py", line 118, in Styler
  File "site-packages\jinja2\environment.py", line 830, in get_template
  File "site-packages\jinja2\environment.py", line 804, in _load_template
  File "site-packages\jinja2\loaders.py", line 113, in load
  File "site-packages\jinja2\loaders.py", line 234, in get_source
  File "site-packages\pkg_resources\__init__.py", line 1459, in has_resource
  File "site-packages\pkg_resources\__init__.py", line 1509, in _has
NotImplementedError: Can't perform this operation for unregistered loader type
[10536] Failed to execute script prepareIncidentCountMail

I am using pandas style with the help of link : Change color of complete row in data frame in pandas

I see style is using jinja2 causing the above error. Is there any way to hook this error or any other tool to convert python script into single executable.

avinashse
  • 1,440
  • 4
  • 30
  • 55

2 Answers2

8

I just solved this myself yesterday using a tweaked version of what giumas did here: https://github.com/pyinstaller/pyinstaller/issues/1898

The issue isn't so much hooking (which was my first attempt at a solution), but the fact that pandas style module imports jinja2 which uses a "get_template" method which in turn uses the pkg_resources module. That last one is the issue, for some reason pyinstaller doesn't play well with the pkg_resources module.

Solution: Find where pandas is installed and go to something like

C:\Users\UserName\AppData\Local\Programs\Python\Python36\Lib\site-packages\pandas\io\formats

In the formats folder find the style.py file and open it in your favorite text editor. In style.py scroll down to about line 118 where you will see this:

template = env.get_template("html.tpl")

change this line to:

template = env.from_string("html.tpl")

save the file and re-run pyinstaller. When you try and run the executable it should perform as expected sans any error messages.

Hope it helps.

Nick Wantz
  • 81
  • 1
  • 3
  • 1
    I wasn't able to get my dataframe to render just by changing 'get_template' to 'from_string'. All that would show up would be the literal string 'html.tpl'. The reason for this I believe is that the 'from_string' method uses the string it is fed as the template and is using the filename as the template. I added in the following code and it fixed it for me. `path = os.path.dirname(__file__) + "/templates/html.tpl" with open(path) as fin: data = fin.read() template = env.from_string(data)` – Nick Anderson Apr 23 '19 at 21:12
  • You put this code before `template = env.from_string("html.tpl")` or after? – igorkf Oct 03 '19 at 14:36
1

The following doesn't require manually changing the library code. This could be added as a change to the official Jinja2 hook in pyinstaller if somebody has the time to do so:

import sys
from jinja2.loaders import FileSystemLoader


class PyInstallerPackageLoader(FileSystemLoader):
    """Load templates from packages deployed as part of a Pyinstaller build.  It is constructed with
    the name of the python package and the path to the templates in that
    package::
        loader = PackageLoader('mypackage', 'views')
    If the package path is not given, ``'templates'`` is assumed.
    Per default the template encoding is ``'utf-8'`` which can be changed
    by setting the `encoding` parameter to something else.  Due to the nature
    of eggs it's only possible to reload templates if the package was loaded
    from the file system and not a zip file.
    """

    def __init__(self, package_name, package_path="templates", encoding="utf-8"):
        # Use the standard pyinstaller convention of storing additional package files
        full_package_path = f"{sys._MEIPASS}/{package_name}/{package_path}"
        # Defer everything else to the FileSystemLoader
        super().__init__(full_package_path, encoding)


def patch_jinja_loader():
    # patching the pandas loader which fails to locate the template when called from a pyinstaller build

    if getattr(sys, "frozen", False):
        import jinja2

        jinja2.PackageLoader = PyInstallerPackageLoader

It basically makes a loader that looks like the Jinja2 PackageLoader that pandas tries to use so that it can be patched. In fact it instead uses the FileSystemLoader which is what we need for pyinstaller to just find the template file.

The above needs to be run before pandas is imported.

I've also posted this solution on the GitHub issue

Mark Adamson
  • 878
  • 10
  • 17