Skip to content

Commit b92af05

Browse files
Merge pull request #16 from datalogics-cgreen/pdfcloud-5464-flatten-layers
PDFCLOUD-5551 Add client methods to flatten layers in PDF
2 parents 0ab9fdd + e53036a commit b92af05

4 files changed

Lines changed: 458 additions & 0 deletions

File tree

src/pdfrest/client.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
PdfEncryptPayload,
9191
PdfFlattenAnnotationsPayload,
9292
PdfFlattenFormsPayload,
93+
PdfFlattenLayersPayload,
9394
PdfFlattenTransparenciesPayload,
9495
PdfInfoPayload,
9596
PdfLinearizePayload,
@@ -3149,6 +3150,32 @@ def flatten_annotations(
31493150
timeout=timeout,
31503151
)
31513152

3153+
def flatten_layers(
3154+
self,
3155+
file: PdfRestFile | Sequence[PdfRestFile],
3156+
*,
3157+
output: str | None = None,
3158+
extra_query: Query | None = None,
3159+
extra_headers: AnyMapping | None = None,
3160+
extra_body: Body | None = None,
3161+
timeout: TimeoutTypes | None = None,
3162+
) -> PdfRestFileBasedResponse:
3163+
"""Flatten all layers in a PDF into a single layer."""
3164+
3165+
payload: dict[str, Any] = {"files": file}
3166+
if output is not None:
3167+
payload["output"] = output
3168+
3169+
return self._post_file_operation(
3170+
endpoint="/flattened-layers-pdf",
3171+
payload=payload,
3172+
payload_model=PdfFlattenLayersPayload,
3173+
extra_query=extra_query,
3174+
extra_headers=extra_headers,
3175+
extra_body=extra_body,
3176+
timeout=timeout,
3177+
)
3178+
31523179
def rasterize_pdf(
31533180
self,
31543181
file: PdfRestFile | Sequence[PdfRestFile],
@@ -4529,6 +4556,32 @@ async def flatten_annotations(
45294556
timeout=timeout,
45304557
)
45314558

4559+
async def flatten_layers(
4560+
self,
4561+
file: PdfRestFile | Sequence[PdfRestFile],
4562+
*,
4563+
output: str | None = None,
4564+
extra_query: Query | None = None,
4565+
extra_headers: AnyMapping | None = None,
4566+
extra_body: Body | None = None,
4567+
timeout: TimeoutTypes | None = None,
4568+
) -> PdfRestFileBasedResponse:
4569+
"""Asynchronously flatten all layers in a PDF."""
4570+
4571+
payload: dict[str, Any] = {"files": file}
4572+
if output is not None:
4573+
payload["output"] = output
4574+
4575+
return await self._post_file_operation(
4576+
endpoint="/flattened-layers-pdf",
4577+
payload=payload,
4578+
payload_model=PdfFlattenLayersPayload,
4579+
extra_query=extra_query,
4580+
extra_headers=extra_headers,
4581+
extra_body=extra_body,
4582+
timeout=timeout,
4583+
)
4584+
45324585
async def rasterize_pdf(
45334586
self,
45344587
file: PdfRestFile | Sequence[PdfRestFile],

src/pdfrest/models/_internal.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,30 @@ class PdfFlattenAnnotationsPayload(BaseModel):
13011301
] = None
13021302

13031303

1304+
class PdfFlattenLayersPayload(BaseModel):
1305+
"""Adapt caller options into a pdfRest-ready flatten-layers request payload."""
1306+
1307+
files: Annotated[
1308+
list[PdfRestFile],
1309+
Field(
1310+
min_length=1,
1311+
max_length=1,
1312+
validation_alias=AliasChoices("file", "files"),
1313+
serialization_alias="id",
1314+
),
1315+
BeforeValidator(_ensure_list),
1316+
AfterValidator(
1317+
_allowed_mime_types("application/pdf", error_msg="Must be a PDF file")
1318+
),
1319+
PlainSerializer(_serialize_as_first_file_id),
1320+
]
1321+
output: Annotated[
1322+
str | None,
1323+
Field(serialization_alias="output", min_length=1, default=None),
1324+
AfterValidator(_validate_output_prefix),
1325+
] = None
1326+
1327+
13041328
class PdfAddAttachmentPayload(BaseModel):
13051329
"""Adapt caller options into a pdfRest-ready add-attachment request payload."""
13061330

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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_layers(
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-layers", id="custom-output"),
29+
],
30+
)
31+
def test_live_flatten_layers_success(
32+
pdfrest_api_key: str,
33+
pdfrest_live_base_url: str,
34+
uploaded_pdf_for_layers: 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_layers(uploaded_pdf_for_layers, **kwargs)
46+
47+
assert response.output_files
48+
output_file = response.output_file
49+
assert output_file.type == "application/pdf"
50+
assert output_file.size > 0
51+
assert response.warning is None
52+
assert str(response.input_id) == str(uploaded_pdf_for_layers.id)
53+
if output_name is not None:
54+
assert output_file.name.startswith(output_name)
55+
else:
56+
assert output_file.name.endswith(".pdf")
57+
58+
59+
@pytest.mark.asyncio
60+
async def test_live_async_flatten_layers_success(
61+
pdfrest_api_key: str,
62+
pdfrest_live_base_url: str,
63+
uploaded_pdf_for_layers: PdfRestFile,
64+
) -> None:
65+
async with AsyncPdfRestClient(
66+
api_key=pdfrest_api_key,
67+
base_url=pdfrest_live_base_url,
68+
) as client:
69+
response = await client.flatten_layers(uploaded_pdf_for_layers, output="async")
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 output_file.size > 0
76+
assert response.warning is None
77+
assert str(response.input_id) == str(uploaded_pdf_for_layers.id)
78+
79+
80+
def test_live_flatten_layers_invalid_file_id(
81+
pdfrest_api_key: str,
82+
pdfrest_live_base_url: str,
83+
uploaded_pdf_for_layers: PdfRestFile,
84+
) -> None:
85+
with (
86+
PdfRestClient(
87+
api_key=pdfrest_api_key,
88+
base_url=pdfrest_live_base_url,
89+
) as client,
90+
pytest.raises(PdfRestApiError, match=r"(?i)(id|file)"),
91+
):
92+
client.flatten_layers(
93+
uploaded_pdf_for_layers,
94+
extra_body={"id": "00000000-0000-0000-0000-000000000000"},
95+
)
96+
97+
98+
@pytest.mark.asyncio
99+
async def test_live_async_flatten_layers_invalid_file_id(
100+
pdfrest_api_key: str,
101+
pdfrest_live_base_url: str,
102+
uploaded_pdf_for_layers: PdfRestFile,
103+
) -> None:
104+
async with AsyncPdfRestClient(
105+
api_key=pdfrest_api_key,
106+
base_url=pdfrest_live_base_url,
107+
) as client:
108+
with pytest.raises(PdfRestApiError, match=r"(?i)(id|file)"):
109+
await client.flatten_layers(
110+
uploaded_pdf_for_layers,
111+
extra_body={"id": "ffffffff-ffff-ffff-ffff-ffffffffffff"},
112+
)

0 commit comments

Comments
 (0)