From 90504662d5919952ea2b6fe172035f72d0426417 Mon Sep 17 00:00:00 2001 From: datalogics-cgreen Date: Mon, 1 Jun 2026 16:03:11 -0500 Subject: [PATCH 1/5] convert_to_pdfa: Normalize PDF/A output types case-insensitively - Normalize PDF/A `output_type` values case-insensitively during payload validation before enforcing the canonical `PdfAType` literals. - Derive the accepted canonical values from `get_args(PdfAType)` instead of maintaining a separate hard-coded list. - Update unit and live PDF/A tests to cover lowercase inputs. Assisted-by: Codex --- src/pdfrest/models/_internal.py | 18 ++++++++- src/pdfrest/types/public.py | 4 +- tests/live/test_live_convert_to_pdfa.py | 49 +++++++++++++++++++++++-- 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/src/pdfrest/models/_internal.py b/src/pdfrest/models/_internal.py index 85bd11fd..60723136 100644 --- a/src/pdfrest/models/_internal.py +++ b/src/pdfrest/models/_internal.py @@ -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 ( @@ -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: @@ -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): @@ -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), + ] output: Annotated[ str | None, Field(serialization_alias="output", min_length=1, default=None), diff --git a/src/pdfrest/types/public.py b/src/pdfrest/types/public.py index 0f15ec2b..b6fca660 100644 --- a/src/pdfrest/types/public.py +++ b/src/pdfrest/types/public.py @@ -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"] diff --git a/tests/live/test_live_convert_to_pdfa.py b/tests/live/test_live_convert_to_pdfa.py index 8b40221d..c15e8f4e 100644 --- a/tests/live/test_live_convert_to_pdfa.py +++ b/tests/live/test_live_convert_to_pdfa.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import cast, get_args +from typing import Any, cast, get_args import pytest @@ -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, @@ -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( @@ -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( From 619a011d9704d91584169a2e4882c8a5eb795d16 Mon Sep 17 00:00:00 2001 From: datalogics-cgreen Date: Mon, 1 Jun 2026 16:45:39 -0500 Subject: [PATCH 2/5] test_live_sign_pdf.py: Cover zero logo opacity separately - Add sync and async live tests proving `logo_opacity=0.0` succeeds. - Keep invalid-opacity cases focused on out-of-range values by supplying required signature names in the overridden payload. Assisted-by: Codex --- tests/live/test_live_sign_pdf.py | 85 +++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/tests/live/test_live_sign_pdf.py b/tests/live/test_live_sign_pdf.py index b852d89b..6b236695 100644 --- a/tests/live/test_live_sign_pdf.py +++ b/tests/live/test_live_sign_pdf.py @@ -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"), ) @@ -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, } @@ -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, + } + ) + }, + 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, @@ -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 From bd807253f8a4de64839a0184b15a2bf037763643 Mon Sep 17 00:00:00 2001 From: datalogics-cgreen Date: Thu, 4 Jun 2026 13:39:48 -0500 Subject: [PATCH 3/5] test_convert_to_pdfa.py: Cover lowercase PDF/A output normalization - Add sync and async MockTransport coverage for lowercase PDF/A output_type inputs in convert_to_pdfa. - Assert the outbound /pdfa request body normalizes those values to canonical PDF/A-2b. - Preserve the intent of the live tests with local unit coverage so request serialization regressions fail before live runs. Assisted-by: Codex --- tests/test_convert_to_pdfa.py | 78 +++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/test_convert_to_pdfa.py b/tests/test_convert_to_pdfa.py index 477ec8fa..a29e308c 100644 --- a/tests/test_convert_to_pdfa.py +++ b/tests/test_convert_to_pdfa.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +from typing import Any, cast import httpx import pytest @@ -210,6 +211,44 @@ def handler(request: httpx.Request) -> httpx.Response: assert timeout_value == pytest.approx(0.33) +def test_convert_to_pdfa_normalizes_lowercase_output_type( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.delenv("PDFREST_API_KEY", raising=False) + input_file = make_pdf_file(PdfRestFileID.generate(1)) + output_id = str(PdfRestFileID.generate()) + + def handler(request: httpx.Request) -> httpx.Response: + if request.method == "POST" and request.url.path == "/pdfa": + payload = json.loads(request.content.decode("utf-8")) + assert payload["output_type"] == "PDF/A-2b" + assert payload["id"] == str(input_file.id) + return httpx.Response( + 200, + json={"inputId": [input_file.id], "outputId": [output_id]}, + ) + if request.method == "GET" and request.url.path == f"/resource/{output_id}": + return httpx.Response( + 200, + json=build_file_info_payload( + output_id, "lowercase.pdf", "application/pdf" + ), + ) + msg = f"Unexpected request {request.method} {request.url}" + raise AssertionError(msg) + + transport = httpx.MockTransport(handler) + with PdfRestClient(api_key=VALID_API_KEY, transport=transport) as client: + response = client.convert_to_pdfa( + input_file, + output_type=cast(Any, "pdf/a-2b"), + ) + + assert isinstance(response, PdfRestFileBasedResponse) + assert response.output_file.name == "lowercase.pdf" + assert str(response.input_id) == str(input_file.id) + + @pytest.mark.asyncio async def test_async_convert_to_pdfa_request_customization( monkeypatch: pytest.MonkeyPatch, @@ -272,6 +311,45 @@ def handler(request: httpx.Request) -> httpx.Response: assert timeout_value == pytest.approx(0.72) +@pytest.mark.asyncio +async def test_async_convert_to_pdfa_normalizes_lowercase_output_type( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.delenv("PDFREST_API_KEY", raising=False) + input_file = make_pdf_file(PdfRestFileID.generate(2)) + output_id = str(PdfRestFileID.generate()) + + def handler(request: httpx.Request) -> httpx.Response: + if request.method == "POST" and request.url.path == "/pdfa": + payload = json.loads(request.content.decode("utf-8")) + assert payload["output_type"] == "PDF/A-2b" + assert payload["id"] == str(input_file.id) + return httpx.Response( + 200, + json={"inputId": [input_file.id], "outputId": [output_id]}, + ) + if request.method == "GET" and request.url.path == f"/resource/{output_id}": + return httpx.Response( + 200, + json=build_file_info_payload( + output_id, "async-lowercase.pdf", "application/pdf" + ), + ) + msg = f"Unexpected request {request.method} {request.url}" + raise AssertionError(msg) + + transport = httpx.MockTransport(handler) + async with AsyncPdfRestClient(api_key=ASYNC_API_KEY, transport=transport) as client: + response = await client.convert_to_pdfa( + input_file, + output_type=cast(Any, "pdf/a-2b"), + ) + + assert isinstance(response, PdfRestFileBasedResponse) + assert response.output_file.name == "async-lowercase.pdf" + assert str(response.input_id) == str(input_file.id) + + def test_convert_to_pdfa_validation(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("PDFREST_API_KEY", raising=False) pdf_file = make_pdf_file(PdfRestFileID.generate(1)) From f072f803a168ef252bc51bc6f3b3cf1fb74ff6e5 Mon Sep 17 00:00:00 2001 From: datalogics-cgreen Date: Thu, 4 Jun 2026 13:45:31 -0500 Subject: [PATCH 4/5] sign_pdf: Allow zero logo opacity - Relax logo_opacity validation from gt=0 to ge=0 so callers can pass 0.0 through the public signature_configuration path. - Update the public signature-configuration type docs to reflect the supported [0, 1] opacity range. - Add sync and async unit coverage for request serialization and update payload bounds tests and live zero-opacity cases to exercise the public argument path instead of extra_body. - Keep zero-opacity support aligned across validation, documentation, unit tests, and live tests. Assisted-by: Codex --- src/pdfrest/models/_internal.py | 2 +- src/pdfrest/types/public.py | 4 +- tests/live/test_live_sign_pdf.py | 22 +------ tests/test_sign_pdf.py | 101 ++++++++++++++++++++++++++++++- 4 files changed, 104 insertions(+), 25 deletions(-) diff --git a/src/pdfrest/models/_internal.py b/src/pdfrest/models/_internal.py index 60723136..50d434e4 100644 --- a/src/pdfrest/models/_internal.py +++ b/src/pdfrest/models/_internal.py @@ -1021,7 +1021,7 @@ class _PdfSignatureDisplayModel(BaseModel): class _PdfSignatureConfigurationModel(BaseModel): type: Literal["new", "existing"] name: str | None = None - logo_opacity: Annotated[float | None, Field(gt=0, le=1, default=None)] = None + logo_opacity: Annotated[float | None, Field(ge=0, le=1, default=None)] = None location: _PdfSignatureLocationModel | None = None display: _PdfSignatureDisplayModel | None = None diff --git a/src/pdfrest/types/public.py b/src/pdfrest/types/public.py index b6fca660..5b7689ea 100644 --- a/src/pdfrest/types/public.py +++ b/src/pdfrest/types/public.py @@ -257,7 +257,7 @@ class PdfNewSignatureConfiguration(TypedDict, total=False): type: Must be ``"new"``. location: Placement rectangle and page as [PdfSignatureLocation][pdfrest.types.PdfSignatureLocation]. name: Optional name for the signature field. - logo_opacity: Optional logo opacity in the range ``(0, 1]``. + logo_opacity: Optional logo opacity in the range ``[0, 1]``. display: Optional visible-signature settings as [PdfSignatureDisplay][pdfrest.types.PdfSignatureDisplay]. """ @@ -275,7 +275,7 @@ class PdfExistingSignatureConfiguration(TypedDict, total=False): type: Must be ``"existing"``. location: Optional placement override as [PdfSignatureLocation][pdfrest.types.PdfSignatureLocation]. name: Optional existing signature field name. - logo_opacity: Optional logo opacity in the range ``(0, 1]``. + logo_opacity: Optional logo opacity in the range ``[0, 1]``. display: Optional visible-signature settings as [PdfSignatureDisplay][pdfrest.types.PdfSignatureDisplay]. """ diff --git a/tests/live/test_live_sign_pdf.py b/tests/live/test_live_sign_pdf.py index 6b236695..80e85866 100644 --- a/tests/live/test_live_sign_pdf.py +++ b/tests/live/test_live_sign_pdf.py @@ -527,21 +527,12 @@ def test_live_sign_pdf_logo_opacity_zero_is_allowed( "type": "new", "name": "live-logo-opacity-zero", "location": make_signature_location(), + "logo_opacity": 0.0, }, 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, - } - ) - }, output="live-logo-opacity-zero", ) @@ -669,21 +660,12 @@ async def test_live_async_sign_pdf_logo_opacity_zero_is_allowed( "type": "new", "name": "live-async-logo-opacity-zero", "location": make_signature_location(), + "logo_opacity": 0.0, }, 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", ) diff --git a/tests/test_sign_pdf.py b/tests/test_sign_pdf.py index 01bbbec0..15a34c6c 100644 --- a/tests/test_sign_pdf.py +++ b/tests/test_sign_pdf.py @@ -773,6 +773,54 @@ def handler(request: httpx.Request) -> httpx.Response: assert seen == {"post": 1, "get": 1} +def test_sign_pdf_allows_zero_logo_opacity_via_public_argument( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.delenv("PDFREST_API_KEY", raising=False) + input_file = make_pdf_file(PdfRestFileID.generate()) + pfx_file = make_pfx_file(str(PdfRestFileID.generate())) + passphrase_file = make_passphrase_file(str(PdfRestFileID.generate())) + output_id = str(PdfRestFileID.generate()) + + def handler(request: httpx.Request) -> httpx.Response: + if request.method == "POST" and request.url.path == "/signed-pdf": + payload = json.loads(request.content.decode("utf-8")) + signature_payload = json.loads(payload["signature_configuration"]) + assert signature_payload["logo_opacity"] == pytest.approx(0.0) + assert signature_payload["type"] == "new" + return httpx.Response( + 200, + json={"inputId": [input_file.id], "outputId": [output_id]}, + ) + if request.method == "GET" and request.url.path == f"/resource/{output_id}": + return httpx.Response( + 200, + json=build_file_info_payload( + output_id, + "logo-opacity-zero.pdf", + "application/pdf", + ), + ) + msg = f"Unexpected request {request.method} {request.url}" + raise AssertionError(msg) + + transport = httpx.MockTransport(handler) + with PdfRestClient(api_key=VALID_API_KEY, transport=transport) as client: + response = client.sign_pdf( + input_file, + signature_configuration={ + "type": "new", + "name": "visible-zero", + "location": make_signature_location(), + "logo_opacity": 0.0, + }, + credentials={"pfx": pfx_file, "passphrase": passphrase_file}, + ) + + assert isinstance(response, PdfRestFileBasedResponse) + assert response.output_file.name == "logo-opacity-zero.pdf" + + @pytest.mark.asyncio async def test_async_sign_pdf_request_customization( monkeypatch: pytest.MonkeyPatch, @@ -849,6 +897,55 @@ def handler(request: httpx.Request) -> httpx.Response: assert seen == {"post": 1, "get": 1} +@pytest.mark.asyncio +async def test_async_sign_pdf_allows_zero_logo_opacity_via_public_argument( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.delenv("PDFREST_API_KEY", raising=False) + input_file = make_pdf_file(PdfRestFileID.generate()) + pfx_file = make_pfx_file(str(PdfRestFileID.generate())) + passphrase_file = make_passphrase_file(str(PdfRestFileID.generate())) + output_id = str(PdfRestFileID.generate()) + + def handler(request: httpx.Request) -> httpx.Response: + if request.method == "POST" and request.url.path == "/signed-pdf": + payload = json.loads(request.content.decode("utf-8")) + signature_payload = json.loads(payload["signature_configuration"]) + assert signature_payload["logo_opacity"] == pytest.approx(0.0) + assert signature_payload["type"] == "new" + return httpx.Response( + 200, + json={"inputId": [input_file.id], "outputId": [output_id]}, + ) + if request.method == "GET" and request.url.path == f"/resource/{output_id}": + return httpx.Response( + 200, + json=build_file_info_payload( + output_id, + "async-logo-opacity-zero.pdf", + "application/pdf", + ), + ) + msg = f"Unexpected request {request.method} {request.url}" + raise AssertionError(msg) + + transport = httpx.MockTransport(handler) + async with AsyncPdfRestClient(api_key=ASYNC_API_KEY, transport=transport) as client: + response = await client.sign_pdf( + input_file, + signature_configuration={ + "type": "new", + "name": "async-visible-zero", + "location": make_signature_location(), + "logo_opacity": 0.0, + }, + credentials={"pfx": pfx_file, "passphrase": passphrase_file}, + ) + + assert isinstance(response, PdfRestFileBasedResponse) + assert response.output_file.name == "async-logo-opacity-zero.pdf" + + def test_sign_payload_requires_location_when_type_new() -> None: input_file = make_pdf_file(PdfRestFileID.generate()) pfx_file = make_pfx_file(str(PdfRestFileID.generate())) @@ -988,6 +1085,7 @@ def test_sign_payload_accepts_logo_tuple_sequence() -> None: @pytest.mark.parametrize( "logo_opacity", [ + pytest.param(0.0, id="zero"), pytest.param(0.01, id="min"), pytest.param(1.0, id="max"), ], @@ -1016,7 +1114,6 @@ def test_sign_payload_accepts_logo_opacity_bounds(logo_opacity: float) -> None: @pytest.mark.parametrize( "invalid_logo_opacity", [ - pytest.param(0.0, id="zero"), pytest.param(-0.01, id="below-min"), pytest.param(1.01, id="above-max"), ], @@ -1030,7 +1127,7 @@ def test_sign_payload_rejects_logo_opacity_out_of_bounds( with pytest.raises( ValidationError, - match=r"greater than 0|less than or equal to 1", + match=r"greater than or equal to 0|less than or equal to 1", ): PdfSignPayload.model_validate( { From a05964aba231adddcc8f15f233c51c21735024fd Mon Sep 17 00:00:00 2001 From: datalogics-cgreen Date: Tue, 9 Jun 2026 10:18:29 -0500 Subject: [PATCH 5/5] pyproject: Bump release to `1.0.4` --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f9ee93ff..c744ad1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pdfrest" -version = "1.0.3" +version = "1.0.4" description = "Python client library for interacting with the pdfRest API" readme = {file = "README.md", content-type = "text/markdown"} authors = [ diff --git a/uv.lock b/uv.lock index ad017ec5..78e59879 100644 --- a/uv.lock +++ b/uv.lock @@ -961,7 +961,7 @@ wheels = [ [[package]] name = "pdfrest" -version = "1.0.3" +version = "1.0.4" source = { editable = "." } dependencies = [ { name = "exceptiongroup" },