From 8390ba50a0fbe020a9cd2b1e78d0debf717a7179 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 a3cb88793..97122c7e9 100644 --- a/pipenv/utils/resolver.py +++ b/pipenv/utils/resolver.py @@ -378,10 +378,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 @@ -422,10 +420,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 @@ -949,6 +945,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: @@ -997,8 +1002,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 9627499cb50ec60a0232077c099ffaf39923a120 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 f264dc85d666cc07d8d3b51b7868aef811b97796 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 1213fcaec7967af4dc00bbd8f49bd7a299ce112a 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 97122c7e9..f8308e69f 100644 --- a/pipenv/utils/resolver.py +++ b/pipenv/utils/resolver.py @@ -379,7 +379,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 @@ -421,7 +423,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 4ad02b52fd4c62dd5c616c504709ee98b15a5b92 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 --- news/6347.bugfix.rst | 1 + pipenv/utils/resolver.py | 23 +----- .../test_dev_package_constraints.py | 76 ------------------- tests/integration/test_requirements.py | 8 +- 4 files changed, 9 insertions(+), 99 deletions(-) create mode 100644 news/6347.bugfix.rst delete mode 100644 tests/integration/test_dev_package_constraints.py diff --git a/news/6347.bugfix.rst b/news/6347.bugfix.rst new file mode 100644 index 000000000..174cfe0df --- /dev/null +++ b/news/6347.bugfix.rst @@ -0,0 +1 @@ +Fix ``pipenv lock`` to properly apply default package version constraints when resolving dev-packages and other categories. When a package is constrained in the ``[packages]`` section (e.g., ``httpx = "==0.24.1"``), dev-packages that depend on it will now be constrained to compatible versions. diff --git a/pipenv/utils/resolver.py b/pipenv/utils/resolver.py index f8308e69f..43e703fcd 100644 --- a/pipenv/utils/resolver.py +++ b/pipenv/utils/resolver.py @@ -378,7 +378,7 @@ def parsed_constraints(self): ) ) - # Always add default constraints for dev packages + # Only add default constraints for dev packages if setting allows if self.category != "default" and self.project.settings.get( "use_default_constraints", True ): @@ -950,14 +950,6 @@ def venv_resolve_deps( 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: @@ -1010,19 +1002,6 @@ def venv_resolve_deps( 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...") diff --git a/tests/integration/test_dev_package_constraints.py b/tests/integration/test_dev_package_constraints.py deleted file mode 100644 index 32395e9be..000000000 --- a/tests/integration/test_dev_package_constraints.py +++ /dev/null @@ -1,76 +0,0 @@ -import pytest - - -@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" diff --git a/tests/integration/test_requirements.py b/tests/integration/test_requirements.py index 887708d39..f611c7b12 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") @@ -101,6 +103,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") @@ -122,7 +126,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 = ( @@ -137,6 +141,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")