From f63095d22abf5e737de1bc8821e1a7b2810726e8 Mon Sep 17 00:00:00 2001 From: Aleksandr Mangin Date: Fri, 5 Jan 2024 14:07:39 +0100 Subject: [PATCH 1/2] fixed a bug with locking packages with uncanonical names --- news/6056.bugfix.rst | 1 + .../resolution/resolvelib/factory.py | 3 ++- tests/integration/test_lock.py | 27 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 news/6056.bugfix.rst diff --git a/news/6056.bugfix.rst b/news/6056.bugfix.rst new file mode 100644 index 0000000000..8ff134e3c6 --- /dev/null +++ b/news/6056.bugfix.rst @@ -0,0 +1 @@ +Fix a bug with locking projects that contains packages with non canonical names from private indexes diff --git a/pipenv/patched/pip/_internal/resolution/resolvelib/factory.py b/pipenv/patched/pip/_internal/resolution/resolvelib/factory.py index fb8274a59b..250006ad6a 100644 --- a/pipenv/patched/pip/_internal/resolution/resolvelib/factory.py +++ b/pipenv/patched/pip/_internal/resolution/resolvelib/factory.py @@ -247,6 +247,7 @@ def _iter_found_candidates( # Hopefully the Project model can correct this mismatch in the future. template = ireqs[0] assert template.req, "Candidates found on index must be PEP 508" + project_name = template.req.name name = canonicalize_name(template.req.name) extras: FrozenSet[str] = frozenset() @@ -282,7 +283,7 @@ def _get_installed_candidate() -> Optional[Candidate]: def iter_index_candidate_infos() -> Iterator[IndexCandidateInfo]: result = self._finder.find_best_candidate( - project_name=name, + project_name=project_name, specifier=specifier, hashes=hashes, ) diff --git a/tests/integration/test_lock.py b/tests/integration/test_lock.py index 0259f6ba13..99f5cd559c 100644 --- a/tests/integration/test_lock.py +++ b/tests/integration/test_lock.py @@ -285,6 +285,33 @@ def test_private_index_lock_requirements(pipenv_instance_private_pypi): assert c.returncode == 0 +@pytest.mark.lock +@pytest.mark.index +@pytest.mark.install # private indexes need to be uncached for resolution +@pytest.mark.requirements +@pytest.mark.needs_internet +def test_private_index_lock_requirements_for_not_canonical_package(pipenv_instance_private_pypi): + with pipenv_instance_private_pypi() as p: + with open(p.pipfile_path, 'w') as f: + contents = """ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[[source]] +url = "https://test.pypi.org/simple" +verify_ssl = true +name = "testpypi" + +[packages] +pipenv_test_private_package = {version = "*", index = "testpypi"} + """.strip() + f.write(contents) + c = p.pipenv('lock') + assert c.returncode == 0 + + @pytest.mark.index @pytest.mark.install def test_lock_updated_source(pipenv_instance_private_pypi): From bc57956f1e4039839ff9d28c2e806a78bef8f825 Mon Sep 17 00:00:00 2001 From: Aleksandr Mangin Date: Mon, 8 Jan 2024 13:08:19 +0100 Subject: [PATCH 2/2] built index mapping using canonical package names instead of raw package names --- .../resolution/resolvelib/factory.py | 3 +-- pipenv/utils/resolver.py | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/pipenv/patched/pip/_internal/resolution/resolvelib/factory.py b/pipenv/patched/pip/_internal/resolution/resolvelib/factory.py index 250006ad6a..fb8274a59b 100644 --- a/pipenv/patched/pip/_internal/resolution/resolvelib/factory.py +++ b/pipenv/patched/pip/_internal/resolution/resolvelib/factory.py @@ -247,7 +247,6 @@ def _iter_found_candidates( # Hopefully the Project model can correct this mismatch in the future. template = ireqs[0] assert template.req, "Candidates found on index must be PEP 508" - project_name = template.req.name name = canonicalize_name(template.req.name) extras: FrozenSet[str] = frozenset() @@ -283,7 +282,7 @@ def _get_installed_candidate() -> Optional[Candidate]: def iter_index_candidate_infos() -> Iterator[IndexCandidateInfo]: result = self._finder.find_best_candidate( - project_name=project_name, + project_name=name, specifier=specifier, hashes=hashes, ) diff --git a/pipenv/utils/resolver.py b/pipenv/utils/resolver.py index 858f7047c1..a8c313e5ca 100644 --- a/pipenv/utils/resolver.py +++ b/pipenv/utils/resolver.py @@ -25,6 +25,7 @@ from pipenv.patched.pip._internal.req.req_install import InstallRequirement from pipenv.patched.pip._internal.utils.temp_dir import global_tempdir_manager from pipenv.patched.pip._vendor import pkg_resources, rich +from pipenv.patched.pip._vendor.packaging.utils import canonicalize_name from pipenv.project import Project from pipenv.utils.fileutils import create_tracked_tempdir from pipenv.utils.requirements import normalize_name @@ -200,6 +201,7 @@ def create( for package_name, dep in deps.items(): # Build up the index and markers lookups if not dep: continue + canonical_package_name = canonicalize_name(package_name) is_constraint = True install_req, _ = expansive_install_req_from_line(dep, expand_env=True) original_deps[package_name] = dep @@ -210,14 +212,18 @@ def create( pipfile_entries[package_name] = pipfile_entry if isinstance(pipfile_entry, dict): if packages[package_name].get("index"): - index_lookup[package_name] = packages[package_name].get("index") + index_lookup[canonical_package_name] = packages[package_name].get( + "index" + ) if packages[package_name].get("skip_resolver"): is_constraint = False skipped[package_name] = dep elif index: - index_lookup[package_name] = index + index_lookup[canonical_package_name] = index else: - index_lookup[package_name] = project.get_default_index()["name"] + index_lookup[canonical_package_name] = project.get_default_index()[ + "name" + ] if install_req.markers: markers_lookup[package_name] = install_req.markers if is_constraint: @@ -546,9 +552,13 @@ def collect_hashes(self, ireq): return set() sources = self.sources # Enforce index restrictions - if ireq.name in self.index_lookup: + canonical_ireq_name = canonicalize_name(ireq.name) + if canonical_ireq_name in self.index_lookup: sources = list( - filter(lambda s: s.get("name") == self.index_lookup[ireq.name], sources) + filter( + lambda s: s.get("name") == self.index_lookup[canonical_ireq_name], + sources, + ) ) source = sources[0] if len(sources) else None if source: