Skip to content

Fix Issue 6347: default constraints not applying to other categories #6364

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions pipenv/utils/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ def parsed_constraints(self):
)
)

# Only add default constraints for dev packages if setting allows
# Always add default constraints for dev packages
if self.category != "default" and self.project.settings.get(
"use_default_constraints", True
):
Expand Down Expand Up @@ -412,7 +412,7 @@ def constraints(self):
for c in possible_constraints_list:
constraints_list.add(c)

# Only use default_constraints when installing dev-packages and setting allows
# Always use default_constraints when installing dev-packages
if self.category != "default" and self.project.settings.get(
"use_default_constraints", True
):
Expand Down Expand Up @@ -852,6 +852,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:
Expand Down Expand Up @@ -900,8 +909,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...")
Expand Down
76 changes: 76 additions & 0 deletions tests/integration/test_dev_package_constraints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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"
8 changes: 7 additions & 1 deletion tests/integration/test_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand All @@ -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 = (
Expand All @@ -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")
Expand Down
Loading