Skip to content

Commit 88bc331

Browse files
committed
Add more Pulp Exceptions.
Assisted-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent e1e023c commit 88bc331

File tree

7 files changed

+114
-30
lines changed

7 files changed

+114
-30
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add more Pulp Exceptions.

pulp_python/app/exceptions.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from gettext import gettext as _
2+
3+
from pulpcore.plugin.exceptions import PulpException
4+
5+
6+
class ProvenanceVerificationError(PulpException):
7+
"""
8+
Raised when provenance verification fails.
9+
"""
10+
11+
error_code = "PLPY0001"
12+
13+
def __init__(self, message):
14+
"""
15+
:param message: Description of the provenance verification error
16+
:type message: str
17+
"""
18+
self.message = message
19+
20+
def __str__(self):
21+
return f"[{self.error_code}] " + _("Provenance verification failed: {message}").format(
22+
message=self.message
23+
)
24+
25+
26+
class AttestationVerificationError(PulpException):
27+
"""
28+
Raised when attestation verification fails.
29+
"""
30+
31+
error_code = "PLPY0002"
32+
33+
def __init__(self, message):
34+
"""
35+
:param message: Description of the attestation verification error
36+
:type message: str
37+
"""
38+
self.message = message
39+
40+
def __str__(self):
41+
return f"[{self.error_code}] " + _("Attestation verification failed: {message}").format(
42+
message=self.message
43+
)
44+
45+
46+
class PackageSubstitutionError(PulpException):
47+
"""
48+
Raised when packages with the same filename but different checksums are being added.
49+
"""
50+
51+
error_code = "PLPY0003"
52+
53+
def __init__(self, duplicates):
54+
"""
55+
:param duplicates: Description of duplicate packages
56+
:type duplicates: str
57+
"""
58+
self.duplicates = duplicates
59+
60+
def __str__(self):
61+
return (
62+
f"[{self.error_code}] "
63+
+ _(
64+
"Found duplicate packages being added with the same filename but different checksums. " # noqa: E501
65+
)
66+
+ _("To allow this, set 'allow_package_substitution' to True on the repository. ")
67+
+ _("Conflicting packages: {duplicates}").format(duplicates=self.duplicates)
68+
)
69+
70+
71+
class UnsupportedProtocolError(PulpException):
72+
"""
73+
Raised when an unsupported protocol is used for syncing.
74+
"""
75+
76+
error_code = "PLPY0004"
77+
78+
def __init__(self, protocol):
79+
"""
80+
:param protocol: The unsupported protocol
81+
:type protocol: str
82+
"""
83+
self.protocol = protocol
84+
85+
def __str__(self):
86+
return f"[{self.error_code}] " + _(
87+
"Only HTTP(S) is supported for python syncing, got: {protocol}"
88+
).format(protocol=self.protocol)

pulp_python/app/models.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
BEFORE_SAVE,
1212
hook,
1313
)
14-
from rest_framework.serializers import ValidationError
1514
from pulpcore.plugin.models import (
1615
AutoAddObjPermsMixin,
1716
Content,
@@ -23,6 +22,7 @@
2322
from pulpcore.plugin.responses import ArtifactResponse
2423

2524
from pathlib import PurePath
25+
from .exceptions import PackageSubstitutionError
2626
from .provenance import Provenance
2727
from .utils import (
2828
artifact_to_python_content_data,
@@ -407,14 +407,10 @@ def finalize_new_version(self, new_version):
407407

408408
def _check_for_package_substitution(self, new_version):
409409
"""
410-
Raise a ValidationError if newly added packages would replace existing packages that have
411-
the same filename but a different sha256 checksum.
410+
Raise a PackageSubstitutionError if newly added packages would replace existing packages
411+
that have the same filename but a different sha256 checksum.
412412
"""
413413
qs = PythonPackageContent.objects.filter(pk__in=new_version.content)
414414
duplicates = collect_duplicates(qs, ("filename",))
415415
if duplicates:
416-
raise ValidationError(
417-
"Found duplicate packages being added with the same filename but different checksums. " # noqa: E501
418-
"To allow this, set 'allow_package_substitution' to True on the repository. "
419-
f"Conflicting packages: {duplicates}"
420-
)
416+
raise PackageSubstitutionError(duplicates)

pulp_python/app/serializers.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@
88
from packaging.requirements import Requirement
99
from rest_framework import serializers
1010
from pypi_attestations import AttestationError
11-
from pydantic import TypeAdapter, ValidationError
11+
from pydantic import TypeAdapter, ValidationError as PydanticValidationError
1212
from urllib.parse import urljoin
1313

14+
from pulpcore.plugin.exceptions import DigestValidationError, ValidationError
1415
from pulpcore.plugin import models as core_models
1516
from pulpcore.plugin import serializers as core_serializers
1617
from pulpcore.plugin.util import get_domain, get_prn, get_current_authenticated_user
1718

1819
from pulp_python.app import models as python_models
20+
from pulp_python.app.exceptions import AttestationVerificationError, ProvenanceVerificationError
1921
from pulp_python.app.provenance import (
2022
Attestation,
2123
Provenance,
@@ -374,7 +376,7 @@ def validate_attestations(self, value):
374376
attestations = TypeAdapter(list[Attestation]).validate_json(value)
375377
else:
376378
attestations = TypeAdapter(list[Attestation]).validate_python(value)
377-
except ValidationError as e:
379+
except PydanticValidationError as e:
378380
raise serializers.ValidationError(_("Invalid attestations: {}".format(e)))
379381
return attestations
380382

@@ -387,9 +389,7 @@ def handle_attestations(self, filename, sha256, attestations, offline=True):
387389
try:
388390
verify_provenance(filename, sha256, provenance, offline=offline)
389391
except AttestationError as e:
390-
raise serializers.ValidationError(
391-
{"attestations": _("Attestations failed verification: {}".format(e))}
392-
)
392+
raise AttestationVerificationError(str(e))
393393
return provenance.model_dump(mode="json")
394394

395395
def deferred_validate(self, data):
@@ -408,26 +408,23 @@ def deferred_validate(self, data):
408408
try:
409409
filename = data["relative_path"]
410410
except KeyError:
411-
raise serializers.ValidationError(detail={"relative_path": _("This field is required")})
411+
raise ValidationError(_("This field is required: relative_path"))
412412

413413
artifact = data["artifact"]
414414
try:
415415
_data = artifact_to_python_content_data(filename, artifact, domain=get_domain())
416416
except ValueError:
417-
raise serializers.ValidationError(
417+
raise ValidationError(
418418
_(
419419
"Extension on {} is not a valid python extension "
420420
"(.whl, .exe, .egg, .tar.gz, .tar.bz2, .zip)"
421421
).format(filename)
422422
)
423423

424424
if data.get("sha256") and data["sha256"] != artifact.sha256:
425-
raise serializers.ValidationError(
426-
detail={
427-
"sha256": _(
428-
"The uploaded artifact's sha256 checksum does not match the one provided"
429-
)
430-
}
425+
raise DigestValidationError(
426+
actual=artifact.sha256,
427+
expected=data["sha256"],
431428
)
432429

433430
data.update(_data)
@@ -641,15 +638,13 @@ def deferred_validate(self, data):
641638
try:
642639
provenance = Provenance.model_validate_json(data["file"].read())
643640
data["provenance"] = provenance.model_dump(mode="json")
644-
except ValidationError as e:
645-
raise serializers.ValidationError(
646-
_("The uploaded provenance is not valid: {}".format(e))
647-
)
641+
except PydanticValidationError as e:
642+
raise ValidationError(_("The uploaded provenance is not valid: {}".format(e)))
648643
if data.pop("verify"):
649644
try:
650645
verify_provenance(data["package"].filename, data["package"].sha256, provenance)
651646
except AttestationError as e:
652-
raise serializers.ValidationError(_("Provenance verification failed: {}".format(e)))
647+
raise ProvenanceVerificationError(str(e))
653648
return data
654649

655650
def retrieve(self, validated_data):

pulp_python/app/tasks/sync.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
Stage,
1818
)
1919

20+
from pulp_python.app.exceptions import UnsupportedProtocolError
2021
from pulp_python.app.models import (
2122
PythonPackageContent,
2223
PythonRemote,
@@ -117,7 +118,8 @@ async def run(self):
117118
url = self.remote.url.rstrip("/")
118119
downloader = self.remote.get_downloader(url=url)
119120
if not isinstance(downloader, HttpDownloader):
120-
raise ValueError("Only HTTP(S) is supported for python syncing")
121+
protocol = type(downloader).__name__
122+
raise UnsupportedProtocolError(protocol)
121123

122124
async with Master(url, allow_non_https=True) as master:
123125
# Replace the session with the remote's downloader session

pulp_python/tests/functional/api/test_attestations.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def test_verify_provenance(python_bindings, twine_package, python_content_factor
6969
with pytest.raises(PulpTaskError) as e:
7070
monitor_task(provenance.task)
7171
assert e.value.task.state == "failed"
72-
assert "twine-6.2.0-py3-none-any.whl != twine-6.2.0.tar.gz" in e.value.task.error["description"]
72+
assert "[PLPY0001]" in e.value.task.error["description"]
7373

7474
# Test creating a provenance without verifying
7575
provenance = python_bindings.ContentProvenanceApi.create(
@@ -239,4 +239,4 @@ def test_bad_attestation_upload(python_bindings, twine_package, monitor_task):
239239
with pytest.raises(PulpTaskError) as e:
240240
monitor_task(task)
241241
assert e.value.task.state == "failed"
242-
assert "Attestations failed verification" in e.value.task.error["description"]
242+
assert "[PLPY0002]" in e.value.task.error["description"]

pulp_python/tests/functional/api/test_crud_content_unit.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def test_content_crud(
112112
with pytest.raises(PulpTaskError) as e:
113113
response = python_bindings.ContentPackagesApi.create(**content_body)
114114
monitor_task(response.task)
115-
msg = "The uploaded artifact's sha256 checksum does not match the one provided"
115+
msg = "[PLP0003]"
116116
assert msg in e.value.task.error["description"]
117117

118118

@@ -241,6 +241,7 @@ def test_disallow_package_substitution(
241241
repository=repo.pulp_href, **content_body2
242242
)
243243
monitor_task(response.task)
244+
assert "[PLPY0003]" in exc.value.task.error["description"]
244245
assert msg1 in exc.value.task.error["description"]
245246
assert msg2 in exc.value.task.error["description"]
246247

@@ -257,6 +258,7 @@ def test_disallow_package_substitution(
257258
body = {"add_content_units": [content2.pulp_href], "base_version": repo.latest_version_href}
258259
with pytest.raises(PulpTaskError) as exc:
259260
monitor_task(python_bindings.RepositoriesPythonApi.modify(repo.pulp_href, body).task)
261+
assert "[PLPY0003]" in exc.value.task.error["description"]
260262
assert msg1 in exc.value.task.error["description"]
261263
assert msg2 in exc.value.task.error["description"]
262264

0 commit comments

Comments
 (0)