Closed vphilippon closed 9 months ago
I looked a bit more into this, and I'm understanding a bit more of what's going on. I hope this will help find a good fix:
This call:
File "c:\python27\lib\site-packages\pip\_vendor\pkg_resources\__init__.py", line 2816, in _compute_dependencies
common = frozenset(reqs_for_extra(None))
tries to gather all requirements for which there is either no marker, or for which the marker, when setting extra
to None
, evaluates as True
.
As an example, given the following requirements:
Requires-Dist: requests (==2.13.0)
Requires-Dist: toto (==7.8.9); sys_platform == "win32"
Requires-Dist: bar (==4.2.1); extra == None
Requires-Dist: foo (==1.2.4); extra == '3.10'
common
would contain the 1st, 2nd and 3rd requirements.
I can see where the 1st one could come from: the install_requires
list.
The second one, probably from extras_require: {':sys_platform == "win32"': ['toto==7.8.9']}
The 3rd one would be from something like extras_require: {None: ['bar==4.2.1']}
, which I'm not sure should be part of common
, or is even possible (I claim here my right to be wrong and to not have a clue).
Now, as I pointed out in my initial comment, the issue happens with the 4th requirements.
When evaluating the marker of that requirements, we call _eval_op
.
And on this line:
spec = Specifier("".join([op.serialize(), rhs]))
We succed, because: op.serialize() -> "==", rhs -> "3.10"
So we call this line of code:
return spec.contains(lhs)
And we get an exception, because: lhs -> None
,
and trying to parse None
with a regex to create a Version
object fails.
So, it looks like the issue is from either:
pkg_resources
, which shouldn't be calling an equivalent of req.marker.evaluate({'extra': None})
, although it does not seem wrong to me (considering, once again, my little knowledge).packaging
, which should either handle the None
in _eval_op
, or in Specifier.contains
, or use some additionnal logic before using Specifier.contains
, such as making sure that lhs
can be parsed as a Version
, such that evaluating this with the Version
logic is what is really intended.While this seems outside of the hand of pip
itself, could someone knowledgeable (@dstufft ?) give me some pointers on which one I should go to and open an issue? I have a feeling that Specifier.contains
in packaging
should be handling None
nicely, but I'd really like a second opinion.
Thanks to whoever will take the time to read all this and reply :) .
Hmm passing in None
as an extra feels wrong to me, but I'm happy to have a PR to packaging that makes _eval_op
handle that case better.
Allright, thanks for your input! I'll try to take a stab at this on the packaging side soon-ish. I suggest we keep this issue active here until the vendored packaging is updated in pip too.
Small update: This still occurs with pip 10 (commit 8f4f15a5a95d7d5b511ceaee9ed261176c181970).
I checked in case some change in the refactor changed so that None
isn't passed anymore.
About that, I submitted a PR in pypa/packaging to deal with this a while ago, but my tests brought up some legitimate questions, putting a pause on the change.
Should I take another approach and fix this from pip
side to not pass a None
?
This isn't critical, I know I haven't seen much "version-like extras", only my own fancy/weird stuff so far, but if we can get this done, let's do it.
Is this still awaiting a fix, or did a change to packaging
ever get made to address it?
Waiting on a fix.
Pushing this down the road to the next release since I don't think this'd happen in time for 18.0.
Pushing this down the road since I don't think this'd happen in time for 18.1.
Reproduced this with the current version of pip as well:
$ pip install ./dist/my_test_pkg-1.2.3-py3-none-any.whl --debug 2>&1
Processing ./dist/my_test_pkg-1.2.3-py3-none-any.whl
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/pkg_resources/__init__.py:3021 in _dep_map │
│ │
│ 3018 │ @property │
│ 3019 │ def _dep_map(self): │
│ 3020 │ │ try: │
│ ❱ 3021 │ │ │ return self.__dep_map │
│ 3022 │ │ except AttributeError: │
│ 3023 │ │ │ self.__dep_map = self._compute_dependencies() │
│ 3024 │ │ │ return self.__dep_map │
│ │
│ ╭────────────────────────────────────── locals ───────────────────────────────────────╮ │
│ │ self = my-test-pkg 1.2.3 (/private/tmp/foo/dist/my_test_pkg-1.2.3-py3-none-any.whl) │ │
│ ╰─────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/pkg_resources/__init__.py:2815 in │
│ __getattr__ │
│ │
│ 2812 │ def __getattr__(self, attr): │
│ 2813 │ │ """Delegate all unrecognized public attributes to .metadata provider""" │
│ 2814 │ │ if attr.startswith('_'): │
│ ❱ 2815 │ │ │ raise AttributeError(attr) │
│ 2816 │ │ return getattr(self._provider, attr) │
│ 2817 │ │
│ 2818 │ def __dir__(self): │
│ │
│ ╭────────────────────────────────────── locals ───────────────────────────────────────╮ │
│ │ attr = '_DistInfoDistribution__dep_map' │ │
│ │ self = my-test-pkg 1.2.3 (/private/tmp/foo/dist/my_test_pkg-1.2.3-py3-none-any.whl) │ │
│ ╰─────────────────────────────────────────────────────────────────────────────────────╯ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
AttributeError: _DistInfoDistribution__dep_map
During handling of the above exception, another exception occurred:
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /Users/pradyunsg/Developer/github/pip/.venv/bin/pip:8 in <module> │
│ │
│ 5 from pip._internal.cli.main import main │
│ 6 if __name__ == '__main__': │
│ 7 │ sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) │
│ ❱ 8 │ sys.exit(main()) │
│ 9 │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ __annotations__ = {} │ │
│ │ __builtins__ = <module 'builtins' (built-in)> │ │
│ │ __cached__ = None │ │
│ │ __doc__ = None │ │
│ │ __file__ = '/Users/pradyunsg/Developer/github/pip/.venv/bin/pip' │ │
│ │ __loader__ = <_frozen_importlib_external.SourceFileLoader object at 0x104a25390> │ │
│ │ __name__ = '__main__' │ │
│ │ __package__ = None │ │
│ │ __spec__ = None │ │
│ │ main = <function main at 0x104b3f010> │ │
│ │ re = <module 're' from │ │
│ │ '/Users/pradyunsg/.asdf/installs/python/3.10.1/lib/python3.10/re.py'> │ │
│ │ sys = <module 'sys' (built-in)> │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_internal/cli/main.py:70 in main │
│ │
│ 67 │ │ logger.debug("Ignoring error %s when setting locale", e) │
│ 68 │ command = create_command(cmd_name, isolated=("--isolated" in cmd_args)) │
│ 69 │ │
│ ❱ 70 │ return command.main(cmd_args) │
│ 71 │
│ │
│ ╭───────────────────────────────────── locals ─────────────────────────────────────╮ │
│ │ args = ['install', './dist/my_test_pkg-1.2.3-py3-none-any.whl', '--debug'] │ │
│ │ cmd_args = ['./dist/my_test_pkg-1.2.3-py3-none-any.whl', '--debug'] │ │
│ │ cmd_name = 'install' │ │
│ │ command = <pip._internal.commands.install.InstallCommand object at 0x106126d10> │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_internal/cli/base_command.py:101 in main │
│ │
│ 98 │ def main(self, args: List[str]) -> int: │
│ 99 │ │ try: │
│ 100 │ │ │ with self.main_context(): │
│ ❱ 101 │ │ │ │ return self._main(args) │
│ 102 │ │ finally: │
│ 103 │ │ │ logging.shutdown() │
│ 104 │
│ │
│ ╭─────────────────────────────────── locals ───────────────────────────────────╮ │
│ │ args = ['./dist/my_test_pkg-1.2.3-py3-none-any.whl', '--debug'] │ │
│ │ self = <pip._internal.commands.install.InstallCommand object at 0x106126d10> │ │
│ ╰──────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_internal/cli/base_command.py:221 in _main │
│ │
│ 218 │ │ │ else: │
│ 219 │ │ │ │ run = self.run │
│ 220 │ │ │ │ rich_traceback.install(show_locals=True) │
│ ❱ 221 │ │ │ return run(options, args) │
│ 222 │ │ finally: │
│ 223 │ │ │ self.handle_pip_version_check(options) │
│ 224 │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ args = ['./dist/my_test_pkg-1.2.3-py3-none-any.whl'] │ │
│ │ intercepts_unhandled_exc = <function Command._main.<locals>.intercepts_unhandled_exc at │ │
│ │ 0x10496bd90> │ │
│ │ level_number = 20 │ │
│ │ options = <Values at 0x106426a10: {'help': None, 'debug_mode': True, │ │
│ │ 'isolated_mode': False, 'require_venv': 1, 'python': None, │ │
│ │ 'verbose': 0, 'version': None, 'quiet': 0, 'log': None, │ │
│ │ 'no_input': False, 'proxy': '', 'retries': 5, 'timeout': 15, │ │
│ │ 'exists_action': [], 'trusted_hosts': [], 'cert': None, │ │
│ │ 'client_cert': None, 'cache_dir': │ │
│ │ '/Users/pradyunsg/Library/Caches/pip', │ │
│ │ 'disable_pip_version_check': False, 'no_color': False, │ │
│ │ 'no_python_version_warning': False, 'features_enabled': [], │ │
│ │ 'deprecated_features_enabled': [], 'requirements': [], │ │
│ │ 'constraints': [], 'ignore_dependencies': False, 'pre': False, │ │
│ │ 'editables': [], 'dry_run': False, 'target_dir': None, │ │
│ │ 'platforms': None, 'python_version': None, 'implementation': │ │
│ │ None, 'abis': None, 'use_user_site': False, 'root_path': None, │ │
│ │ 'prefix_path': None, 'src_dir': │ │
│ │ '/Users/pradyunsg/Developer/github/pip/.venv/src', 'upgrade': │ │
│ │ None, 'upgrade_strategy': 'only-if-needed', 'force_reinstall': │ │
│ │ None, 'ignore_installed': None, 'ignore_requires_python': None, │ │
│ │ 'build_isolation': True, 'use_pep517': None, 'check_build_deps': │ │
│ │ False, 'config_settings': None, 'install_options': None, │ │
│ │ 'global_options': None, 'compile': True, 'warn_script_location': │ │
│ │ True, 'warn_about_conflicts': True, 'format_control': │ │
│ │ FormatControl(set(), set()), 'prefer_binary': False, │ │
│ │ 'require_hashes': False, 'progress_bar': 'on', │ │
│ │ 'root_user_action': 'warn', 'index_url': │ │
│ │ 'https://pypi.org/simple', 'extra_index_urls': [], 'no_index': │ │
│ │ False, 'find_links': [], 'json_report_file': None, 'no_clean': │ │
│ │ False}> │ │
│ │ run = <bound method with_cleanup.<locals>.wrapper of │ │
│ │ <pip._internal.commands.install.InstallCommand object at │ │
│ │ 0x106126d10>> │ │
│ │ self = <pip._internal.commands.install.InstallCommand object at │ │
│ │ 0x106126d10> │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_internal/cli/req_command.py:247 in wrapper │
│ │
│ 244 │ │ │ configure_tempdir_registry(self.tempdir_registry) │
│ 245 │ │ │
│ 246 │ │ try: │
│ ❱ 247 │ │ │ return func(self, options, args) │
│ 248 │ │ except PreviousBuildDirError: │
│ 249 │ │ │ # This kind of conflict can occur when the user passes an explicit │
│ 250 │ │ │ # build directory with a pre-existing folder. In that case we do │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ args = ['./dist/my_test_pkg-1.2.3-py3-none-any.whl'] │ │
│ │ configure_tempdir_registry = <function with_cleanup.<locals>.configure_tempdir_registry at │ │
│ │ 0x106422710> │ │
│ │ func = <function InstallCommand.run at 0x106422680> │ │
│ │ options = <Values at 0x106426a10: {'help': None, 'debug_mode': True, │ │
│ │ 'isolated_mode': False, 'require_venv': 1, 'python': None, │ │
│ │ 'verbose': 0, 'version': None, 'quiet': 0, 'log': None, │ │
│ │ 'no_input': False, 'proxy': '', 'retries': 5, 'timeout': 15, │ │
│ │ 'exists_action': [], 'trusted_hosts': [], 'cert': None, │ │
│ │ 'client_cert': None, 'cache_dir': │ │
│ │ '/Users/pradyunsg/Library/Caches/pip', │ │
│ │ 'disable_pip_version_check': False, 'no_color': False, │ │
│ │ 'no_python_version_warning': False, 'features_enabled': [], │ │
│ │ 'deprecated_features_enabled': [], 'requirements': [], │ │
│ │ 'constraints': [], 'ignore_dependencies': False, 'pre': False, │ │
│ │ 'editables': [], 'dry_run': False, 'target_dir': None, │ │
│ │ 'platforms': None, 'python_version': None, 'implementation': │ │
│ │ None, 'abis': None, 'use_user_site': False, 'root_path': None, │ │
│ │ 'prefix_path': None, 'src_dir': │ │
│ │ '/Users/pradyunsg/Developer/github/pip/.venv/src', 'upgrade': │ │
│ │ None, 'upgrade_strategy': 'only-if-needed', 'force_reinstall': │ │
│ │ None, 'ignore_installed': None, 'ignore_requires_python': None, │ │
│ │ 'build_isolation': True, 'use_pep517': None, │ │
│ │ 'check_build_deps': False, 'config_settings': None, │ │
│ │ 'install_options': None, 'global_options': None, 'compile': │ │
│ │ True, 'warn_script_location': True, 'warn_about_conflicts': │ │
│ │ True, 'format_control': FormatControl(set(), set()), │ │
│ │ 'prefer_binary': False, 'require_hashes': False, │ │
│ │ 'progress_bar': 'on', 'root_user_action': 'warn', 'index_url': │ │
│ │ 'https://pypi.org/simple', 'extra_index_urls': [], 'no_index': │ │
│ │ False, 'find_links': [], 'json_report_file': None, 'no_clean': │ │
│ │ False}> │ │
│ │ self = <pip._internal.commands.install.InstallCommand object at │ │
│ │ 0x106126d10> │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_internal/commands/install.py:400 in run │
│ │
│ 397 │ │ │ │
│ 398 │ │ │ self.trace_basic_info(finder) │
│ 399 │ │ │ │
│ ❱ 400 │ │ │ requirement_set = resolver.resolve( │
│ 401 │ │ │ │ reqs, check_supported_wheels=not options.target_dir │
│ 402 │ │ │ ) │
│ 403 │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ args = ['./dist/my_test_pkg-1.2.3-py3-none-any.whl'] │ │
│ │ build_tracker = <pip._internal.operations.build.build_tracker.BuildTracker object at │ │
│ │ 0x1064bf550> │ │
│ │ directory = <repr-error 'Attempted to access deleted path: │ │
│ │ /private/var/folders/y1/j465wvf92vs938kmgqh63bj80000gn/T/pip-install… │ │
│ │ finder = <pip._internal.index.package_finder.PackageFinder object at │ │
│ │ 0x106427760> │ │
│ │ global_options = [] │ │
│ │ install_options = [] │ │
│ │ options = <Values at 0x106426a10: {'help': None, 'debug_mode': True, │ │
│ │ 'isolated_mode': False, 'require_venv': 1, 'python': None, 'verbose': │ │
│ │ 0, 'version': None, 'quiet': 0, 'log': None, 'no_input': False, │ │
│ │ 'proxy': '', 'retries': 5, 'timeout': 15, 'exists_action': [], │ │
│ │ 'trusted_hosts': [], 'cert': None, 'client_cert': None, 'cache_dir': │ │
│ │ '/Users/pradyunsg/Library/Caches/pip', 'disable_pip_version_check': │ │
│ │ False, 'no_color': False, 'no_python_version_warning': False, │ │
│ │ 'features_enabled': [], 'deprecated_features_enabled': [], │ │
│ │ 'requirements': [], 'constraints': [], 'ignore_dependencies': False, │ │
│ │ 'pre': False, 'editables': [], 'dry_run': False, 'target_dir': None, │ │
│ │ 'platforms': None, 'python_version': None, 'implementation': None, │ │
│ │ 'abis': None, 'use_user_site': False, 'root_path': None, │ │
│ │ 'prefix_path': None, 'src_dir': │ │
│ │ '/Users/pradyunsg/Developer/github/pip/.venv/src', 'upgrade': None, │ │
│ │ 'upgrade_strategy': 'only-if-needed', 'force_reinstall': None, │ │
│ │ 'ignore_installed': None, 'ignore_requires_python': None, │ │
│ │ 'build_isolation': True, 'use_pep517': None, 'check_build_deps': │ │
│ │ False, 'config_settings': None, 'install_options': None, │ │
│ │ 'global_options': None, 'compile': True, 'warn_script_location': │ │
│ │ True, 'warn_about_conflicts': True, 'format_control': │ │
│ │ FormatControl(set(), set()), 'prefer_binary': False, │ │
│ │ 'require_hashes': False, 'progress_bar': 'on', 'root_user_action': │ │
│ │ 'warn', 'index_url': 'https://pypi.org/simple', 'extra_index_urls': │ │
│ │ [], 'no_index': False, 'find_links': [], 'json_report_file': None, │ │
│ │ 'no_clean': False}> │ │
│ │ preparer = <pip._internal.operations.prepare.RequirementPreparer object at │ │
│ │ 0x1064bfbb0> │ │
│ │ req = <InstallRequirement object: my-test-pkg==1.2.3 from │ │
│ │ file:///private/tmp/foo/dist/my_test_pkg-1.2.3-py3-none-any.whl │ │
│ │ editable=False> │ │
│ │ reqs = [ │ │
│ │ │ <InstallRequirement object: my-test-pkg==1.2.3 from │ │
│ │ file:///private/tmp/foo/dist/my_test_pkg-1.2.3-py3-none-any.whl │ │
│ │ editable=False> │ │
│ │ ] │ │
│ │ resolver = <pip._internal.resolution.resolvelib.resolver.Resolver object at │ │
│ │ 0x1064bfd60> │ │
│ │ self = <pip._internal.commands.install.InstallCommand object at 0x106126d10> │ │
│ │ session = <pip._internal.network.session.PipSession object at 0x106427400> │ │
│ │ target_python = <pip._internal.models.target_python.TargetPython object at │ │
│ │ 0x10643dfc0> │ │
│ │ target_temp_dir = None │ │
│ │ target_temp_dir_path = None │ │
│ │ upgrade_strategy = 'to-satisfy-only' │ │
│ │ wheel_cache = <pip._internal.cache.WheelCache object at 0x1064bdf00> │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_internal/resolution/resolvelib/resolver.py:92 in │
│ resolve │
│ │
│ 89 │ │ │
│ 90 │ │ try: │
│ 91 │ │ │ try_to_avoid_resolution_too_deep = 2000000 │
│ ❱ 92 │ │ │ result = self._result = resolver.resolve( │
│ 93 │ │ │ │ collected.requirements, max_rounds=try_to_avoid_resolution_too_deep │
│ 94 │ │ │ ) │
│ 95 │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ check_supported_wheels = True │ │
│ │ collected = CollectedRootRequirements( │ │
│ │ │ requirements=[ │ │
│ │ │ │ │ │
│ │ ExplicitRequirement(LinkCandidate('file:///private/tmp/f… │ │
│ │ │ ], │ │
│ │ │ constraints={}, │ │
│ │ │ user_requested={'my-test-pkg': 0} │ │
│ │ ) │ │
│ │ provider = <pip._internal.resolution.resolvelib.provider.PipProvider │ │
│ │ object at 0x106522320> │ │
│ │ reporter = <pip._internal.resolution.resolvelib.reporter.PipReporter │ │
│ │ object at 0x106552f50> │ │
│ │ resolver = <pip._vendor.resolvelib.resolvers.Resolver object at │ │
│ │ 0x106552f80> │ │
│ │ root_reqs = [ │ │
│ │ │ <InstallRequirement object: my-test-pkg==1.2.3 from │ │
│ │ file:///private/tmp/foo/dist/my_test_pkg-1.2.3-py3-none-… │ │
│ │ editable=False> │ │
│ │ ] │ │
│ │ self = <pip._internal.resolution.resolvelib.resolver.Resolver │ │
│ │ object at 0x1064bfd60> │ │
│ │ try_to_avoid_resolution_too_deep = 2000000 │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/resolvelib/resolvers.py:481 in resolve │
│ │
│ 478 │ │ │ `max_rounds` argument. │
│ 479 │ │ """ │
│ 480 │ │ resolution = Resolution(self.provider, self.reporter) │
│ ❱ 481 │ │ state = resolution.resolve(requirements, max_rounds=max_rounds) │
│ 482 │ │ return _build_result(state) │
│ 483 │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ max_rounds = 2000000 │ │
│ │ requirements = [ │ │
│ │ │ │ │
│ │ ExplicitRequirement(LinkCandidate('file:///private/tmp/foo/dist/my_test_pkg-… │ │
│ │ ] │ │
│ │ resolution = <pip._vendor.resolvelib.resolvers.Resolution object at 0x106552aa0> │ │
│ │ self = <pip._vendor.resolvelib.resolvers.Resolver object at 0x106552f80> │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/resolvelib/resolvers.py:373 in resolve │
│ │
│ 370 │ │ │ │
│ 371 │ │ │ # Choose the most preferred unpinned criterion to try. │
│ 372 │ │ │ name = min(unsatisfied_names, key=self._get_preference) │
│ ❱ 373 │ │ │ failure_causes = self._attempt_to_pin_criterion(name) │
│ 374 │ │ │ │
│ 375 │ │ │ if failure_causes: │
│ 376 │ │ │ │ causes = [i for c in failure_causes for i in c.information] │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ max_rounds = 2000000 │ │
│ │ name = 'my-test-pkg' │ │
│ │ r = ExplicitRequirement(LinkCandidate('file:///private/tmp/foo/dist/my_test… │ │
│ │ requirements = [ │ │
│ │ │ │ │
│ │ ExplicitRequirement(LinkCandidate('file:///private/tmp/foo/dist/my_test… │ │
│ │ ] │ │
│ │ round_index = 0 │ │
│ │ self = <pip._vendor.resolvelib.resolvers.Resolution object at 0x106552aa0> │ │
│ │ unsatisfied_names = ['my-test-pkg'] │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/resolvelib/resolvers.py:213 in │
│ _attempt_to_pin_criterion │
│ │
│ 210 │ │ causes = [] │
│ 211 │ │ for candidate in criterion.candidates: │
│ 212 │ │ │ try: │
│ ❱ 213 │ │ │ │ criteria = self._get_updated_criteria(candidate) │
│ 214 │ │ │ except RequirementsConflicted as e: │
│ 215 │ │ │ │ causes.append(e.criterion) │
│ 216 │ │ │ │ continue │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ candidate = LinkCandidate('file:///private/tmp/foo/dist/my_test_pkg-1.2.3-py3-none-any.whl') │ │
│ │ causes = [] │ │
│ │ criterion = Criterion((ExplicitRequirement(LinkCandidate('file:///private/tmp/foo/dist/my_t… │ │
│ │ via=None)) │ │
│ │ name = 'my-test-pkg' │ │
│ │ self = <pip._vendor.resolvelib.resolvers.Resolution object at 0x106552aa0> │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/resolvelib/resolvers.py:203 in │
│ _get_updated_criteria │
│ │
│ 200 │ │
│ 201 │ def _get_updated_criteria(self, candidate): │
│ 202 │ │ criteria = self.state.criteria.copy() │
│ ❱ 203 │ │ for requirement in self._p.get_dependencies(candidate=candidate): │
│ 204 │ │ │ self._add_to_criteria(criteria, requirement, parent=candidate) │
│ 205 │ │ return criteria │
│ 206 │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ candidate = LinkCandidate('file:///private/tmp/foo/dist/my_test_pkg-1.2.3-py3-none-any.whl') │ │
│ │ criteria = { │ │
│ │ │ 'my-test-pkg': │ │
│ │ Criterion((ExplicitRequirement(LinkCandidate('file:///private/tmp/foo/dist/my_t… │ │
│ │ via=None)) │ │
│ │ } │ │
│ │ self = <pip._vendor.resolvelib.resolvers.Resolution object at 0x106552aa0> │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_internal/resolution/resolvelib/provider.py:237 in │
│ get_dependencies │
│ │
│ 234 │ │
│ 235 │ def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]: │
│ 236 │ │ with_requires = not self._ignore_dependencies │
│ ❱ 237 │ │ return [r for r in candidate.iter_dependencies(with_requires) if r is not None] │
│ 238 │ │
│ 239 │ @staticmethod │
│ 240 │ def is_backtrack_cause( │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ candidate = LinkCandidate('file:///private/tmp/foo/dist/my_test_pkg-1.2.3-py3-none-any.… │ │
│ │ self = <pip._internal.resolution.resolvelib.provider.PipProvider object at │ │
│ │ 0x106522320> │ │
│ │ with_requires = True │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_internal/resolution/resolvelib/provider.py:237 in │
│ <listcomp> │
│ │
│ 234 │ │
│ 235 │ def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]: │
│ 236 │ │ with_requires = not self._ignore_dependencies │
│ ❱ 237 │ │ return [r for r in candidate.iter_dependencies(with_requires) if r is not None] │
│ 238 │ │
│ 239 │ @staticmethod │
│ 240 │ def is_backtrack_cause( │
│ │
│ ╭────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ .0 = <generator object _InstallRequirementBackedCandidate.iter_dependencies at 0x1064b7df0> │ │
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_internal/resolution/resolvelib/candidates.py:247 │
│ in iter_dependencies │
│ │
│ 244 │ │ return dist │
│ 245 │ │
│ 246 │ def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]: │
│ ❱ 247 │ │ requires = self.dist.iter_dependencies() if with_requires else () │
│ 248 │ │ for r in requires: │
│ 249 │ │ │ yield self._factory.make_requirement_from_spec(str(r), self._ireq) │
│ 250 │ │ yield self._factory.make_requires_python_requirement(self.dist.requires_python) │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ self = LinkCandidate('file:///private/tmp/foo/dist/my_test_pkg-1.2.3-py3-none-any.… │ │
│ │ with_requires = True │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_internal/metadata/pkg_resources.py:216 in │
│ iter_dependencies │
│ │
│ 213 │ def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]: │
│ 214 │ │ if extras: # pkg_resources raises on invalid extras, so we sanitize. │
│ 215 │ │ │ extras = frozenset(extras).intersection(self._dist.extras) │
│ ❱ 216 │ │ return self._dist.requires(extras) │
│ 217 │ │
│ 218 │ def iter_provided_extras(self) -> Iterable[str]: │
│ 219 │ │ return self._dist.extras │
│ │
│ ╭─────────────────────────────────────── locals ────────────────────────────────────────╮ │
│ │ extras = () │ │
│ │ self = my-test-pkg 1.2.3 (/private/tmp/foo/dist/my_test_pkg-1.2.3-py3-none-any.whl) │ │
│ ╰───────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/pkg_resources/__init__.py:2736 in requires │
│ │
│ 2733 │ │
│ 2734 │ def requires(self, extras=()): │
│ 2735 │ │ """List of Requirements needed for this distro if `extras` are used""" │
│ ❱ 2736 │ │ dm = self._dep_map │
│ 2737 │ │ deps = [] │
│ 2738 │ │ deps.extend(dm.get(None, ())) │
│ 2739 │ │ for ext in extras: │
│ │
│ ╭─────────────────────────────────────── locals ────────────────────────────────────────╮ │
│ │ extras = () │ │
│ │ self = my-test-pkg 1.2.3 (/private/tmp/foo/dist/my_test_pkg-1.2.3-py3-none-any.whl) │ │
│ ╰───────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/pkg_resources/__init__.py:3023 in _dep_map │
│ │
│ 3020 │ │ try: │
│ 3021 │ │ │ return self.__dep_map │
│ 3022 │ │ except AttributeError: │
│ ❱ 3023 │ │ │ self.__dep_map = self._compute_dependencies() │
│ 3024 │ │ │ return self.__dep_map │
│ 3025 │ │
│ 3026 │ def _compute_dependencies(self): │
│ │
│ ╭────────────────────────────────────── locals ───────────────────────────────────────╮ │
│ │ self = my-test-pkg 1.2.3 (/private/tmp/foo/dist/my_test_pkg-1.2.3-py3-none-any.whl) │ │
│ ╰─────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/pkg_resources/__init__.py:3040 in │
│ _compute_dependencies │
│ │
│ 3037 │ │ │ │ if not req.marker or req.marker.evaluate({'extra': extra}): │
│ 3038 │ │ │ │ │ yield req │
│ 3039 │ │ │
│ ❱ 3040 │ │ common = frozenset(reqs_for_extra(None)) │
│ 3041 │ │ dm[None].extend(common) │
│ 3042 │ │ │
│ 3043 │ │ for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []: │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ dm = {None: []} │ │
│ │ req = "requests (<3.0.0) ; extra == '3.2.1'" │ │
│ │ reqs = [Requirement.parse('requests<3.0.0; extra == "3.2.1"')] │ │
│ │ reqs_for_extra = <function │ │
│ │ DistInfoDistribution._compute_dependencies.<locals>.reqs_for_extra at │ │
│ │ 0x106541e10> │ │
│ │ self = my-test-pkg 1.2.3 │ │
│ │ (/private/tmp/foo/dist/my_test_pkg-1.2.3-py3-none-any.whl) │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/pkg_resources/__init__.py:3037 in │
│ reqs_for_extra │
│ │
│ 3034 │ │ │
│ 3035 │ │ def reqs_for_extra(extra): │
│ 3036 │ │ │ for req in reqs: │
│ ❱ 3037 │ │ │ │ if not req.marker or req.marker.evaluate({'extra': extra}): │
│ 3038 │ │ │ │ │ yield req │
│ 3039 │ │ │
│ 3040 │ │ common = frozenset(reqs_for_extra(None)) │
│ │
│ ╭──────────────────────────── locals ─────────────────────────────╮ │
│ │ extra = None │ │
│ │ req = Requirement.parse('requests<3.0.0; extra == "3.2.1"') │ │
│ │ reqs = [Requirement.parse('requests<3.0.0; extra == "3.2.1"')] │ │
│ ╰─────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/packaging/markers.py:304 in evaluate │
│ │
│ 301 │ │ if environment is not None: │
│ 302 │ │ │ current_environment.update(environment) │
│ 303 │ │ │
│ ❱ 304 │ │ return _evaluate_markers(self._markers, current_environment) │
│ 305 │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ current_environment = { │ │
│ │ │ 'implementation_name': 'cpython', │ │
│ │ │ 'implementation_version': '3.10.1', │ │
│ │ │ 'os_name': 'posix', │ │
│ │ │ 'platform_machine': 'arm64', │ │
│ │ │ 'platform_release': '21.6.0', │ │
│ │ │ 'platform_system': 'Darwin', │ │
│ │ │ 'platform_version': 'Darwin Kernel Version 21.6.0: Wed Aug 10 │ │
│ │ 14:28:35 PDT 2022; root:xnu-8020.141.5~'+21, │ │
│ │ │ 'python_full_version': '3.10.1', │ │
│ │ │ 'platform_python_implementation': 'CPython', │ │
│ │ │ 'python_version': '3.10', │ │
│ │ │ ... +2 │ │
│ │ } │ │
│ │ environment = {'extra': None} │ │
│ │ self = <Marker('extra == "3.2.1"')> │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/packaging/markers.py:240 in │
│ _evaluate_markers │
│ │
│ 237 │ │ │ │ lhs_value = lhs.value │
│ 238 │ │ │ │ rhs_value = _get_env(environment, rhs.value) │
│ 239 │ │ │ │
│ ❱ 240 │ │ │ groups[-1].append(_eval_op(lhs_value, op, rhs_value)) │
│ 241 │ │ else: │
│ 242 │ │ │ assert marker in ["and", "or"] │
│ 243 │ │ │ if marker == "or": │
│ │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │ environment = { │ │
│ │ │ 'implementation_name': 'cpython', │ │
│ │ │ 'implementation_version': '3.10.1', │ │
│ │ │ 'os_name': 'posix', │ │
│ │ │ 'platform_machine': 'arm64', │ │
│ │ │ 'platform_release': '21.6.0', │ │
│ │ │ 'platform_system': 'Darwin', │ │
│ │ │ 'platform_version': 'Darwin Kernel Version 21.6.0: Wed Aug 10 14:28:35 PDT │ │
│ │ 2022; root:xnu-8020.141.5~'+21, │ │
│ │ │ 'python_full_version': '3.10.1', │ │
│ │ │ 'platform_python_implementation': 'CPython', │ │
│ │ │ 'python_version': '3.10', │ │
│ │ │ ... +2 │ │
│ │ } │ │
│ │ groups = [[]] │ │
│ │ lhs = <Variable('extra')> │ │
│ │ lhs_value = None │ │
│ │ marker = (<Variable('extra')>, <Op('==')>, <Value('3.2.1')>) │ │
│ │ markers = [(<Variable('extra')>, <Op('==')>, <Value('3.2.1')>)] │ │
│ │ op = <Op('==')> │ │
│ │ rhs = <Value('3.2.1')> │ │
│ │ rhs_value = '3.2.1' │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/packaging/markers.py:195 in _eval_op │
│ │
│ 192 │ except InvalidSpecifier: │
│ 193 │ │ pass │
│ 194 │ else: │
│ ❱ 195 │ │ return spec.contains(lhs) │
│ 196 │ │
│ 197 │ oper: Optional[Operator] = _operators.get(op.serialize()) │
│ 198 │ if oper is None: │
│ │
│ ╭─────────── locals ────────────╮ │
│ │ lhs = None │ │
│ │ op = <Op('==')> │ │
│ │ rhs = '3.2.1' │ │
│ │ spec = <Specifier('==3.2.1')> │ │
│ ╰───────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/packaging/specifiers.py:178 in contains │
│ │
│ 175 │ │ │
│ 176 │ │ # Normalize item to a Version or LegacyVersion, this allows us to have │
│ 177 │ │ # a shortcut for ``"2.0" in Specifier(">=2") │
│ ❱ 178 │ │ normalized_item = self._coerce_version(item) │
│ 179 │ │ │
│ 180 │ │ # Determine if we should be supporting prereleases in this specifier │
│ 181 │ │ # or not, if we do not support prereleases than we can short circuit │
│ │
│ ╭─────────────── locals ───────────────╮ │
│ │ item = None │ │
│ │ prereleases = False │ │
│ │ self = <Specifier('==3.2.1')> │ │
│ ╰──────────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/packaging/specifiers.py:146 in │
│ _coerce_version │
│ │
│ 143 │ │
│ 144 │ def _coerce_version(self, version: UnparsedVersion) -> ParsedVersion: │
│ 145 │ │ if not isinstance(version, (LegacyVersion, Version)): │
│ ❱ 146 │ │ │ version = parse(version) │
│ 147 │ │ return version │
│ 148 │ │
│ 149 │ @property │
│ │
│ ╭───────────── locals ─────────────╮ │
│ │ self = <Specifier('==3.2.1')> │ │
│ │ version = None │ │
│ ╰──────────────────────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/packaging/version.py:49 in parse │
│ │
│ 46 │ a valid PEP 440 version or a legacy version. │
│ 47 │ """ │
│ 48 │ try: │
│ ❱ 49 │ │ return Version(version) │
│ 50 │ except InvalidVersion: │
│ 51 │ │ return LegacyVersion(version) │
│ 52 │
│ │
│ ╭──── locals ────╮ │
│ │ version = None │ │
│ ╰────────────────╯ │
│ │
│ /Users/pradyunsg/Developer/github/pip/src/pip/_vendor/packaging/version.py:264 in __init__ │
│ │
│ 261 │ def __init__(self, version: str) -> None: │
│ 262 │ │ │
│ 263 │ │ # Validate the version and parse it into pieces │
│ ❱ 264 │ │ match = self._regex.search(version) │
│ 265 │ │ if not match: │
│ 266 │ │ │ raise InvalidVersion(f"Invalid version: '{version}'") │
│ 267 │
│ │
│ ╭─────────────────────────────── locals ────────────────────────────────╮ │
│ │ self = <repr-error "'Version' object has no attribute '_version'"> │ │
│ │ version = None │ │
│ ╰───────────────────────────────────────────────────────────────────────╯ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
TypeError: expected string or bytes-like object
Why not just do the same https://github.com/pypa/pip/pull/11112 (passing empty string extra instead of None) in https://github.com/pypa/pip/blob/a8ba0eec6ac3c1f6cf23f1e2e4c64954bd7a08ed/src/pip/_vendor/pkg_resources/__init__.py#L3035-L3040
--- common = frozenset(reqs_for_extra(None))
+++ common = frozenset(reqs_for_extra(''))
This change also needs new packaging release for pkg_resources because of this https://github.com/pypa/packaging/pull/545. I tried to write some tests but it fails without this pr
# pkg_resources/tests/test_resources.py
def test_marker_evaluation_with_extras2(self):
"""Extras are also evaluated as markers at resolution time."""
ad = pkg_resources.Environment([])
ws = WorkingSet([])
Foo = Distribution.from_filename(
"/foo_dir/Foo-1.2.dist-info",
metadata=Metadata(("METADATA", "Provides-Extra: 3.2.1\n"
"Requires-Dist: quux; extra=='3.2.1'"))
)
ad.add(Foo)
assert list(ws.resolve(parse_requirements("Foo"), ad)) == [Foo]
quux = Distribution.from_filename("/foo_dir/quux-1.0.dist-info")
ad.add(quux)
res = list(ws.resolve(parse_requirements("Foo[3.2.1]"), ad))
assert res == [Foo, quux]
It was fixed, I can't reproduce with 23.1.2
It was fixed, I can't reproduce with 23.1.2
Closing, then.
Description:
I have a package which provides extras. The extras keys happen to have the format of PEP440 versions, such as
3.2.1
or3.0
. When installing the package usingpip install
, I end up with an exception (see in the "What I've run" section).Note: While this occured using
pip
, I'm not exactly sure who is beign faulty (pip, setuptools, pkg_resources, packaging?)What I've run:
Here's the setup.py to make a package that reproduces the issue:
Then, make a wheel:
Then, install the package (not even using the extra):
And the result:
What I figured out
I went around the code in the stack trace and managed to figure what's happening:
The
TypeError
comes from trying to parseNone
as apackaging.Version
object. TheNone
comes fromDistInfoDistribution._compute_dependencies
, with this call:common = frozenset(reqs_for_extra(None))
.Later, during the markers evaluation in
packaging.markers
, at line 185, we have this function:As
rhs
is the extra key, a PEP440 version (3.2.1
), theSpecifier
creation succeeds, and we callreturn spec.contains(lhs)
. In this case,lhs
is theNone
I pointed out earlier, which cause theTypeError
when trying to parse it as a version. Note that ifrhs
was not a version-like string (such asfoobar
orsecurity
), theSpecifier
creation would fail, (I think) and we would executeoper = _operators.get(op.serialize())
andreturn oper(lhs, rhs)
instead.How to fix
I'm not sure. I feel like the logic in
_eval_op
is not precise enough, or maybe the marker evaluation is not meant to be used this way. I'm not familiar enough with pip/pkg_ressources/packaging structure and interaction to tell what's going to happen if I change any of this.I'd need the input of someone with a lot more knowledge about the project to help sort this out.
I tried making this as clear as possible, but don't hesitate to ask for details.