PyInstaller + PyFiglet = Trouble
Hugo Martins
Posted on October 21, 2019
This essay is a slightly extended version of a comment I made on PyInstaller
's official repository, regarding PyInstaller
and PyFiglet
integration, as well as some known issues.
PyFiglet allows you to create really large letters out of ordinary text, as per figlet's own description, which essentially results in interesting displays commonly seen in command line interfaces. One example of this would be:
# From wwww.figlet.org.
_ _ _ _ _ _
| (_) | _____ | |_| |__ (_)___
| | | |/ / _ \ | __| '_ \| / __|
| | | < __/ | |_| | | | \__ \
|_|_|_|\_\___| \__|_| |_|_|___/
Whenever you want to freeze a Python package with PyInstaller
, PyInstaller
will run a series of built-in or custom hooks which define what a specific package needs in order to be freezable. PyFiglet
doesn't have a built-in hook in PyInstaller
so someone suggested that a hook should be created so that everyone could use PyInstaller
to freeze applications that use PyFiglet
. This is where things started to become troublesome.
As it appears, PyFiglet
uses pkg_resources
as a way to find out which fonts you would like to use for the displayed text. A runtime hook in PyInstaller
- these are different from the pre-runtime hooks mentioned above - is registering pkg_resources.NullProvider
which means that everything that uses pkg_resources
will forcibly use the NullProvider
, triggering the error: NotImplementedError: Can't perform this operation for unregistered loader type
. In essence, this means that the registered provider for pkg_resources
is incompatible with importing some packages for freezing.
Since, we cannot register a new provider, as the NullProvider
will always be registered first, and we also can't easily change the core of PyInstaller
, in order to register a different provider, I've come upon a workaround that builds on top of the suggestions in the mentioned discussion.
We start by adding a custom hook to be run by PyInstaller
, suggested in the initial issue:
# hook-pyfiglet.py
from PyInstaller.utils.hooks import collect_all
datas, binaries, hiddenimports = collect_all("pyfiglet")
This will force PyInstaller
to import all known PyFiglet
modules, as well as data. Following that, we need to force registering a new provider, somewhere inside the script that will be frozen, before any use of pyfiglet
occurs. An example of this will be:
import pkg_resources
import sys
from pyimod03_importers import FrozenImporter
if getattr(sys, 'frozen', False):
pkg_resources.register_loader_type(FrozenImporter, pkg_resources.DefaultProvider)
As I said in my comment, in essence, instead of registering NullProvider
, we now register FrozenImporter
with DefaultProvider
, which inherits from NullProvider
anyway - but doesn't error apparently. I couldn't figure out any side effects from this but I would advise caution, when using this workaround, as well as thorough testing, to guarantee that there are no side effects that affect the script that is going to be frozen.
Originally published at hugomartins.io on June 1, 2019.
Posted on October 21, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024