From 10be5b9c1613d305ea46a5562c31ddeefbc65309 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Mon, 11 Sep 2023 05:13:27 -0400 Subject: [PATCH] Include the package name in the information to the resolver (#5930) * Include the package name in the information to the resolver * Fix unit tests * fix unit tests --- pipenv/resolver.py | 12 ++++++------ pipenv/utils/dependencies.py | 6 +++--- pipenv/utils/resolver.py | 19 +++++++------------ tests/unit/test_utils.py | 36 ++++++++++++++++++------------------ 4 files changed, 34 insertions(+), 39 deletions(-) diff --git a/pipenv/resolver.py b/pipenv/resolver.py index 82e01ca3..ef4e11b3 100644 --- a/pipenv/resolver.py +++ b/pipenv/resolver.py @@ -83,7 +83,11 @@ def handle_parsed_args(parsed): with open(parsed.constraints_file) as constraints: file_constraints = constraints.read().strip().split("\n") os.unlink(parsed.constraints_file) - parsed.packages += sorted(file_constraints) + packages = {} + for line in file_constraints: + dep_name, pip_line = line.split(",", 1) + packages[dep_name] = pip_line + parsed.packages = packages return parsed @@ -579,12 +583,8 @@ def resolve_packages( else None ) - if not isinstance(packages, set): - packages = set(packages) - if not isinstance(constraints, set): - constraints = set(constraints) if constraints else set() if constraints: - packages |= constraints + packages.update(constraints) def resolve( packages, pre, project, sources, clear, system, category, requirements_dir=None diff --git a/pipenv/utils/dependencies.py b/pipenv/utils/dependencies.py index d31d78ed..90475c76 100644 --- a/pipenv/utils/dependencies.py +++ b/pipenv/utils/dependencies.py @@ -467,14 +467,14 @@ def convert_deps_to_pip( include_index=False, ): """ "Converts a Pipfile-formatted dependency to a pip-formatted one.""" - dependencies = [] + dependencies = {} if indexes is None: indexes = [] for dep_name, dep in deps.items(): req = dependency_as_pip_install_line( dep_name, dep, include_hashes, include_markers, include_index, indexes ) - dependencies.append(req) + dependencies[dep_name] = req return dependencies @@ -942,7 +942,7 @@ def expansive_install_req_from_line( :return: A tuple of the InstallRequirement and the name of the package (if determined). """ name = None - pip_line = pip_line.strip("'") + pip_line = pip_line.strip("'").lstrip(" ") for new_req_symbol in ("@ ", " @ "): # Check for new style pip lines if new_req_symbol in pip_line: pip_line_parts = pip_line.split(new_req_symbol, 1) diff --git a/pipenv/utils/resolver.py b/pipenv/utils/resolver.py index d5c7832f..06c8c8cb 100644 --- a/pipenv/utils/resolver.py +++ b/pipenv/utils/resolver.py @@ -7,7 +7,7 @@ import tempfile import warnings from functools import lru_cache from pathlib import Path -from typing import Dict, List, Optional, Set +from typing import Dict, List, Optional from pipenv import environments, resolver from pipenv.exceptions import ResolutionFailure @@ -40,7 +40,6 @@ except ImportError: from .dependencies import ( HackedPythonVersion, convert_deps_to_pip, - determine_package_name, expansive_install_req_from_line, get_constraints_from_deps, get_lockfile_section_using_pipfile_category, @@ -174,7 +173,7 @@ class Resolver: @classmethod def create( cls, - deps: Set[str], + deps: Dict[str, str], project: Project, index_lookup: Dict[str, str] = None, markers_lookup: Dict[str, str] = None, @@ -198,15 +197,11 @@ class Resolver: sources = project.sources packages = project.get_pipfile_section(category) constraints = set() - for dep in deps: # Build up the index and markers lookups + for package_name, dep in deps.items(): # Build up the index and markers lookups if not dep: continue is_constraint = True - install_req, package_name = expansive_install_req_from_line( - dep, expand_env=True - ) - if package_name is None: - package_name = determine_package_name(install_req) + install_req, _ = expansive_install_req_from_line(dep, expand_env=True) original_deps[package_name] = dep install_reqs[package_name] = install_req index, extra_index, trust_host, remainder = parse_indexes(dep) @@ -782,7 +777,6 @@ def venv_resolve_deps( deps = convert_deps_to_pip( deps, project.pipfile_sources(), include_index=True ) - constraints = set(deps) # Useful for debugging and hitting breakpoints in the resolver if project.s.PIPENV_RESOLVER_PARENT_PYTHON: try: @@ -795,7 +789,7 @@ def venv_resolve_deps( requirements_dir=req_dir, packages=deps, category=category, - constraints=constraints, + constraints=deps, ) if results: st.console.print( @@ -831,7 +825,8 @@ def venv_resolve_deps( with tempfile.NamedTemporaryFile( mode="w+", prefix="pipenv", suffix="constraints.txt", delete=False ) as constraints_file: - constraints_file.write(str("\n".join(constraints))) + for dep_name, pip_line in deps.items(): + constraints_file.write(f"{dep_name}, {pip_line}\n") cmd.append("--constraints-file") cmd.append(constraints_file.name) st.console.print("Resolving dependencies...") diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 3c63971d..22a96415 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -13,10 +13,10 @@ from pipenv.exceptions import PipenvUsageError # Pipfile format <-> requirements.txt format. DEP_PIP_PAIRS = [ - ({"django": ">1.10"}, "django>1.10"), - ({"Django": ">1.10"}, "Django>1.10"), - ({"requests": {"extras": ["socks"], "version": ">1.10"}}, "requests[socks]>1.10"), - ({"requests": {"extras": ["socks"], "version": "==1.10"}}, "requests[socks]==1.10"), + ({"django": ">1.10"}, {"django": "django>1.10"}), + ({"Django": ">1.10"}, {"Django": "Django>1.10"}), + ({"requests": {"extras": ["socks"], "version": ">1.10"}}, {"requests": "requests[socks]>1.10"}), + ({"requests": {"extras": ["socks"], "version": "==1.10"}}, {"requests": "requests[socks]==1.10"}), ( { "dataclasses-json": { @@ -25,11 +25,11 @@ DEP_PIP_PAIRS = [ "editable": True, } }, - "dataclasses-json@ git+https://github.com/lidatong/dataclasses-json.git@v0.5.7", + {"dataclasses-json": "dataclasses-json@ git+https://github.com/lidatong/dataclasses-json.git@v0.5.7"}, ), ( {"dataclasses-json": {"git": "https://github.com/lidatong/dataclasses-json.git", "ref": "v0.5.7"}}, - "dataclasses-json@ git+https://github.com/lidatong/dataclasses-json.git@v0.5.7", + {"dataclasses-json": "dataclasses-json@ git+https://github.com/lidatong/dataclasses-json.git@v0.5.7"}, ), ( # Extras in url @@ -39,7 +39,7 @@ DEP_PIP_PAIRS = [ "extras": ["pipenv"], } }, - "dparse[pipenv] @ https://github.com/oz123/dparse/archive/refs/heads/master.zip", + {"dparse": "dparse[pipenv] @ https://github.com/oz123/dparse/archive/refs/heads/master.zip"}, ), ( { @@ -50,7 +50,7 @@ DEP_PIP_PAIRS = [ "editable": False, } }, - "requests[security]@ git+https://github.com/requests/requests.git@main", + {"requests": "requests[security]@ git+https://github.com/requests/requests.git@main"}, ), ] @@ -64,23 +64,23 @@ def mock_unpack(link, source_dir, download_dir, only_download=False, session=Non @pytest.mark.parametrize("deps, expected", DEP_PIP_PAIRS) @pytest.mark.needs_internet def test_convert_deps_to_pip(deps, expected): - assert dependencies.convert_deps_to_pip(deps) == [expected] + assert dependencies.convert_deps_to_pip(deps) == expected @pytest.mark.utils @pytest.mark.needs_internet def test_convert_deps_to_pip_star_specifier(): deps = {"uvicorn": "*"} - expected = "uvicorn" - assert dependencies.convert_deps_to_pip(deps) == [expected] + expected = {"uvicorn": "uvicorn"} + assert dependencies.convert_deps_to_pip(deps) == expected @pytest.mark.utils @pytest.mark.needs_internet def test_convert_deps_to_pip_extras_no_version(): deps = {"uvicorn": {"extras": ["standard"], "version": "*"}} - expected = "uvicorn[standard]" - assert dependencies.convert_deps_to_pip(deps) == [expected] + expected = {"uvicorn": "uvicorn[standard]"} + assert dependencies.convert_deps_to_pip(deps) == expected @pytest.mark.utils @@ -95,7 +95,7 @@ def test_convert_deps_to_pip_extras_no_version(): "hash": "sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", } }, - "FooProject==1.2 --hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", + {"FooProject": "FooProject==1.2 --hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"}, ), ( { @@ -105,7 +105,7 @@ def test_convert_deps_to_pip_extras_no_version(): "hash": "sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", } }, - "FooProject[stuff]==1.2 --hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", + {"FooProject": "FooProject[stuff]==1.2 --hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"}, ), ( { @@ -115,7 +115,7 @@ def test_convert_deps_to_pip_extras_no_version(): "extras": ["standard"], } }, - "git+https://github.com/encode/uvicorn.git@master#egg=uvicorn[standard]", + {"uvicorn": "git+https://github.com/encode/uvicorn.git@master#egg=uvicorn[standard]"}, ), ], ) @@ -126,8 +126,8 @@ def test_convert_deps_to_pip_one_way(deps, expected): @pytest.mark.utils def test_convert_deps_to_pip_one_way(): deps = {"uvicorn": {}} - expected = "uvicorn" - assert dependencies.convert_deps_to_pip(deps) == [expected.lower()] + expected = {"uvicorn": "uvicorn"} + assert dependencies.convert_deps_to_pip(deps) == expected @pytest.mark.utils