question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Excluding packages when extra is specified

See original GitHub issue

I have found a dependency resolving issue in PIP, here is how to reproduce. Nor the “old” or the “2020-resolver” is able to fix the issue. However they both fails in different ways.

This is a very problematic for us. When do you think it could be resolved ?

Environment:

pip list

Package    Version
---------- -------
pip        20.2
setuptools 49.2.0
wheel      0.34.2

Script to reproduce

#!/usr/bin/env python

"""The setup script."""

from setuptools import setup

# We ask to install is-sorted by pinned version (points to 0.0.1: https://pypi.org/project/is-sorted/0.0.1/)
install_requires = ["is-sorted==0.0.1; extra != 'env_A' and extra != 'env_B'"]

extras_require = {
    "env_a": ["random-string"],  # a random package
    "env_b": ["get-random"],  # a random package
}

# We ask to install is-sorted by range (currently points to 0.0.2: https://pypi.org/project/is-sorted/0.0.2/)
extras_require = {key: val + ["is-sorted>=0.0.1,<0.1"] for key, val in extras_require.items()}

setup(
    name='test_pkg',
    version='1.0.0',
    install_requires=install_requires,
    extras_require=extras_require,
)

What shall be expected

  • if installed without any extra require environment, only installs:
    • is-sorted==0.0.1
  • if installed with extra require environment env_a:
    • random-string
    • is-sorted>=0.0.1,<0.1
  • if installed with extra require environment env_b:
    • get-random
    • is-sorted>=0.0.1,<0.1
  • if installed with extra require environments env_a && env_b:
    • random-string # from env_a
    • get-random # from env_b
    • is-sorted>=0.0.1,<0.1

What happens in practice ?

0. Let’s build the wheel: python setup.py bdist_wheel

$ python setup.py bdist_wheel

running bdist_wheel
running build
installing to build/bdist.linux-x86_64/wheel
running install
running install_egg_info
running egg_info
writing test_pkg.egg-info/PKG-INFO
writing dependency_links to test_pkg.egg-info/dependency_links.txt
writing requirements to test_pkg.egg-info/requires.txt
writing top-level names to test_pkg.egg-info/top_level.txt
reading manifest file 'test_pkg.egg-info/SOURCES.txt'
writing manifest file 'test_pkg.egg-info/SOURCES.txt'
Copying test_pkg.egg-info to build/bdist.linux-x86_64/wheel/test_pkg-1.0.0-py3.6.egg-info
running install_scripts
creating build/bdist.linux-x86_64/wheel/test_pkg-1.0.0.dist-info/WHEEL
creating 'dist/test_pkg-1.0.0-py3-none-any.whl' and adding 'build/bdist.linux-x86_64/wheel' to it
adding 'test_pkg-1.0.0.dist-info/METADATA'
adding 'test_pkg-1.0.0.dist-info/WHEEL'
adding 'test_pkg-1.0.0.dist-info/top_level.txt'
adding 'test_pkg-1.0.0.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel

If we look at the file METADATA, everything seems OK:

Requires-Dist: is-sorted (==0.0.1) ; extra != "env_a" and extra != "env_b"
Provides-Extra: env_a
Requires-Dist: random-string ; extra == 'env_a'
Requires-Dist: is-sorted (<0.1,>=0.0.1) ; extra == 'env_a'
Provides-Extra: env_b
Requires-Dist: get-random ; extra == 'env_b'
Requires-Dist: is-sorted (<0.1,>=0.0.1) ; extra == 'env_b'

1. Install with NO extra require

$ pip install "dist/test_pkg-1.0.0-py3-none-any.whl"

Processing ./dist/test_pkg-1.0.0-py3-none-any.whl
Collecting is-sorted==0.0.1; extra != "env_a" and extra != "env_b"
  Downloading is_sorted-0.0.1-py3-none-any.whl (2.8 kB)
Installing collected packages: is-sorted, test-pkg
Successfully installed is-sorted-0.0.1 test-pkg-1.0.0

Conclusion: Everything is good.

2. Install with env_a extra require

$ pip install "dist/test_pkg-1.0.0-py3-none-any.whl[env_a]"

Processing ./dist/test_pkg-1.0.0-py3-none-any.whl
  Ignoring is-sorted: markers 'extra != "env_a" and extra != "env_b"' don't match your environment
Collecting is-sorted<0.1,>=0.0.1; extra == "env_a"
  Downloading is_sorted-0.0.2-py3-none-any.whl (3.3 kB)
Collecting random-string; extra == "env_a"
  Downloading random-string-1.00.tar.gz (996 bytes)
Building wheels for collected packages: random-string
  Building wheel for random-string (setup.py) ... done
  Created wheel for random-string: filename=random_string-1.0-py3-none-any.whl size=1897 sha256=14603b692b5977dfe8f4bc8d4c3ea54a796b5b4c86760c0bb97b21f3f1aaf2cb
  Stored in directory: /root/.cache/pip/wheels/47/2d/fe/13b8d8df913e51efdd1c8a66632a2dd0b54abb101c2c9ac399
Successfully built random-string
Installing collected packages: is-sorted, random-string, test-pkg
ERROR: After October 2020 you may experience errors when installing or updating packages. This is because pip will change the way that it resolves dependency conflicts.

We recommend you use --use-feature=2020-resolver to test your packages with the new resolver before it becomes the default.

test-pkg 1.0.0 requires is-sorted==0.0.1; extra != "env_a" and extra != "env_b", but you'll have is-sorted 0.0.2 which is incompatible.
Successfully installed is-sorted-0.0.2 random-string-1.0 test-pkg-1.0.0

Conclusion: A scary “red” warning for no reason: is-sorted-0.0.2 random-string-1.0 test-pkg-1.0.0 shall be the correct set of dependencies

3. Install with env_b extra require

$ pip install "dist/test_pkg-1.0.0-py3-none-any.whl[env_b]"

Processing ./dist/test_pkg-1.0.0-py3-none-any.whl
  Ignoring is-sorted: markers 'extra != "env_a" and extra != "env_b"' don't match your environment
Collecting get-random; extra == "env_b"
  Downloading get_random-0.1.4.tar.gz (2.5 kB)
Collecting is-sorted<0.1,>=0.0.1; extra == "env_b"
  Downloading is_sorted-0.0.2-py3-none-any.whl (3.3 kB)
Building wheels for collected packages: get-random
  Building wheel for get-random (setup.py) ... done
  Created wheel for get-random: filename=get_random-0.1.4-py3-none-any.whl size=2516 sha256=5fdc7d9551dcf4e377d839c938865dc5053745d1e67aaad9cfd418fa67e28848
  Stored in directory: /root/.cache/pip/wheels/ec/01/58/0ee893b104a22036186016ffaf29e1aca6a343c11341677a4c
Successfully built get-random
Installing collected packages: get-random, is-sorted, test-pkg
ERROR: After October 2020 you may experience errors when installing or updating packages. This is because pip will change the way that it resolves dependency conflicts.

We recommend you use --use-feature=2020-resolver to test your packages with the new resolver before it becomes the default.

test-pkg 1.0.0 requires is-sorted==0.0.1; extra != "env_a" and extra != "env_b", but you'll have is-sorted 0.0.2 which is incompatible.
Successfully installed get-random-0.1.4 is-sorted-0.0.2 test-pkg-1.0.0

Conclusion: A scary “red” warning for no reason: get-random-0.1.4 is-sorted-0.0.2 test-pkg-1.0.0 shall be the correct set of dependencies

4. Install with env_a && env_b extra require

$ pip install "dist/test_pkg-1.0.0-py3-none-any.whl[env_a,env_b]"

Processing ./dist/test_pkg-1.0.0-py3-none-any.whl
  Ignoring is-sorted: markers 'extra != "env_a" and extra != "env_b"' don't match your environment
Collecting is-sorted<0.1,>=0.0.1; extra == "env_a"
  Downloading is_sorted-0.0.2-py3-none-any.whl (3.3 kB)
Collecting random-string; extra == "env_a"
  Downloading random-string-1.00.tar.gz (996 bytes)
Collecting get-random; extra == "env_b"
  Downloading get_random-0.1.4.tar.gz (2.5 kB)
Building wheels for collected packages: random-string, get-random
  Building wheel for random-string (setup.py) ... done
  Created wheel for random-string: filename=random_string-1.0-py3-none-any.whl size=1897 sha256=6a38795c861b315d94332cc3b381c7441fc83c54dd8a4db8b34cd43f638dca33
  Stored in directory: /root/.cache/pip/wheels/47/2d/fe/13b8d8df913e51efdd1c8a66632a2dd0b54abb101c2c9ac399
  Building wheel for get-random (setup.py) ... done
  Created wheel for get-random: filename=get_random-0.1.4-py3-none-any.whl size=2516 sha256=aa4677747ad67d64a16789e1611e3ca781800d380b8798bde2466aa6f005e4ba
  Stored in directory: /root/.cache/pip/wheels/ec/01/58/0ee893b104a22036186016ffaf29e1aca6a343c11341677a4c
Successfully built random-string get-random
Installing collected packages: is-sorted, random-string, get-random, test-pkg
ERROR: After October 2020 you may experience errors when installing or updating packages. This is because pip will change the way that it resolves dependency conflicts.

We recommend you use --use-feature=2020-resolver to test your packages with the new resolver before it becomes the default.

test-pkg 1.0.0 requires is-sorted==0.0.1; extra != "env_a" and extra != "env_b", but you'll have is-sorted 0.0.2 which is incompatible.
Successfully installed get-random-0.1.4 is-sorted-0.0.2 random-string-1.0 test-pkg-1.0.0

Conclusion: A scary “red” warning for no reason: get-random-0.1.4 is-sorted-0.0.2 random-string-1.0 test-pkg-1.0.0 shall be the correct set of dependencies

How about with --use-feature=2020-resolver ?

Now it’s even worse, we don’t have a warning anymore because the dependency resolving is wrong.

1. Install with NO extra require and --use-feature=2020-resolver

$ pip install "dist/test_pkg-1.0.0-py3-none-any.whl" --use-feature=2020-resolver

Processing ./dist/test_pkg-1.0.0-py3-none-any.whl
Collecting is-sorted==0.0.1
  Downloading is_sorted-0.0.1-py3-none-any.whl (2.8 kB)
Installing collected packages: is-sorted, test-pkg
Successfully installed is-sorted-0.0.1 test-pkg-1.0.0

Conclusion: Everything is good.

2. Install with env_a extra require and --use-feature=2020-resolver

$ pip install "dist/test_pkg-1.0.0-py3-none-any.whl[env_a]"  --use-feature=2020-resolver

Processing ./dist/test_pkg-1.0.0-py3-none-any.whl
Ignoring is-sorted: markers 'extra != "env_a" and extra != "env_b"' don't match your environment
Collecting random-string
  Downloading random-string-1.00.tar.gz (996 bytes)
Collecting is-sorted<0.1,>=0.0.1
  Downloading is_sorted-0.0.1-py3-none-any.whl (2.8 kB)
Building wheels for collected packages: random-string
  Building wheel for random-string (setup.py) ... done
  Created wheel for random-string: filename=random_string-1.0-py3-none-any.whl size=1897 sha256=f9032cea18e5f1c5af61ad3041c16722150c1360aee6ac34c7134e03c30577be
  Stored in directory: /root/.cache/pip/wheels/47/2d/fe/13b8d8df913e51efdd1c8a66632a2dd0b54abb101c2c9ac399
Successfully built random-string
Installing collected packages: is-sorted, test-pkg, random-string
Successfully installed is-sorted-0.0.1 random-string-1.0 test-pkg-1.0.0

Conclusion: No error anymore, however it installs is-sorted-0.0.1 and should install: is-sorted-0.0.2

3. Install with env_b extra require and --use-feature=2020-resolver

$ pip install "dist/test_pkg-1.0.0-py3-none-any.whl[env_b]"  --use-feature=2020-resolver

Processing ./dist/test_pkg-1.0.0-py3-none-any.whl
Ignoring is-sorted: markers 'extra != "env_a" and extra != "env_b"' don't match your environment
Collecting is-sorted<0.1,>=0.0.1
  Downloading is_sorted-0.0.1-py3-none-any.whl (2.8 kB)
Collecting get-random
  Downloading get_random-0.1.4.tar.gz (2.5 kB)
Building wheels for collected packages: get-random
  Building wheel for get-random (setup.py) ... done
  Created wheel for get-random: filename=get_random-0.1.4-py3-none-any.whl size=2516 sha256=abca081d35ed6ae4bb11f1f36da294b25bad3b0dd7bb3293bc6912ec2e54f81c
  Stored in directory: /root/.cache/pip/wheels/ec/01/58/0ee893b104a22036186016ffaf29e1aca6a343c11341677a4c
Successfully built get-random
Installing collected packages: is-sorted, test-pkg, get-random
Successfully installed get-random-0.1.4 is-sorted-0.0.1 test-pkg-1.0.0

Conclusion: No error anymore, however it installs is-sorted-0.0.1 and should install: is-sorted-0.0.2

4. Install with env_a && env_b extra require and --use-feature=2020-resolver:

$ pip install "dist/test_pkg-1.0.0-py3-none-any.whl[env_a,env_b]"  --use-feature=2020-resolver

Processing ./dist/test_pkg-1.0.0-py3-none-any.whl
Ignoring is-sorted: markers 'extra != "env_a" and extra != "env_b"' don't match your environment
Collecting random-string
  Downloading random-string-1.00.tar.gz (996 bytes)
Collecting is-sorted<0.1,>=0.0.1
  Downloading is_sorted-0.0.1-py3-none-any.whl (2.8 kB)
Collecting get-random
  Downloading get_random-0.1.4.tar.gz (2.5 kB)
Building wheels for collected packages: random-string, get-random
  Building wheel for random-string (setup.py) ... done
  Created wheel for random-string: filename=random_string-1.0-py3-none-any.whl size=1897 sha256=6ba41eea0fce2aa432afc47ad7bd5e4aa89c5e1992c0a7dacfb27c3885838dbb
  Stored in directory: /root/.cache/pip/wheels/47/2d/fe/13b8d8df913e51efdd1c8a66632a2dd0b54abb101c2c9ac399
  Building wheel for get-random (setup.py) ... done
  Created wheel for get-random: filename=get_random-0.1.4-py3-none-any.whl size=2516 sha256=2eacde1bfa69a76ad369ea1e085e812c9302e2bd866c8882f148f772948faf50
  Stored in directory: /root/.cache/pip/wheels/ec/01/58/0ee893b104a22036186016ffaf29e1aca6a343c11341677a4c
Successfully built random-string get-random
Installing collected packages: is-sorted, test-pkg, random-string, get-random
Successfully installed get-random-0.1.4 is-sorted-0.0.1 random-string-1.0 test-pkg-1.0.0

Conclusion: No error anymore, however it installs is-sorted-0.0.1 and should install: is-sorted-0.0.2

Issue Analytics

  • State:open
  • Created 3 years ago
  • Comments:26 (14 by maintainers)

github_iconTop GitHub Comments

2reactions
uranusjrcommented, Aug 3, 2020

I think the misunderstanding is the communication channel is not as well-known as I anticipated, sorry. PyPA does have multiple communication channels, as listed on pypa.io. The Discourse forum is the most active these days, but feel free to choose any one you feel the most comfortable with.

0reactions
uranusjrcommented, Aug 19, 2021

Unfortunately this is not pip bug, but a design issue. The reason pip does not do what you want is not because pip interprets extra != "env_a" incorrectly, but that expression does not mean what you think it does (if it does, pip would have done what you want).

When you install a package with extras, say foo[a,b], you are actually requesting extras, not an extra singular. The way this is broken down, as specified in PEP 508, is to break a package’s dependencies into parts: those included when no extras are specified, those included only if you want the a extra, and those included only if you want the b extra. When foo[a,b] is requested, all three are combined together.

Now if you extend the concept to when only one extra is requested, say foo[a], you are still not requesting one extra, but a list of extras of length 1, and the package’s dependencies are broken into two: those included when no extras are specified, and those only if you want the a extra. With this design, bar; extra != 'a' goes into the former group—because when no extras are requested, the expression evaluates to True. So when those parts are installed, bar will still end up in the environment.

I hope this clears up why the current PEP 508 markers is confusing in this aspect and why pip seems to interpret it wrong. Again, pip actually interprets it correctly as designed; its the design that gives extra != value a semantic meaning that you do not expect (and that semantic meaning means there’s currently no way to achieve what you want). So this should not be fixed by pip because pip should not deviate from the standard; you need to fix the standard.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Pip install to custom target directory and exclude specific ...
I want to add extra packages in my target directory without duplicate dependencies for my default install/interpreter. I plan to add the custom ......
Read more >
Help packaging optional application features, using extras?
Hey everyone! Excuse the long post. I've broken it up into sections to hopefully make it easier to jump between sections.
Read more >
Dependencies Management in Setuptools
Each dependency, regardless of type, needs to be specified according to PEP 508 ... This is where a package declares its core dependencies,...
Read more >
Commands | Documentation | Poetry - Python dependency ...
If you want to exclude one or more dependency groups for the installation, ... If the package(s) you want to install provide extras,...
Read more >
Poetry: Dependency Management for Python - PyPI
You can explicitly specify to Poet that a set of globs should be ignored or included for the purposes of packaging. The globs...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found