Skip to content

Commit 14d3da6

Browse files
Add Flatten Annotations
Assisted-by: Codex
1 parent 2147b67 commit 14d3da6

4 files changed

Lines changed: 468 additions & 0 deletions

File tree

src/pdfrest/client.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
GifPdfRestPayload,
8282
JpegPdfRestPayload,
8383
OcrPdfPayload,
84+
PdfFlattenAnnotationsPayload,
8485
PdfFlattenFormsPayload,
8586
PdfFlattenTransparenciesPayload,
8687
PdfInfoPayload,
@@ -2607,6 +2608,32 @@ def linearize_pdf(
26072608
timeout=timeout,
26082609
)
26092610

2611+
def flatten_annotations(
2612+
self,
2613+
file: PdfRestFile | Sequence[PdfRestFile],
2614+
*,
2615+
output: str | None = None,
2616+
extra_query: Query | None = None,
2617+
extra_headers: AnyMapping | None = None,
2618+
extra_body: Body | None = None,
2619+
timeout: TimeoutTypes | None = None,
2620+
) -> PdfRestFileBasedResponse:
2621+
"""Flatten annotations into the PDF content."""
2622+
2623+
payload: dict[str, Any] = {"files": file}
2624+
if output is not None:
2625+
payload["output"] = output
2626+
2627+
return self._post_file_operation(
2628+
endpoint="/flattened-annotations-pdf",
2629+
payload=payload,
2630+
payload_model=PdfFlattenAnnotationsPayload,
2631+
extra_query=extra_query,
2632+
extra_headers=extra_headers,
2633+
extra_body=extra_body,
2634+
timeout=timeout,
2635+
)
2636+
26102637
def rasterize_pdf(
26112638
self,
26122639
file: PdfRestFile | Sequence[PdfRestFile],
@@ -3533,6 +3560,32 @@ async def linearize_pdf(
35333560
timeout=timeout,
35343561
)
35353562

3563+
async def flatten_annotations(
3564+
self,
3565+
file: PdfRestFile | Sequence[PdfRestFile],
3566+
*,
3567+
output: str | None = None,
3568+
extra_query: Query | None = None,
3569+
extra_headers: AnyMapping | None = None,
3570+
extra_body: Body | None = None,
3571+
timeout: TimeoutTypes | None = None,
3572+
) -> PdfRestFileBasedResponse:
3573+
"""Asynchronously flatten annotations into the PDF content."""
3574+
3575+
payload: dict[str, Any] = {"files": file}
3576+
if output is not None:
3577+
payload["output"] = output
3578+
3579+
return await self._post_file_operation(
3580+
endpoint="/flattened-annotations-pdf",
3581+
payload=payload,
3582+
payload_model=PdfFlattenAnnotationsPayload,
3583+
extra_query=extra_query,
3584+
extra_headers=extra_headers,
3585+
extra_body=extra_body,
3586+
timeout=timeout,
3587+
)
3588+
35363589
async def rasterize_pdf(
35373590
self,
35383591
file: PdfRestFile | Sequence[PdfRestFile],

src/pdfrest/models/_internal.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,30 @@ class PdfFlattenTransparenciesPayload(BaseModel):
938938
quality: Literal["low", "medium", "high"] = "medium"
939939

940940

941+
class PdfFlattenAnnotationsPayload(BaseModel):
942+
"""Adapt caller options into a pdfRest-ready flatten-annotations request payload."""
943+
944+
files: Annotated[
945+
list[PdfRestFile],
946+
Field(
947+
min_length=1,
948+
max_length=1,
949+
validation_alias=AliasChoices("file", "files"),
950+
serialization_alias="id",
951+
),
952+
BeforeValidator(_ensure_list),
953+
AfterValidator(
954+
_allowed_mime_types("application/pdf", error_msg="Must be a PDF file")
955+
),
956+
PlainSerializer(_serialize_as_first_file_id),
957+
]
958+
output: Annotated[
959+
str | None,
960+
Field(serialization_alias="output", min_length=1, default=None),
961+
AfterValidator(_validate_output_prefix),
962+
] = None
963+
964+
941965
class BmpPdfRestPayload(BasePdfRestGraphicPayload[Literal["rgb", "gray"]]):
942966
"""Adapt caller options into a pdfRest-ready BMP request payload."""
943967

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from pdfrest import AsyncPdfRestClient, PdfRestApiError, PdfRestClient
6+
from pdfrest.models import PdfRestFile
7+
8+
from ..resources import get_test_resource_path
9+
10+
11+
@pytest.fixture(scope="module")
12+
def uploaded_pdf_for_annotations(
13+
pdfrest_api_key: str,
14+
pdfrest_live_base_url: str,
15+
) -> PdfRestFile:
16+
resource = get_test_resource_path("report.pdf")
17+
with PdfRestClient(
18+
api_key=pdfrest_api_key,
19+
base_url=pdfrest_live_base_url,
20+
) as client:
21+
return client.files.create_from_paths([resource])[0]
22+
23+
24+
@pytest.mark.parametrize(
25+
"output_name",
26+
[
27+
pytest.param(None, id="default-output"),
28+
pytest.param("flatten-annotations", id="custom-output"),
29+
],
30+
)
31+
def test_live_flatten_annotations_success(
32+
pdfrest_api_key: str,
33+
pdfrest_live_base_url: str,
34+
uploaded_pdf_for_annotations: PdfRestFile,
35+
output_name: str | None,
36+
) -> None:
37+
kwargs: dict[str, str] = {}
38+
if output_name is not None:
39+
kwargs["output"] = output_name
40+
41+
with PdfRestClient(
42+
api_key=pdfrest_api_key,
43+
base_url=pdfrest_live_base_url,
44+
) as client:
45+
response = client.flatten_annotations(uploaded_pdf_for_annotations, **kwargs)
46+
47+
assert response.output_files
48+
output_file = response.output_file
49+
assert output_file.type == "application/pdf"
50+
assert str(response.input_id) == str(uploaded_pdf_for_annotations.id)
51+
if output_name is not None:
52+
assert output_file.name.startswith(output_name)
53+
else:
54+
assert output_file.name.endswith(".pdf")
55+
56+
57+
@pytest.mark.asyncio
58+
async def test_live_async_flatten_annotations_success(
59+
pdfrest_api_key: str,
60+
pdfrest_live_base_url: str,
61+
uploaded_pdf_for_annotations: PdfRestFile,
62+
) -> None:
63+
async with AsyncPdfRestClient(
64+
api_key=pdfrest_api_key,
65+
base_url=pdfrest_live_base_url,
66+
) as client:
67+
response = await client.flatten_annotations(
68+
uploaded_pdf_for_annotations, output="async"
69+
)
70+
71+
assert response.output_files
72+
output_file = response.output_file
73+
assert output_file.name.startswith("async")
74+
assert output_file.type == "application/pdf"
75+
assert str(response.input_id) == str(uploaded_pdf_for_annotations.id)
76+
77+
78+
def test_live_flatten_annotations_invalid_file_id(
79+
pdfrest_api_key: str,
80+
pdfrest_live_base_url: str,
81+
uploaded_pdf_for_annotations: PdfRestFile,
82+
) -> None:
83+
with (
84+
PdfRestClient(
85+
api_key=pdfrest_api_key,
86+
base_url=pdfrest_live_base_url,
87+
) as client,
88+
pytest.raises(PdfRestApiError),
89+
):
90+
client.flatten_annotations(
91+
uploaded_pdf_for_annotations,
92+
extra_body={"id": "00000000-0000-0000-0000-000000000000"},
93+
)
94+
95+
96+
@pytest.mark.asyncio
97+
async def test_live_async_flatten_annotations_invalid_file_id(
98+
pdfrest_api_key: str,
99+
pdfrest_live_base_url: str,
100+
uploaded_pdf_for_annotations: PdfRestFile,
101+
) -> None:
102+
async with AsyncPdfRestClient(
103+
api_key=pdfrest_api_key,
104+
base_url=pdfrest_live_base_url,
105+
) as client:
106+
with pytest.raises(PdfRestApiError):
107+
await client.flatten_annotations(
108+
uploaded_pdf_for_annotations,
109+
extra_body={"id": "ffffffff-ffff-ffff-ffff-ffffffffffff"},
110+
)

0 commit comments

Comments
 (0)