Skip to content

Commit 4bead24

Browse files
client: Add demo fallback handling for 404 file-info lookups
- Introduced `_is_demo_fallback_file_id` and `_build_demo_fallback_file` to detect and return placeholder metadata for missing demo fallback files. - Updated `file_info` and `async_file_info` methods to return fallback data when a 404 error is encountered with a demo fallback file ID. - Improved logging to notify users when fallback data is being returned. tests: Add coverage for demo fallback file handling - Added tests to validate sync and async handling of demo fallback files. - Verified proper logging when returning placeholder metadata for 404 cases. Assisted-by: Codex
1 parent d55e163 commit 4bead24

2 files changed

Lines changed: 135 additions & 2 deletions

File tree

src/pdfrest/client.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,11 @@
193193
RETRYABLE_STATUS_CODES = {408, 425, 429, 499}
194194
_SUCCESSFUL_DELETION_MESSAGE = "successfully deleted"
195195
_DEMO_RESTRICTION_MESSAGE_FIELDS = ("message", "warning", "keyMessage")
196+
_DEMO_FALLBACK_FILE_ID = "00000000-0000-4000-8000-000000000000"
197+
_DEMO_FALLBACK_FILE_URL = "https://pdfrest.com/demo-redacted"
198+
_DEMO_FALLBACK_MIME_TYPE = "application/octet-stream"
199+
_DEMO_FALLBACK_FILE_NAME = "demo-redacted.bin"
200+
_DEMO_FALLBACK_FILE_SIZE = 1
196201

197202

198203
HttpMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]
@@ -320,6 +325,23 @@ def _extract_uploaded_file_ids(payload: Any) -> list[str]:
320325
return file_ids
321326

322327

328+
def _is_demo_fallback_file_id(file_id: str) -> bool:
329+
return file_id.strip().lower() == _DEMO_FALLBACK_FILE_ID
330+
331+
332+
def _build_demo_fallback_file(file_id: str) -> PdfRestFile:
333+
return PdfRestFile.model_validate(
334+
{
335+
"id": file_id,
336+
"name": _DEMO_FALLBACK_FILE_NAME,
337+
"url": _DEMO_FALLBACK_FILE_URL,
338+
"type": _DEMO_FALLBACK_MIME_TYPE,
339+
"size": _DEMO_FALLBACK_FILE_SIZE,
340+
"modified": datetime.now(timezone.utc),
341+
}
342+
)
343+
344+
323345
def _handle_deletion_failures(response: PdfRestDeletionResponse) -> None:
324346
failures: list[PdfRestDeleteError] = []
325347
for file_id, result in response.deletion_responses.items():
@@ -1198,7 +1220,17 @@ def fetch_file_info(
11981220
extra_headers=extra_headers,
11991221
timeout=timeout,
12001222
)
1201-
payload = self._send_request(request)
1223+
try:
1224+
payload = self._send_request(request)
1225+
except PdfRestApiError as exc:
1226+
if exc.status_code == 404 and _is_demo_fallback_file_id(file_id):
1227+
self._logger.warning(
1228+
"Demo fallback file id %s was not found during file-info lookup; "
1229+
"returning placeholder metadata.",
1230+
file_id,
1231+
)
1232+
return _build_demo_fallback_file(file_id)
1233+
raise
12021234
return PdfRestFile.model_validate(payload)
12031235

12041236

@@ -1473,7 +1505,17 @@ async def fetch_file_info(
14731505
extra_headers=extra_headers,
14741506
timeout=timeout,
14751507
)
1476-
payload = await self._send_request(request)
1508+
try:
1509+
payload = await self._send_request(request)
1510+
except PdfRestApiError as exc:
1511+
if exc.status_code == 404 and _is_demo_fallback_file_id(file_id):
1512+
self._logger.warning(
1513+
"Demo fallback file id %s was not found during file-info lookup; "
1514+
"returning placeholder metadata.",
1515+
file_id,
1516+
)
1517+
return _build_demo_fallback_file(file_id)
1518+
raise
14771519
return PdfRestFile.model_validate(payload)
14781520

14791521

tests/test_unzip_file.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,3 +357,94 @@ def handler(request: httpx.Request) -> httpx.Response:
357357
"Demo value XXXXXXXXX-XXXXXXXXX-XXXX-XXXXXXXXXXXX detected in id; "
358358
"replaced with 00000000-0000-4000-8000-000000000000" in caplog.text
359359
)
360+
361+
362+
def test_unzip_file_demo_fallback_file_info_on_404(
363+
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
364+
) -> None:
365+
monkeypatch.delenv("PDFREST_API_KEY", raising=False)
366+
caplog.set_level(logging.WARNING, logger="pdfrest.client")
367+
zip_file = make_zip_file(str(PdfRestFileID.generate()))
368+
369+
def handler(request: httpx.Request) -> httpx.Response:
370+
if request.method == "POST" and request.url.path == "/unzip":
371+
return httpx.Response(
372+
200,
373+
json={
374+
"inputId": [zip_file.id],
375+
"files": [
376+
{
377+
"name": "inner.txt",
378+
"id": DEMO_REPLACEMENT_ID,
379+
"outputUrl": None,
380+
}
381+
],
382+
},
383+
)
384+
if (
385+
request.method == "GET"
386+
and request.url.path == f"/resource/{DEMO_REPLACEMENT_ID}"
387+
):
388+
return httpx.Response(404, json={"error": "The file does not exist."})
389+
msg = f"Unexpected request {request.method} {request.url}"
390+
raise AssertionError(msg)
391+
392+
transport = httpx.MockTransport(handler)
393+
with PdfRestClient(api_key=VALID_API_KEY, transport=transport) as client:
394+
response = client.unzip_file(zip_file)
395+
396+
assert response.output_file.id == DEMO_REPLACEMENT_ID
397+
assert response.output_file.name == "demo-redacted.bin"
398+
assert str(response.output_file.url) == "https://pdfrest.com/demo-redacted"
399+
assert response.output_file.type == "application/octet-stream"
400+
assert response.output_file.size == 1
401+
assert (
402+
"Demo fallback file id 00000000-0000-4000-8000-000000000000 was not found "
403+
"during file-info lookup; returning placeholder metadata." in caplog.text
404+
)
405+
406+
407+
@pytest.mark.asyncio
408+
async def test_async_unzip_file_demo_fallback_file_info_on_404(
409+
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
410+
) -> None:
411+
monkeypatch.delenv("PDFREST_API_KEY", raising=False)
412+
caplog.set_level(logging.WARNING, logger="pdfrest.client")
413+
zip_file = make_zip_file(str(PdfRestFileID.generate()))
414+
415+
def handler(request: httpx.Request) -> httpx.Response:
416+
if request.method == "POST" and request.url.path == "/unzip":
417+
return httpx.Response(
418+
200,
419+
json={
420+
"inputId": [zip_file.id],
421+
"files": [
422+
{
423+
"name": "inner-async.txt",
424+
"id": DEMO_REPLACEMENT_ID,
425+
"outputUrl": None,
426+
}
427+
],
428+
},
429+
)
430+
if (
431+
request.method == "GET"
432+
and request.url.path == f"/resource/{DEMO_REPLACEMENT_ID}"
433+
):
434+
return httpx.Response(404, json={"error": "The file does not exist."})
435+
msg = f"Unexpected request {request.method} {request.url}"
436+
raise AssertionError(msg)
437+
438+
transport = httpx.MockTransport(handler)
439+
async with AsyncPdfRestClient(api_key=ASYNC_API_KEY, transport=transport) as client:
440+
response = await client.unzip_file(zip_file)
441+
442+
assert response.output_file.id == DEMO_REPLACEMENT_ID
443+
assert response.output_file.name == "demo-redacted.bin"
444+
assert str(response.output_file.url) == "https://pdfrest.com/demo-redacted"
445+
assert response.output_file.type == "application/octet-stream"
446+
assert response.output_file.size == 1
447+
assert (
448+
"Demo fallback file id 00000000-0000-4000-8000-000000000000 was not found "
449+
"during file-info lookup; returning placeholder metadata." in caplog.text
450+
)

0 commit comments

Comments
 (0)