Coverage for src/qdrant_loader/core/conversion/docling_engine.py: 100%
21 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-20 10:15 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-20 10:15 +0000
1"""The docling conversion engine — the one module that touches docling directly.
3:class:`DoclingConverter` is a thin facade over a single docling
4``DocumentConverter``. It owns construction *once* via ``cached_property`` (a
5class-enforced lazy-once, not a lazy-null singleton) and composes the pure
6:class:`~.options.DoclingOptionsBuilder` and :class:`~.formats.FormatPolicy` rather
7than inlining their logic.
9It structurally satisfies :class:`~.engine.ConversionEngine` — no base class, no
10registration.
11"""
13from __future__ import annotations
15from functools import cached_property
16from typing import TYPE_CHECKING
18from .config import ConversionConfig
19from .formats import FormatPolicy
20from .options import DoclingOptionsBuilder
21from .outcome import ConversionOutcome
23if TYPE_CHECKING:
24 from docling.document_converter import DocumentConverter
26 from .engine import ConversionSource
29class DoclingConverter:
30 """Source -> ConversionOutcome via docling."""
32 def __init__(self, config: ConversionConfig) -> None:
33 self._config = config
34 self._options_builder = DoclingOptionsBuilder(config)
35 self._format_policy = FormatPolicy(config)
37 @cached_property
38 def _converter(self) -> DocumentConverter:
39 """Construct the docling ``DocumentConverter`` once, then reuse it.
41 docling caches pipelines across calls, so one converter handles every
42 document. ``compile_torch_models`` lives on a process-global settings
43 singleton, so it is set here (the one intentional global mutation), not
44 in the pure option builder.
45 """
46 from docling.datamodel.settings import settings
47 from docling.document_converter import DocumentConverter
49 settings.inference.compile_torch_models = self._config.compile_models
50 return DocumentConverter(
51 allowed_formats=self._format_policy.allowed_formats(),
52 format_options=self._options_builder.build_format_options(),
53 )
55 def convert(self, source: ConversionSource) -> ConversionOutcome:
56 """Convert a single source into a typed outcome.
58 Status, not exceptions, drives control flow: ``raises_on_error=False``
59 makes docling return a result carrying its status, which the outcome
60 mapper translates into our typed value.
61 """
62 result = self._converter.convert(
63 source,
64 raises_on_error=False,
65 max_file_size=self._config.max_file_size,
66 )
67 return ConversionOutcome.from_docling_result(result)