From 47e581be6504ca4da6f3f8e550f235b796d8353a Mon Sep 17 00:00:00 2001 From: Georg Heiler Date: Sun, 4 Oct 2026 09:46:05 +0200 Subject: [PATCH] chore(types): mark method overrides explicitly Signed-off-by: Georg Heiler --- docling/backend/asciidoc_backend.py | 6 ++++++ docling/backend/csv_backend.py | 6 ++++++ docling/backend/docling_parse_backend.py | 21 +++++++++++++++++++ docling/backend/docx/latex/omml.py | 4 ++++ docling/backend/email_backend.py | 5 +++++ docling/backend/image_backend.py | 16 ++++++++++++++ docling/backend/iwork/keynote_iwa.py | 2 ++ docling/backend/latex/backend.py | 6 ++++++ docling/backend/latex/engines/tectonic.py | 3 +++ docling/backend/managed_pdfium_backend.py | 4 ++++ docling/backend/md_backend.py | 5 +++++ docling/backend/mets_gbs_backend.py | 16 ++++++++++++++ docling/backend/mspowerpoint_backend.py | 1 + docling/backend/noop_backend.py | 5 +++++ docling/backend/pdf_backend.py | 4 ++++ docling/backend/pypdfium2_backend.py | 17 +++++++++++++++ docling/backend/xml/uspto_backend.py | 2 ++ docling/datamodel/base_models.py | 3 +++ docling/datamodel/chart_extraction_options.py | 6 +++++- docling/datamodel/document.py | 5 +++++ docling/datamodel/pipeline_options.py | 3 ++- docling/document_converter.py | 5 ++++- .../models/table_crops_layout_model.py | 3 +++ .../pipeline/threaded_layout_vlm_pipeline.py | 7 +++++++ docling/models/base_layout_model.py | 3 +++ .../base_layout_postprocessing_model.py | 4 ++++ docling/models/base_model.py | 5 ++++- docling/models/base_ocr_model.py | 3 +++ docling/models/base_table_model.py | 4 ++++ .../nuextract_transformers_model.py | 2 ++ .../transformers_extraction_model.py | 2 ++ .../api_kserve_v2_engine.py | 3 +++ .../image_classification/base.py | 2 ++ .../onnxruntime_engine.py | 3 +++ .../transformers_engine.py | 3 +++ .../object_detection/api_kserve_v2_engine.py | 3 +++ .../object_detection/base.py | 2 ++ .../object_detection/onnxruntime_engine.py | 3 +++ .../object_detection/transformers_engine.py | 3 +++ .../vlm/api_openai_compatible_engine.py | 4 ++++ .../vlm/auto_inline_engine.py | 5 +++++ docling/models/inference_engines/vlm/base.py | 2 ++ .../inference_engines/vlm/mlx_engine.py | 5 +++++ .../vlm/transformers_engine.py | 4 ++++ .../inference_engines/vlm/vllm_engine.py | 5 +++++ .../models/picture_description_base_model.py | 4 ++++ .../models/postprocessing/reading_order_rb.py | 2 ++ .../stages/chart_extraction/granite_vision.py | 3 +++ .../stages/code_formula/code_formula_model.py | 3 +++ .../code_formula/code_formula_vlm_model.py | 3 +++ docling/models/stages/layout/layout_model.py | 3 +++ .../layout/layout_object_detection_model.py | 3 +++ .../layout/layout_postprocessing_model.py | 3 +++ .../models/stages/ocr/_nemotron_ocr_model.py | 5 +++++ docling/models/stages/ocr/auto_ocr_model.py | 4 ++++ docling/models/stages/ocr/easyocr_model.py | 5 +++++ .../models/stages/ocr/kserve_v2_ocr_model.py | 3 +++ .../models/stages/ocr/nemotron_ocr_model.py | 5 +++++ docling/models/stages/ocr/ocr_mac_model.py | 5 +++++ docling/models/stages/ocr/rapid_ocr_model.py | 6 ++++++ .../stages/ocr/tesseract_ocr_cli_model.py | 5 +++++ .../models/stages/ocr/tesseract_ocr_model.py | 5 +++++ .../page_assemble/page_assemble_model.py | 2 ++ .../page_preprocessing_model.py | 2 ++ .../document_picture_classifier.py | 3 +++ .../picture_description_api_model.py | 3 +++ .../picture_description_vlm_engine_model.py | 3 +++ .../picture_description_vlm_model.py | 3 +++ .../table_structure/table_structure_model.py | 3 +++ .../table_structure_model_granite_vision.py | 3 +++ .../table_structure_model_v2.py | 3 +++ .../stages/vlm_convert/vlm_convert_model.py | 2 ++ docling/models/utils/generation_utils.py | 5 +++++ docling/models/utils/hf_stopping_criteria.py | 2 ++ .../vlm_pipeline_models/api_vlm_model.py | 3 +++ .../hf_transformers_model.py | 3 +++ .../models/vlm_pipeline_models/mlx_model.py | 3 +++ .../models/vlm_pipeline_models/vllm_model.py | 3 +++ docling/pipeline/asr_pipeline.py | 5 +++++ docling/pipeline/asr_transcriber.py | 2 ++ docling/pipeline/base_pipeline.py | 5 +++++ docling/pipeline/extraction_vlm_pipeline.py | 4 ++++ docling/pipeline/native_pdf_pipeline.py | 7 +++++++ docling/pipeline/simple_pipeline.py | 6 ++++++ docling/pipeline/standard_pdf_pipeline.py | 9 ++++++++ docling/pipeline/video_pipeline.py | 5 +++++ docling/pipeline/vlm_pipeline.py | 5 +++++ docling/service_client/exceptions.py | 5 +++++ docling/utils/chandra_utils.py | 5 +++++ docling/utils/nemotron_parse_utils.py | 4 ++++ docling/utils/ocr_language.py | 2 ++ 91 files changed, 398 insertions(+), 4 deletions(-) diff --git a/docling/backend/asciidoc_backend.py b/docling/backend/asciidoc_backend.py index 56862f7c36..8d362b42e6 100644 --- a/docling/backend/asciidoc_backend.py +++ b/docling/backend/asciidoc_backend.py @@ -23,6 +23,7 @@ TableData, TextItem, ) +from typing_extensions import override from docling.backend.abstract_backend import DeclarativeDocumentBackend from docling.backend.utils.image_resource_loader import ImageResourceLoader @@ -84,20 +85,25 @@ def __init__( ) from e return + @override def is_valid(self) -> bool: return self.valid @classmethod + @override def supports_pagination(cls) -> bool: return False + @override def unload(self): return @classmethod + @override def supported_formats(cls) -> set[InputFormat]: return {InputFormat.ASCIIDOC} + @override def convert(self) -> DoclingDocument: """ Parses the ASCII into a structured document model. diff --git a/docling/backend/csv_backend.py b/docling/backend/csv_backend.py index b7ab0ba490..ccb9e862cb 100644 --- a/docling/backend/csv_backend.py +++ b/docling/backend/csv_backend.py @@ -10,6 +10,7 @@ from typing import Final, Optional, Set, Union from docling_core.types.doc import DoclingDocument, DocumentOrigin, TableCell, TableData +from typing_extensions import override from docling.backend.abstract_backend import DeclarativeDocumentBackend from docling.datamodel.backend_options import CsvBackendOptions @@ -76,22 +77,27 @@ def __init__( ) from e return + @override def is_valid(self) -> bool: return self.valid @classmethod + @override def supports_pagination(cls) -> bool: return False + @override def unload(self): if isinstance(self.path_or_stream, BytesIO): self.path_or_stream.close() self.path_or_stream = None @classmethod + @override def supported_formats(cls) -> Set[InputFormat]: return {InputFormat.CSV} + @override def convert(self) -> DoclingDocument: """Parse the CSV content into a DoclingDocument. diff --git a/docling/backend/docling_parse_backend.py b/docling/backend/docling_parse_backend.py index 129fbc6618..729efb761d 100644 --- a/docling/backend/docling_parse_backend.py +++ b/docling/backend/docling_parse_backend.py @@ -17,6 +17,7 @@ TextCell, ) from PIL import Image +from typing_extensions import override from docling.backend.pdf_backend import PdfDocumentBackend, PdfPageBackend from docling.datamodel.accelerator_options import AcceleratorOptions @@ -130,15 +131,19 @@ def __init__(self, result: PageParseResult, rendered: bool = True): self._seg_page: Optional[SegmentedPdfPage] = None @property + @override def page_no(self) -> int: return self._result.page_number + @override def is_valid(self) -> bool: return self._result.success + @override def get_error_message(self) -> str: return self._result.error_message + @override def get_text_in_rect(self, bbox: BoundingBox) -> str: segmented_page = self.get_segmented_page() if segmented_page is None: @@ -155,6 +160,7 @@ def get_text_in_rect(self, bbox: BoundingBox) -> str: return text_piece + @override def get_segmented_page(self) -> Optional[SegmentedPdfPage]: if not self.is_valid(): return None @@ -170,18 +176,21 @@ def get_segmented_page(self) -> Optional[SegmentedPdfPage]: self._seg_page = seg_page return self._seg_page + @override def get_text_cells(self) -> Iterable[TextCell]: segmented_page = self.get_segmented_page() if segmented_page is None: return [] return segmented_page.textline_cells + @override def get_visible_text_cells(self) -> Optional[list[TextCell]]: segmented_page = self.get_segmented_page() if segmented_page is None: return [] return _visible_text_cells(segmented_page.textline_cells) + @override def get_bitmap_rects(self, scale: float = 1) -> Iterable[BoundingBox]: segmented_page = self.get_segmented_page() if segmented_page is None: @@ -197,6 +206,7 @@ def get_bitmap_rects(self, scale: float = 1) -> Iterable[BoundingBox]: cropboxes.append(cropbox.scaled(scale=scale)) return cropboxes + @override def has_content_in( self, *, @@ -211,6 +221,7 @@ def has_content_in( bbox=bbox, chars=chars, shapes=shapes, bitmaps=bitmaps ) + @override def get_shape_lines( self, *, @@ -229,6 +240,7 @@ def get_shape_lines( ) ] + @override def get_connected_shape_bounding_boxes( self, *, tolerance: float = 0.0 ) -> Optional[list[BoundingBox]]: @@ -243,6 +255,7 @@ def get_connected_shape_bounding_boxes( ) ] + @override def get_page_image( self, scale: float = 1, cropbox: Optional[BoundingBox] = None ) -> Image.Image: @@ -253,9 +266,11 @@ def get_page_image( ) return self._result.get_image(scale=scale, cropbox=cropbox).convert("RGB") + @override def get_size(self) -> Size: return Size(width=self._result.page_width, height=self._result.page_height) + @override def unload(self) -> None: return None @@ -344,29 +359,35 @@ def __init__( f"docling-parse could not load document {self.document_hash}." ) from e + @override def is_valid(self) -> bool: return not self._closed and self.page_count() > 0 + @override def page_count(self) -> int: return self.parser.page_count(self.doc_key) + @override def get_document_outline(self) -> list[_PdfOutlineItem]: """Extract the outline from the threaded parser's document annotations.""" annotations = self.parser.get_annotations(self.doc_key) toc = annotations.table_of_contents if annotations is not None else None return extract_outline_from_docling_parse(toc) + @override def load_page(self, page_no: int) -> PdfPageBackend: raise NotImplementedError( "ThreadedDoclingParseDocumentBackend only supports iter_pages()." ) + @override def iter_pages(self) -> Iterator[ThreadedDoclingParsePageBackend]: self._iterating = True for result in self.parser.iterate_results(): yield ThreadedDoclingParsePageBackend(result, rendered=self._render_pages) self._iterating = False + @override def unload(self) -> None: if self._closed: return diff --git a/docling/backend/docx/latex/omml.py b/docling/backend/docx/latex/omml.py index 049cac8e41..4fdad3d0a3 100644 --- a/docling/backend/docx/latex/omml.py +++ b/docling/backend/docx/latex/omml.py @@ -18,6 +18,7 @@ import lxml.etree as ET from lxml.etree import _Element +from typing_extensions import override try: # pragma: no cover - import-time guard from pylatexenc.latexencode import UnicodeToLatexEncoder @@ -243,6 +244,7 @@ def __init__(self, elm: _Element): self.__innerdict = {} self.text = self.process_children(elm) + @override def __str__(self) -> str: """Return string representation.""" return self.text @@ -318,6 +320,7 @@ def __init__(self, element: _Element): ) self._latex = self.process_children(element) + @override def __str__(self) -> str: """Return LaTeX string with normalized spacing.""" return self.latex.replace(" ", " ") @@ -326,6 +329,7 @@ def __unicode__(self) -> str: """Return unicode representation.""" return self.__str__() + @override def process_unknow(self, elm: _Element, stag: str) -> Any | None: """Handle unknown element types. diff --git a/docling/backend/email_backend.py b/docling/backend/email_backend.py index 9e5ea2e0c0..0a1577a5a1 100644 --- a/docling/backend/email_backend.py +++ b/docling/backend/email_backend.py @@ -12,6 +12,7 @@ from pathlib import Path from docling_core.types.doc import DocItemLabel, DoclingDocument, DocumentOrigin +from typing_extensions import override from docling.backend.abstract_backend import DeclarativeDocumentBackend from docling.backend.html_backend import HTMLDocumentBackend @@ -200,14 +201,17 @@ def _msg_to_rfc822_bytes(data: bytes) -> bytes: return email_message.as_bytes() + @override def is_valid(self) -> bool: return self.valid @classmethod + @override def supports_pagination(cls) -> bool: return False @classmethod + @override def supported_formats(cls) -> set[InputFormat]: return {InputFormat.EMAIL} @@ -316,6 +320,7 @@ def _get_attachment_labels(self) -> list[str]: labels.append(f"{filename} ({content_type})" if content_type else filename) return labels + @override def convert(self) -> DoclingDocument: if not self.is_valid() or self.mail is None: raise RuntimeError( diff --git a/docling/backend/image_backend.py b/docling/backend/image_backend.py index 0deb1866d0..7308939738 100644 --- a/docling/backend/image_backend.py +++ b/docling/backend/image_backend.py @@ -17,6 +17,7 @@ TextCell, ) from PIL import Image, ImageOps +from typing_extensions import override from docling.backend.abstract_backend import AbstractDocumentBackend from docling.backend.pdf_backend import PdfDocumentBackend, PdfPageBackend @@ -93,16 +94,20 @@ def __init__( self.valid: bool = self._image is not None @property + @override def page_no(self) -> int: return self._page_no + 1 + @override def is_valid(self) -> bool: return self.valid + @override def get_text_in_rect(self, bbox: BoundingBox) -> str: # No text extraction from raw images without OCR return "" + @override def get_segmented_page(self) -> SegmentedPdfPage: # Return empty segmented page with proper dimensions for raw images assert self._image is not None @@ -134,10 +139,12 @@ def get_segmented_page(self) -> SegmentedPdfPage: has_lines=False, ) + @override def get_text_cells(self) -> Iterable[TextCell]: # No text cells on raw images return [] + @override def get_bitmap_rects(self, scale: float = 1) -> Iterable[BoundingBox]: # For raw images, the entire page is a bitmap assert self._image is not None @@ -153,6 +160,7 @@ def get_bitmap_rects(self, scale: float = 1) -> Iterable[BoundingBox]: full_page_bbox = full_page_bbox.scaled(scale=scale) yield full_page_bbox + @override def get_page_image( self, scale: float = 1, cropbox: BoundingBox | None = None ) -> Image.Image: @@ -194,6 +202,7 @@ def get_page_image( box=source_box, ) + @override def get_size(self) -> Size: assert self._image is not None return Size( @@ -201,6 +210,7 @@ def get_size(self) -> Size: height=self._image.height * _POINTS_PER_INCH / self._dpi[1], ) + @override def unload(self): # Help GC and free memory self._image = None @@ -263,12 +273,15 @@ def __init__( f"Could not load image for document {self.file}" ) from e + @override def is_valid(self) -> bool: return len(self._frames) > 0 + @override def page_count(self) -> int: return len(self._frames) + @override def load_page(self, page_no: int) -> _ImagePageBackend: if not (0 <= page_no < len(self._frames)): raise IndexError(f"Page index out of range: {page_no}") @@ -277,14 +290,17 @@ def load_page(self, page_no: int) -> _ImagePageBackend: ) @classmethod + @override def supported_formats(cls) -> set[InputFormat]: # Only IMAGE here; PDF handling remains in PDF-oriented backends return {InputFormat.IMAGE} @classmethod + @override def supports_pagination(cls) -> bool: return True + @override def unload(self): for frame in self._frames: frame.close() diff --git a/docling/backend/iwork/keynote_iwa.py b/docling/backend/iwork/keynote_iwa.py index a2345ebab2..675a0b17fa 100644 --- a/docling/backend/iwork/keynote_iwa.py +++ b/docling/backend/iwork/keynote_iwa.py @@ -19,6 +19,7 @@ import zipfile from docling_core.types.doc import DocItemLabel +from typing_extensions import override from docling.backend.iwork.archives import ( SHAPE_STORAGE_FIELD, @@ -348,6 +349,7 @@ def _blocks( for block in self._drawable_blocks(identifier) ], [] + @override def _drawable_blocks(self, identifier: int) -> list[Block]: """Read a drawable, reading a chart here and deferring anything else. diff --git a/docling/backend/latex/backend.py b/docling/backend/latex/backend.py index 2fedeaaee6..830a9a1aab 100644 --- a/docling/backend/latex/backend.py +++ b/docling/backend/latex/backend.py @@ -10,6 +10,7 @@ from docling_core.types.doc import DocItemLabel, DoclingDocument, NodeItem from docling_core.types.doc.document import Formatting +from typing_extensions import override from docling.backend.abstract_backend import DeclarativeDocumentBackend from docling.backend.latex.handlers.environments import EnvironmentHandlerMixin @@ -81,6 +82,7 @@ def __init__( self._tikz_futures: list[concurrent.futures.Future] = [] self.latex_text = decode_latex_content(self.path_or_stream) + @override def is_valid(self) -> bool: text = self.latex_text.strip() if not text: @@ -88,10 +90,12 @@ def is_valid(self) -> bool: return True @classmethod + @override def supports_pagination(cls) -> bool: return False @classmethod + @override def supported_formats(cls) -> set[InputFormat]: return {InputFormat.LATEX} @@ -150,6 +154,7 @@ def _do_parse_and_process(self, doc: DoclingDocument) -> DoclingDocument: return doc + @override def convert(self) -> DoclingDocument: doc = DoclingDocument(name=self.file.stem) opts = cast(LatexBackendOptions, self.options) @@ -191,6 +196,7 @@ def _worker_fn(): return doc + @override def _process_nodes( self, nodes, diff --git a/docling/backend/latex/engines/tectonic.py b/docling/backend/latex/engines/tectonic.py index bbaa2c3853..4b4216b611 100644 --- a/docling/backend/latex/engines/tectonic.py +++ b/docling/backend/latex/engines/tectonic.py @@ -12,6 +12,7 @@ from docling_core.types.doc.document import ImageRef from PIL import Image, ImageChops +from typing_extensions import override from docling.backend.latex.engines.base import RenderEngine from docling.backend.latex.utils.encoding import decode_latex_content @@ -88,6 +89,7 @@ def __init__( self._is_available = False self.install() + @override def is_available(self) -> bool: return self._is_available @@ -283,6 +285,7 @@ def _build_command(self, tex_file: Path) -> list[str]: cmd.append(str(tex_file)) return cmd + @override def render( self, tikz_code: str, preamble: str = "", source_root: Path | None = None ) -> ImageRef | None: diff --git a/docling/backend/managed_pdfium_backend.py b/docling/backend/managed_pdfium_backend.py index 7b95f8dbe1..5cc6acb109 100644 --- a/docling/backend/managed_pdfium_backend.py +++ b/docling/backend/managed_pdfium_backend.py @@ -8,6 +8,8 @@ from pathlib import Path from typing import TYPE_CHECKING, Optional, Union +from typing_extensions import override + from docling.backend.pdf_backend import PdfDocumentBackend, PdfPageBackend from docling.datamodel.backend_options import PdfBackendOptions @@ -33,6 +35,7 @@ def __init__( def _close_native_document(self) -> None: pass + @override def unload(self) -> None: if self._closed: return @@ -51,6 +54,7 @@ def __init__(self) -> None: def _close_native_page(self) -> None: pass + @override def unload(self) -> None: if self._closed: return diff --git a/docling/backend/md_backend.py b/docling/backend/md_backend.py index 06101307dc..c148d62c76 100644 --- a/docling/backend/md_backend.py +++ b/docling/backend/md_backend.py @@ -922,22 +922,27 @@ def _load_image_ref(self, dest: str) -> Optional[ImageRef]: ) return self._get_image_loader().load_image_ref(dest, base_path) + @override def is_valid(self) -> bool: return self.valid + @override def unload(self): if isinstance(self.path_or_stream, BytesIO): self.path_or_stream.close() self.path_or_stream = None @classmethod + @override def supports_pagination(cls) -> bool: return False @classmethod + @override def supported_formats(cls) -> set[InputFormat]: return {InputFormat.MD} + @override def convert(self) -> DoclingDocument: _log.debug("converting Markdown...") diff --git a/docling/backend/mets_gbs_backend.py b/docling/backend/mets_gbs_backend.py index 5b793b5e43..bbf8a1b1c8 100644 --- a/docling/backend/mets_gbs_backend.py +++ b/docling/backend/mets_gbs_backend.py @@ -23,6 +23,7 @@ from lxml import etree from PIL import Image from PIL.Image import Image as PILImage +from typing_extensions import override from docling.backend.pdf_backend import PdfDocumentBackend, PdfPageBackend from docling.datamodel.backend_options import MetsGbsBackendOptions @@ -68,9 +69,11 @@ def __init__( self.valid = parsed_page is not None and page_im is not None @property + @override def page_no(self) -> int: return self._page_no + 1 + @override def is_valid(self) -> bool: return self.valid @@ -102,6 +105,7 @@ def _require_image(self) -> PILImage: ) return self._im + @override def get_text_in_rect(self, bbox: BoundingBox) -> str: dpage = self._require_page() # Find intersecting cells on the page @@ -128,12 +132,15 @@ def get_text_in_rect(self, bbox: BoundingBox) -> str: return text_piece + @override def get_segmented_page(self) -> SegmentedPdfPage | None: return self._dpage + @override def get_text_cells(self) -> Iterable[TextCell]: return self._require_page().textline_cells + @override def get_bitmap_rects(self, scale: float = 1) -> Iterable[BoundingBox]: dpage = self._require_page() AREA_THRESHOLD = 0 # 32 * 32 @@ -150,6 +157,7 @@ def get_bitmap_rects(self, scale: float = 1) -> Iterable[BoundingBox]: yield cropbox + @override def get_page_image( self, scale: float = 1, cropbox: BoundingBox | None = None ) -> Image.Image: @@ -175,10 +183,12 @@ def get_page_image( ).crop(cropbox.scaled(scale=scale).as_tuple()) return image + @override def get_size(self) -> Size: dpage = self._require_page() return Size(width=dpage.dimension.width, height=dpage.dimension.height) + @override def unload(self) -> None: self._im = None self._dpage = None @@ -527,25 +537,31 @@ def _parse_page( ) return page, im + @override def page_count(self) -> int: return len(self.page_map) + @override def load_page(self, page_no: int) -> MetsGbsPageBackend: # TODO: is this thread-safe? page, im = self._parse_page(page_no) return MetsGbsPageBackend(parsed_page=page, page_im=im, page_no=page_no) + @override def is_valid(self) -> bool: return self.root_mets is not None and self.page_count() > 0 @classmethod + @override def supported_formats(cls) -> set[InputFormat]: return {InputFormat.METS_GBS} @classmethod + @override def supports_pagination(cls) -> bool: return True + @override def unload(self) -> None: super().unload() self._tar.close() diff --git a/docling/backend/mspowerpoint_backend.py b/docling/backend/mspowerpoint_backend.py index 1810356672..1bd2eebbb4 100644 --- a/docling/backend/mspowerpoint_backend.py +++ b/docling/backend/mspowerpoint_backend.py @@ -234,6 +234,7 @@ def __init__( return + @override def page_count(self) -> int: if self.is_valid(): assert self.pptx_obj is not None diff --git a/docling/backend/noop_backend.py b/docling/backend/noop_backend.py index dd58fc1140..344432e5e6 100644 --- a/docling/backend/noop_backend.py +++ b/docling/backend/noop_backend.py @@ -6,6 +6,8 @@ from pathlib import Path from typing import Set, Union +from typing_extensions import override + from docling.backend.abstract_backend import AbstractDocumentBackend from docling.datamodel.base_models import InputFormat from docling.datamodel.document import InputDocument @@ -42,13 +44,16 @@ def __init__(self, in_doc: "InputDocument", path_or_stream: Union[BytesIO, Path] _log.error(f"NoOpBackend validation failed: {e}") self.valid = False + @override def is_valid(self) -> bool: return self.valid @classmethod + @override def supports_pagination(cls) -> bool: return False @classmethod + @override def supported_formats(cls) -> Set[InputFormat]: return set(InputFormat) diff --git a/docling/backend/pdf_backend.py b/docling/backend/pdf_backend.py index 4a93f3e84e..d196b51aca 100644 --- a/docling/backend/pdf_backend.py +++ b/docling/backend/pdf_backend.py @@ -10,6 +10,7 @@ from docling_core.types.doc import BoundingBox, Size from docling_core.types.doc.page import SegmentedPdfPage, TextCell from PIL import Image +from typing_extensions import override from docling.backend.abstract_backend import PaginatedDocumentBackend from docling.datamodel.backend_options import PdfBackendOptions @@ -138,6 +139,7 @@ def load_page(self, page_no: int) -> PdfPageBackend: pass @abstractmethod + @override def page_count(self) -> int: pass @@ -155,10 +157,12 @@ def get_document_outline(self) -> list[_PdfOutlineItem]: return [] @classmethod + @override def supported_formats(cls) -> Set[InputFormat]: return {InputFormat.PDF} @classmethod + @override def supports_pagination(cls) -> bool: return True diff --git a/docling/backend/pypdfium2_backend.py b/docling/backend/pypdfium2_backend.py index 2f252b87de..cb9f1fd806 100644 --- a/docling/backend/pypdfium2_backend.py +++ b/docling/backend/pypdfium2_backend.py @@ -23,6 +23,7 @@ from pypdfium2 import PdfTextPage from pypdfium2._helpers.misc import PdfiumError from rtree import index +from typing_extensions import override from docling.backend.managed_pdfium_backend import ( ManagedPdfiumDocumentBackend, @@ -277,10 +278,12 @@ def __init__( # the text or path objects. self._object_index: dict[int, List[tuple[BoundingBox, bool]]] = {} + @override def is_valid(self) -> bool: return self.valid @property + @override def page_no(self) -> int: return self._page_no + 1 @@ -502,6 +505,7 @@ def _object_rects( continue yield bbox + @override def get_bitmap_rects(self, scale: float = 1) -> Iterable[BoundingBox]: AREA_THRESHOLD = 0 # 32 * 32 @@ -509,6 +513,7 @@ def get_bitmap_rects(self, scale: float = 1) -> Iterable[BoundingBox]: if cropbox.area() > AREA_THRESHOLD: yield cropbox.scaled(scale=scale) + @override def has_content_in( self, *, @@ -551,6 +556,7 @@ def has_content_in( return False + @override def get_connected_shape_bounding_boxes( self, *, tolerance: float = 0.0 ) -> Optional[List[BoundingBox]]: @@ -566,6 +572,7 @@ def get_connected_shape_bounding_boxes( list(self._object_rects(pdfium_c.FPDF_PAGEOBJ_PATH)), tolerance ) + @override def get_text_in_rect(self, bbox: BoundingBox) -> str: with pypdfium2_lock: page = self._require_page() @@ -587,6 +594,7 @@ def get_text_in_rect(self, bbox: BoundingBox) -> str: return text_piece + @override def get_segmented_page(self) -> Optional[SegmentedPdfPage]: if not self.valid: return None @@ -611,9 +619,11 @@ def get_segmented_page(self) -> Optional[SegmentedPdfPage]: ) return self._seg_page + @override def get_text_cells(self) -> Iterable[TextCell]: return self._compute_text_cells() + @override def get_page_image( self, scale: float = 1, cropbox: Optional[BoundingBox] = None ) -> Image.Image: @@ -650,11 +660,13 @@ def get_page_image( return image + @override def get_size(self) -> Size: with pypdfium2_lock: page = self._require_page() return Size(width=page.get_width(), height=page.get_height()) + @override def _close_native_page(self) -> None: with pypdfium2_lock: if self.text_page is not None: @@ -690,23 +702,28 @@ def __init__( f"pypdfium could not load document with hash {self.document_hash}" ) from e + @override def page_count(self) -> int: with pypdfium2_lock: return len(self._pdoc) + @override def load_page(self, page_no: int) -> PyPdfiumPageBackend: with pypdfium2_lock: return PyPdfiumPageBackend(self._pdoc, self.document_hash, page_no) + @override def is_valid(self) -> bool: return self.page_count() > 0 + @override def get_document_outline(self) -> list[_PdfOutlineItem]: """Extract the PDF outline from the pypdfium2 document (title, depth, page, position).""" if self._pdoc is None: return [] return extract_outline_from_pdfium(self._pdoc) + @override def _close_native_document(self) -> None: with pypdfium2_lock: if self._pdoc is not None: diff --git a/docling/backend/xml/uspto_backend.py b/docling/backend/xml/uspto_backend.py index 3e0cf07bfe..16054ab1a0 100644 --- a/docling/backend/xml/uspto_backend.py +++ b/docling/backend/xml/uspto_backend.py @@ -264,6 +264,7 @@ def __init__(self) -> None: """Build an instance of PatentUsptoIce class.""" self.handler = PatentUsptoIce.PatentHandler() + @override def parse(self, patent_content: str) -> DoclingDocument | None: try: parser = make_parser() @@ -1144,6 +1145,7 @@ def store_content(self, section: str, field: str, value: str) -> None: parent=self.parents[self.level], ) + @override def parse(self, patent_content: str) -> DoclingDocument | None: self.doc = self.doc = DoclingDocument(name="file") section: str = "" diff --git a/docling/datamodel/base_models.py b/docling/datamodel/base_models.py index e337a8502e..4be64f94d6 100644 --- a/docling/datamodel/base_models.py +++ b/docling/datamodel/base_models.py @@ -38,6 +38,7 @@ field_validator, model_validator, ) +from typing_extensions import override if TYPE_CHECKING: from docling.backend.pdf_backend import PdfPageBackend @@ -747,6 +748,7 @@ def _accept_flat_confidence_scores(cls, value, handler): @computed_field # type: ignore @property + @override def mean_score(self) -> ScoreValue: if not np.isnan(self._mean_score_override): return self._mean_score_override @@ -760,6 +762,7 @@ def mean_score(self) -> ScoreValue: @computed_field # type: ignore @property + @override def low_score(self) -> ScoreValue: if not np.isnan(self._low_score_override): return self._low_score_override diff --git a/docling/datamodel/chart_extraction_options.py b/docling/datamodel/chart_extraction_options.py index ac7658fcec..40a08e137d 100644 --- a/docling/datamodel/chart_extraction_options.py +++ b/docling/datamodel/chart_extraction_options.py @@ -6,7 +6,7 @@ from typing import Any, ClassVar, Dict, Iterator, Literal, Optional from pydantic import Field, model_validator -from typing_extensions import Self +from typing_extensions import Self, override from docling.datamodel.stage_model_specs import StagePresetMixin, VlmModelSpec from docling.models.inference_engines.vlm.base import VlmEngineOptionsMixin @@ -196,12 +196,15 @@ def __init__(self, name: str, value: str) -> None: self.name = name self.value = value + @override def __repr__(self) -> str: return f"" + @override def __str__(self) -> str: return self.value + @override def __eq__(self, other: object) -> bool: if isinstance(other, str): return self.value == other @@ -209,6 +212,7 @@ def __eq__(self, other: object) -> bool: return self.value == other.value return NotImplemented + @override def __hash__(self) -> int: return hash(self.value) diff --git a/docling/datamodel/document.py b/docling/datamodel/document.py index 2b9d8c8a68..1cc5c32bcf 100644 --- a/docling/datamodel/document.py +++ b/docling/datamodel/document.py @@ -49,6 +49,7 @@ TypeAdapter, ValidationError, ) +from typing_extensions import override from docling.backend.abstract_backend import ( AbstractDocumentBackend, @@ -601,17 +602,21 @@ class _DummyBackend(AbstractDocumentBackend): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + @override def is_valid(self) -> bool: return False @classmethod + @override def supported_formats(cls) -> set[InputFormat]: return set() @classmethod + @override def supports_pagination(cls) -> bool: return False + @override def unload(self): return super().unload() diff --git a/docling/datamodel/pipeline_options.py b/docling/datamodel/pipeline_options.py index d3f52a7a6b..66579f1d0c 100644 --- a/docling/datamodel/pipeline_options.py +++ b/docling/datamodel/pipeline_options.py @@ -21,7 +21,7 @@ field_validator, model_validator, ) -from typing_extensions import deprecated +from typing_extensions import deprecated, override from docling.datamodel import ( asr_model_specs, @@ -1677,6 +1677,7 @@ class LayoutOptions(BaseLayoutOptions): ), ] = DOCLING_LAYOUT_HERON + @override def model_post_init(self, context: Any, /) -> None: super().model_post_init(context) warnings.warn( diff --git a/docling/document_converter.py b/docling/document_converter.py index 9f8bb6c6f6..124f6cbe57 100644 --- a/docling/document_converter.py +++ b/docling/document_converter.py @@ -16,7 +16,7 @@ from docling_core.types.doc.page import TextCellUnit from pydantic import ConfigDict, Field, model_validator, validate_call -from typing_extensions import Self +from typing_extensions import Self, override from docling.backend.abstract_backend import ( AbstractDocumentBackend, @@ -122,6 +122,7 @@ class FormatOption(BaseFormatOption): pipeline_cls: Type[BasePipeline] backend_options: Optional[BackendOptions] = None + @override def backend_options_for_input( self, source: Path | str | DocumentStream ) -> BackendOptions | None: @@ -188,6 +189,7 @@ class AsciiDocFormatOption(FormatOption): backend: Type[AbstractDocumentBackend] = AsciiDocBackend backend_options: AsciiDocBackendOptions | None = None + @override def backend_options_for_input( self, source: Path | str | DocumentStream ) -> AsciiDocBackendOptions | None: @@ -209,6 +211,7 @@ class HTMLFormatOption(FormatOption): backend: Type[AbstractDocumentBackend] = HTMLDocumentBackend backend_options: Optional[HTMLBackendOptions] = None + @override def backend_options_for_input( self, source: Path | str | DocumentStream ) -> HTMLBackendOptions | None: diff --git a/docling/experimental/models/table_crops_layout_model.py b/docling/experimental/models/table_crops_layout_model.py index 823cfba021..e1a059f300 100644 --- a/docling/experimental/models/table_crops_layout_model.py +++ b/docling/experimental/models/table_crops_layout_model.py @@ -10,6 +10,7 @@ from typing import Optional from docling_core.types.doc import BoundingBox, DocItemLabel +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import Cluster, LayoutPrediction, Page @@ -44,9 +45,11 @@ def __init__( self.accelerator_options = accelerator_options @classmethod + @override def get_options_type(cls) -> type[TableCropsLayoutOptions]: return TableCropsLayoutOptions + @override def predict_layout( self, conv_res: ConversionResult, diff --git a/docling/experimental/pipeline/threaded_layout_vlm_pipeline.py b/docling/experimental/pipeline/threaded_layout_vlm_pipeline.py index 90c16d0d36..9838a7cb2f 100644 --- a/docling/experimental/pipeline/threaded_layout_vlm_pipeline.py +++ b/docling/experimental/pipeline/threaded_layout_vlm_pipeline.py @@ -18,6 +18,7 @@ from docling_core.types.doc import DoclingDocument, ImageRef, PictureItem from docling_core.types.doc.document import DocTagsDocument +from typing_extensions import override if TYPE_CHECKING: from docling_core.types.doc.page import SegmentedPage @@ -275,6 +276,7 @@ def _create_run_ctx(self) -> RunContext: stages=stages, first_stage=layout_stage, output_queue=output_q ) + @override def _build_document(self, conv_res: ConversionResult) -> ConversionResult: """Build document using threaded layout+VLM pipeline.""" assert isinstance(conv_res.input._backend, PdfDocumentBackend) @@ -484,20 +486,25 @@ def _integrate_results( else: conv_res.status = ConversionStatus.SUCCESS + @override def _assemble_document(self, conv_res: ConversionResult) -> ConversionResult: return conv_res @classmethod + @override def get_default_options(cls) -> ThreadedLayoutVlmPipelineOptions: return ThreadedLayoutVlmPipelineOptions() @classmethod + @override def is_backend_supported(cls, backend: AbstractDocumentBackend) -> bool: return isinstance(backend, PdfDocumentBackend) + @override def _determine_status(self, conv_res: ConversionResult) -> ConversionStatus: return conv_res.status + @override def _unload(self, conv_res: ConversionResult) -> None: for p in conv_res.pages: if p._backend is not None: diff --git a/docling/models/base_layout_model.py b/docling/models/base_layout_model.py index 1ccdcc687f..e6bd741bbf 100644 --- a/docling/models/base_layout_model.py +++ b/docling/models/base_layout_model.py @@ -8,6 +8,7 @@ from typing import Type from docling_core.types.doc import DocItemLabel +from typing_extensions import override from docling.datamodel.base_models import LayoutPrediction, Page from docling.datamodel.document import ConversionResult @@ -42,6 +43,7 @@ class BaseLayoutModel(BasePageModel, BaseModelWithOptions, ABC): @classmethod @abstractmethod + @override def get_options_type(cls) -> Type[BaseLayoutOptions]: """Return the options type supported by this layout model.""" @@ -53,6 +55,7 @@ def predict_layout( ) -> Sequence[LayoutPrediction]: """Produce layout predictions for the provided pages.""" + @override def __call__( self, conv_res: ConversionResult, diff --git a/docling/models/base_layout_postprocessing_model.py b/docling/models/base_layout_postprocessing_model.py index b54b31aaab..695bbe15e1 100644 --- a/docling/models/base_layout_postprocessing_model.py +++ b/docling/models/base_layout_postprocessing_model.py @@ -7,6 +7,8 @@ from collections.abc import Iterable, Sequence from typing import Type +from typing_extensions import override + from docling.datamodel.base_models import LayoutPrediction, Page from docling.datamodel.document import ConversionResult from docling.datamodel.pipeline_options import BaseLayoutPostprocessorOptions @@ -18,6 +20,7 @@ class BaseLayoutPostprocessingModel(BasePageModel, BaseModelWithOptions, ABC): @classmethod @abstractmethod + @override def get_options_type(cls) -> Type[BaseLayoutPostprocessorOptions]: """Return the options type supported by this post-processing model.""" @@ -29,6 +32,7 @@ def postprocess_layout( ) -> Sequence[LayoutPrediction]: """Finalize raw layout predictions for the provided pages.""" + @override def __call__( self, conv_res: ConversionResult, diff --git a/docling/models/base_model.py b/docling/models/base_model.py index 0f58773928..2e57eb48e2 100644 --- a/docling/models/base_model.py +++ b/docling/models/base_model.py @@ -15,7 +15,7 @@ PictureItem, ) from PIL.Image import Image -from typing_extensions import TypeVar +from typing_extensions import TypeVar, override from docling.datamodel.base_models import ( ItemAndImageEnrichmentElement, @@ -98,6 +98,7 @@ def _build_prompt_safe(self, page: Page) -> str: return self.vlm_options.build_prompt(page.parsed_page) @abstractmethod + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -167,6 +168,7 @@ def __call__( class BaseEnrichmentModel(GenericEnrichmentModel[NodeItem]): + @override def prepare_element( self, conv_res: ConversionResult, element: NodeItem ) -> Optional[NodeItem]: @@ -181,6 +183,7 @@ class BaseItemAndImageEnrichmentModel( images_scale: float expansion_factor: float = 0.0 + @override def prepare_element( self, conv_res: ConversionResult, element: NodeItem ) -> Optional[ItemAndImageEnrichmentElement]: diff --git a/docling/models/base_ocr_model.py b/docling/models/base_ocr_model.py index 405eacfc63..b42647d081 100644 --- a/docling/models/base_ocr_model.py +++ b/docling/models/base_ocr_model.py @@ -21,6 +21,7 @@ ) from PIL import Image, ImageDraw from scipy.ndimage import binary_dilation, find_objects, label +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import Page @@ -598,6 +599,7 @@ def draw_ocr_rects_and_cells(self, conv_res, page, ocr_rects, show: bool = False image.save(str(out_file), format="png") @abstractmethod + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -605,5 +607,6 @@ def __call__( @classmethod @abstractmethod + @override def get_options_type(cls) -> type[OcrOptions]: pass diff --git a/docling/models/base_table_model.py b/docling/models/base_table_model.py index 0844d27a32..198cf709fb 100644 --- a/docling/models/base_table_model.py +++ b/docling/models/base_table_model.py @@ -7,6 +7,8 @@ from collections.abc import Iterable, Sequence from typing import Type +from typing_extensions import override + from docling.datamodel.base_models import Page, TableStructurePrediction from docling.datamodel.document import ConversionResult from docling.datamodel.pipeline_options import BaseTableStructureOptions @@ -20,6 +22,7 @@ class BaseTableStructureModel(BasePageModel, BaseModelWithOptions, ABC): @classmethod @abstractmethod + @override def get_options_type(cls) -> Type[BaseTableStructureOptions]: """Return the options type supported by this table model.""" @@ -31,6 +34,7 @@ def predict_tables( ) -> Sequence[TableStructurePrediction]: """Produce table structure predictions for the provided pages.""" + @override def __call__( self, conv_res: ConversionResult, diff --git a/docling/models/extraction/nuextract_transformers_model.py b/docling/models/extraction/nuextract_transformers_model.py index 6e4457467d..0870405561 100644 --- a/docling/models/extraction/nuextract_transformers_model.py +++ b/docling/models/extraction/nuextract_transformers_model.py @@ -11,6 +11,7 @@ import numpy as np from PIL.Image import Image from transformers import AutoModelForImageTextToText, AutoProcessor, GenerationConfig +from typing_extensions import override from docling.datamodel.accelerator_options import ( AcceleratorOptions, @@ -169,6 +170,7 @@ def __init__( # Load generation config self.generation_config = GenerationConfig.from_pretrained(artifacts_path) + @override def process_images( self, image_batch: Iterable[Union[Image, np.ndarray]], diff --git a/docling/models/extraction/transformers_extraction_model.py b/docling/models/extraction/transformers_extraction_model.py index 2ef5a5817a..401c1f683b 100644 --- a/docling/models/extraction/transformers_extraction_model.py +++ b/docling/models/extraction/transformers_extraction_model.py @@ -14,6 +14,7 @@ import torch from PIL.Image import Image from transformers import AutoModelForImageTextToText, AutoProcessor, GenerationConfig +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import VlmPrediction, VlmStopReason @@ -133,6 +134,7 @@ def __init__( artifacts_path ) + @override def process_images( self, image_batch: Iterable[Union[Image, np.ndarray]], diff --git a/docling/models/inference_engines/image_classification/api_kserve_v2_engine.py b/docling/models/inference_engines/image_classification/api_kserve_v2_engine.py index 26767b39d7..3c6a5e34be 100644 --- a/docling/models/inference_engines/image_classification/api_kserve_v2_engine.py +++ b/docling/models/inference_engines/image_classification/api_kserve_v2_engine.py @@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, List, Optional, Union import numpy as np +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.image_classification_engine_options import ( @@ -90,6 +91,7 @@ def _resolve_tensor_names(self) -> tuple[str, str]: output_name = metadata.outputs[0].name return input_name, output_name + @override def initialize(self) -> None: """Initialize preprocessor/labels and prepare remote client.""" _log.info("Initializing KServe v2 image-classification engine") @@ -143,6 +145,7 @@ def initialize(self) -> None: self._output_name, ) + @override def predict_batch( self, input_batch: List[ImageClassificationEngineInput] ) -> List[ImageClassificationEngineOutput]: diff --git a/docling/models/inference_engines/image_classification/base.py b/docling/models/inference_engines/image_classification/base.py index 929766acf2..0ff9cda64b 100644 --- a/docling/models/inference_engines/image_classification/base.py +++ b/docling/models/inference_engines/image_classification/base.py @@ -24,6 +24,7 @@ from PIL.Image import Image from pydantic import BaseModel, ConfigDict, Field, field_validator from pydantic_core import PydanticUndefined +from typing_extensions import override if TYPE_CHECKING: from docling.datamodel.stage_model_specs import EngineModelConfig @@ -64,6 +65,7 @@ class BaseImageClassificationEngineOptions(BaseModel): ] = {} @classmethod + @override def __pydantic_init_subclass__(cls, **kwargs): super().__pydantic_init_subclass__(**kwargs) diff --git a/docling/models/inference_engines/image_classification/onnxruntime_engine.py b/docling/models/inference_engines/image_classification/onnxruntime_engine.py index c9cd9f7bd2..c539fa71a1 100644 --- a/docling/models/inference_engines/image_classification/onnxruntime_engine.py +++ b/docling/models/inference_engines/image_classification/onnxruntime_engine.py @@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, List, Optional, Union import numpy as np +from typing_extensions import override if TYPE_CHECKING: import onnxruntime as ort @@ -98,6 +99,7 @@ def _resolve_output_name(self, session: ort.InferenceSession) -> str: raise RuntimeError("ONNX model exposes no outputs") return output_nodes[0].name + @override def initialize(self) -> None: """Initialize ONNX session and preprocessor.""" import onnxruntime as ort @@ -154,6 +156,7 @@ def _resolve_providers(self) -> List[str]: ) return ["CPUExecutionProvider"] + @override def predict_batch( self, input_batch: List[ImageClassificationEngineInput] ) -> List[ImageClassificationEngineOutput]: diff --git a/docling/models/inference_engines/image_classification/transformers_engine.py b/docling/models/inference_engines/image_classification/transformers_engine.py index f473a52b31..8e2725bc42 100644 --- a/docling/models/inference_engines/image_classification/transformers_engine.py +++ b/docling/models/inference_engines/image_classification/transformers_engine.py @@ -12,6 +12,7 @@ from typing import TYPE_CHECKING, List, Optional, Union from packaging import version +from typing_extensions import override if TYPE_CHECKING: import torch @@ -100,6 +101,7 @@ def _resolve_torch_dtype(self) -> Optional[torch.dtype]: ) return dtype + @override def initialize(self) -> None: """Initialize PyTorch model and preprocessor.""" import torch @@ -171,6 +173,7 @@ def initialize(self) -> None: self._model.dtype, # type: ignore[union-attr] ) + @override def predict_batch( self, input_batch: List[ImageClassificationEngineInput] ) -> List[ImageClassificationEngineOutput]: diff --git a/docling/models/inference_engines/object_detection/api_kserve_v2_engine.py b/docling/models/inference_engines/object_detection/api_kserve_v2_engine.py index 4435a8d009..f66e320b16 100644 --- a/docling/models/inference_engines/object_detection/api_kserve_v2_engine.py +++ b/docling/models/inference_engines/object_detection/api_kserve_v2_engine.py @@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, List, Optional, Union import numpy as np +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.kserve_transport_utils import resolve_kserve_transport_base_url @@ -104,6 +105,7 @@ def _resolve_tensor_names(self) -> tuple[str, str, str, str, str]: output_scores_name, ) + @override def initialize(self) -> None: """Initialize preprocessor/labels and prepare remote client.""" _log.info("Initializing KServe v2 object-detection engine") @@ -166,6 +168,7 @@ def initialize(self) -> None: self._output_scores_name, ) + @override def predict_batch( self, input_batch: List[ObjectDetectionEngineInput] ) -> List[ObjectDetectionEngineOutput]: diff --git a/docling/models/inference_engines/object_detection/base.py b/docling/models/inference_engines/object_detection/base.py index 9771f5881c..2fc68a6f05 100644 --- a/docling/models/inference_engines/object_detection/base.py +++ b/docling/models/inference_engines/object_detection/base.py @@ -24,6 +24,7 @@ from PIL.Image import Image from pydantic import BaseModel, ConfigDict, Field, field_validator from pydantic_core import PydanticUndefined +from typing_extensions import override if TYPE_CHECKING: from docling.datamodel.stage_model_specs import EngineModelConfig @@ -63,6 +64,7 @@ class BaseObjectDetectionEngineOptions(BaseModel): ] = {} @classmethod + @override def __pydantic_init_subclass__(cls, **kwargs): super().__pydantic_init_subclass__(**kwargs) diff --git a/docling/models/inference_engines/object_detection/onnxruntime_engine.py b/docling/models/inference_engines/object_detection/onnxruntime_engine.py index db1ae65df6..b1a3800a3b 100644 --- a/docling/models/inference_engines/object_detection/onnxruntime_engine.py +++ b/docling/models/inference_engines/object_detection/onnxruntime_engine.py @@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, List, Optional, Union import numpy as np +from typing_extensions import override if TYPE_CHECKING: import onnxruntime as ort @@ -98,6 +99,7 @@ def _resolve_model_filename(self) -> str: filename = extra_filename return filename + @override def initialize(self) -> None: """Initialize ONNX session and preprocessor.""" import onnxruntime as ort @@ -157,6 +159,7 @@ def _resolve_providers(self) -> List[str]: ) return ["CPUExecutionProvider"] + @override def predict_batch( self, input_batch: List[ObjectDetectionEngineInput] ) -> List[ObjectDetectionEngineOutput]: diff --git a/docling/models/inference_engines/object_detection/transformers_engine.py b/docling/models/inference_engines/object_detection/transformers_engine.py index c5135b4e3f..b8414ef39c 100644 --- a/docling/models/inference_engines/object_detection/transformers_engine.py +++ b/docling/models/inference_engines/object_detection/transformers_engine.py @@ -12,6 +12,7 @@ from typing import TYPE_CHECKING, List, Optional, Union from packaging import version +from typing_extensions import override if TYPE_CHECKING: import torch @@ -116,6 +117,7 @@ def _resolve_torch_dtype(self) -> Optional[torch.dtype]: ) return dtype + @override def initialize(self) -> None: """Initialize PyTorch model and preprocessor.""" import torch @@ -199,6 +201,7 @@ def initialize(self) -> None: f"Transformers engine ready (device={self._device}, dtype={self._model.dtype})" # type: ignore[union-attr] ) + @override def predict_batch( self, input_batch: List[ObjectDetectionEngineInput] ) -> List[ObjectDetectionEngineOutput]: diff --git a/docling/models/inference_engines/vlm/api_openai_compatible_engine.py b/docling/models/inference_engines/vlm/api_openai_compatible_engine.py index 29170930f0..b311175719 100644 --- a/docling/models/inference_engines/vlm/api_openai_compatible_engine.py +++ b/docling/models/inference_engines/vlm/api_openai_compatible_engine.py @@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, List, Optional from PIL.Image import Image +from typing_extensions import override from docling.datamodel.vlm_engine_options import ApiVlmEngineOptions from docling.exceptions import OperationNotAllowed @@ -81,6 +82,7 @@ def __init__( # not mixed in (prevents conflicts for vendor-specific keys like model_id). self.user_params: dict = self.options.params.copy() + @override def initialize(self) -> None: """Initialize the API engine. @@ -100,6 +102,7 @@ def initialize(self) -> None: self._initialized = True _log.info("API runtime initialized") + @override def predict_batch(self, input_batch: List[VlmEngineInput]) -> List[VlmEngineOutput]: """Run inference on a batch of inputs using concurrent API requests. @@ -236,6 +239,7 @@ def _process_single_input(input_data: VlmEngineInput) -> VlmEngineOutput: return outputs + @override def cleanup(self) -> None: """Clean up API runtime resources. diff --git a/docling/models/inference_engines/vlm/auto_inline_engine.py b/docling/models/inference_engines/vlm/auto_inline_engine.py index caafdb6a26..423bc6b696 100644 --- a/docling/models/inference_engines/vlm/auto_inline_engine.py +++ b/docling/models/inference_engines/vlm/auto_inline_engine.py @@ -10,6 +10,8 @@ from pathlib import Path from typing import TYPE_CHECKING, List, Union +from typing_extensions import override + from docling.datamodel.accelerator_options import AcceleratorDevice, AcceleratorOptions from docling.datamodel.vlm_engine_options import ( AutoInlineVlmEngineOptions, @@ -172,6 +174,7 @@ def _engine_version_is_sufficient(self, engine_type: VlmEngineType) -> bool: ) return engine_version_satisfied(engine_type, required) + @override def initialize(self) -> None: """Initialize by selecting and creating the actual engine.""" if self._initialized: @@ -245,6 +248,7 @@ def initialize(self) -> None: f"Auto-inline engine initialized with {self.selected_engine_type.value}" ) + @override def predict_batch(self, input_batch: List[VlmEngineInput]) -> List[VlmEngineOutput]: """Run inference on a batch of inputs using the selected engine. @@ -262,6 +266,7 @@ def predict_batch(self, input_batch: List[VlmEngineInput]) -> List[VlmEngineOutp # Delegate to the actual engine's batch implementation return self.actual_engine.predict_batch(input_batch) + @override def cleanup(self) -> None: """Clean up the actual engine resources.""" if self.actual_engine is not None: diff --git a/docling/models/inference_engines/vlm/base.py b/docling/models/inference_engines/vlm/base.py index 68a66ee297..9ce34604fb 100644 --- a/docling/models/inference_engines/vlm/base.py +++ b/docling/models/inference_engines/vlm/base.py @@ -22,6 +22,7 @@ from PIL.Image import Image from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny, field_validator from pydantic_core import PydanticUndefined +from typing_extensions import override if TYPE_CHECKING: from docling.datamodel.stage_model_specs import EngineModelConfig @@ -81,6 +82,7 @@ class BaseVlmEngineOptions(BaseModel): _registry: ClassVar[Dict[VlmEngineType, Type["BaseVlmEngineOptions"]]] = {} @classmethod + @override def __pydantic_init_subclass__(cls, **kwargs): super().__pydantic_init_subclass__(**kwargs) diff --git a/docling/models/inference_engines/vlm/mlx_engine.py b/docling/models/inference_engines/vlm/mlx_engine.py index 9c80eb2ae3..5860497dbb 100644 --- a/docling/models/inference_engines/vlm/mlx_engine.py +++ b/docling/models/inference_engines/vlm/mlx_engine.py @@ -13,6 +13,7 @@ from typing import TYPE_CHECKING, Any, Callable, List, Union from PIL.Image import Image +from typing_extensions import override from docling.datamodel.vlm_engine_options import MlxVlmEngineOptions from docling.models.inference_engines.vlm._utils import ( @@ -50,6 +51,7 @@ class _ProcessorKwargsWarningFilter(logging.Filter): + @override def filter(self, record: logging.LogRecord) -> bool: return _PROCESSOR_KWARGS_WARNING not in record.getMessage() @@ -105,6 +107,7 @@ def __init__( if self.model_config is not None: self.initialize() + @override def initialize(self) -> None: """Initialize the MLX model and processor.""" if self._initialized: @@ -201,6 +204,7 @@ def _load_model_for_repo(self, repo_id: str, revision: str = "main") -> None: f"Loaded MLX model {repo_id} (revision: {revision}) in {load_time:.2f} sec." ) + @override def predict_batch(self, input_batch: List[VlmEngineInput]) -> List[VlmEngineOutput]: """Run inference on a batch of inputs. @@ -350,6 +354,7 @@ def predict_batch(self, input_batch: List[VlmEngineInput]) -> List[VlmEngineOutp return outputs + @override def cleanup(self) -> None: """Clean up model resources.""" if self.vlm_model is not None: diff --git a/docling/models/inference_engines/vlm/transformers_engine.py b/docling/models/inference_engines/vlm/transformers_engine.py index 0227e3e57a..87d9cd22d5 100644 --- a/docling/models/inference_engines/vlm/transformers_engine.py +++ b/docling/models/inference_engines/vlm/transformers_engine.py @@ -25,6 +25,7 @@ StoppingCriteriaList, StopStringCriteria, ) +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorDevice, AcceleratorOptions from docling.datamodel.pipeline_options_vlm_model import ( @@ -128,6 +129,7 @@ def __init__( if self.model_config is not None: self.initialize() + @override def initialize(self) -> None: """Initialize the Transformers model and processor.""" if self._initialized: @@ -335,6 +337,7 @@ def _get_tokenizer(self) -> Any: return None return getattr(self.processor, "tokenizer", None) or self.processor + @override def predict_batch(self, input_batch: List[VlmEngineInput]) -> List[VlmEngineOutput]: """Run inference on a batch of inputs efficiently. @@ -576,6 +579,7 @@ def predict_batch(self, input_batch: List[VlmEngineInput]) -> List[VlmEngineOutp return outputs + @override def cleanup(self) -> None: """Clean up model resources.""" if self.vlm_model is not None: diff --git a/docling/models/inference_engines/vlm/vllm_engine.py b/docling/models/inference_engines/vlm/vllm_engine.py index 016db6ffff..428363dc4e 100644 --- a/docling/models/inference_engines/vlm/vllm_engine.py +++ b/docling/models/inference_engines/vlm/vllm_engine.py @@ -9,6 +9,8 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union +from typing_extensions import override + from docling.datamodel.accelerator_options import AcceleratorDevice, AcceleratorOptions from docling.datamodel.pipeline_options_vlm_model import TransformersPromptStyle from docling.datamodel.vlm_engine_options import VllmVlmEngineOptions @@ -121,6 +123,7 @@ def __init__( if self.model_config is not None: self.initialize() + @override def initialize(self) -> None: """Initialize the vLLM engine.""" if self._initialized: @@ -261,6 +264,7 @@ def download_wrapper(repo_id: str, revision: str) -> Path: self._initialized = True _log.info("vLLM runtime initialized") + @override def predict_batch(self, input_batch: List[VlmEngineInput]) -> List[VlmEngineOutput]: """Run inference on a batch of inputs using vLLM. @@ -376,6 +380,7 @@ def predict_batch(self, input_batch: List[VlmEngineInput]) -> List[VlmEngineOutp return results + @override def cleanup(self) -> None: """Clean up vLLM resources.""" if self.llm is not None: diff --git a/docling/models/picture_description_base_model.py b/docling/models/picture_description_base_model.py index 033f39aa91..366f79a57e 100644 --- a/docling/models/picture_description_base_model.py +++ b/docling/models/picture_description_base_model.py @@ -16,6 +16,7 @@ ) from docling_core.types.doc.document import PictureDescriptionData from PIL import Image +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import ApiImageRequestResult @@ -57,6 +58,7 @@ def __init__( self.elements_batch_size = options.batch_size self.images_scale = options.scale + @override def is_processable(self, doc: DoclingDocument, element: NodeItem) -> bool: return self.enabled and isinstance(element, PictureItem) @@ -65,6 +67,7 @@ def _annotate_images( ) -> Iterable[str | ApiImageRequestResult]: raise NotImplementedError + @override def __call__( self, doc: DoclingDocument, @@ -131,6 +134,7 @@ def __call__( @classmethod @abstractmethod + @override def get_options_type(cls) -> Type[PictureDescriptionBaseOptions]: pass diff --git a/docling/models/postprocessing/reading_order_rb.py b/docling/models/postprocessing/reading_order_rb.py index e45e1f8773..d7165580c4 100644 --- a/docling/models/postprocessing/reading_order_rb.py +++ b/docling/models/postprocessing/reading_order_rb.py @@ -13,6 +13,7 @@ from docling_core.types.doc.document import RefItem from docling_core.types.doc.labels import DocItemLabel from rtree import index as rtree_index +from typing_extensions import override _log = logging.getLogger(__name__) @@ -30,6 +31,7 @@ class PageElement(BoundingBox): label: DocItemLabel + @override def __str__(self): return f"{self.cid:6.2f}\t{self.label!s:<10}\t{self.l:6.2f}, {self.b:6.2f}, {self.r:6.2f}, {self.t:6.2f}" diff --git a/docling/models/stages/chart_extraction/granite_vision.py b/docling/models/stages/chart_extraction/granite_vision.py index f6bcf49639..66d8f195d9 100644 --- a/docling/models/stages/chart_extraction/granite_vision.py +++ b/docling/models/stages/chart_extraction/granite_vision.py @@ -23,6 +23,7 @@ TabularChartMetaField, ) from docling_core.types.doc.document import CodeMetaField +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import ItemAndImageEnrichmentElement @@ -110,6 +111,7 @@ def __init__( # BaseItemAndImageEnrichmentModel protocol # ------------------------------------------------------------------ + @override def is_processable(self, doc: DoclingDocument, element: NodeItem) -> bool: if not self.enabled: return False @@ -130,6 +132,7 @@ def _resolve_runtime_engine_type(self) -> VlmEngineType: return selected_engine_type return self.options.engine_options.engine_type + @override def __call__( self, doc: DoclingDocument, diff --git a/docling/models/stages/code_formula/code_formula_model.py b/docling/models/stages/code_formula/code_formula_model.py index b9d5779fc6..11d3de0b43 100644 --- a/docling/models/stages/code_formula/code_formula_model.py +++ b/docling/models/stages/code_formula/code_formula_model.py @@ -17,6 +17,7 @@ from docling_core.types.doc.labels import CodeLanguageLabel from PIL import Image from pydantic import BaseModel +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorDevice, AcceleratorOptions from docling.datamodel.base_models import ItemAndImageEnrichmentElement @@ -136,6 +137,7 @@ def download_models( progress=progress, ) + @override def is_processable(self, doc: DoclingDocument, element: NodeItem) -> bool: """ Determines if a given element in a document can be processed by the model. @@ -282,6 +284,7 @@ def clean_text(text: str) -> str: return [clean_text(t) for t in texts] + @override def __call__( self, doc: DoclingDocument, diff --git a/docling/models/stages/code_formula/code_formula_vlm_model.py b/docling/models/stages/code_formula/code_formula_vlm_model.py index 176287b515..b28adce177 100644 --- a/docling/models/stages/code_formula/code_formula_vlm_model.py +++ b/docling/models/stages/code_formula/code_formula_vlm_model.py @@ -23,6 +23,7 @@ ) from docling_core.types.doc.labels import CodeLanguageLabel from PIL import Image +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import ItemAndImageEnrichmentElement @@ -122,6 +123,7 @@ def __init__( _log.info("CodeFormulaVlmModel initialized successfully") + @override def is_processable(self, doc: DoclingDocument, element: NodeItem) -> bool: """Determine if an element can be processed by this stage. @@ -227,6 +229,7 @@ def clean_text(text: str) -> str: return [clean_text(t) for t in texts] + @override def __call__( self, doc: DoclingDocument, diff --git a/docling/models/stages/layout/layout_model.py b/docling/models/stages/layout/layout_model.py index ff6beb9183..3010aeca20 100644 --- a/docling/models/stages/layout/layout_model.py +++ b/docling/models/stages/layout/layout_model.py @@ -13,6 +13,8 @@ from pathlib import Path from typing import Optional +from typing_extensions import override + from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.layout_model_specs import ( DOCLING_LAYOUT_HERON, @@ -80,6 +82,7 @@ def __init__( ) @classmethod + @override def get_options_type(cls) -> type[LayoutOptions]: # ty: ignore[invalid-method-override] # LayoutFactory dispatches on the exact options type, so the shim must # keep claiming LayoutOptions rather than inheriting the parent's. diff --git a/docling/models/stages/layout/layout_object_detection_model.py b/docling/models/stages/layout/layout_object_detection_model.py index 9a8a3b5e68..aeed302457 100644 --- a/docling/models/stages/layout/layout_object_detection_model.py +++ b/docling/models/stages/layout/layout_object_detection_model.py @@ -11,6 +11,7 @@ from docling_core.types.doc import BoundingBox, CoordOrigin, DocItemLabel from PIL import Image +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import Cluster, LayoutPrediction, Page @@ -78,9 +79,11 @@ def _build_label_map(self) -> Dict[int, DocItemLabel]: return label_map @classmethod + @override def get_options_type(cls) -> type[LayoutObjectDetectionOptions]: return LayoutObjectDetectionOptions + @override def predict_layout( self, conv_res: ConversionResult, diff --git a/docling/models/stages/layout/layout_postprocessing_model.py b/docling/models/stages/layout/layout_postprocessing_model.py index 63ac2c7589..eb1e497447 100644 --- a/docling/models/stages/layout/layout_postprocessing_model.py +++ b/docling/models/stages/layout/layout_postprocessing_model.py @@ -14,6 +14,7 @@ from typing import List, Type import numpy as np +from typing_extensions import override from docling.datamodel.base_models import Cluster, LayoutPrediction, Page from docling.datamodel.document import ConversionResult @@ -34,9 +35,11 @@ def __init__(self, *, options: LayoutPostprocessorOptions) -> None: self.options = options @classmethod + @override def get_options_type(cls) -> Type[LayoutPostprocessorOptions]: return LayoutPostprocessorOptions + @override def postprocess_layout( self, conv_res: ConversionResult, diff --git a/docling/models/stages/ocr/_nemotron_ocr_model.py b/docling/models/stages/ocr/_nemotron_ocr_model.py index 28001bf15b..c75242d048 100644 --- a/docling/models/stages/ocr/_nemotron_ocr_model.py +++ b/docling/models/stages/ocr/_nemotron_ocr_model.py @@ -14,6 +14,7 @@ import numpy from docling_core.types.doc import BoundingBox, CoordOrigin from docling_core.types.doc.page import BoundingRectangle, TextCell +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import Page @@ -153,6 +154,7 @@ def __init__( lang=code, ) + @override def supported_ocr_languages(self) -> OcrLanguageSupport: r"""Report the BCP74 and native languages without script whenever it is not needed""" return OcrLanguageSupport( @@ -163,6 +165,7 @@ def supported_ocr_languages(self) -> OcrLanguageSupport: native=sorted(_NEMOTRON_CODE_TO_ARTIFACT), ) + @override def map_ocr_language(self, language: OcrLanguage) -> str: if language.is_passthrough(): # `english`, `multilingual`: nemotron's own recognizer names. @@ -350,6 +353,7 @@ def _run_buffer( buffer.clear() + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -436,5 +440,6 @@ def drain_completed() -> Iterable[Page]: yield from drain_completed() @classmethod + @override def get_options_type(cls) -> Type[OcrOptions]: return NemotronOcrOptions diff --git a/docling/models/stages/ocr/auto_ocr_model.py b/docling/models/stages/ocr/auto_ocr_model.py index 3ff1151f5c..9e4c2efe22 100644 --- a/docling/models/stages/ocr/auto_ocr_model.py +++ b/docling/models/stages/ocr/auto_ocr_model.py @@ -7,6 +7,8 @@ from pathlib import Path from typing import NamedTuple, Optional, Type +from typing_extensions import override + from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import Page from docling.datamodel.document import ConversionResult @@ -216,6 +218,7 @@ def __init__( ) _log.warning("No OCR engine found. Please review the install details.") + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -225,5 +228,6 @@ def __call__( yield from self._engine(conv_res, page_batch) @classmethod + @override def get_options_type(cls) -> Type[OcrOptions]: return OcrAutoOptions diff --git a/docling/models/stages/ocr/easyocr_model.py b/docling/models/stages/ocr/easyocr_model.py index 82c5419b00..040a4bcbe0 100644 --- a/docling/models/stages/ocr/easyocr_model.py +++ b/docling/models/stages/ocr/easyocr_model.py @@ -13,6 +13,7 @@ import numpy from docling_core.types.doc import BoundingBox, CoordOrigin from docling_core.types.doc.page import BoundingRectangle, TextCell +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorDevice, AcceleratorOptions from docling.datamodel.base_models import Page @@ -229,6 +230,7 @@ def __init__( verbose=False, ) + @override def supported_ocr_languages(self) -> OcrLanguageSupport: r"""Report the native and BCP74 languages without script whenever it is not needed""" tags: set[str] = set() @@ -246,6 +248,7 @@ def supported_ocr_languages(self) -> OcrLanguageSupport: native.add(code) return OcrLanguageSupport(bcp47=sorted(tags), native=sorted(native)) + @override def map_ocr_language(self, language: OcrLanguage) -> str | List[str]: code = _easyocr_code(language) if code is None: @@ -305,6 +308,7 @@ def download_models( return local_dir + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -375,5 +379,6 @@ def __call__( yield page @classmethod + @override def get_options_type(cls) -> Type[OcrOptions]: return EasyOcrOptions diff --git a/docling/models/stages/ocr/kserve_v2_ocr_model.py b/docling/models/stages/ocr/kserve_v2_ocr_model.py index cb18e55717..ffc5f250f0 100644 --- a/docling/models/stages/ocr/kserve_v2_ocr_model.py +++ b/docling/models/stages/ocr/kserve_v2_ocr_model.py @@ -12,6 +12,7 @@ from docling_core.types.doc import BoundingBox, CoordOrigin from docling_core.types.doc.page import BoundingRectangle, TextCell from PIL import Image +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import ( @@ -213,6 +214,7 @@ def _create_text_cells( return cells + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -307,6 +309,7 @@ def __call__( yield page @classmethod + @override def get_options_type(cls) -> Type[OcrOptions]: """Get the options type for this OCR model. diff --git a/docling/models/stages/ocr/nemotron_ocr_model.py b/docling/models/stages/ocr/nemotron_ocr_model.py index 51f57721b2..a5448f7c80 100644 --- a/docling/models/stages/ocr/nemotron_ocr_model.py +++ b/docling/models/stages/ocr/nemotron_ocr_model.py @@ -14,6 +14,7 @@ import numpy from docling_core.types.doc import BoundingBox, CoordOrigin from docling_core.types.doc.page import BoundingRectangle, TextCell +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import Page @@ -172,6 +173,7 @@ def __init__( lang=code, ) + @override def supported_ocr_languages(self) -> OcrLanguageSupport: r"""Report the BCP74 and native languages without script whenever it is not needed""" return OcrLanguageSupport( @@ -184,6 +186,7 @@ def supported_ocr_languages(self) -> OcrLanguageSupport: native=sorted(_NEMOTRON_CODE_TO_ARTIFACT), ) + @override def map_ocr_language(self, language: OcrLanguage) -> str: if language.is_passthrough(): # `english`, `multilingual`: nemotron's own recognizer names. @@ -381,6 +384,7 @@ def _run_buffer( buffer.clear() + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -467,5 +471,6 @@ def drain_completed() -> Iterable[Page]: yield from drain_completed() @classmethod + @override def get_options_type(cls) -> Type[OcrOptions]: return NemotronOcrOptions diff --git a/docling/models/stages/ocr/ocr_mac_model.py b/docling/models/stages/ocr/ocr_mac_model.py index 36769e7ce9..1bba2fe7aa 100644 --- a/docling/models/stages/ocr/ocr_mac_model.py +++ b/docling/models/stages/ocr/ocr_mac_model.py @@ -10,6 +10,7 @@ from docling_core.types.doc import BoundingBox, CoordOrigin from docling_core.types.doc.page import BoundingRectangle, TextCell +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import Page @@ -99,6 +100,7 @@ def __init__( self._vision_languages = _get_vision_languages(self.options.recognition) self._native_codes = self.resolve_ocr_languages() + @override def supported_ocr_languages(self) -> OcrLanguageSupport: # Map the Vision language tags to the canonical tags. tags: set[str] = set() @@ -128,6 +130,7 @@ def supported_ocr_languages(self) -> OcrLanguageSupport: return OcrLanguageSupport(bcp47=sorted(tags), native=sorted(native)) + @override def map_ocr_language(self, language: OcrLanguage) -> str | list[str]: if language.is_passthrough(): # One of Vision's own recognition languages @@ -148,6 +151,7 @@ def map_ocr_language(self, language: OcrLanguage) -> str | list[str]: ) return code + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -235,5 +239,6 @@ def __call__( yield page @classmethod + @override def get_options_type(cls) -> Type[OcrOptions]: return OcrMacOptions diff --git a/docling/models/stages/ocr/rapid_ocr_model.py b/docling/models/stages/ocr/rapid_ocr_model.py index a8ada94930..dd9c03612f 100644 --- a/docling/models/stages/ocr/rapid_ocr_model.py +++ b/docling/models/stages/ocr/rapid_ocr_model.py @@ -11,6 +11,7 @@ import numpy from docling_core.types.doc import BoundingBox, CoordOrigin from docling_core.types.doc.page import BoundingRectangle, TextCell +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorDevice, AcceleratorOptions from docling.datamodel.base_models import Page @@ -667,9 +668,11 @@ def __init__( params=params, ) + @override def supported_ocr_languages(self) -> OcrLanguageSupport: return _ppocr_supported_languages(_rapidocr_vocabulary(self.options.backend)) + @override def resolve_ocr_languages(self) -> list[str]: # An empty `lang` list means "the engine's own default", which for PP-OCR # is the Simplified Chinese recognizer. Resolved the same way a request @@ -687,6 +690,7 @@ def resolve_ocr_languages(self) -> list[str]: return [code] return super().resolve_ocr_languages() + @override def map_ocr_language(self, language: OcrLanguage) -> str: code = _ppocr_code(language, _rapidocr_vocabulary(self.options.backend)) if code is None: @@ -741,6 +745,7 @@ def download_models( fw.write(buf.read()) return local_dir + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -819,5 +824,6 @@ def __call__( yield page @classmethod + @override def get_options_type(cls) -> Type[OcrOptions]: return RapidOcrOptions diff --git a/docling/models/stages/ocr/tesseract_ocr_cli_model.py b/docling/models/stages/ocr/tesseract_ocr_cli_model.py index 8b42884d87..2b2b2e88b8 100644 --- a/docling/models/stages/ocr/tesseract_ocr_cli_model.py +++ b/docling/models/stages/ocr/tesseract_ocr_cli_model.py @@ -16,6 +16,7 @@ import pandas as pd from docling_core.types.doc import BoundingBox, CoordOrigin from docling_core.types.doc.page import TextCell +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import Page @@ -111,9 +112,11 @@ def __init__( self._sanitize_lang(lang) for lang in self.resolve_ocr_languages() ] + @override def supported_ocr_languages(self) -> OcrLanguageSupport: return installed_tesseract_languages(self._tesseract_vocabulary or []) + @override def map_ocr_language(self, language: OcrLanguage) -> str | List[str]: assert self._tesseract_vocabulary is not None name = language_to_tesseract_code(language) @@ -313,6 +316,7 @@ def _set_languages(self): df_list = pd.read_csv(io.StringIO(decoded_data), header=None) self._tesseract_vocabulary = tesseract_vocabulary(df_list[0].tolist()[1:]) + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -427,6 +431,7 @@ def __call__( yield page @classmethod + @override def get_options_type(cls) -> Type[OcrOptions]: return TesseractCliOcrOptions diff --git a/docling/models/stages/ocr/tesseract_ocr_model.py b/docling/models/stages/ocr/tesseract_ocr_model.py index 800e5e2aec..d9e00cadf1 100644 --- a/docling/models/stages/ocr/tesseract_ocr_model.py +++ b/docling/models/stages/ocr/tesseract_ocr_model.py @@ -9,6 +9,7 @@ from docling_core.types.doc import BoundingBox, CoordOrigin from docling_core.types.doc.page import TextCell +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import Page @@ -141,9 +142,11 @@ def __init__( ) self._reader_RIL = tesserocr.RIL + @override def supported_ocr_languages(self) -> OcrLanguageSupport: return installed_tesseract_languages(self._tesseract_vocabulary) + @override def map_ocr_language(self, language: OcrLanguage) -> str | list[str]: name = language_to_tesseract_code(language) if name not in self._tesseract_vocabulary: @@ -162,6 +165,7 @@ def __del__(self): for reader in self._script_readers.values(): reader.End() + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -299,5 +303,6 @@ def __call__( yield page @classmethod + @override def get_options_type(cls) -> Type[OcrOptions]: return TesseractOcrOptions diff --git a/docling/models/stages/page_assemble/page_assemble_model.py b/docling/models/stages/page_assemble/page_assemble_model.py index a7bd223f63..b584a9a179 100644 --- a/docling/models/stages/page_assemble/page_assemble_model.py +++ b/docling/models/stages/page_assemble/page_assemble_model.py @@ -10,6 +10,7 @@ import numpy as np from docling_core.types.doc import BoundingBox from pydantic import AnyUrl, BaseModel, ValidationError +from typing_extensions import override from docling.datamodel.base_models import ( AssembledUnit, @@ -177,6 +178,7 @@ def sanitize_text(self, lines: List[str]) -> str: return sanitized_text.strip() # Strip any leading or trailing whitespace + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: diff --git a/docling/models/stages/page_preprocessing/page_preprocessing_model.py b/docling/models/stages/page_preprocessing/page_preprocessing_model.py index 26a188fb7e..db52e84fb9 100644 --- a/docling/models/stages/page_preprocessing/page_preprocessing_model.py +++ b/docling/models/stages/page_preprocessing/page_preprocessing_model.py @@ -13,6 +13,7 @@ from PIL import ImageDraw from PIL.Image import Image from pydantic import BaseModel +from typing_extensions import override from docling.datamodel.base_models import Page from docling.datamodel.document import ConversionResult @@ -56,6 +57,7 @@ def __init__(self, options: PagePreprocessingOptions): r"(?:/\w+\s*){2,}" ) # Two or more "/token " sequences + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: diff --git a/docling/models/stages/picture_classifier/document_picture_classifier.py b/docling/models/stages/picture_classifier/document_picture_classifier.py index 9e17d3a9b5..d85a5ffdb2 100644 --- a/docling/models/stages/picture_classifier/document_picture_classifier.py +++ b/docling/models/stages/picture_classifier/document_picture_classifier.py @@ -17,6 +17,7 @@ ) from docling_core.types.doc.document import PictureClassificationPrediction from PIL import Image +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import ItemAndImageEnrichmentElement @@ -105,6 +106,7 @@ def __init__( self.engine.initialize() self._classes = self.engine.get_label_mapping() + @override def is_processable(self, doc: DoclingDocument, element: NodeItem) -> bool: """ Determines if the given element can be processed by the classifier. @@ -123,6 +125,7 @@ def is_processable(self, doc: DoclingDocument, element: NodeItem) -> bool: """ return self.enabled and isinstance(element, PictureItem) + @override def __call__( self, doc: DoclingDocument, diff --git a/docling/models/stages/picture_description/picture_description_api_model.py b/docling/models/stages/picture_description/picture_description_api_model.py index 6d9c736535..7d37456ab6 100644 --- a/docling/models/stages/picture_description/picture_description_api_model.py +++ b/docling/models/stages/picture_description/picture_description_api_model.py @@ -7,6 +7,7 @@ from typing import Optional, Type, Union from PIL import Image +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import ApiImageRequestResult @@ -23,6 +24,7 @@ class PictureDescriptionApiModel(PictureDescriptionBaseModel): # elements_batch_size = 4 @classmethod + @override def get_options_type(cls) -> Type[PictureDescriptionBaseOptions]: return PictureDescriptionApiOptions @@ -51,6 +53,7 @@ def __init__( "pipeline_options.enable_remote_services=True." ) + @override def _annotate_images( self, images: Iterable[Image.Image] ) -> Iterable[ApiImageRequestResult]: diff --git a/docling/models/stages/picture_description/picture_description_vlm_engine_model.py b/docling/models/stages/picture_description/picture_description_vlm_engine_model.py index 2de16a721c..aaef56322c 100644 --- a/docling/models/stages/picture_description/picture_description_vlm_engine_model.py +++ b/docling/models/stages/picture_description/picture_description_vlm_engine_model.py @@ -13,6 +13,7 @@ from typing import Optional, Type, Union from PIL import Image +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import ApiImageRequestResult, VlmStopReason @@ -62,6 +63,7 @@ class PictureDescriptionVlmEngineModel(PictureDescriptionBaseModel): """ @classmethod + @override def get_options_type(cls) -> Type[PictureDescriptionBaseOptions]: return PictureDescriptionVlmEngineOptions @@ -154,6 +156,7 @@ def _build_engine_inputs( for image in image_list ] + @override def _annotate_images( self, images: Iterable[Image.Image] ) -> Iterable[ApiImageRequestResult]: diff --git a/docling/models/stages/picture_description/picture_description_vlm_model.py b/docling/models/stages/picture_description/picture_description_vlm_model.py index 7c4c8947b0..1697db0ef3 100644 --- a/docling/models/stages/picture_description/picture_description_vlm_model.py +++ b/docling/models/stages/picture_description/picture_description_vlm_model.py @@ -8,6 +8,7 @@ from typing import Optional, Type, Union from PIL import Image +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.pipeline_options import ( @@ -29,6 +30,7 @@ class PictureDescriptionVlmModel( PictureDescriptionBaseModel, HuggingFaceModelDownloadMixin ): @classmethod + @override def get_options_type(cls) -> Type[PictureDescriptionBaseOptions]: return PictureDescriptionVlmOptions @@ -92,6 +94,7 @@ def __init__( self.provenance = f"{self.options.repo_id}" + @override def _annotate_images(self, images: Iterable[Image.Image]) -> Iterable[str]: from transformers import GenerationConfig diff --git a/docling/models/stages/table_structure/table_structure_model.py b/docling/models/stages/table_structure/table_structure_model.py index 4957777f21..2b52330ce9 100644 --- a/docling/models/stages/table_structure/table_structure_model.py +++ b/docling/models/stages/table_structure/table_structure_model.py @@ -15,6 +15,7 @@ TextCellUnit, ) from PIL import ImageDraw +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorDevice, AcceleratorOptions from docling.datamodel.base_models import Page, Table, TableStructurePrediction @@ -96,6 +97,7 @@ def __init__( self.scale = 2.0 # Scale up table input images to 144 dpi @classmethod + @override def get_options_type(cls) -> type[TableStructureOptions]: return TableStructureOptions @@ -178,6 +180,7 @@ def draw_table_and_cells( out_file = out_path / f"table_struct_page_{page.page_no:05}.png" image.save(str(out_file), format="png") + @override def predict_tables( self, conv_res: ConversionResult, diff --git a/docling/models/stages/table_structure/table_structure_model_granite_vision.py b/docling/models/stages/table_structure/table_structure_model_granite_vision.py index f49a39d892..f8ecbf1fc4 100644 --- a/docling/models/stages/table_structure/table_structure_model_granite_vision.py +++ b/docling/models/stages/table_structure/table_structure_model_granite_vision.py @@ -11,6 +11,7 @@ import torch from docling_core.types.doc import DocItemLabel from transformers import AutoModelForImageTextToText, AutoProcessor +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorDevice, AcceleratorOptions from docling.datamodel.base_models import Page, Table, TableStructurePrediction @@ -64,6 +65,7 @@ def __init__( self._load_model(artifacts_path) @classmethod + @override def get_options_type(cls) -> type[GraniteVisionTableStructureOptions]: return GraniteVisionTableStructureOptions @@ -119,6 +121,7 @@ def _load_model(self, artifacts_path: Path) -> None: ) self._model.eval() + @override def predict_tables( self, conv_res: ConversionResult, diff --git a/docling/models/stages/table_structure/table_structure_model_v2.py b/docling/models/stages/table_structure/table_structure_model_v2.py index 9553a5f2ed..b9701e60b8 100644 --- a/docling/models/stages/table_structure/table_structure_model_v2.py +++ b/docling/models/stages/table_structure/table_structure_model_v2.py @@ -16,6 +16,7 @@ ) from PIL import Image, ImageDraw from transformers import AutoTokenizer +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorDevice, AcceleratorOptions from docling.datamodel.base_models import Cluster, Page, Table, TableStructurePrediction @@ -98,6 +99,7 @@ def __init__( self.scale = 2.0 # Scale up table input images to 144 dpi @classmethod + @override def get_options_type(cls) -> type[TableStructureV2Options]: return TableStructureV2Options @@ -397,6 +399,7 @@ def draw_table_and_cells( out_file = out_path / f"table_struct_v2_page_{page.page_no:05}.png" image.save(str(out_file), format="png") + @override def predict_tables( self, conv_res: ConversionResult, diff --git a/docling/models/stages/vlm_convert/vlm_convert_model.py b/docling/models/stages/vlm_convert/vlm_convert_model.py index 2547608e6b..5aa178e325 100644 --- a/docling/models/stages/vlm_convert/vlm_convert_model.py +++ b/docling/models/stages/vlm_convert/vlm_convert_model.py @@ -13,6 +13,7 @@ from pathlib import Path from PIL import Image as PILImage +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import ( @@ -253,6 +254,7 @@ def _predict_mineru2(self, images: list[PILImage.Image]) -> list[VlmEngineOutput ) return combined_outputs + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: diff --git a/docling/models/utils/generation_utils.py b/docling/models/utils/generation_utils.py index 0a30e33084..005068e794 100644 --- a/docling/models/utils/generation_utils.py +++ b/docling/models/utils/generation_utils.py @@ -9,6 +9,8 @@ from copy import deepcopy from typing import TYPE_CHECKING, Any, List +from typing_extensions import override + if TYPE_CHECKING: from transformers import GenerationConfig @@ -145,6 +147,7 @@ def __init__( self.max_unit = max(1, int(max_unit)) self._lookback_tokens = max(1, int(lookback_tokens)) + @override def lookback_tokens(self) -> int: return self._lookback_tokens @@ -157,6 +160,7 @@ def repeated_unit(self, text: str) -> int | None: max_unit=self.max_unit, ) + @override def should_stop(self, s: str) -> bool: return self.repeated_unit(s) is not None @@ -207,6 +211,7 @@ def _regular(self, vals: List[int]) -> bool: tol = 0.2 * mean return all(abs(d - mean) <= tol for d in diffs) + @override def should_stop(self, s: str) -> bool: """ Trip only on **consecutive** runs (no other matched blocks between) of ≥3 items diff --git a/docling/models/utils/hf_stopping_criteria.py b/docling/models/utils/hf_stopping_criteria.py index 18eefceb82..fab376527a 100644 --- a/docling/models/utils/hf_stopping_criteria.py +++ b/docling/models/utils/hf_stopping_criteria.py @@ -5,6 +5,7 @@ import torch from transformers import StoppingCriteria +from typing_extensions import override from docling.models.utils.generation_utils import GenerationStopper @@ -28,6 +29,7 @@ def __init__( self.stopper = stopper self.skip_special_tokens = skip_special_tokens + @override def __call__(self, input_ids, scores, **kwargs) -> torch.BoolTensor: """Flag the rows whose decoded tail trips the stopper. diff --git a/docling/models/vlm_pipeline_models/api_vlm_model.py b/docling/models/vlm_pipeline_models/api_vlm_model.py index a63bd57f9c..763b7c983e 100644 --- a/docling/models/vlm_pipeline_models/api_vlm_model.py +++ b/docling/models/vlm_pipeline_models/api_vlm_model.py @@ -7,6 +7,7 @@ import numpy as np from PIL.Image import Image +from typing_extensions import override from docling.datamodel.base_models import Page, VlmPrediction, VlmStopReason from docling.datamodel.document import ConversionResult @@ -48,6 +49,7 @@ def __init__( "temperature": self.vlm_options.temperature, } + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -97,6 +99,7 @@ def __call__( for page in original_order: yield page + @override def process_images( self, image_batch: Iterable[Union[Image, np.ndarray]], diff --git a/docling/models/vlm_pipeline_models/hf_transformers_model.py b/docling/models/vlm_pipeline_models/hf_transformers_model.py index 94dbb358e7..0639daf8b6 100644 --- a/docling/models/vlm_pipeline_models/hf_transformers_model.py +++ b/docling/models/vlm_pipeline_models/hf_transformers_model.py @@ -14,6 +14,7 @@ from packaging import version from PIL.Image import Image from transformers import StoppingCriteria, StoppingCriteriaList, StopStringCriteria +from typing_extensions import override from docling.datamodel.accelerator_options import ( AcceleratorOptions, @@ -190,6 +191,7 @@ def __init__( artifacts_path, revision=vlm_options.revision ) + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -246,6 +248,7 @@ def __call__( for page in valid_pages: yield page + @override def process_images( self, image_batch: Iterable[Union[Image, np.ndarray]], diff --git a/docling/models/vlm_pipeline_models/mlx_model.py b/docling/models/vlm_pipeline_models/mlx_model.py index 9ec3f9b0d9..65ce13832f 100644 --- a/docling/models/vlm_pipeline_models/mlx_model.py +++ b/docling/models/vlm_pipeline_models/mlx_model.py @@ -12,6 +12,7 @@ import numpy as np from PIL.Image import Image from transformers import StoppingCriteria +from typing_extensions import override from docling.datamodel.accelerator_options import ( AcceleratorOptions, @@ -114,6 +115,7 @@ def __init__( f"Found {criteria.__name__}. Use GenerationStopper instead." ) + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -170,6 +172,7 @@ def __call__( for page in valid_pages: yield page + @override def process_images( self, image_batch: Iterable[Union[Image, np.ndarray]], diff --git a/docling/models/vlm_pipeline_models/vllm_model.py b/docling/models/vlm_pipeline_models/vllm_model.py index c9d6aedc28..a0e7352783 100644 --- a/docling/models/vlm_pipeline_models/vllm_model.py +++ b/docling/models/vlm_pipeline_models/vllm_model.py @@ -10,6 +10,7 @@ import numpy as np from PIL.Image import Image +from typing_extensions import override from docling.datamodel.accelerator_options import AcceleratorOptions from docling.datamodel.base_models import ( @@ -210,6 +211,7 @@ def __init__( **gen_cfg, ) + @override def __call__( self, conv_res: ConversionResult, page_batch: Iterable[Page] ) -> Iterable[Page]: @@ -269,6 +271,7 @@ def __call__( for page in original_order: yield page + @override def process_images( self, image_batch: Iterable[Union[Image, np.ndarray]], diff --git a/docling/pipeline/asr_pipeline.py b/docling/pipeline/asr_pipeline.py index 737421cdcb..67f1212073 100644 --- a/docling/pipeline/asr_pipeline.py +++ b/docling/pipeline/asr_pipeline.py @@ -4,6 +4,7 @@ import logging from docling_core.types.doc import DoclingDocument +from typing_extensions import override from docling.backend.abstract_backend import AbstractDocumentBackend from docling.backend.noop_backend import NoOpBackend @@ -50,6 +51,7 @@ def _has_text(self, document: "DoclingDocument") -> bool: return True return False + @override def _determine_status(self, conv_res: ConversionResult) -> ConversionStatus: if conv_res.status == ConversionStatus.FAILURE or conv_res.errors: return ConversionStatus.FAILURE @@ -62,9 +64,11 @@ def _determine_status(self, conv_res: ConversionResult) -> ConversionStatus: return ConversionStatus.SUCCESS @classmethod + @override def get_default_options(cls) -> AsrPipelineOptions: return AsrPipelineOptions() + @override def _build_document(self, conv_res: ConversionResult) -> ConversionResult: _log.info("Transcribing audio document %s.", conv_res.input.file.name) with TimeRecorder(conv_res, "doc_build", scope=ProfilingScope.DOCUMENT): @@ -72,5 +76,6 @@ def _build_document(self, conv_res: ConversionResult) -> ConversionResult: return conv_res @classmethod + @override def is_backend_supported(cls, backend: AbstractDocumentBackend): return isinstance(backend, NoOpBackend) diff --git a/docling/pipeline/asr_transcriber.py b/docling/pipeline/asr_transcriber.py index d4d7f160da..54325fcd2e 100644 --- a/docling/pipeline/asr_transcriber.py +++ b/docling/pipeline/asr_transcriber.py @@ -17,6 +17,7 @@ TrackSource, ) from pydantic import BaseModel, Field +from typing_extensions import override from docling.backend.abstract_backend import AbstractDocumentBackend from docling.backend.noop_backend import NoOpBackend @@ -162,6 +163,7 @@ def __lt__(self, other): return NotImplemented return self.start_time < other.start_time + @override def __eq__(self, other): if not isinstance(other, _ConversationItem): return NotImplemented diff --git a/docling/pipeline/base_pipeline.py b/docling/pipeline/base_pipeline.py index 186ead3c7f..1603fe7546 100644 --- a/docling/pipeline/base_pipeline.py +++ b/docling/pipeline/base_pipeline.py @@ -11,6 +11,7 @@ from typing import Any, Callable, List, Optional from docling_core.types.doc import ContentLayer, DocItem, DoclingDocument, NodeItem +from typing_extensions import override from docling.backend.abstract_backend import ( AbstractDocumentBackend, @@ -272,6 +273,7 @@ def _get_picture_description_model( @classmethod @abstractmethod + @override def get_default_options(cls) -> ConvertPipelineOptions: pass @@ -289,6 +291,7 @@ def _apply_on_pages( yield from page_batch + @override def _build_document(self, conv_res: ConversionResult) -> ConversionResult: if not isinstance(conv_res.input._backend, PaginatedDocumentBackend): raise RuntimeError( @@ -388,6 +391,7 @@ def _build_document(self, conv_res: ConversionResult) -> ConversionResult: return conv_res + @override def _unload(self, conv_res: ConversionResult) -> ConversionResult: for page in conv_res.pages: if page._backend is not None: @@ -398,6 +402,7 @@ def _unload(self, conv_res: ConversionResult) -> ConversionResult: return conv_res + @override def _determine_status(self, conv_res: ConversionResult) -> ConversionStatus: status = conv_res.status if status in [ diff --git a/docling/pipeline/extraction_vlm_pipeline.py b/docling/pipeline/extraction_vlm_pipeline.py index 7d204cca6b..417ae7ec81 100644 --- a/docling/pipeline/extraction_vlm_pipeline.py +++ b/docling/pipeline/extraction_vlm_pipeline.py @@ -10,6 +10,7 @@ from PIL.Image import Image from pydantic import BaseModel +from typing_extensions import override from docling.backend.pdf_backend import PdfDocumentBackend, iter_pdf_page_backends from docling.datamodel.base_models import ( @@ -54,6 +55,7 @@ def __init__(self, pipeline_options: VlmExtractionPipelineOptions): prompt_style=pipeline_options.extraction_prompt_style, ) + @override def _extract_data( self, ext_res: ExtractionResult, @@ -158,6 +160,7 @@ def _extract_data( return ext_res + @override def _determine_status(self, ext_res: ExtractionResult) -> ConversionStatus: """Determine the status based on extraction results.""" if ext_res.pages and not any(page.errors for page in ext_res.pages): @@ -232,5 +235,6 @@ class ExtractionTemplateFactory(ModelFactory[template]): # type: ignore raise ValueError(f"Unsupported template type: {type(template)}") @classmethod + @override def get_default_options(cls) -> PipelineOptions: return VlmExtractionPipelineOptions() diff --git a/docling/pipeline/native_pdf_pipeline.py b/docling/pipeline/native_pdf_pipeline.py index 3059c4b804..db174ca88b 100644 --- a/docling/pipeline/native_pdf_pipeline.py +++ b/docling/pipeline/native_pdf_pipeline.py @@ -19,6 +19,7 @@ Size, ) from docling_core.types.doc.page import SegmentedPdfPage +from typing_extensions import override from docling.backend.abstract_backend import AbstractDocumentBackend from docling.backend.pdf_backend import ( @@ -93,6 +94,7 @@ def __init__(self, pipeline_options: NativePdfPipelineOptions) -> None: self.pipeline_options: NativePdfPipelineOptions = pipeline_options self.keep_images = pipeline_options.generate_page_images + @override def _build_document(self, conv_res: ConversionResult) -> ConversionResult: backend = conv_res.input._backend if not isinstance(backend, PdfDocumentBackend): @@ -280,6 +282,7 @@ def _record_page_failure( ) conv_res.status = ConversionStatus.PARTIAL_SUCCESS + @override def _assemble_document(self, conv_res: ConversionResult) -> ConversionResult: timings = _NativeAssemblyTimings() with TimeRecorder(conv_res, "doc_assemble", scope=ProfilingScope.DOCUMENT): @@ -413,11 +416,13 @@ def _to_prov_bbox(bbox: BoundingBox, size: Size) -> BoundingBox: return bbox return bbox.to_bottom_left_origin(page_height=size.height) + @override def _determine_status(self, conv_res: ConversionResult) -> ConversionStatus: if conv_res.status in (ConversionStatus.PENDING, ConversionStatus.STARTED): return ConversionStatus.SUCCESS return conv_res.status + @override def _unload(self, conv_res: ConversionResult) -> None: for page in conv_res.pages: if page._backend is not None: @@ -426,9 +431,11 @@ def _unload(self, conv_res: ConversionResult) -> None: conv_res.input._backend.unload() @classmethod + @override def get_default_options(cls) -> NativePdfPipelineOptions: return NativePdfPipelineOptions() @classmethod + @override def is_backend_supported(cls, backend: AbstractDocumentBackend) -> bool: return isinstance(backend, PdfDocumentBackend) diff --git a/docling/pipeline/simple_pipeline.py b/docling/pipeline/simple_pipeline.py index 0e0c8f7a07..2558b6d554 100644 --- a/docling/pipeline/simple_pipeline.py +++ b/docling/pipeline/simple_pipeline.py @@ -3,6 +3,8 @@ import logging +from typing_extensions import override + from docling.backend.abstract_backend import ( AbstractDocumentBackend, DeclarativeDocumentBackend, @@ -26,6 +28,7 @@ class SimplePipeline(ConvertPipeline): def __init__(self, pipeline_options: ConvertPipelineOptions): super().__init__(pipeline_options) + @override def _build_document(self, conv_res: ConversionResult) -> ConversionResult: if not isinstance(conv_res.input._backend, DeclarativeDocumentBackend): raise RuntimeError( @@ -43,6 +46,7 @@ def _build_document(self, conv_res: ConversionResult) -> ConversionResult: conv_res.document = conv_res.input._backend.convert() return conv_res + @override def _determine_status(self, conv_res: ConversionResult) -> ConversionStatus: # This is called only if the previous steps didn't raise. # Since we don't have anything else to evaluate, we can @@ -50,9 +54,11 @@ def _determine_status(self, conv_res: ConversionResult) -> ConversionStatus: return ConversionStatus.SUCCESS @classmethod + @override def get_default_options(cls) -> ConvertPipelineOptions: return ConvertPipelineOptions() @classmethod + @override def is_backend_supported(cls, backend: AbstractDocumentBackend): return isinstance(backend, DeclarativeDocumentBackend) diff --git a/docling/pipeline/standard_pdf_pipeline.py b/docling/pipeline/standard_pdf_pipeline.py index 664caf1aa5..76361d6226 100644 --- a/docling/pipeline/standard_pdf_pipeline.py +++ b/docling/pipeline/standard_pdf_pipeline.py @@ -37,6 +37,7 @@ Size, TableItem, ) +from typing_extensions import override from docling.backend.abstract_backend import AbstractDocumentBackend from docling.backend.pdf_backend import PdfDocumentBackend, iter_pdf_page_backends @@ -433,6 +434,7 @@ def __init__( timed_out_run_ids=timed_out_run_ids, ) + @override def _process_batch(self, batch: Sequence[ThreadedItem]) -> list[ThreadedItem]: groups: dict[int, list[ThreadedItem]] = defaultdict(list) for itm in batch: @@ -712,6 +714,7 @@ def _make_ocr_model(self, art_path: Path | None) -> Any: **extra, ) + @override def _release_page_resources(self, item: ThreadedItem) -> None: page = item.payload if page is None: @@ -806,6 +809,7 @@ def _create_run_ctx(self) -> RunContext: def _get_expected_page_nos(self, conv_res: ConversionResult) -> list[int]: return get_expected_page_nos(conv_res) + @override def _build_document(self, conv_res: ConversionResult) -> ConversionResult: """Stream-build the document with a dedicated producer thread. @@ -1045,6 +1049,7 @@ def _integrate_results( p.parsed_page = None # ---------------------------------------------------------------- assemble + @override def _assemble_document(self, conv_res: ConversionResult) -> ConversionResult: elements, headers, body = [], [], [] with TimeRecorder(conv_res, "doc_assemble", scope=ProfilingScope.DOCUMENT): @@ -1186,16 +1191,20 @@ def _add_failed_pages_to_document( # ---------------------------------------------------------------- misc @classmethod + @override def get_default_options(cls) -> ThreadedPdfPipelineOptions: return ThreadedPdfPipelineOptions() @classmethod + @override def is_backend_supported(cls, backend: AbstractDocumentBackend) -> bool: return isinstance(backend, PdfDocumentBackend) + @override def _determine_status(self, conv_res: ConversionResult) -> ConversionStatus: return conv_res.status + @override def _unload(self, conv_res: ConversionResult) -> None: self._page_sizes_by_no = {} for p in conv_res.pages: diff --git a/docling/pipeline/video_pipeline.py b/docling/pipeline/video_pipeline.py index 69a3d99725..27d955a555 100644 --- a/docling/pipeline/video_pipeline.py +++ b/docling/pipeline/video_pipeline.py @@ -26,6 +26,7 @@ ImageRef, TrackSource, ) +from typing_extensions import override from docling.backend.abstract_backend import AbstractDocumentBackend from docling.backend.noop_backend import NoOpBackend @@ -118,13 +119,16 @@ def __init__(self, pipeline_options: VideoPipelineOptions): ) @classmethod + @override def get_default_options(cls) -> VideoPipelineOptions: return VideoPipelineOptions() @classmethod + @override def is_backend_supported(cls, backend: AbstractDocumentBackend) -> bool: return isinstance(backend, NoOpBackend) + @override def _determine_status(self, conv_res: ConversionResult) -> ConversionStatus: if conv_res.status == ConversionStatus.FAILURE: return ConversionStatus.FAILURE @@ -146,6 +150,7 @@ def _determine_status(self, conv_res: ConversionResult) -> ConversionStatus: return ConversionStatus.PARTIAL_SUCCESS return ConversionStatus.SUCCESS + @override def _build_document(self, conv_res: ConversionResult) -> ConversionResult: _log.info("Processing video document %s.", conv_res.input.file.name) with TimeRecorder(conv_res, "doc_build", scope=ProfilingScope.DOCUMENT): diff --git a/docling/pipeline/vlm_pipeline.py b/docling/pipeline/vlm_pipeline.py index ff67a579fa..972148ae87 100644 --- a/docling/pipeline/vlm_pipeline.py +++ b/docling/pipeline/vlm_pipeline.py @@ -196,6 +196,7 @@ def _initialize_legacy_vlm_models( f"Could not instantiate the right type of VLM pipeline: {vlm_options.inference_framework}" ) + @override def initialize_page(self, conv_res: ConversionResult, page: Page) -> Page: raise NotImplementedError("VlmPipeline initializes pages in _build_document()") @@ -212,6 +213,7 @@ def _initialize_page(self, conv_res: ConversionResult, page: Page) -> Page: return page + @override def _build_document(self, conv_res: ConversionResult) -> ConversionResult: if not isinstance(conv_res.input._backend, PdfDocumentBackend): raise RuntimeError( @@ -441,6 +443,7 @@ def _determine_status(self, conv_res: ConversionResult) -> ConversionStatus: status = ConversionStatus.PARTIAL_SUCCESS return status + @override def _assemble_document(self, conv_res: ConversionResult) -> ConversionResult: return conv_res @@ -804,9 +807,11 @@ def _finalize_page_output(self, document: DoclingDocument, page: Page) -> None: ) @classmethod + @override def get_default_options(cls) -> VlmPipelineOptions: return VlmPipelineOptions() @classmethod + @override def is_backend_supported(cls, backend: AbstractDocumentBackend): return isinstance(backend, PdfDocumentBackend) diff --git a/docling/service_client/exceptions.py b/docling/service_client/exceptions.py index 4c672cbf88..6353cbb45b 100644 --- a/docling/service_client/exceptions.py +++ b/docling/service_client/exceptions.py @@ -7,6 +7,8 @@ from dataclasses import dataclass +from typing_extensions import override + from docling.datamodel.service.responses import PublicFailureInfo @@ -22,6 +24,7 @@ class ServiceError(DoclingServiceClientError): status_code: int | None = None detail: str | None = None + @override def __str__(self) -> str: if self.status_code is None: return self.message @@ -54,6 +57,7 @@ def __init__( self.current_usage = current_usage self.limit = limit + @override def __str__(self) -> str: base = super().__str__() if self.current_usage is None or self.limit is None: @@ -110,5 +114,6 @@ class BatchConversionError(DoclingServiceClientError): message: str failures: list[Exception] + @override def __str__(self) -> str: return f"{self.message} ({len(self.failures)} failure(s))" diff --git a/docling/utils/chandra_utils.py b/docling/utils/chandra_utils.py index 9381ee7d39..c44ba6b2d6 100644 --- a/docling/utils/chandra_utils.py +++ b/docling/utils/chandra_utils.py @@ -38,6 +38,7 @@ ) from PIL import Image as PILImage from pydantic import AnyUrl, ValidationError +from typing_extensions import override from docling.utils.code_language import detect_code_language @@ -144,6 +145,7 @@ def __init__(self, content: str): ) self.close() + @override def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: # Accept omitted closing tags for adjacent cells, rows, and list items. closes = { @@ -166,17 +168,20 @@ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None if tag not in _VOID_TAGS: self.stack.append(node) + @override def handle_startendtag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: self.handle_starttag(tag, attrs) if tag not in _VOID_TAGS: self.handle_endtag(tag) + @override def handle_endtag(self, tag: str) -> None: for index in range(len(self.stack) - 1, 0, -1): if self.stack[index].tag == tag: del self.stack[index:] break + @override def handle_data(self, data: str) -> None: self.stack[-1].children.append(data) diff --git a/docling/utils/nemotron_parse_utils.py b/docling/utils/nemotron_parse_utils.py index 134f794201..7a4dd032b2 100644 --- a/docling/utils/nemotron_parse_utils.py +++ b/docling/utils/nemotron_parse_utils.py @@ -26,6 +26,7 @@ TabularChartMetaField, ) from PIL import Image as PILImage +from typing_extensions import override from docling.backend.latex_backend import LatexDocumentBackend from docling.backend.md_backend import MarkdownDocumentBackend @@ -72,14 +73,17 @@ def __init__(self) -> None: super().__init__(convert_charrefs=True) self._parts: list[str] = [] + @override def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: if tag.lower() == "br": self._append_break() + @override def handle_endtag(self, tag: str) -> None: if tag.lower() in self._BLOCK_TAGS: self._append_break() + @override def handle_data(self, data: str) -> None: self._parts.append(re.sub(r"\s+", " ", data)) diff --git a/docling/utils/ocr_language.py b/docling/utils/ocr_language.py index 003dd9b1e0..dc61094055 100644 --- a/docling/utils/ocr_language.py +++ b/docling/utils/ocr_language.py @@ -22,6 +22,7 @@ import langcodes from pydantic import BaseModel, ConfigDict +from typing_extensions import override _log = logging.getLogger(__name__) @@ -124,6 +125,7 @@ def has_default_script(self) -> bool: default_script = None return self.bcp47_script == default_script + @override def __str__(self) -> str: return self.tag()