Bug 2020635 - pip install fails if the setuptools weak dep is not installed
Summary: pip install fails if the setuptools weak dep is not installed
Keywords:
Status: CLOSED ERRATA
Alias: None
Product: Fedora
Classification: Fedora
Component: python-pip
Version: 35
Hardware: Unspecified
OS: Unspecified
unspecified
unspecified
Target Milestone: ---
Assignee: Tomáš Hrnčiar
QA Contact: Fedora Extras Quality Assurance
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2021-11-05 13:28 UTC by Lukáš Hrázký
Modified: 2023-02-10 16:35 UTC (History)
10 users (show)

Fixed In Version: python-pip-22.0.4-2.fc37
Clone Of:
Environment:
Last Closed: 2022-04-27 08:00:08 UTC
Type: Bug
Embargoed:


Attachments (Terms of Use)


Links
System ID Private Priority Status Summary Last Updated
Github pypa pip pull 10717 0 None Draft Fallback to pep517 if setup.py is present and setuptools cannot be imported 2022-01-04 10:13:49 UTC

Description Lukáš Hrázký 2021-11-05 13:28:20 UTC
Description of problem:
pip install fails if setuptools is not installed on the system. It is a weak dependency, but those are meant to be used for optional extra functionality, not causing hard failures in core functionality even if possibly under special circumstances.

Version-Release number of selected component (if applicable):
python3-pip-21.2.3-3.fc35.noarch

How reproducible:
always

Steps to Reproduce:
podman run --rm -ti fedora:35
dnf --setopt=install_weak_deps=False install python3-pip
pip install pyftpdlib


Actual results:
[root@6288faa4fb04 /]# pip install pyftpdlib
Collecting pyftpdlib
  Downloading pyftpdlib-1.5.6.tar.gz (188 kB)
     |████████████████████████████████| 188 kB 3.5 MB/s 
    ERROR: Command errored out with exit status 1:
     command: /usr/bin/python3 -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-8wqv8hnm/pyftpdlib_13b96f7bef754e36b0e8130ebf8a9e07/setup.py'"'"'; __file__='"'"'/tmp/pip-install-8wqv8hnm/pyftpdlib_13b96f7bef754e36b0e8130ebf8a9e07/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /tmp/pip-pip-egg-info-xvold042
         cwd: /tmp/pip-install-8wqv8hnm/pyftpdlib_13b96f7bef754e36b0e8130ebf8a9e07/
    Complete output (3 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
    ModuleNotFoundError: No module named 'setuptools'
    ----------------------------------------
WARNING: Discarding https://files.pythonhosted.org/packages/31/61/63ef60aca6de07eba1639d9d47f3f8e29462e8bb49d6a8dce9aeff240646/pyftpdlib-1.5.6.tar.gz#sha256=fda655d81f29af52885ca2f8a2704134baed540f16d66a0b26e8fdfafd12db5e (from https://pypi.org/simple/pyftpdlib/). Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

... repeated multiple times


Expected results:
Command succeeds.


Additional info:
python3-setuptools was changed to a weak dependency in https://src.fedoraproject.org/rpms/python-pip/pull-request/58

Comment 1 Miro Hrončok 2021-11-05 13:41:25 UTC
Technically, setuptools is merely a build dependency of pyftpdlib. If pip required setuptools because of pyftpdlib, should it also require all the other build dependencies of all the existing Python packages? Should it require flit, poetry, numpy, cython, in case some setup.py script out there in the wild imports from them?


OTOH Since the wast majority of existing Python packages import setuptools from setup.py, we decided to recommend setuptools to support the most common use case by default. Users who explicitly opt-out of setuptools obviously know that they don't need them.


In my opinion, a missing optional dependency might cause hard failures under special circumstances. E.g. if the user wants to use an optional feature, it should fail if the optional dependency required for that feature is not installed, no?



About the pyftpdlib use case: You can explicitly pass the --use-pep517 option to install that package or you can submit a pull request to upstream that adds a pyproject.toml file with setuptools build backend:

[build-system]
# Minimum requirements for the build system to execute.
requires = ["setuptools", "wheel"]  # PEP 508 specifications.
build-backend = "setuptools.build_meta"


That way, pip will actually know it should install setuptools to the build environment.

Comment 2 Miro Hrončok 2021-11-05 13:43:19 UTC
See also https://github.com/pypa/pip/issues/9175

Comment 3 Miro Hrončok 2021-11-05 14:02:39 UTC
To clarify things, only some packages fail to install with `pip install` if setuptools is not installed on the system. All of the following conditions must be met to get the failure:

 - the package must not have a suitable wheel on PyPI, only sdist¹
 - the sdist must not contain pyproject.toml²
 - the sdist must contain setup.py which does an unconditional import of setuptools


¹ Or the user must opt into using sdist-only explicitly, which is not the default.
² I am not currently sure if the pyproject.toml must be entirely missing, or if it might exist without build-system.build-backend set.

Comment 4 Lukáš Hrázký 2021-11-05 15:25:18 UTC
> Technically, setuptools is merely a build dependency of pyftpdlib. If pip required setuptools because of pyftpdlib, should it also require all the other build dependencies of all the existing Python packages? Should it require flit, poetry, numpy, cython, in case some setup.py script out there in the wild imports from them?

No idea, seems like pip should pull those build deps if needed, but I know very little about how this works and I don't want to dig into it.

> In my opinion, a missing optional dependency might cause hard failures under special circumstances. E.g. if the user wants to use an optional feature, it should fail if the optional dependency required for that feature is not installed, no?

The thing here is I'm a mere clueless user of pip and I'm not able to distinguish the cases for which I need setuptools and for which I don't. And I don't think you can ask such users to dig into implementation details to find out whether they need this weak dep (and having to trial and error is ultimately just as bad).

> OTOH Since the wast majority of existing Python packages import setuptools from setup.py, we decided to recommend setuptools to support the most common use case by default.

If setuptools is actually required for the majority of packages, then it's not the special case. The special case is then those packages that don't require it...

And we're coming back to the semantics of weak deps, which seem to lump a lot of varying situations into a few categories which sometimes fit and sometimes not so well.

FWIW my use case is our CI, where we disable weak deps altogether, as we don't need the bells and whistles of the packages, just the core functionality... which fails for this case. Now what should we do, enable all weak deps and get a buch of extra deps pulled in? Or special-case this dep even though it's arbitrary from our point of view, we don't care about it, we just want a working pip?

I'll leave the resolution to you, but again from my (dumb user) point of view pip is meant to install packages and if it tracebacks in majority of cases without setuptools, that it seems like a strong dependency, not a weak one.

Also from a clean user experience perspective, if it (by decision of its developers/maintainers) is a weak dependency, I would expect it to not traceback, but print a message telling me I need to install X and Y weak deps to be able to install this package. That's obviously asking a bit too much as I can imagine that wouldn't be too pretty, but the traceback makes the matters worse.

Comment 5 Miro Hrončok 2021-11-05 15:41:31 UTC
(In reply to Lukáš Hrázký from comment #4)
> > Technically, setuptools is merely a build dependency of pyftpdlib. If pip required setuptools because of pyftpdlib, should it also require all the other build dependencies of all the existing Python packages? Should it require flit, poetry, numpy, cython, in case some setup.py script out there in the wild imports from them?
> 
> No idea, seems like pip should pull those build deps if needed, but I know
> very little about how this works and I don't want to dig into it.

It actually does that, but only for packages that clearly communicate they need some build deps: pyftpdlib doesn't.


> > In my opinion, a missing optional dependency might cause hard failures under special circumstances. E.g. if the user wants to use an optional feature, it should fail if the optional dependency required for that feature is not installed, no?
> 
> The thing here is I'm a mere clueless user of pip and I'm not able to
> distinguish the cases for which I need setuptools and for which I don't. And
> I don't think you can ask such users to dig into implementation details to
> find out whether they need this weak dep (and having to trial and error is
> ultimately just as bad).

I can relate to this opinion as I like things to "just work" as well, however, this really depends on what you are installing -- I don't see an easy way out. This is the first user report we got and setuptools has been only-recommended since Fedora 33. That makes me think that most of our users are not impacted by this decision.


> > OTOH Since the wast majority of existing Python packages import setuptools from setup.py, we decided to recommend setuptools to support the most common use case by default.
> 
> If setuptools is actually required for the majority of packages, then it's
> not the special case. The special case is then those packages that don't
> require it...

No, the special case is when they require it, but they don't declare it. There are plenty of packages like pyftpdlib (most of the old packages are like that I'd say), but the most popular packages on PyPI have wheels and/or they specify their build dependencies and users won't generally see this problem.


> And we're coming back to the semantics of weak deps, which seem to lump a
> lot of varying situations into a few categories which sometimes fit and
> sometimes not so well.
> 
> FWIW my use case is our CI, where we disable weak deps altogether, as we
> don't need the bells and whistles of the packages, just the core
> functionality... which fails for this case. Now what should we do, enable
> all weak deps and get a buch of extra deps pulled in? Or special-case this
> dep even though it's arbitrary from our point of view, we don't care about
> it, we just want a working pip?

I'd say go and fix pyftpdlib, so you don't have this problem. Or special case setuptools if installing packages like pyftpdlib is required for your CI to work.


> I'll leave the resolution to you, but again from my (dumb user) point of
> view pip is meant to install packages and if it tracebacks in majority of
> cases without setuptools, that it seems like a strong dependency, not a weak
> one.

I wouldn't say it tracebacks in the majority of cases. But that's just an opinion, unfortunately, we don't have any data.


> Also from a clean user experience perspective, if it (by decision of its
> developers/maintainers) is a weak dependency, I would expect it to not
> traceback, but print a message telling me I need to install X and Y weak
> deps to be able to install this package. That's obviously asking a bit too
> much as I can imagine that wouldn't be too pretty, but the traceback makes
> the matters worse.

That's virtually impossible. With pyftpdlib, you get "ModuleNotFoundError: No module named 'setuptools'" but with a different package, you might get "ModuleNotFoundError: No module named 'cython'" or "error: [Errno 2] No such file or directory: 'gcc'". We would need to catch those errors and figure out how to translate them to actionable error messages.

Note that `pip install <something>` basically means "go download a tarball from the internet and run a script from it" (at least when wheel or pyproject.toml is not available). The script can fail to execute for thousands of reasons.

Comment 6 Lukáš Hrázký 2021-11-05 16:35:52 UTC
> This is the first user report we got and setuptools has been only-recommended since Fedora 33.

We've only hit this on Fedora 35. I won't go looking, I assume setuptools was pulled in by something else on earlier Fedoras...

> No, the special case is when they require it, but they don't declare it. There are plenty of packages like pyftpdlib (most of the old packages are like that I'd say), but the most popular packages on PyPI have wheels and/or they specify their build dependencies and users won't generally see this problem.

I see, then it's more of a packaging issue of pyftpdlib and a lot of others.

> I'd say go and fix pyftpdlib, so you don't have this problem.

I likely won't just because it's not an efficient time investment :( but if you'd have pointers at hand and/or it was simple enough...

I'm confused by this in the output (if you don't want to spend time on this it's absolutely fine):
command: /usr/bin/python3 -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-8wqv8hnm/pyftpdlib_13b96f7bef754e36b0e8130ebf8a9e07/setup.py'"'"'; __file__='"'"'/tmp/pip-install-8wqv8hnm/pyftpdlib_13b96f7bef754e36b0e8130ebf8a9e07/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /tmp/pip-pip-egg-info-xvold042

First, this is ugly, second, it directly imports the setuptools there. I just checked pyftpdlib code and I don't see this, is it coming from pip itself?

pyftpdlib imports setuptools conditionally in setup.py, seems it's not a hard req. It's also present in some Makefile targets which I'm not sure are related...

Comment 7 Miro Hrončok 2021-11-05 17:23:39 UTC
This is indeed ugly. I wonder what does pip actually tries to accomplish here, considering it import setuptools even when setup.py does not :/ Most likely it assumes that setup.py is present => setuptools will be used. 

https://github.com/pypa/pip/blob/main/src/pip/_internal/utils/setuptools_build.py

If pip uses setuptools like this, maybe the requirement is in order after all (or maybe it only sues it when certain conditions ar met, I'll need to check).


I won't be able to look into that soon, but feel free to keep this open and I'll eventually get to it.


> I likely won't just because it's not an efficient time investment :( but if you'd have pointers at hand and/or it was simple enough...

As said, including a file called pyproject.toml with this content in the release tarball uploaded to pypi should do:

[build-system]
# Minimum requirements for the build system to execute.
requires = ["setuptools", "wheel"]  # PEP 508 specifications.
build-backend = "setuptools.build_meta"

Comment 8 Miro Hrončok 2021-11-05 17:52:50 UTC
OK, I've checked and even with setup.py like this:

from distutils.core import setup
setup()

pip fails with:

  Traceback (most recent call last):
    File "<string>", line 1, in <module>
  ModuleNotFoundError: No module named 'setuptools'


This makes me wonder whether pip should indeed produce a nicer error message in this case, recommending installing setuptools or using --use-pep517.

I'll bring this up on https://discuss.python.org/c/packaging/14 next week.

Comment 9 Lukáš Hrázký 2021-11-08 13:55:36 UTC
> This makes me wonder whether pip should indeed produce a nicer error message in this case

Or add an implicit dependency on setuptools and install it automatically, if it knows it's required?

Thanks for looking into this.

Comment 10 Miro Hrončok 2021-11-08 14:00:15 UTC
That is a possibility, but I'd like to avoid that for practical reasons (explicitly removing setuptools should still remain possible).

Comment 11 Miro Hrončok 2021-11-08 14:27:02 UTC
(In reply to Miro Hrončok from comment #8)
> I'll bring this up on https://discuss.python.org/c/packaging/14 next week.

https://discuss.python.org/t/pip-without-setuptools-could-the-experience-be-improved/11810

Comment 12 Miro Hrončok 2022-01-04 12:44:20 UTC
https://github.com/pypa/pip/pull/10717 was approved upstream. Wehn merged, we might want to backport it.

Comment 13 Tomáš Hrnčiar 2022-04-26 10:32:47 UTC
PR was finally merged. I've opened PR to backport it.

https://src.fedoraproject.org/rpms/python-pip/pull-request/104

Comment 14 Fedora Update System 2022-04-27 07:58:09 UTC
FEDORA-2022-d91de515c5 has been submitted as an update to Fedora 37. https://bodhi.fedoraproject.org/updates/FEDORA-2022-d91de515c5

Comment 15 Fedora Update System 2022-04-27 08:00:08 UTC
FEDORA-2022-d91de515c5 has been pushed to the Fedora 37 stable repository.
If problem still persists, please make note of it in this bug report.

Comment 16 Fedora Update System 2023-02-10 16:35:24 UTC
FEDORA-2023-446b0b621c has been submitted as an update to Fedora 39. https://bodhi.fedoraproject.org/updates/FEDORA-2023-446b0b621c


Note You need to log in before you can comment on or make changes to this bug.