{"record":{"id":"e78018f842e101fb","repo":"docling-project/docling","slug":"invalid-page-range-start-must-be-1-and-end-must","errorCode":null,"errorMessage":"Invalid page range: start must be ≥ 1 and end must be ≥ start.","messagePattern":"Invalid page range: start must be ≥ 1 and end must be ≥ start\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"docling/datamodel/settings.py","lineNumber":12,"sourceCode":"import sys\nfrom contextlib import contextmanager\nfrom pathlib import Path\nfrom typing import Annotated, Iterator, Optional, Tuple\n\nfrom pydantic import AfterValidator, BaseModel\nfrom pydantic_settings import BaseSettings, SettingsConfigDict\n\n\ndef _validate_page_range(v: Tuple[int, int]) -> Tuple[int, int]:\n    if v[0] < 1 or v[1] < v[0]:\n        raise ValueError(\n            \"Invalid page range: start must be ≥ 1 and end must be ≥ start.\"\n        )\n    return v\n\n\nPageRange = Annotated[Tuple[int, int], AfterValidator(_validate_page_range)]\n\nDEFAULT_PAGE_RANGE: PageRange = (1, sys.maxsize)\n\n\nclass DocumentLimits(BaseModel):\n    max_num_pages: int = sys.maxsize\n    max_file_size: int = sys.maxsize\n    page_range: PageRange = DEFAULT_PAGE_RANGE\n\n\nclass BatchConcurrencySettings(BaseModel):\n    doc_batch_size: int = 1  # Number of documents processed in one batch. Should be >= doc_batch_concurrency","sourceCodeStart":1,"sourceCodeEnd":30,"githubUrl":"https://github.com/docling-project/docling/blob/61d76f1ff3f8428065465889f7b4577da7df704c/docling/datamodel/settings.py#L1-L30","documentation":"The PageRange type is a Tuple[int, int] with an AfterValidator that enforces a 1-based, ordered range: the start must be >= 1 and the end must be >= the start. It is used for settings like page_range when converting documents, and the default is (1, sys.maxsize). Any out-of-order or zero/negative bound raises this ValueError at settings construction.","triggerScenarios":"Passing page_range=(0, 10), page_range=(5, 3), or page_range=(-1, 5) to DocumentConverter.convert / convert_all, or setting a page-range-typed field on a settings object. Tuple order matters: it is (start, end), not (end, start).","commonSituations":"Treating page ranges as 0-based (coming from Python slice conventions) and passing start=0; swapping the tuple elements so start > end; computing end dynamically (e.g. num_pages - 2) and going below start on very short documents.","solutions":["Use 1-based inclusive bounds with start <= end, e.g. page_range=(3, 10).","Clamp computed ranges: page_range=(max(1, start), max(start, end)).","For 'up to the last page', pass the default (1, sys.maxsize) or omit page_range."],"exampleFix":"# before\nresult = converter.convert(pdf, page_range=(0, 5))  # 0 is invalid\n\n# after\nresult = converter.convert(pdf, page_range=(1, 5))","handlingStrategy":"validation","validationCode":"def valid_page_range(r: tuple[int, int]) -> bool:\n    start, end = r\n    return start >= 1 and end >= start\n\nassert valid_page_range((start, end)), \"page_range must be 1-based with start <= end\"","typeGuard":"def is_valid_page_range(r: tuple[int, int]) -> bool:\n    s, e = r\n    return isinstance(s, int) and isinstance(e, int) and 1 <= s <= e","tryCatchPattern":"try:\n    converter.convert(doc, page_range=(s, e))\nexcept ValidationError as e:\n    if \"Invalid page range\" in str(e):\n        s2, e2 = max(1, s), max(max(1, s), e)\n        result = converter.convert(doc, page_range=(s2, e2))\n    else:\n        raise","preventionTips":["Remember docling page ranges are 1-based inclusive, unlike Python slices.","Clamp dynamically computed ranges, especially end = total - k on short documents."],"tags":["validation","page-range","pdf","settings","off-by-one"],"backgroundTag":null,"analyzedSha":"61d76f1ff3f8428065465889f7b4577da7df704c","analyzedAt":"2026-08-14T23:53:18.727Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}