Skip to content

Commit 411b1e2

Browse files
delete: Raise exceptions for failures
- Raises PdfRestDeleteError when a file can be deleted. - Since there can be more than one file deleted at a time, raises the error inside a PdfRestErrorGroup. Assisted-by: Codex
1 parent 3f896e8 commit 411b1e2

3 files changed

Lines changed: 59 additions & 5 deletions

File tree

src/pdfrest/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
PdfRestApiError,
88
PdfRestAuthenticationError,
99
PdfRestConfigurationError,
10+
PdfRestDeleteError,
1011
PdfRestError,
12+
PdfRestErrorGroup,
1113
PdfRestRequestError,
1214
PdfRestTimeoutError,
1315
PdfRestTransportError,
@@ -21,7 +23,9 @@
2123
"PdfRestAuthenticationError",
2224
"PdfRestClient",
2325
"PdfRestConfigurationError",
26+
"PdfRestDeleteError",
2427
"PdfRestError",
28+
"PdfRestErrorGroup",
2529
"PdfRestRequestError",
2630
"PdfRestTimeoutError",
2731
"PdfRestTransportError",

src/pdfrest/client.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@
5050
PdfRestAuthenticationError,
5151
PdfRestConfigurationError,
5252
PdfRestConnectTimeoutError,
53+
PdfRestDeleteError,
5354
PdfRestError,
55+
PdfRestErrorGroup,
5456
PdfRestPoolTimeoutError,
5557
PdfRestRequestError,
5658
PdfRestTimeoutError,
@@ -111,6 +113,7 @@
111113
MAX_BACKOFF_SECONDS = 8.0
112114
BACKOFF_JITTER_SECONDS = 0.1
113115
RETRYABLE_STATUS_CODES = {408, 425, 429, 499}
116+
_SUCCESSFUL_DELETION_MESSAGE = "successfully deleted"
114117

115118

116119
HttpMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]
@@ -227,6 +230,20 @@ def _extract_uploaded_file_ids(payload: Any) -> list[str]:
227230
return file_ids
228231

229232

233+
def _handle_deletion_failures(response: PdfRestDeletionResponse) -> None:
234+
failures: list[PdfRestDeleteError] = []
235+
for file_id, result in response.deletion_responses.items():
236+
normalized_result = result.strip().lower()
237+
if normalized_result != _SUCCESSFUL_DELETION_MESSAGE:
238+
failures.append(PdfRestDeleteError(file_id, result))
239+
if failures:
240+
msg = "Failed to delete one or more files."
241+
raise PdfRestErrorGroup(
242+
msg,
243+
failures,
244+
)
245+
246+
230247
def _normalize_headers(headers: Mapping[str, str]) -> Mapping[str, str]:
231248
return {str(key): str(value) for key, value in headers.items()}
232249

@@ -1553,7 +1570,7 @@ def delete(
15531570
extra_headers: AnyMapping | None = None,
15541571
extra_body: Body | None = None,
15551572
timeout: TimeoutTypes | None = None,
1556-
) -> PdfRestDeletionResponse:
1573+
) -> None:
15571574
"""Delete one or more uploaded files by reference."""
15581575

15591576
payload = DeletePayload.model_validate({"files": files})
@@ -1569,7 +1586,9 @@ def delete(
15691586
timeout=timeout,
15701587
)
15711588
raw_payload = self._client.send_request(request)
1572-
return PdfRestDeletionResponse.model_validate(raw_payload)
1589+
deletion_response = PdfRestDeletionResponse.model_validate(raw_payload)
1590+
_handle_deletion_failures(deletion_response)
1591+
return
15731592

15741593
def read_bytes(
15751594
self,
@@ -1853,7 +1872,7 @@ async def delete(
18531872
extra_headers: AnyMapping | None = None,
18541873
extra_body: Body | None = None,
18551874
timeout: TimeoutTypes | None = None,
1856-
) -> PdfRestDeletionResponse:
1875+
) -> None:
18571876
"""Delete one or more uploaded files by reference."""
18581877

18591878
payload = DeletePayload.model_validate({"files": files})
@@ -1869,7 +1888,9 @@ async def delete(
18691888
timeout=timeout,
18701889
)
18711890
raw_payload = await self._client.send_request(request)
1872-
return PdfRestDeletionResponse.model_validate(raw_payload)
1891+
deletion_response = PdfRestDeletionResponse.model_validate(raw_payload)
1892+
_handle_deletion_failures(deletion_response)
1893+
return
18731894

18741895
async def read_bytes(
18751896
self,

src/pdfrest/exceptions.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,32 @@
22

33
from __future__ import annotations
44

5-
from typing import Any
5+
from collections.abc import Sequence
6+
from typing import TYPE_CHECKING, Any
67

78
import httpx
89
from typing_extensions import override
910

11+
if TYPE_CHECKING: # pragma: no cover
12+
from .models import PdfRestFileID
13+
1014
__all__ = (
1115
"PdfRestApiError",
1216
"PdfRestAuthenticationError",
1317
"PdfRestConfigurationError",
1418
"PdfRestConnectTimeoutError",
19+
"PdfRestDeleteError",
1520
"PdfRestError",
21+
"PdfRestErrorGroup",
1622
"PdfRestPoolTimeoutError",
1723
"PdfRestRequestError",
1824
"PdfRestTimeoutError",
1925
"PdfRestTransportError",
2026
"translate_httpx_error",
2127
)
2228

29+
from exceptiongroup import ExceptionGroup
30+
2331

2432
class PdfRestError(Exception):
2533
"""Base exception for all pdfrest client errors."""
@@ -78,6 +86,27 @@ class PdfRestAuthenticationError(PdfRestApiError):
7886
"""Raised when authentication with the pdfRest API fails."""
7987

8088

89+
class PdfRestDeleteError(PdfRestError):
90+
"""Raised when an individual file cannot be deleted."""
91+
92+
def __init__(self, file_id: PdfRestFileID | str, message: str) -> None:
93+
self.file_id = str(file_id)
94+
self.detail = message
95+
super().__init__(f"Failed to delete file {self.file_id}: {message}")
96+
97+
98+
class PdfRestErrorGroup(ExceptionGroup):
99+
"""Group of PdfRestError exceptions produced by the PDF REST library."""
100+
101+
def __init__(self, message: str, exceptions: Sequence[Exception], /) -> None:
102+
# enforce that everything inside is from your library
103+
for e in exceptions:
104+
if not isinstance(e, PdfRestError):
105+
msg = f"PdfRestErrorGroup may only contain PdfRestError instances, got {type(e)}"
106+
raise TypeError(msg)
107+
super().__init__(message, list(exceptions))
108+
109+
81110
def translate_httpx_error(exc: httpx.HTTPError) -> PdfRestError:
82111
"""Convert an httpx exception into a library-specific exception."""
83112

0 commit comments

Comments
 (0)