From db392b9ac6e6e9a51375998536fe7cb9c0655b0f Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 20 Mar 2025 19:42:29 -0400 Subject: [PATCH 1/5] Fix issue with default constraints --- pipenv/utils/resolver.py | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/pipenv/utils/resolver.py b/pipenv/utils/resolver.py index 1a1c4432a..b2064984a 100644 --- a/pipenv/utils/resolver.py +++ b/pipenv/utils/resolver.py @@ -367,10 +367,8 @@ def parsed_constraints(self): ) ) - # Only add default constraints for dev packages if setting allows - if self.category != "default" and self.project.settings.get( - "use_default_constraints", True - ): + # Always add default constraints for dev packages + if self.category != "default": constraints.extend(self.parsed_default_constraints) return constraints @@ -412,10 +410,8 @@ def constraints(self): for c in possible_constraints_list: constraints_list.add(c) - # Only use default_constraints when installing dev-packages and setting allows - if self.category != "default" and self.project.settings.get( - "use_default_constraints", True - ): + # Always use default_constraints when installing dev-packages + if self.category != "default": constraints_list |= self.default_constraints return constraints_list @@ -852,6 +848,15 @@ def venv_resolve_deps( deps = convert_deps_to_pip( deps, project.pipfile_sources(), include_index=True ) + + # For dev packages, add constraints from default packages + constraints = deps.copy() + if pipfile_category != "packages" and "default" in lockfile: + # Get the locked versions from default packages + for pkg_name, pkg_data in lockfile["default"].items(): + if isinstance(pkg_data, dict) and "version" in pkg_data: + # Add as a constraint to ensure compatibility + constraints[pkg_name] = pkg_data["version"] # Useful for debugging and hitting breakpoints in the resolver if project.s.PIPENV_RESOLVER_PARENT_PYTHON: try: @@ -900,8 +905,23 @@ def venv_resolve_deps( with tempfile.NamedTemporaryFile( mode="w+", prefix="pipenv", suffix="constraints.txt", delete=False ) as constraints_file: + # Write the current category dependencies for dep_name, pip_line in deps.items(): constraints_file.write(f"{dep_name}, {pip_line}\n") + + # For dev packages, add explicit constraints from default packages + if pipfile_category != "packages" and "default" in lockfile: + for pkg_name, pkg_data in lockfile["default"].items(): + if isinstance(pkg_data, dict) and "version" in pkg_data: + # Add as a constraint to ensure compatibility + version = pkg_data["version"] + constraints_file.write( + f"{pkg_name}, {pkg_name}{version}\n" + ) + st.console.print( + f"Adding constraint: {pkg_name}{version}" + ) + cmd.append("--constraints-file") cmd.append(constraints_file.name) st.console.print("Resolving dependencies...") From 0b1b74cc0b76af043c88650fda65e430b3ab387a Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 20 Mar 2025 19:45:43 -0400 Subject: [PATCH 2/5] Fix issue with default constraints --- .../test_dev_package_constraints.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 tests/integration/test_dev_package_constraints.py diff --git a/tests/integration/test_dev_package_constraints.py b/tests/integration/test_dev_package_constraints.py new file mode 100644 index 000000000..1b0a52b96 --- /dev/null +++ b/tests/integration/test_dev_package_constraints.py @@ -0,0 +1,79 @@ +import os +import pytest + +from pipenv.utils.shell import temp_environ + + +@pytest.mark.lock +@pytest.mark.dev +def test_dev_packages_respect_default_package_constraints(pipenv_instance_private_pypi): + """ + Test that dev packages respect constraints from default packages. + + This test verifies the fix for the issue where pipenv may ignore install_requires + from setup.py and lock incompatible versions. The specific case is when httpx is + pinned in default packages and respx is in dev packages, respx should be locked + to a version compatible with the httpx version. + """ + with pipenv_instance_private_pypi() as p: + # First test: explicit version constraint in Pipfile + with open(p.pipfile_path, "w") as f: + contents = """ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +httpx = "==0.24.1" + +[dev-packages] +respx = "*" + +[requires] +python_version = "3.9" + """.strip() + f.write(contents) + + c = p.pipenv("lock") + assert c.returncode == 0 + + # Verify httpx is locked to 0.24.1 + assert "httpx" in p.lockfile["default"] + assert p.lockfile["default"]["httpx"]["version"] == "==0.24.1" + + # Verify respx is locked to a compatible version (0.21.1 is the last compatible version) + assert "respx" in p.lockfile["develop"] + assert p.lockfile["develop"]["respx"]["version"] == "==0.21.1" + + # Second test: implicit version constraint through another dependency + with open(p.pipfile_path, "w") as f: + contents = """ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +httpx = "*" +xrpl-py = ">=1.8.0" +websockets = ">=9.0.1,<11.0" + +[dev-packages] +respx = "*" + +[requires] +python_version = "3.9" + """.strip() + f.write(contents) + + c = p.pipenv("lock") + assert c.returncode == 0 + + # Verify httpx is still locked to 0.24.1 (due to constraints from other packages) + assert "httpx" in p.lockfile["default"] + assert p.lockfile["default"]["httpx"]["version"] == "==0.24.1" + + # Verify respx is still locked to a compatible version + assert "respx" in p.lockfile["develop"] + assert p.lockfile["develop"]["respx"]["version"] == "==0.21.1" From 1f7a2c01ef07899fa6d9cead553011ae1711ed85 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Mon, 21 Apr 2025 05:03:37 -0400 Subject: [PATCH 3/5] fix ruff --- .../integration/test_dev_package_constraints.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/integration/test_dev_package_constraints.py b/tests/integration/test_dev_package_constraints.py index 1b0a52b96..32395e9be 100644 --- a/tests/integration/test_dev_package_constraints.py +++ b/tests/integration/test_dev_package_constraints.py @@ -1,15 +1,12 @@ -import os import pytest -from pipenv.utils.shell import temp_environ - @pytest.mark.lock @pytest.mark.dev def test_dev_packages_respect_default_package_constraints(pipenv_instance_private_pypi): """ Test that dev packages respect constraints from default packages. - + This test verifies the fix for the issue where pipenv may ignore install_requires from setup.py and lock incompatible versions. The specific case is when httpx is pinned in default packages and respx is in dev packages, respx should be locked @@ -37,15 +34,15 @@ def test_dev_packages_respect_default_package_constraints(pipenv_instance_privat c = p.pipenv("lock") assert c.returncode == 0 - + # Verify httpx is locked to 0.24.1 assert "httpx" in p.lockfile["default"] assert p.lockfile["default"]["httpx"]["version"] == "==0.24.1" - + # Verify respx is locked to a compatible version (0.21.1 is the last compatible version) assert "respx" in p.lockfile["develop"] assert p.lockfile["develop"]["respx"]["version"] == "==0.21.1" - + # Second test: implicit version constraint through another dependency with open(p.pipfile_path, "w") as f: contents = """ @@ -66,14 +63,14 @@ def test_dev_packages_respect_default_package_constraints(pipenv_instance_privat python_version = "3.9" """.strip() f.write(contents) - + c = p.pipenv("lock") assert c.returncode == 0 - + # Verify httpx is still locked to 0.24.1 (due to constraints from other packages) assert "httpx" in p.lockfile["default"] assert p.lockfile["default"]["httpx"]["version"] == "==0.24.1" - + # Verify respx is still locked to a compatible version assert "respx" in p.lockfile["develop"] assert p.lockfile["develop"]["respx"]["version"] == "==0.21.1" From 330fa33a4307be576ab796cc3ada472defe5efde Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Mon, 21 Apr 2025 05:19:31 -0400 Subject: [PATCH 4/5] Correction --- pipenv/utils/resolver.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pipenv/utils/resolver.py b/pipenv/utils/resolver.py index 9566fb648..c8ccff763 100644 --- a/pipenv/utils/resolver.py +++ b/pipenv/utils/resolver.py @@ -368,7 +368,9 @@ def parsed_constraints(self): ) # Always add default constraints for dev packages - if self.category != "default": + if self.category != "default" and self.project.settings.get( + "use_default_constraints", True + ): constraints.extend(self.parsed_default_constraints) return constraints @@ -411,7 +413,9 @@ def constraints(self): constraints_list.add(c) # Always use default_constraints when installing dev-packages - if self.category != "default": + if self.category != "default" and self.project.settings.get( + "use_default_constraints", True + ): constraints_list |= self.default_constraints return constraints_list From 6f4db5b6b072170282604071fc5349942b4f0226 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Mon, 21 Apr 2025 05:30:58 -0400 Subject: [PATCH 5/5] Address test concerns --- tests/integration/test_requirements.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_requirements.py b/tests/integration/test_requirements.py index c761ded1f..3d68f2943 100644 --- a/tests/integration/test_requirements.py +++ b/tests/integration/test_requirements.py @@ -18,6 +18,8 @@ def test_requirements_generates_requirements_from_lockfile(pipenv_instance_pypi) {packages[0]}= "=={packages[1]}" [dev-packages] {dev_packages[0]}= "=={dev_packages[1]}" + [pipenv] + use_default_constraints = false """.strip() f.write(contents) p.pipenv("lock") @@ -100,6 +102,8 @@ def test_requirements_generates_requirements_from_lockfile_from_categories( {test_packages[0]}= "=={test_packages[1]}" [doc] {doc_packages[0]}= "=={doc_packages[1]}" + [pipenv] + use_default_constraints = false """.strip() f.write(contents) result = p.pipenv("lock") @@ -121,7 +125,7 @@ def test_requirements_generates_requirements_from_lockfile_from_categories( @pytest.mark.requirements -def test_requirements_generates_requirements_with_from_pipfile(pipenv_instance_pypi): +def test_requirements_generates_requirements_from_pipfile(pipenv_instance_pypi): with pipenv_instance_pypi() as p: packages = ("requests", "2.31.0") sub_packages = ( @@ -136,6 +140,8 @@ def test_requirements_generates_requirements_with_from_pipfile(pipenv_instance_p {packages[0]} = "=={packages[1]}" [dev-packages] {dev_packages[0]} = "=={dev_packages[1]}" + [pipenv] + use_default_constraints = false """.strip() f.write(contents) p.pipenv("lock")