Setuptools: SystemError: Parent module 'setuptools' not loaded, cannot perform relative import with setuptools 50

Created on 31 Aug 2020  ยท  23Comments  ยท  Source: pypa/setuptools

After upgrading setuptools to 50.0 today, the environment fails to locate the entry points as it could not import distutils

$ python --version
Python 3.5.1
$ python -c "import distutils"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "<frozen importlib._bootstrap>", line 969, in _find_and_load
  File "<frozen importlib._bootstrap>", line 958, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 666, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 577, in module_from_spec
  File "/home/gchan/tmp/setuptools-python-3.5/lib/python3.5/site-packages/_distutils_hack/__init__.py", line 82, in create_module
    return importlib.import_module('._distutils', 'setuptools')
  File "/home/gchan/tmp/setuptools-python-3.5/lib64/python3.5/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 981, in _gcd_import
  File "<frozen importlib._bootstrap>", line 931, in _sanity_check
SystemError: Parent module 'setuptools' not loaded, cannot perform relative import

The issue could not be found in the python 3.8 environment.

blocker bug

Most helpful comment

Same issue.
Worked around with downgrading and blacklisting 50.0:

pip install "setuptools!=50.0" --force-reinstall

and in requirements.txt:

setuptools!=50.0

All 23 comments

Found the same issue with python 3.6.1 (pyenv)

Same here, Ubuntu 16.04. Seems to be another symptom of #2350.

 SystemError: Parent module 'setuptools' not loaded, cannot perform relative import
   File "<frozen importlib._bootstrap>", line 931, in _sanity_check
   File "<frozen importlib._bootstrap>", line 981, in _gcd_import
     return _bootstrap._gcd_import(name[level:], package, level)
   File "/usr/lib/python3.5/importlib/__init__.py", line 126, in import_module
     return importlib.import_module('._distutils', 'setuptools')
   File "/usr/local/lib/python3.5/dist-packages/_distutils_hack/__init__.py", line 82, in create_module
   File "<frozen importlib._bootstrap>", line 577, in module_from_spec
   File "<frozen importlib._bootstrap>", line 666, in _load_unlocked
   File "<frozen importlib._bootstrap>", line 958, in _find_and_load_unlocked
   File "<frozen importlib._bootstrap>", line 969, in _find_and_load
     from distutils.version import StrictVersion
   File "/usr/local/lib/python3.5/dist-packages/redis/connection.py", line 2, in <module>
     from redis.connection import (ConnectionPool, UnixDomainSocketConnection,
   File "/usr/local/lib/python3.5/dist-packages/redis/client.py", line 12, in <module>
     from redis.client import Redis, StrictRedis
   File "/usr/local/lib/python3.5/dist-packages/redis/__init__.py", line 1, in <module>
     from redis import Redis
   [...]

The same can be said for Matplotlib:

main.py:6: in <module>
    import matplotlib
../venv/lib/python3.5/site-packages/matplotlib/__init__.py:120: in <module>
    import distutils.version
<frozen importlib._bootstrap>:969: in _find_and_load
    ???
<frozen importlib._bootstrap>:958: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:666: in _load_unlocked
    ???
<frozen importlib._bootstrap>:577: in module_from_spec
    ???
../venv/lib/python3.5/site-packages/_distutils_hack/__init__.py:82: in create_module
    return importlib.import_module('._distutils', 'setuptools')
../venv/lib/python3.5/importlib/__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
<frozen importlib._bootstrap>:981: in _gcd_import
    ???
<frozen importlib._bootstrap>:931: in _sanity_check
    ???
E   SystemError: Parent module 'setuptools' not loaded, cannot perform relative import

Same issue.
Worked around with downgrading and blacklisting 50.0:

pip install "setuptools!=50.0" --force-reinstall

and in requirements.txt:

setuptools!=50.0

The workaround described in the changelog worked for me:

export SETUPTOOLS_USE_DISTUTILS=stdlib

Same issue here with python 3.5.2,

โฏ pipenv
Traceback (most recent call last):
  File "/home/apinio/.local/bin/pipenv", line 5, in <module>
    from pipenv import cli
  File "/home/apinio/.local/lib/python3.5/site-packages/pipenv/__init__.py", line 55, in <module>
    from .cli import cli
  File "/home/apinio/.local/lib/python3.5/site-packages/pipenv/cli/__init__.py", line 4, in <module>
    from .command import cli    # noqa
  File "/home/apinio/.local/lib/python3.5/site-packages/pipenv/cli/command.py", line 15, in <module>
    from .options import (
  File "/home/apinio/.local/lib/python3.5/site-packages/pipenv/cli/options.py", line 14, in <module>
    from ..utils import is_valid_url
  File "/home/apinio/.local/lib/python3.5/site-packages/pipenv/utils.py", line 16, in <module>
    from distutils.spawn import find_executable
  File "<frozen importlib._bootstrap>", line 969, in _find_and_load
  File "<frozen importlib._bootstrap>", line 958, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 666, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 577, in module_from_spec
  File "/home/apinio/.local/lib/python3.5/site-packages/_distutils_hack/__init__.py", line 82, in create_module
    return importlib.import_module('._distutils', 'setuptools')
  File "/usr/lib/python3.5/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 981, in _gcd_import
  File "<frozen importlib._bootstrap>", line 931, in _sanity_check
SystemError: Parent module 'setuptools' not loaded, cannot perform relative import

Downgrading to setuptools-49.6.0 fixes the problem.

Can anyone clarify what SETUPTOOLS_USE_DISTUTILS=stdlib actually does? I realize it's a workaround, and it's definitely necessary right now for many of us, but how different is it from pre-50 behavior, if at all?

The environment variable is explained in the changelog, along with the change that caused the breakage: https://setuptools.readthedocs.io/en/latest/history.html#v50-0-0 . But I think the number of "environments or invocations where this behavior is undesirable" was far larger than expected, being basically anybody on Debian, Ubuntu, or any other Debian-derived distribution.

For what it's worth:

  1. I'm not terribly new to Python, but the impact of "Once again, Setuptools overrides the stdlib distutils on import." is completely lost on me, especially when it comes to discovering entrypoints.
  2. Whatever this "number of environments" is, by far the biggest impact to me is that my organization heavily uses Python (and setuptools entrypoints) inside of Docker images... and _all_ of our images are based on Debian. I'd expect that many people/organizations are in this same situation (given the popularity of Debian as a base for Docker images) but may not have rebuilt images recently enough for the impact to be discovered.

On my side the impact is significant as every environments (by default installed by the latest setuptools) breaks. Many dependencies / libraries directly import distutils for the LooseVersion class. We cannot ask the downstream users to inject the environment variable always as well. I think eventually we should accept that setuptools monkeypatch distutils internally. But I just do not understand

  1. Why does it occur on older Python 3 versions, e.g. python 3.5 and 3.6?

  2. Why does setuptools hack the stdlib distutils even when other packages import distutils directly?

The environment variable is explained in the changelog, along with the change that caused the breakage: https://setuptools.readthedocs.io/en/latest/history.html#v50-0-0 . But I think the number of "environments or invocations where this behavior is undesirable" was far larger than expected, being basically anybody on Debian, Ubuntu, or any other Debian-derived distribution.

@jmbowman I read it, but I am concerned about the phrase "temporary escape hatch".... this is turning out to be a significant change that'll have a broad negative impact (especially, as @jantman notes, it will impact many who don't regularly rebuild their images and will run into this in the coming days and weeks). The thought that this will soon become impossible to override is worrisome - some reassurance that this won't be so temporary an escape hatch would be appreciated.

I'd be happy just to pin down to < 50.0 in the requirements until this no longer breaks things, but that's highly discouraged when it comes to a requirements-based install. I'm not sure there's any other viable workaround except for the escape hatch above.

(FWIW I'm @MartyMacGyver above so this is part of the same thread)

Why does setuptools hack the stdlib distutils even when other packages import distutils directly?

Mainly because of the use-case where a package imports distutils before importing setuptools described in #2259. Probably that should be the issue linked in the changelog. Perhaps the hack could be made more sophisticated to inspect the call stack and only enable the hack when a setup.py is present.

Why does it occur on older Python 3 versions, e.g. python 3.5 and 3.6?

I'm yet unsure. The issue doesn't occur on all Python 3.6 or 3.5 environments. Can someone suggest a way to replicate the issue, preferably minimally?

But I think the number of "environments or invocations where this behavior is undesirable" was far larger than expected, being basically anybody on Debian, Ubuntu, or any other Debian-derived distribution.

Indeed, the impact is undesirably huge. I had gone down the route of attempting to maintain compatibility and incorporate the debian-specific changes, but decided against that. However, that was before #2259, which expanded the scope of the hack. Perhaps we should re-open #2232 to address Debian-specific needs.

I'll use this ticket to focus surgically on the reported error.

Using Ubuntu Bionic and Python 3.6, the error does not occur for me:

draft $ docker run -it jaraco/multipy-tox python3.6 -m pip-run -q setuptools==50 -- -c "import distutils; print('done')"
WARNING: You are using pip version 19.3.1; however, version 20.2.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
done

Similarly on Python 3.5 (unable to reproduce):

$ docker run -it jaraco/multipy-tox python3.5 -m pip-run --no-build-isolation -q setuptools==50 -- -c "import distutils; print(distutils
.__file__); import setuptools; print(setuptools.__version__)"
/usr/local/lib/python3.5/dist-packages/setuptools/_distutils/__init__.py
50.0.0

Still present in 50.0.1

18:55:48         Collecting setuptools!=50.0.0
18:55:48           Downloading setuptools-50.0.1-py3-none-any.whl (784 kB)
18:55:48         
18:55:48         Installing collected packages: setuptools
18:55:48           Attempting uninstall: setuptools
18:55:48             Found existing installation: setuptools 49.6.0
18:55:48             Uninstalling setuptools-49.6.0:
18:55:48               Successfully uninstalled setuptools-49.6.0
18:55:50         Successfully installed setuptools-50.0.1
18:55:50         nox > Command python -cimport sys; from distutils.sysconfig import get_python_lib; sys.stdout.write(get_python_lib()) failed with exit code 1:
18:55:50         Traceback (most recent call last):
18:55:50           File "<string>", line 1, in <module>
18:55:50           File "<frozen importlib._bootstrap>", line 969, in _find_and_load
18:55:50           File "<frozen importlib._bootstrap>", line 958, in _find_and_load_unlocked
18:55:50           File "<frozen importlib._bootstrap>", line 666, in _load_unlocked
18:55:50           File "<frozen importlib._bootstrap>", line 577, in module_from_spec
18:55:50           File "/tmp/kitchen/testing/.nox/runtests-parametrized-3-coverage-true-crypto-m2crypto-transport-zeromq/lib/python3.5/site-packages/_distutils_hack/__init__.py", line 83, in create_module
18:55:50             return importlib.import_module('._distutils', 'setuptools')
18:55:50           File "/usr/lib/python3.5/importlib/__init__.py", line 126, in import_module
18:55:50             return _bootstrap._gcd_import(name[level:], package, level)
18:55:50           File "<frozen importlib._bootstrap>", line 981, in _gcd_import
18:55:50           File "<frozen importlib._bootstrap>", line 931, in _sanity_check
18:55:50         SystemError: Parent module 'setuptools' not loaded, cannot perform relative import
18:55:50         nox > Session runtests-parametrized-3(coverage=True, crypto='m2crypto', transport='zeromq') failed.

I think the micro version of Python is key here and is why some folks can't reproduce this issue while others can. Older Python 3.5.x, 3.6.x and 3.7.x versions were affected by https://bugs.python.org/issue30876.

Using an absolute import instead of a relative import at _distutils_hack/__init__.py#L83 seems to do the trick for Python 3.6.2, i.e.

return importlib.import_module('setuptools._distutils')

instead of

return importlib.import_module('._distutils', 'setuptools')

I can reproduce this on Python 3.5.3 on a Debian stretch VM, but not on my local Ubuntu with Python 3.5.9 (from the deadsnakes PPA). Form those who've reported reproduces above and included a patch number, they're all 3.5.3 or lower.
Might there be a patch somewhere between Python 3.5.4-3.5.9 which mitigates the issue?

I think the micro version of Python is key here and is why some folks can't reproduce this issue while others can. Older Python 3.5.x, 3.6.x and 3.7.x versions were affected by https://bugs.python.org/issue30876.

Using an absolute import instead of a relative import at _distutils_hack/__init__.py#L83 seems to do the trick for Python 3.6.2, i.e.

return importlib.import_module('setuptools._distutils')

instead of

return importlib.import_module('._distutils', 'setuptools')

Applying this fix to my local _distutils_hack/__init__.py fixes the issue for me (Python 3.5.3).

Good insight. Sounds like a good mitigation.

Is this change, as implemented in 50.0.2, supposed to eliminate the need for the SETUPTOOLS_USE_DISTUTILS=stdlib workaround?

If so, it is not working out that way. Seeing the same problem as before (without the workaround) and the build is picking up 50.0.2

Ubuntu 18.04 with python3 (3.6.7-1~18.04) in Docker

@MartinFalatic - Can you share a log/traceback? The particular exception in the original issue report was removed in Python 3.6.3 RC1+ as part of the fix for https://bugs.python.org/issue30876.

Note the absence of https://github.com/python/cpython/blob/v3.6.2/Lib/importlib/_bootstrap.py#L920-L923 in https://github.com/python/cpython/blob/v3.6.7/Lib/importlib/_bootstrap.py#L917-L930.

Using setuptools 50.0.0 without workaround:

[14:34:24][Docker Compose Build] Step 5/18 : RUN DJANGO_SECRET_KEY=redacted ENABLE_DEBUG=1 python3 manage.py collectstatic --noinput --clear
[14:34:24][Docker Compose Build]  ---> Running in 97410cbaf12e
[14:34:25][Docker Compose Build] Traceback (most recent call last):
[14:34:25][Docker Compose Build]   File "manage.py", line 18, in <module>
[14:34:25][Docker Compose Build]     execute_from_command_line(sys.argv)
[14:34:25][Docker Compose Build]   File "/usr/local/lib/python3.6/dist-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
[14:34:25][Docker Compose Build]     utility.execute()
[14:34:25][Docker Compose Build]   File "/usr/local/lib/python3.6/dist-packages/django/core/management/__init__.py", line 357, in execute
[14:34:25][Docker Compose Build]     django.setup()
[14:34:25][Docker Compose Build]   File "/usr/local/lib/python3.6/dist-packages/django/__init__.py", line 24, in setup
[14:34:25][Docker Compose Build]     apps.populate(settings.INSTALLED_APPS)
[14:34:25][Docker Compose Build]   File "/usr/local/lib/python3.6/dist-packages/django/apps/registry.py", line 91, in populate
[14:34:25][Docker Compose Build]     app_config = AppConfig.create(entry)
[14:34:25][Docker Compose Build]   File "/usr/local/lib/python3.6/dist-packages/django/apps/config.py", line 90, in create
[14:34:25][Docker Compose Build]     module = import_module(entry)
[14:34:25][Docker Compose Build]   File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
[14:34:25][Docker Compose Build]     return _bootstrap._gcd_import(name[level:], package, level)
[14:34:25][Docker Compose Build]   File "<frozen importlib._bootstrap>", line 994, in _gcd_import
[14:34:25][Docker Compose Build]   File "<frozen importlib._bootstrap>", line 971, in _find_and_load
[14:34:25][Docker Compose Build]   File "<frozen importlib._bootstrap>", line 953, in _find_and_load_unlocked
[14:34:25][Docker Compose Build] ModuleNotFoundError: No module named 'dal'
[14:34:25][Docker Compose Build] Service 'nginx' failed to build: The command '/bin/sh -c DJANGO_SECRET_KEY=redacted ENABLE_DEBUG=1 python3 manage.py collectstatic --noinput --clear' returned a non-zero code: 1

Using setuptools 50.0.2 without workaround:

[15:50:39][Docker Compose Build] Step 5/18 : RUN DJANGO_SECRET_KEY=redacted ENABLE_DEBUG=1 python3 manage.py collectstatic --noinput --clear
[15:50:39][Docker Compose Build]  ---> Running in 49057705f89d
[15:50:40][Docker Compose Build] Traceback (most recent call last):
[15:50:40][Docker Compose Build]   File "manage.py", line 18, in <module>
[15:50:40][Docker Compose Build]     execute_from_command_line(sys.argv)
[15:50:40][Docker Compose Build]   File "/usr/local/lib/python3.6/dist-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
[15:50:40][Docker Compose Build]     utility.execute()
[15:50:40][Docker Compose Build]   File "/usr/local/lib/python3.6/dist-packages/django/core/management/__init__.py", line 357, in execute
[15:50:40][Docker Compose Build]     django.setup()
[15:50:40][Docker Compose Build]   File "/usr/local/lib/python3.6/dist-packages/django/__init__.py", line 24, in setup
[15:50:40][Docker Compose Build]     apps.populate(settings.INSTALLED_APPS)
[15:50:40][Docker Compose Build]   File "/usr/local/lib/python3.6/dist-packages/django/apps/registry.py", line 91, in populate
[15:50:40][Docker Compose Build]     app_config = AppConfig.create(entry)
[15:50:40][Docker Compose Build]   File "/usr/local/lib/python3.6/dist-packages/django/apps/config.py", line 90, in create
[15:50:40][Docker Compose Build]     module = import_module(entry)
[15:50:40][Docker Compose Build]   File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
[15:50:40][Docker Compose Build]     return _bootstrap._gcd_import(name[level:], package, level)
[15:50:40][Docker Compose Build]   File "<frozen importlib._bootstrap>", line 994, in _gcd_import
[15:50:40][Docker Compose Build]   File "<frozen importlib._bootstrap>", line 971, in _find_and_load
[15:50:40][Docker Compose Build]   File "<frozen importlib._bootstrap>", line 953, in _find_and_load_unlocked
[15:50:40][Docker Compose Build] ModuleNotFoundError: No module named 'dal'
[15:50:40][Docker Compose Build] Service 'nginx' failed to build: The command '/bin/sh -c DJANGO_SECRET_KEY=redacted ENABLE_DEBUG=1 python3 manage.py collectstatic --noinput --clear' returned a non-zero code: 1

Those appear to be identical.

The following does get mentioned in the log during the main pip3 install part of the build (with and without the workaround). I mention it only for completeness as this problem is certainly affected by the setuptools version being >= 50. Note that setuptools is NOT being pinned in the requirements (as that would be problematic).

[15:48:20][Docker Compose Build]   Found existing installation: setuptools 39.0.1
[15:48:20][Docker Compose Build]     Not uninstalling setuptools at /usr/lib/python3/dist-packages, outside environment /usr

Also, 'dal' = django-autocomplete-light.

It's worth noting that this was closed as duplicate when I brought it up here... https://github.com/pypa/setuptools/issues/2356#issuecomment-684121083

Similar but perhaps not quite the same problem after all, perhaps.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rossmacarthur picture rossmacarthur  ยท  3Comments

lorilew picture lorilew  ยท  4Comments

AdrianEggenberger picture AdrianEggenberger  ยท  4Comments

Gumnos picture Gumnos  ยท  4Comments

rickstaa picture rickstaa  ยท  5Comments