Skip to content

Commit 40c4983

Browse files
convert_to_pdfa: Accept case-insensitive PDF/A output_type values
- Add a caller-facing `PdfAOutputType` alias and normalize values before payload validation so lowercase inputs map to canonical API literals. - Update unit and live PDF/A tests to cover lowercase sync and async inputs. Assisted-by: Codex
1 parent 1e395c8 commit 40c4983

6 files changed

Lines changed: 78 additions & 9 deletions

File tree

src/pdfrest/client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@
145145
JpegColorModel,
146146
OcrLanguage,
147147
PdfAddTextObject,
148-
PdfAType,
148+
PdfAOutputType,
149149
PdfConversionCompression,
150150
PdfConversionDownsample,
151151
PdfConversionLocale,
@@ -5289,7 +5289,7 @@ def convert_to_pdfa(
52895289
self,
52905290
file: PdfRestFile | Sequence[PdfRestFile],
52915291
*,
5292-
output_type: PdfAType,
5292+
output_type: PdfAOutputType,
52935293
output: str | None = None,
52945294
rasterize_if_errors_encountered: bool = False,
52955295
extra_query: Query | None = None,
@@ -8355,7 +8355,7 @@ async def convert_to_pdfa(
83558355
self,
83568356
file: PdfRestFile | Sequence[PdfRestFile],
83578357
*,
8358-
output_type: PdfAType,
8358+
output_type: PdfAOutputType,
83598359
output: str | None = None,
83608360
rasterize_if_errors_encountered: bool = False,
83618361
extra_query: Query | None = None,

src/pdfrest/models/_internal.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@
4949
from .public import PdfRestFile, PdfRestFileID
5050

5151
PdfConvertColorProfile = PdfPresetColorProfile | Literal["custom"]
52+
PDFA_OUTPUT_TYPES: tuple[PdfAType, ...] = (
53+
"PDF/A-1b",
54+
"PDF/A-2b",
55+
"PDF/A-2u",
56+
"PDF/A-3b",
57+
"PDF/A-3u",
58+
)
59+
PDFA_OUTPUT_TYPE_MAP: dict[str, PdfAType] = {
60+
output_type.casefold(): output_type for output_type in PDFA_OUTPUT_TYPES
61+
}
5262

5363

5464
def _ensure_list(value: Any) -> Any:
@@ -188,6 +198,12 @@ def _bool_to_true_false(value: Any) -> Any:
188198
return value
189199

190200

201+
def _normalize_pdfa_output_type(value: Any) -> Any:
202+
if not isinstance(value, str):
203+
return value
204+
return PDFA_OUTPUT_TYPE_MAP.get(value.casefold(), value)
205+
206+
191207
def _serialize_page_ranges(value: list[str | int | tuple[str | int, ...]]) -> str:
192208
def join_tuple(value: str | int | tuple[str | int, ...]) -> str:
193209
if isinstance(value, tuple):
@@ -1344,7 +1360,11 @@ class PdfToPdfaPayload(BaseModel):
13441360
),
13451361
PlainSerializer(_serialize_as_first_file_id),
13461362
]
1347-
output_type: Annotated[PdfAType, Field(serialization_alias="output_type")]
1363+
output_type: Annotated[
1364+
PdfAType,
1365+
Field(serialization_alias="output_type"),
1366+
BeforeValidator(_normalize_pdfa_output_type),
1367+
]
13481368
output: Annotated[
13491369
str | None,
13501370
Field(serialization_alias="output", min_length=1, default=None),

src/pdfrest/types/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
JpegColorModel,
1818
OcrLanguage,
1919
PdfAddTextObject,
20+
PdfAOutputType,
2021
PdfAType,
2122
PdfCMYKColor,
2223
PdfColorProfile,
@@ -69,6 +70,7 @@
6970
"HtmlWebLayout",
7071
"JpegColorModel",
7172
"OcrLanguage",
73+
"PdfAOutputType",
7274
"PdfAType",
7375
"PdfAddTextObject",
7476
"PdfCMYKColor",

src/pdfrest/types/public.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"HtmlWebLayout",
2929
"JpegColorModel",
3030
"OcrLanguage",
31+
"PdfAOutputType",
3132
"PdfAType",
3233
"PdfAddTextObject",
3334
"PdfCMYKColor",
@@ -323,8 +324,11 @@ class PdfPemCredentials(TypedDict):
323324
#: [AsyncPdfRestClient.sign_pdf][pdfrest.AsyncPdfRestClient.sign_pdf].
324325
PdfSignatureCredentials = PdfPfxCredentials | PdfPemCredentials
325326

326-
#: PDF/A conformance targets accepted by ``convert_to_pdfa``.
327+
#: Canonical PDF/A conformance targets accepted by ``convert_to_pdfa``.
327328
PdfAType = Literal["PDF/A-1b", "PDF/A-2b", "PDF/A-2u", "PDF/A-3b", "PDF/A-3u"]
329+
#: Caller-facing ``convert_to_pdfa`` input type. Values are matched
330+
#: case-insensitively and normalized to [PdfAType][pdfrest.types.PdfAType].
331+
PdfAOutputType = PdfAType | str
328332
#: PDF/X conformance targets accepted by ``convert_to_pdfx``.
329333
PdfXType = Literal["PDF/X-1a", "PDF/X-3", "PDF/X-4", "PDF/X-6"]
330334
#: Granularity modes for extracted full text payloads.

tests/live/test_live_convert_to_pdfa.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,28 @@ def test_live_convert_to_pdfa_with_rasterize_option(
101101
assert str(response.input_id) == str(uploaded_pdf_for_pdfa.id)
102102

103103

104+
def test_live_convert_to_pdfa_accepts_lowercase_output_type(
105+
pdfrest_api_key: str,
106+
pdfrest_live_base_url: str,
107+
uploaded_pdf_for_pdfa: PdfRestFile,
108+
) -> None:
109+
with PdfRestClient(
110+
api_key=pdfrest_api_key,
111+
base_url=pdfrest_live_base_url,
112+
) as client:
113+
response = client.convert_to_pdfa(
114+
uploaded_pdf_for_pdfa,
115+
output_type="pdf/a-2b",
116+
output="pdfa-lowercase",
117+
)
118+
119+
assert response.output_files
120+
output_file = response.output_file
121+
assert output_file.name.startswith("pdfa-lowercase")
122+
assert output_file.type == "application/pdf"
123+
assert str(response.input_id) == str(uploaded_pdf_for_pdfa.id)
124+
125+
104126
@pytest.mark.asyncio
105127
async def test_live_async_convert_to_pdfa_with_rasterize_option(
106128
pdfrest_api_key: str,
@@ -125,12 +147,34 @@ async def test_live_async_convert_to_pdfa_with_rasterize_option(
125147
assert str(response.input_id) == str(uploaded_pdf_for_pdfa.id)
126148

127149

150+
@pytest.mark.asyncio
151+
async def test_live_async_convert_to_pdfa_accepts_lowercase_output_type(
152+
pdfrest_api_key: str,
153+
pdfrest_live_base_url: str,
154+
uploaded_pdf_for_pdfa: PdfRestFile,
155+
) -> None:
156+
async with AsyncPdfRestClient(
157+
api_key=pdfrest_api_key,
158+
base_url=pdfrest_live_base_url,
159+
) as client:
160+
response = await client.convert_to_pdfa(
161+
uploaded_pdf_for_pdfa,
162+
output_type="pdf/a-2b",
163+
output="async-pdfa-lowercase",
164+
)
165+
166+
assert response.output_files
167+
output_file = response.output_file
168+
assert output_file.name.startswith("async-pdfa-lowercase")
169+
assert output_file.type == "application/pdf"
170+
assert str(response.input_id) == str(uploaded_pdf_for_pdfa.id)
171+
172+
128173
@pytest.mark.parametrize(
129174
"invalid_output_type",
130175
[
131176
pytest.param("PDF/A-0", id="pdfa-0"),
132177
pytest.param("PDF/A-99", id="pdfa-99"),
133-
pytest.param("pdf/a-2b", id="lowercase"),
134178
],
135179
)
136180
def test_live_convert_to_pdfa_invalid_output_type(
@@ -159,7 +203,6 @@ def test_live_convert_to_pdfa_invalid_output_type(
159203
[
160204
pytest.param("PDF/A-0", id="pdfa-0"),
161205
pytest.param("PDF/A-99", id="pdfa-99"),
162-
pytest.param("pdf/a-2b", id="lowercase"),
163206
],
164207
)
165208
async def test_live_async_convert_to_pdfa_invalid_output_type(

tests/test_convert_to_pdfa.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def handler(request: httpx.Request) -> httpx.Response:
189189
with PdfRestClient(api_key=VALID_API_KEY, transport=transport) as client:
190190
response = client.convert_to_pdfa(
191191
input_file,
192-
output_type="PDF/A-3b",
192+
output_type="pdf/a-3b",
193193
output="custom",
194194
rasterize_if_errors_encountered="on",
195195
extra_query={"trace": "true"},
@@ -251,7 +251,7 @@ def handler(request: httpx.Request) -> httpx.Response:
251251
async with AsyncPdfRestClient(api_key=ASYNC_API_KEY, transport=transport) as client:
252252
response = await client.convert_to_pdfa(
253253
input_file,
254-
output_type="PDF/A-2u",
254+
output_type="pdf/a-2u",
255255
output="async-custom",
256256
rasterize_if_errors_encountered="off",
257257
extra_query={"trace": "async"},

0 commit comments

Comments
 (0)