Skip to content

Commit 2b3adde

Browse files
committed
pylock: check that url and path match if name is not set
1 parent e3af78f commit 2b3adde

File tree

2 files changed

+165
-20
lines changed

2 files changed

+165
-20
lines changed

src/packaging/pylock.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,25 @@ def _validate_path_url(path: str | None, url: str | None) -> None:
233233
raise PylockValidationError("path or url must be provided")
234234

235235

236+
def _validate_path_url_names(
237+
name: str | None, path: str | None, url: str | None
238+
) -> None:
239+
if name:
240+
# When name is set, it is authoritative,
241+
# and the path and url names can be anything.
242+
return
243+
if not path or not url:
244+
# We only need to validate if both path and url are set.
245+
return
246+
path_name = _path_name(path)
247+
url_name = _url_name(url)
248+
if path_name != url_name:
249+
raise PylockValidationError(
250+
f"'path' name {path_name!r} and 'url' name {url_name!r} must be identical "
251+
f"when 'name' is not set"
252+
)
253+
254+
236255
def _path_name(path: str | None) -> str | None:
237256
if not path:
238257
return None
@@ -447,6 +466,9 @@ def _from_dict(cls, d: Mapping[str, Any]) -> Self:
447466
hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
448467
)
449468
_validate_path_url(package_sdist.path, package_sdist.url)
469+
_validate_path_url_names(
470+
package_sdist.name, package_sdist.path, package_sdist.url
471+
)
450472
try:
451473
parse_sdist_filename(package_sdist.filename)
452474
except Exception as e:
@@ -458,8 +480,11 @@ def _from_dict(cls, d: Mapping[str, Any]) -> Self:
458480
@property
459481
def filename(self) -> str:
460482
"""Get the filename of the sdist."""
461-
filename = self.name or _url_name(self.url) or _path_name(self.path)
483+
# name is authoritative if set, else url and path names are guaranteed
484+
# to be identical by validation.
485+
filename = self.name or _path_name(self.path) or _url_name(self.url)
462486
if not filename:
487+
# This error will be caught by validation too.
463488
raise PylockValidationError("Cannot determine sdist filename")
464489
return filename
465490

@@ -502,6 +527,9 @@ def _from_dict(cls, d: Mapping[str, Any]) -> Self:
502527
hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
503528
)
504529
_validate_path_url(package_wheel.path, package_wheel.url)
530+
_validate_path_url_names(
531+
package_wheel.name, package_wheel.path, package_wheel.url
532+
)
505533
try:
506534
parse_wheel_filename(package_wheel.filename)
507535
except Exception as e:
@@ -513,8 +541,11 @@ def _from_dict(cls, d: Mapping[str, Any]) -> Self:
513541
@property
514542
def filename(self) -> str:
515543
"""Get the filename of the wheel."""
516-
filename = self.name or _url_name(self.url) or _path_name(self.path)
544+
# name is authoritative if set, else url and path names are guaranteed
545+
# to be identical by validation.
546+
filename = self.name or _path_name(self.path) or _url_name(self.url)
517547
if not filename:
548+
# This error will be caught by validation too.
518549
raise PylockValidationError("Cannot determine wheel filename")
519550
return filename
520551

tests/test_pylock.py

Lines changed: 132 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -331,15 +331,6 @@ def test_pylock_invalid_vcs() -> None:
331331
),
332332
"example-2.0.tar.gz",
333333
),
334-
(
335-
# url preferred over path
336-
PackageSdist(
337-
url="https://example.com/example-2.0.tar.gz",
338-
path="./example-1.0.tar.gz",
339-
hashes={},
340-
),
341-
"example-2.0.tar.gz",
342-
),
343334
# wheels
344335
(
345336
PackageWheel(
@@ -387,15 +378,6 @@ def test_pylock_invalid_vcs() -> None:
387378
),
388379
"example-2.0-py3-none-any.whl",
389380
),
390-
(
391-
# url preferred over path
392-
PackageWheel(
393-
url="https://example.com/example-2.0-py3-none-any.whl",
394-
path="./example-1.0-py3-none-any.whl",
395-
hashes={},
396-
),
397-
"example-2.0-py3-none-any.whl",
398-
),
399381
],
400382
)
401383
def test_dist_filename(
@@ -460,6 +442,138 @@ def test_pylock_invalid_sdist_filename() -> None:
460442
)
461443

462444

445+
def test_pylock_sdist_path_url_mismatch() -> None:
446+
data = {
447+
"lock-version": "1.0",
448+
"created-by": "pip",
449+
"packages": [
450+
{
451+
"name": "example",
452+
"sdist": {
453+
"path": "./that-1.0.tar.gz",
454+
"url": "https://example.com/this-1.0.tar.gz",
455+
"hashes": {"sha256": "f" * 40},
456+
},
457+
},
458+
],
459+
}
460+
with pytest.raises(PylockValidationError) as exc_info:
461+
Pylock.from_dict(data)
462+
assert str(exc_info.value) == (
463+
"'path' name 'that-1.0.tar.gz' and 'url' name 'this-1.0.tar.gz' "
464+
"must be identical when 'name' is not set in 'packages[0].sdist'"
465+
)
466+
467+
468+
def test_pylock_sdist_path_url_match() -> None:
469+
data = {
470+
"lock-version": "1.0",
471+
"created-by": "pip",
472+
"packages": [
473+
{
474+
"name": "example",
475+
"sdist": {
476+
"path": "./that-1.0.tar.gz",
477+
"url": "https://example.com/that-1.0.tar.gz",
478+
"hashes": {"sha256": "f" * 40},
479+
},
480+
},
481+
],
482+
}
483+
Pylock.from_dict(data)
484+
485+
486+
def test_pylock_wheel_path_url_mismatch() -> None:
487+
data = {
488+
"lock-version": "1.0",
489+
"created-by": "pip",
490+
"packages": [
491+
{
492+
"name": "example",
493+
"wheels": [
494+
{
495+
"path": "./that-1.0-py3-none-any.whl",
496+
"url": "http://example.com/this-1.0-py3-none-any.whl",
497+
"hashes": {"sha256": "f" * 40},
498+
}
499+
],
500+
},
501+
],
502+
}
503+
with pytest.raises(PylockValidationError) as exc_info:
504+
Pylock.from_dict(data)
505+
assert str(exc_info.value) == (
506+
"'path' name 'that-1.0-py3-none-any.whl' and "
507+
"'url' name 'this-1.0-py3-none-any.whl' "
508+
"must be identical when 'name' is not set in 'packages[0].wheels[0]'"
509+
)
510+
511+
512+
def test_pylock_wheel_path_url_match() -> None:
513+
data = {
514+
"lock-version": "1.0",
515+
"created-by": "pip",
516+
"packages": [
517+
{
518+
"name": "example",
519+
"wheels": [
520+
{
521+
"path": "./that-1.0-py3-none-any.whl",
522+
"url": "http://example.com/that-1.0-py3-none-any.whl",
523+
"hashes": {"sha256": "f" * 40},
524+
}
525+
],
526+
},
527+
],
528+
}
529+
Pylock.from_dict(data)
530+
531+
532+
def test_pylock_sdist_path_url_mismatch_use_name() -> None:
533+
data = {
534+
"lock-version": "1.0",
535+
"created-by": "pip",
536+
"packages": [
537+
{
538+
"name": "example",
539+
"sdist": {
540+
"name": "./example-1.0.tar.gz",
541+
"path": "./that-1.0.tar.gz",
542+
"url": "https://example.com/this-1.0.tar.gz",
543+
"hashes": {"sha256": "f" * 40},
544+
},
545+
},
546+
],
547+
}
548+
Pylock.from_dict(data)
549+
pylock = Pylock.from_dict(data)
550+
assert pylock.packages[0].sdist
551+
assert pylock.packages[0].sdist.filename == pylock.packages[0].sdist.name
552+
553+
554+
def test_pylock_wheel_path_url_mismatch_use_name() -> None:
555+
data = {
556+
"lock-version": "1.0",
557+
"created-by": "pip",
558+
"packages": [
559+
{
560+
"name": "example",
561+
"wheels": [
562+
{
563+
"name": "example-1.0-py3-none-any.whl",
564+
"path": "./that-1.0-py3-none-any.whl",
565+
"url": "http://example.com/this-1.0-py3-none-any.whl",
566+
"hashes": {"sha256": "f" * 40},
567+
}
568+
],
569+
},
570+
],
571+
}
572+
pylock = Pylock.from_dict(data)
573+
assert pylock.packages[0].wheels
574+
assert pylock.packages[0].wheels[0].filename == pylock.packages[0].wheels[0].name
575+
576+
463577
def test_pylock_invalid_wheel() -> None:
464578
data = {
465579
"lock-version": "1.0",

0 commit comments

Comments
 (0)