Search code examples
pythonvirtualenvpyinstallerpipenv

PyInstaller failing to import distuitls when used with pipenv


When running the code snip below within a pipenv (2018.11.26) using virtualenv (16.7.7) in a raspbian , it executes flawlessly and all the operations complete as expected.

import logging
import distutils
from PIL import Image, ImageDraw, ImageFont, ImageFile, ImageOps
from pathlib import Path

logger = logging.getLogger(__name__)
logger.root.setLevel('DEBUG')

image = Path('/home/pi/tmp/4886.jpg').expanduser()

size = (100, 200)
try:
    logging.info(f'opening image: {image}')
    im = Image.open(image)
    im.thumbnail(size)

except (PermissionError, FileNotFoundError, OSError) as e:
    logging.warning(f'could not open image file: {image}')
    logging.warning(f'image error: {e}')
    logging.warning(f'using empty image')

print(f'image size is: {im.size}')

output This produces the expected result

INFO:root:opening image: /home/pi/tmp/4886.jpg
image size is: (100, 90)

After packaging it with pipenv run python -m PyInstaller im_open.py the compiled version complains DEBUG:PIL.Image:Image: failed to import JpegImagePlugin: No module named 'distutils'

output

INFO:root:opening image: /home/pi/tmp/4886.jpg
...
DEBUG:PIL.Image:Importing IptcImagePlugin
DEBUG:PIL.Image:Importing JpegImagePlugin
DEBUG:PIL.Image:Image: failed to import JpegImagePlugin: No module named 'distutils'
DEBUG:PIL.Image:Importing Jpeg2KImagePlugin
DEBUG:PIL.Image:Importing McIdasImagePlugin
DEBUG:PIL.Image:Importing MicImagePlugin
DEBUG:PIL.Image:Image: failed to import MicImagePlugin: No module named 'olefile'
DEBUG:PIL.Image:Importing MpegImagePlugin
DEBUG:PIL.Image:Importing MpoImagePlugin
DEBUG:PIL.Image:Image: failed to import MpoImagePlugin: No module named 'distutils'
DEBUG:PIL.Image:Importing MspImagePlugin
DEBUG:PIL.Image:Importing PalmImagePlugin
DEBUG:PIL.Image:Importing PcdImagePlugin
...
WARNING:root:could not open image file: /home/pi/tmp/4886.jpg
WARNING:root:image error: cannot identify image file '/home/pi/tmp/4886.jpg'
WARNING:root:using empty image
Traceback (most recent call last):
  File "im_open.py", line 69, in <module>
    print(f'image size is: {im.size}')
NameError: name 'im' is not defined

I've tried the following:

  • explicitly including distutils (as seen above in the code snip)
  • adding distutils to the hiddenimports list in the .spec file: hiddeimports=['distutils']

Relevant research


Solution

  • There is a workaround for this issue in another bug report. The following method appears to resolve this issue for virtualenv 16.4.0 through at least 16.7.7

    1. Install PyInstaller within pipenv (I'm unsure why this fixes the issue, but appears to be the most reliable at the moment): pipenv install PyInstaller
    2. Open the PyInstaller directory created by pipenv: pipenv open PyInstaller and edit hooks/pre_find_module_path/hook-distutils.py to match the patch shown below.

    patched pre_find_module_path/hook-distutils.py

    import distutils
    import os
    
    from PyInstaller.utils.hooks import logger
    
    
    def pre_find_module_path(api):
        # Absolute path of the system-wide "distutils" package when run from within
        # a venv or None otherwise.
        distutils_dir = getattr(distutils, 'distutils_path', None)
        if distutils_dir is not None:
            # workaround for https://github.com/pyinstaller/pyinstaller/issues/4064
            if distutils_dir.endswith('__init__.py'):
                distutils_dir = os.path.dirname(distutils_dir)
            # end workaround patch
    
            # Find this package in its parent directory.
            api.search_dirs = [os.path.dirname(distutils_dir)]
            logger.info('distutils: retargeting to non-venv dir %r' % distutils_dir)