diff --git a/src/packaging/pylock.py b/src/packaging/pylock.py index 96aa35c6d..1c72fb0df 100644 --- a/src/packaging/pylock.py +++ b/src/packaging/pylock.py @@ -447,12 +447,6 @@ def _from_dict(cls, d: Mapping[str, Any]) -> Self: hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract] ) _validate_path_url(package_sdist.path, package_sdist.url) - try: - parse_sdist_filename(package_sdist.filename) - except Exception as e: - raise PylockValidationError( - f"Invalid sdist filename {package_sdist.filename!r}" - ) from e return package_sdist @property @@ -502,12 +496,6 @@ def _from_dict(cls, d: Mapping[str, Any]) -> Self: hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract] ) _validate_path_url(package_wheel.path, package_wheel.url) - try: - parse_wheel_filename(package_wheel.filename) - except Exception as e: - raise PylockValidationError( - f"Invalid wheel filename {package_wheel.filename!r}" - ) from e return package_wheel @property @@ -597,6 +585,46 @@ def _from_dict(cls, d: Mapping[str, Any]) -> Self: "Exactly one of vcs, directory, archive must be set " "if sdist and wheels are not set" ) + for i, wheel in enumerate(package.wheels or []): + try: + (name, version, _, _) = parse_wheel_filename(wheel.filename) + except Exception as e: + raise PylockValidationError( + f"Invalid wheel filename {wheel.filename!r}", + context=f"wheels[{i}]", + ) from e + if name != package.name: + raise PylockValidationError( + f"Name in {wheel.filename!r} is not consistent with " + f"package name {package.name!r}", + context=f"wheels[{i}]", + ) + if package.version and version != package.version: + raise PylockValidationError( + f"Version in {wheel.filename!r} is not consistent with " + f"package version {str(package.version)!r}", + context=f"wheels[{i}]", + ) + if package.sdist: + try: + name, version = parse_sdist_filename(package.sdist.filename) + except Exception as e: + raise PylockValidationError( + f"Invalid sdist filename {package.sdist.filename!r}", + context="sdist", + ) from e + if name != package.name: + raise PylockValidationError( + f"Name in {package.sdist.filename!r} is not consistent with " + f"package name {package.name!r}", + context="sdist", + ) + if package.version and version != package.version: + raise PylockValidationError( + f"Version in {package.sdist.filename!r} is not consistent with " + f"package version {str(package.version)!r}", + context="sdist", + ) try: for i, attestation_identity in enumerate( # noqa: B007 package.attestation_identities or [] diff --git a/tests/test_pylock.py b/tests/test_pylock.py index 7c0090ecc..f29b47e5e 100644 --- a/tests/test_pylock.py +++ b/tests/test_pylock.py @@ -306,6 +306,13 @@ def test_pylock_invalid_vcs() -> None: ), "example-1.0.tar.gz", ), + ( + PackageSdist( + path="example-1.0.tar.gz", + hashes={}, + ), + "example-1.0.tar.gz", + ), ( PackageSdist( url="https://example.com/example-1.0.tar.gz", @@ -362,6 +369,13 @@ def test_pylock_invalid_vcs() -> None: ), "example-1.0-py3-none-any.whl", ), + ( + PackageWheel( + path="example-1.0-py3-none-any.whl", + hashes={}, + ), + "example-1.0-py3-none-any.whl", + ), ( PackageWheel( url="https://example.com/example-1.0-py3-none-any.whl", @@ -439,6 +453,55 @@ def test_pylock_invalid_wheel_filename() -> None: ) +def test_pylock_inconsistent_wheel_name() -> None: + data = { + "lock-version": "1.0", + "created-by": "pip", + "packages": [ + { + "name": "foo", + "wheels": [ + { + "url": "http://example.com/bar-1.0-py3-none-any.whl", + "hashes": {"sha256": "f" * 40}, + } + ], + } + ], + } + with pytest.raises(PylockValidationError) as exc_info: + Pylock.from_dict(data) + assert str(exc_info.value) == ( + "Name in 'bar-1.0-py3-none-any.whl' is not consistent " + "with package name 'foo' in 'packages[0].wheels[0]'" + ) + + +def test_pylock_inconsistent_wheel_version() -> None: + data = { + "lock-version": "1.0", + "created-by": "pip", + "packages": [ + { + "name": "bar", + "version": "2.0", + "wheels": [ + { + "url": "http://example.com/bar-1.0-py3-none-any.whl", + "hashes": {"sha256": "f" * 40}, + } + ], + } + ], + } + with pytest.raises(PylockValidationError) as exc_info: + Pylock.from_dict(data) + assert str(exc_info.value) == ( + "Version in 'bar-1.0-py3-none-any.whl' is not consistent " + "with package version '2.0' in 'packages[0].wheels[0]'" + ) + + def test_pylock_invalid_sdist_filename() -> None: data = { "lock-version": "1.0", @@ -460,6 +523,51 @@ def test_pylock_invalid_sdist_filename() -> None: ) +def test_pylock_inconsistent_sdist_name() -> None: + data = { + "lock-version": "1.0", + "created-by": "pip", + "packages": [ + { + "name": "foo", + "sdist": { + "path": "./bar-1.0.tar.gz", + "hashes": {"sha256": "f" * 40}, + }, + }, + ], + } + with pytest.raises(PylockValidationError) as exc_info: + Pylock.from_dict(data) + assert str(exc_info.value) == ( + "Name in 'bar-1.0.tar.gz' is not consistent " + "with package name 'foo' in 'packages[0].sdist'" + ) + + +def test_pylock_inconsistent_sdist_version() -> None: + data = { + "lock-version": "1.0", + "created-by": "pip", + "packages": [ + { + "name": "bar", + "version": "2.0", + "sdist": { + "path": "./bar-1.0.tar.gz", + "hashes": {"sha256": "f" * 40}, + }, + }, + ], + } + with pytest.raises(PylockValidationError) as exc_info: + Pylock.from_dict(data) + assert str(exc_info.value) == ( + "Version in 'bar-1.0.tar.gz' is not consistent " + "with package version '2.0' in 'packages[0].sdist'" + ) + + def test_pylock_invalid_wheel() -> None: data = { "lock-version": "1.0",