Skip to content

Commit 2dab9a5

Browse files
Refactor page range validation and serialization
- Use Pydantic models to decompose and validate page ranges rather than longer bespoke parsing code. - Simplified page range handling by removing `_require_positive_page` and `_validate_page_range_entry` in favor of new `AscendingPageRange` and related validators. - Replaced `PageRangeEntry` with `AscendingPageRange` in `BasePdfRestGraphicPayload`. - Enhanced serialization logic with `_serialize_page_ranges`. - Updated supporting type definitions and removed redundant code.
1 parent a71b0ce commit 2dab9a5

1 file changed

Lines changed: 35 additions & 47 deletions

File tree

src/pdfrest/models/_internal.py

Lines changed: 35 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -75,50 +75,6 @@ def _validate_output_prefix(value: str | None) -> str | None:
7575
return value
7676

7777

78-
def _require_positive_page(
79-
text: str, *, description: str, require_page_word: bool = False
80-
) -> str:
81-
if not text.isdigit() or int(text) < 1:
82-
message = (
83-
f"{description} must be a page number greater than or equal to 1."
84-
if require_page_word
85-
else f"{description} must be greater than or equal to 1."
86-
)
87-
raise ValueError(message)
88-
return text
89-
90-
91-
def _validate_page_range_entry(value: str) -> str:
92-
"""Normalize and validate a single page range entry."""
93-
if not isinstance(value, str):
94-
msg = "Each page range entry must be a string."
95-
raise TypeError(msg)
96-
entry = value.strip()
97-
if entry == "":
98-
msg = "Each page range entry must be a non-empty string."
99-
raise ValueError(msg)
100-
if entry == "last":
101-
return entry
102-
if entry.isdigit():
103-
return _require_positive_page(entry, description="Page numbers")
104-
if "-" in entry:
105-
start_raw, end_raw = (part.strip() for part in entry.split("-", maxsplit=1))
106-
start = _require_positive_page(
107-
start_raw, description="Page range start", require_page_word=True
108-
)
109-
if end_raw == "last":
110-
return f"{start}-last"
111-
end = _require_positive_page(
112-
end_raw, description="Page range end", require_page_word=True
113-
)
114-
if int(end) < int(start):
115-
msg = "Page range end must be greater than or equal to the start."
116-
raise ValueError(msg)
117-
return f"{start}-{end}"
118-
msg = "Page range entries must be positive integers, 'last', or a range like '1-3' or '6-last'."
119-
raise ValueError(msg)
120-
121-
12278
def _split_comma_list(value: Any) -> Any:
12379
if isinstance(value, str):
12480
return value.split(",")
@@ -157,7 +113,13 @@ def _serialize_as_comma_separated_string(value: list[Any] | None) -> str | None:
157113
return ",".join(str(element) for element in value)
158114

159115

160-
PageRangeEntry = Annotated[str, AfterValidator(_validate_page_range_entry)]
116+
def _serialize_page_ranges(value: list[str | int | tuple[str | int, ...]]) -> str:
117+
def join_tuple(value: str | int | tuple[str | int, ...]) -> str:
118+
if isinstance(value, tuple):
119+
return "-".join(str(e) for e in value)
120+
return str(value)
121+
122+
return ",".join(join_tuple(v) for v in value)
161123

162124

163125
def _serialize_redactions(value: list[_PdfRedactionVariant]) -> str:
@@ -204,6 +166,32 @@ class UploadURLs(BaseModel):
204166

205167
ColorModelT = TypeVar("ColorModelT", bound=str)
206168

169+
PageNumber = Annotated[int, Field(ge=1), PlainSerializer(lambda x: str(x))]
170+
171+
172+
def _split_page_range_tuple(x: str) -> tuple[str, str]:
173+
start, end = x.split("-", maxsplit=1)
174+
return start, end
175+
176+
177+
def _ascending_page_range(
178+
range: tuple[int, int | Literal["last"]],
179+
) -> tuple[int, int | Literal["last"]]:
180+
start, end = range
181+
if end != "last" and int(start) > int(end):
182+
msg = "The start page must be less than or equal to the end page."
183+
raise ValueError(msg)
184+
return range
185+
186+
187+
_AscendingPageRangeTuple = Annotated[
188+
tuple[PageNumber, PageNumber] | tuple[PageNumber, Literal["last"]],
189+
BeforeValidator(_split_page_range_tuple),
190+
AfterValidator(_ascending_page_range),
191+
]
192+
193+
AscendingPageRange = PageNumber | Literal["last"] | _AscendingPageRangeTuple
194+
207195

208196
class BasePdfRestGraphicPayload(BaseModel, Generic[ColorModelT]):
209197
files: Annotated[
@@ -226,12 +214,12 @@ class BasePdfRestGraphicPayload(BaseModel, Generic[ColorModelT]):
226214
AfterValidator(_validate_output_prefix),
227215
]
228216
page_range: Annotated[
229-
list[PageRangeEntry] | None,
217+
list[AscendingPageRange] | None,
230218
Field(serialization_alias="pages", min_length=1, default=None),
231219
BeforeValidator(_ensure_list),
232220
BeforeValidator(_split_comma_list),
233221
BeforeValidator(_int_to_string),
234-
PlainSerializer(_serialize_as_comma_separated_string),
222+
PlainSerializer(_serialize_page_ranges),
235223
]
236224
resolution: Annotated[int, Field(ge=12, le=2400, default=300)]
237225
color_model: Annotated[ColorModelT, Field(default=...)]

0 commit comments

Comments
 (0)