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
11 changes: 10 additions & 1 deletion src/packaging/pylock.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,15 @@ def _validate_path_url(path: str | None, url: str | None) -> None:
raise PylockValidationError("path or url must be provided")


def _validate_index_url(url: str) -> str:
parsed_url = urlparse(url)
if not parsed_url.scheme:
raise PylockValidationError(f"URL {url!r} must be absolute")
if parsed_url.scheme in {"http", "https"} and not parsed_url.netloc:
raise PylockValidationError(f"URL {url!r} must include a host")
return url


def _path_name(path: str | None) -> str | None:
if not path:
return None
Expand Down Expand Up @@ -580,7 +589,7 @@ def _from_dict(cls, d: Mapping[str, Any]) -> Self:
vcs=_get_object(d, PackageVcs, "vcs"),
directory=_get_object(d, PackageDirectory, "directory"),
archive=_get_object(d, PackageArchive, "archive"),
index=_get(d, str, "index"),
index=_get_as(d, str, _validate_index_url, "index"),
sdist=_get_object(d, PackageSdist, "sdist"),
wheels=_get_sequence_of_objects(d, PackageWheel, "wheels"),
attestation_identities=_get_sequence(d, Mapping, "attestation-identities"), # type: ignore[type-abstract]
Expand Down
52 changes: 52 additions & 0 deletions tests/test_pylock.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,58 @@ def test_pylock_basic_package() -> None:
assert pylock.to_dict() == data


@pytest.mark.parametrize("index", ["not-a-url", "https:///simple"])
def test_pylock_invalid_package_index(index: str) -> None:
data = {
"lock-version": "1.0",
"created-by": "pip",
"packages": [
{
"name": "example",
"index": index,
"wheels": [
{
"name": "example-1.0-py3-none-any.whl",
"url": "https://example.com/example-1.0-py3-none-any.whl",
"hashes": {"sha256": "f" * 40},
}
],
}
],
}
with pytest.raises(PylockValidationError) as exc_info:
Pylock.from_dict(data)
expected_error = (
f"URL {index!r} must be absolute"
if index == "not-a-url"
else f"URL {index!r} must include a host"
)
assert str(exc_info.value) == f"{expected_error} in 'packages[0].index'"


def test_pylock_package_index_file_url() -> None:
data = {
"lock-version": "1.0",
"created-by": "pip",
"packages": [
{
"name": "example",
"index": "file:///tmp/simple/",
"wheels": [
{
"name": "example-1.0-py3-none-any.whl",
"url": "https://example.com/example-1.0-py3-none-any.whl",
"hashes": {"sha256": "f" * 40},
}
],
}
],
}
pylock = Pylock.from_dict(data)
assert pylock.packages[0].index == "file:///tmp/simple/"
assert pylock.to_dict() == data


def test_pylock_vcs_package() -> None:
data = {
"lock-version": "1.0",
Expand Down