Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/pdfrest/models/_internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import re
from collections.abc import Callable, Mapping, Sequence
from pathlib import PurePath
from typing import Annotated, Any, Generic, Literal, TypeVar, cast
from typing import Annotated, Any, Generic, Literal, TypeVar, cast, get_args

from langcodes import tag_is_valid
from pydantic import (
Expand Down Expand Up @@ -49,6 +49,10 @@
from .public import PdfRestFile, PdfRestFileID

PdfConvertColorProfile = PdfPresetColorProfile | Literal["custom"]
PDFA_OUTPUT_TYPES: tuple[PdfAType, ...] = cast(tuple[PdfAType, ...], get_args(PdfAType))
PDFA_OUTPUT_TYPE_MAP: dict[str, PdfAType] = {
output_type.casefold(): output_type for output_type in PDFA_OUTPUT_TYPES
}


def _ensure_list(value: Any) -> Any:
Expand Down Expand Up @@ -188,6 +192,12 @@ def _bool_to_true_false(value: Any) -> Any:
return value


def _normalize_pdfa_output_type(value: Any) -> Any:
if not isinstance(value, str):
return value
return PDFA_OUTPUT_TYPE_MAP.get(value.casefold(), value)


def _serialize_page_ranges(value: list[str | int | tuple[str | int, ...]]) -> str:
def join_tuple(value: str | int | tuple[str | int, ...]) -> str:
if isinstance(value, tuple):
Expand Down Expand Up @@ -1344,7 +1354,11 @@ class PdfToPdfaPayload(BaseModel):
),
PlainSerializer(_serialize_as_first_file_id),
]
output_type: Annotated[PdfAType, Field(serialization_alias="output_type")]
output_type: Annotated[
PdfAType,
Field(serialization_alias="output_type"),
BeforeValidator(_normalize_pdfa_output_type),
Comment thread
datalogics-kam marked this conversation as resolved.
]
output: Annotated[
str | None,
Field(serialization_alias="output", min_length=1, default=None),
Expand Down
4 changes: 3 additions & 1 deletion src/pdfrest/types/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,9 @@ class PdfPemCredentials(TypedDict):
#: [AsyncPdfRestClient.sign_pdf][pdfrest.AsyncPdfRestClient.sign_pdf].
PdfSignatureCredentials = PdfPfxCredentials | PdfPemCredentials

#: PDF/A conformance targets accepted by ``convert_to_pdfa``.
#: Canonical PDF/A conformance targets accepted by ``convert_to_pdfa``.
#: Payload validation accepts case-insensitive string input and normalizes it
#: to one of these literals before serialization.
PdfAType = Literal["PDF/A-1b", "PDF/A-2b", "PDF/A-2u", "PDF/A-3b", "PDF/A-3u"]
#: PDF/X conformance targets accepted by ``convert_to_pdfx``.
PdfXType = Literal["PDF/X-1a", "PDF/X-3", "PDF/X-4", "PDF/X-6"]
Expand Down
49 changes: 46 additions & 3 deletions tests/live/test_live_convert_to_pdfa.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import cast, get_args
from typing import Any, cast, get_args

import pytest

Expand Down Expand Up @@ -101,6 +101,28 @@ def test_live_convert_to_pdfa_with_rasterize_option(
assert str(response.input_id) == str(uploaded_pdf_for_pdfa.id)


def test_live_convert_to_pdfa_accepts_lowercase_output_type(
pdfrest_api_key: str,
pdfrest_live_base_url: str,
uploaded_pdf_for_pdfa: PdfRestFile,
) -> None:
with PdfRestClient(
api_key=pdfrest_api_key,
base_url=pdfrest_live_base_url,
) as client:
response = client.convert_to_pdfa(
uploaded_pdf_for_pdfa,
output_type=cast(Any, "pdf/a-2b"),
output="pdfa-lowercase",
)

assert response.output_files
output_file = response.output_file
assert output_file.name.startswith("pdfa-lowercase")
assert output_file.type == "application/pdf"
assert str(response.input_id) == str(uploaded_pdf_for_pdfa.id)


@pytest.mark.asyncio
async def test_live_async_convert_to_pdfa_with_rasterize_option(
pdfrest_api_key: str,
Expand All @@ -125,12 +147,34 @@ async def test_live_async_convert_to_pdfa_with_rasterize_option(
assert str(response.input_id) == str(uploaded_pdf_for_pdfa.id)


@pytest.mark.asyncio
async def test_live_async_convert_to_pdfa_accepts_lowercase_output_type(
pdfrest_api_key: str,
pdfrest_live_base_url: str,
uploaded_pdf_for_pdfa: PdfRestFile,
) -> None:
async with AsyncPdfRestClient(
api_key=pdfrest_api_key,
base_url=pdfrest_live_base_url,
) as client:
response = await client.convert_to_pdfa(
uploaded_pdf_for_pdfa,
output_type=cast(Any, "pdf/a-2b"),
output="async-pdfa-lowercase",
)

assert response.output_files
output_file = response.output_file
assert output_file.name.startswith("async-pdfa-lowercase")
assert output_file.type == "application/pdf"
assert str(response.input_id) == str(uploaded_pdf_for_pdfa.id)


@pytest.mark.parametrize(
"invalid_output_type",
[
pytest.param("PDF/A-0", id="pdfa-0"),
pytest.param("PDF/A-99", id="pdfa-99"),
pytest.param("pdf/a-2b", id="lowercase"),
],
)
def test_live_convert_to_pdfa_invalid_output_type(
Expand Down Expand Up @@ -159,7 +203,6 @@ def test_live_convert_to_pdfa_invalid_output_type(
[
pytest.param("PDF/A-0", id="pdfa-0"),
pytest.param("PDF/A-99", id="pdfa-99"),
pytest.param("pdf/a-2b", id="lowercase"),
],
)
async def test_live_async_convert_to_pdfa_invalid_output_type(
Expand Down
85 changes: 84 additions & 1 deletion tests/live/test_live_sign_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
)

INVALID_LOGO_OPACITY_VALUES = (
pytest.param(0.0, id="zero"),
pytest.param(-0.1, id="below-min"),
pytest.param(1.1, id="above-max"),
)

Expand Down Expand Up @@ -501,6 +501,7 @@ def test_live_sign_pdf_invalid_logo_opacity(
"signature_configuration": _to_json_string(
{
"type": "new",
"name": "live-invalid-logo-opacity",
"location": make_signature_location(),
"logo_opacity": invalid_logo_opacity,
}
Expand All @@ -509,6 +510,46 @@ def test_live_sign_pdf_invalid_logo_opacity(
)


def test_live_sign_pdf_logo_opacity_zero_is_allowed(
pdfrest_api_key: str,
pdfrest_live_base_url: str,
uploaded_pdf_for_signing: PdfRestFile,
uploaded_pfx_credential: PdfRestFile,
uploaded_passphrase: PdfRestFile,
) -> None:
with PdfRestClient(
api_key=pdfrest_api_key,
base_url=pdfrest_live_base_url,
) as client:
response = client.sign_pdf(
uploaded_pdf_for_signing,
signature_configuration={
"type": "new",
"name": "live-logo-opacity-zero",
"location": make_signature_location(),
},
credentials={
"pfx": uploaded_pfx_credential,
"passphrase": uploaded_passphrase,
},
extra_body={
"signature_configuration": _to_json_string(
{
"type": "new",
"name": "live-logo-opacity-zero",
"location": make_signature_location(),
"logo_opacity": 0.0,
Comment thread
datalogics-kam marked this conversation as resolved.
Outdated
}
)
},
output="live-logo-opacity-zero",
)

assert response.output_file.type == "application/pdf"
assert response.output_file.name == "live-logo-opacity-zero.pdf"
assert str(uploaded_pdf_for_signing.id) in response.input_ids


@pytest.mark.asyncio
async def test_live_async_sign_pdf_invalid_signature_configuration(
pdfrest_api_key: str,
Expand Down Expand Up @@ -601,9 +642,51 @@ async def test_live_async_sign_pdf_invalid_logo_opacity(
"signature_configuration": _to_json_string(
{
"type": "new",
"name": "live-async-invalid-logo-opacity",
"location": make_signature_location(),
"logo_opacity": invalid_logo_opacity,
}
)
},
)


@pytest.mark.asyncio
async def test_live_async_sign_pdf_logo_opacity_zero_is_allowed(
pdfrest_api_key: str,
pdfrest_live_base_url: str,
uploaded_pdf_for_signing: PdfRestFile,
uploaded_pfx_credential: PdfRestFile,
uploaded_passphrase: PdfRestFile,
) -> None:
async with AsyncPdfRestClient(
api_key=pdfrest_api_key,
base_url=pdfrest_live_base_url,
) as client:
response = await client.sign_pdf(
uploaded_pdf_for_signing,
signature_configuration={
"type": "new",
"name": "live-async-logo-opacity-zero",
"location": make_signature_location(),
},
credentials={
"pfx": uploaded_pfx_credential,
"passphrase": uploaded_passphrase,
},
extra_body={
"signature_configuration": _to_json_string(
{
"type": "new",
"name": "live-async-logo-opacity-zero",
"location": make_signature_location(),
"logo_opacity": 0.0,
}
)
},
output="live-async-logo-opacity-zero",
)

assert response.output_file.type == "application/pdf"
assert response.output_file.name == "live-async-logo-opacity-zero.pdf"
assert str(uploaded_pdf_for_signing.id) in response.input_ids
Loading