Skip to content

Commit 0f0c91c

Browse files
committed
pylock: refine validation of package sources
1 parent 900de62 commit 0f0c91c

File tree

2 files changed

+43
-20
lines changed

2 files changed

+43
-20
lines changed

src/pip/_internal/models/pylock.py

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,7 @@ def _get_list_of_markers(d: Dict[str, Any], key: str) -> Optional[List[Marker]]:
129129
result.append(Marker(item))
130130
except InvalidMarker:
131131
raise PylockValidationError(
132-
f"Item {i} in list {key!r} "
133-
f"is not a valid environment marker: {item!r}"
132+
f"Item {i} in list {key!r} is not a valid environment marker: {item!r}"
134133
)
135134
return result
136135

@@ -186,14 +185,14 @@ def _get_required_list_of_objects(
186185
return result
187186

188187

189-
def _validate_exactly_one_of(o: object, attrs: List[str]) -> None:
190-
"""Validate that exactly one of the attributes is truthy."""
191-
count = 0
192-
for attr in attrs:
193-
if getattr(o, attr):
194-
count += 1
195-
if count != 1:
196-
raise PylockValidationError(f"Exactly one of {', '.join(attrs)} must be set")
188+
def _exactly_one(iterable: Iterable[object]) -> bool:
189+
found = False
190+
for item in iterable:
191+
if item:
192+
if found:
193+
return False
194+
found = True
195+
return found
197196

198197

199198
class PylockValidationError(Exception):
@@ -221,7 +220,8 @@ class PackageVcs:
221220

222221
def __post_init__(self) -> None:
223222
# TODO validate supported vcs type
224-
_validate_exactly_one_of(self, ["url", "path"])
223+
if not self.path and not self.url:
224+
raise PylockValidationError("No path nor url set for vcs package")
225225

226226
@classmethod
227227
def from_dict(cls, d: Dict[str, Any]) -> Self:
@@ -260,7 +260,8 @@ class PackageArchive:
260260
subdirectory: Optional[str]
261261

262262
def __post_init__(self) -> None:
263-
_validate_exactly_one_of(self, ["url", "path"])
263+
if not self.path and not self.url:
264+
raise PylockValidationError("No path nor url set for archive package")
264265

265266
@classmethod
266267
def from_dict(cls, d: Dict[str, Any]) -> Self:
@@ -283,7 +284,8 @@ class PackageSdist:
283284
hashes: Dict[str, str]
284285

285286
def __post_init__(self) -> None:
286-
_validate_exactly_one_of(self, ["url", "path"])
287+
if not self.path and not self.url:
288+
raise PylockValidationError("No path nor url set for sdist package")
287289

288290
@classmethod
289291
def from_dict(cls, d: Dict[str, Any]) -> Self:
@@ -306,7 +308,8 @@ class PackageWheel:
306308
hashes: Dict[str, str]
307309

308310
def __post_init__(self) -> None:
309-
_validate_exactly_one_of(self, ["url", "path"])
311+
if not self.path and not self.url:
312+
raise PylockValidationError("No path nor url set for wheel package")
310313

311314
@classmethod
312315
def from_dict(cls, d: Dict[str, Any]) -> Self:
@@ -337,9 +340,19 @@ class Package:
337340
# (not supported) tool: Optional[Dict[str, Any]]
338341

339342
def __post_init__(self) -> None:
340-
_validate_exactly_one_of(
341-
self, ["vcs", "directory", "archive", "sdist", "wheels"]
342-
)
343+
if self.sdist or self.wheels:
344+
if any([self.vcs, self.directory, self.archive]):
345+
raise PylockValidationError(
346+
"None of vcs, directory, archive "
347+
"must be set if sdist or wheels are set"
348+
)
349+
else:
350+
# no sdist nor wheels
351+
if not _exactly_one([self.vcs, self.directory, self.archive]):
352+
raise PylockValidationError(
353+
"Exactly one of vcs, directory, archive must be set "
354+
"if sdist and wheels are not set"
355+
)
343356

344357
@classmethod
345358
def from_dict(cls, d: Dict[str, Any]) -> Self:

tests/unit/test_pylock.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,19 @@
99
PylockRequiredKeyError,
1010
PylockUnsupportedVersionError,
1111
PylockValidationError,
12+
_exactly_one,
1213
)
1314

1415

16+
def test_exactly_one() -> None:
17+
assert not _exactly_one([])
18+
assert not _exactly_one([False])
19+
assert not _exactly_one([False, False])
20+
assert not _exactly_one([True, True])
21+
assert _exactly_one([True])
22+
assert _exactly_one([True, False])
23+
24+
1525
@pytest.mark.parametrize("version", ["1.0", "1.1"])
1626
def test_pylock_version(version: str) -> None:
1727
data = {
@@ -80,9 +90,9 @@ def test_pylock_packages_without_dist() -> None:
8090
}
8191
with pytest.raises(PylockValidationError) as exc_info:
8292
Pylock.from_dict(data)
83-
assert (
84-
str(exc_info.value)
85-
== "Exactly one of vcs, directory, archive, sdist, wheels must be set"
93+
assert str(exc_info.value) == (
94+
"Exactly one of vcs, directory, archive must be set "
95+
"if sdist and wheels are not set"
8696
)
8797

8898

0 commit comments

Comments
 (0)