Skip to content

Commit 8f1df81

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 a7091dc commit 8f1df81

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
@@ -187,6 +187,11 @@
187187
RETRYABLE_STATUS_CODES = {408, 425, 429, 499}
188188
_SUCCESSFUL_DELETION_MESSAGE = "successfully deleted"
189189
_DEMO_RESTRICTION_MESSAGE_FIELDS = ("message", "warning", "keyMessage")
190+
_DEMO_FALLBACK_FILE_ID = "00000000-0000-4000-8000-000000000000"
191+
_DEMO_FALLBACK_FILE_URL = "https://pdfrest.com/demo-redacted"
192+
_DEMO_FALLBACK_MIME_TYPE = "application/octet-stream"
193+
_DEMO_FALLBACK_FILE_NAME = "demo-redacted.bin"
194+
_DEMO_FALLBACK_FILE_SIZE = 1
190195

191196

192197
HttpMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]
@@ -314,6 +319,23 @@ def _extract_uploaded_file_ids(payload: Any) -> list[str]:
314319
return file_ids
315320

316321

322+
def _is_demo_fallback_file_id(file_id: str) -> bool:
323+
return file_id.strip().lower() == _DEMO_FALLBACK_FILE_ID
324+
325+
326+
def _build_demo_fallback_file(file_id: str) -> PdfRestFile:
327+
return PdfRestFile.model_validate(
328+
{
329+
"id": file_id,
330+
"name": _DEMO_FALLBACK_FILE_NAME,
331+
"url": _DEMO_FALLBACK_FILE_URL,
332+
"type": _DEMO_FALLBACK_MIME_TYPE,
333+
"size": _DEMO_FALLBACK_FILE_SIZE,
334+
"modified": datetime.now(timezone.utc),
335+
}
336+
)
337+
338+
317339
def _handle_deletion_failures(response: PdfRestDeletionResponse) -> None:
318340
failures: list[PdfRestDeleteError] = []
319341
for file_id, result in response.deletion_responses.items():
@@ -1193,7 +1215,17 @@ def fetch_file_info(
11931215
extra_headers=extra_headers,
11941216
timeout=timeout,
11951217
)
1196-
payload = self._send_request(request)
1218+
try:
1219+
payload = self._send_request(request)
1220+
except PdfRestApiError as exc:
1221+
if exc.status_code == 404 and _is_demo_fallback_file_id(file_id):
1222+
self._logger.warning(
1223+
"Demo fallback file id %s was not found during file-info lookup; "
1224+
"returning placeholder metadata.",
1225+
file_id,
1226+
)
1227+
return _build_demo_fallback_file(file_id)
1228+
raise
11971229
return PdfRestFile.model_validate(payload)
11981230

11991231

@@ -1468,7 +1500,17 @@ async def fetch_file_info(
14681500
extra_headers=extra_headers,
14691501
timeout=timeout,
14701502
)
1471-
payload = await self._send_request(request)
1503+
try:
1504+
payload = await self._send_request(request)
1505+
except PdfRestApiError as exc:
1506+
if exc.status_code == 404 and _is_demo_fallback_file_id(file_id):
1507+
self._logger.warning(
1508+
"Demo fallback file id %s was not found during file-info lookup; "
1509+
"returning placeholder metadata.",
1510+
file_id,
1511+
)
1512+
return _build_demo_fallback_file(file_id)
1513+
raise
14721514
return PdfRestFile.model_validate(payload)
14731515

14741516

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)