Skip to content
Open
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
10 changes: 8 additions & 2 deletions pipenv/utils/locking.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,14 @@ def format_requirement_for_lockfile(
entry["version"] = str(req.req.specifier)
elif req.specifier:
entry["version"] = str(req.specifier)
if req.link and req.link.is_file:
entry["file"] = req.link.url
if req.link:
if req.link.is_file:
entry["file"] = req.link.url
elif req.link.scheme in ("http", "https"):
# Handle direct URL dependencies (PEP 508 style: package @ https://...)
entry["file"] = req.link.url
entry.pop("version", None) # URL deps don't need version
entry.pop("index", None) # URL deps don't use index
# Add index information
if name in index_lookup:
entry["index"] = index_lookup[name]
Expand Down
140 changes: 140 additions & 0 deletions tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -779,3 +779,143 @@
assert project.default_source["url"] in [primary_index, extra_index]
finally:
os.chdir(original_dir)


class TestFormatRequirementForLockfile:
"""Tests for format_requirement_for_lockfile in locking.py.

These tests verify that various requirement types are correctly formatted
for the lockfile, including direct URL dependencies (PEP 508 style).
"""

def test_direct_url_dependency_https(self):
"""Test that HTTPS direct URL dependencies are stored in lockfile.

This is the fix for issue #5967 - when a package has an extra dependency
with a direct URL like:
my-private-dependency @ https://my-private-artifactory/.../package.whl
The URL should be stored in the lockfile entry.
"""
from pipenv.patched.pip._internal.models.link import Link

Check failure on line 799 in tests/unit/test_utils.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (F401)

tests/unit/test_utils.py:799:62: F401 `pipenv.patched.pip._internal.models.link.Link` imported but unused
from pipenv.patched.pip._internal.req.constructors import (
install_req_from_line,
)
from pipenv.utils.locking import format_requirement_for_lockfile

# Create an InstallRequirement with a direct HTTPS URL (PEP 508 style)
req_str = "my-private-package @ https://my-artifactory.com/api/pypi/repo/my-private-package/1.0.0/my-private-package-1.0.0-py3-none-any.whl"
req = install_req_from_line(req_str)

# Verify the link properties
assert req.link is not None
assert req.link.scheme == "https"
assert req.link.is_file is False
assert req.link.is_vcs is False

# Format for lockfile
name, entry = format_requirement_for_lockfile(
req,
markers_lookup={},
index_lookup={},
original_deps={},
pipfile_entries={},
hashes=None,
)

# Verify the URL is stored in the lockfile entry
assert name == "my-private-package"
assert "file" in entry, "Direct URL should be stored in 'file' key"
assert entry["file"] == "https://my-artifactory.com/api/pypi/repo/my-private-package/1.0.0/my-private-package-1.0.0-py3-none-any.whl"
assert "version" not in entry, "URL deps should not have version"
assert "index" not in entry, "URL deps should not have index"

def test_direct_url_dependency_http(self):
"""Test that HTTP direct URL dependencies are stored in lockfile."""
from pipenv.patched.pip._internal.req.constructors import (
install_req_from_line,
)
from pipenv.utils.locking import format_requirement_for_lockfile

# Create an InstallRequirement with a direct HTTP URL
req_str = "example-package @ http://internal-server.local/packages/example-package-2.0.0.tar.gz"
req = install_req_from_line(req_str)

# Verify the link properties
assert req.link is not None
assert req.link.scheme == "http"

# Format for lockfile
name, entry = format_requirement_for_lockfile(
req,
markers_lookup={},
index_lookup={},
original_deps={},
pipfile_entries={},
hashes=None,
)

# Verify the URL is stored
assert name == "example-package"
assert "file" in entry
assert entry["file"] == "http://internal-server.local/packages/example-package-2.0.0.tar.gz"

def test_file_url_dependency(self):
"""Test that local file:// URLs are still handled correctly."""
from pipenv.patched.pip._internal.req.constructors import (
install_req_from_line,
)
from pipenv.utils.locking import format_requirement_for_lockfile

# Create an InstallRequirement with a file:// URL
req_str = "local-package @ file:///home/user/packages/local-package-1.0.0.whl"
req = install_req_from_line(req_str)

# Verify the link properties
assert req.link is not None
assert req.link.scheme == "file"
assert req.link.is_file is True

# Format for lockfile
name, entry = format_requirement_for_lockfile(
req,
markers_lookup={},
index_lookup={},
original_deps={},
pipfile_entries={},
hashes=None,
)

# Verify the URL is stored
assert name == "local-package"
assert "file" in entry
assert entry["file"] == "file:///home/user/packages/local-package-1.0.0.whl"

def test_regular_pypi_dependency(self):
"""Test that regular PyPI dependencies still work correctly."""
from pipenv.patched.pip._internal.req.constructors import (
install_req_from_line,
)
from pipenv.utils.locking import format_requirement_for_lockfile

# Create a regular PyPI requirement
req_str = "requests==2.28.0"
req = install_req_from_line(req_str)

# Regular requirements don't have a link
assert req.link is None

# Format for lockfile
name, entry = format_requirement_for_lockfile(
req,
markers_lookup={},
index_lookup={},
original_deps={},
pipfile_entries={},
hashes=None,
)

# Verify version is stored, not URL
assert name == "requests"
assert "version" in entry
assert entry["version"] == "==2.28.0"
assert "file" not in entry
Loading