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

1"""The docling conversion engine — the one module that touches docling directly. 

2 

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. 

8 

9It structurally satisfies :class:`~.engine.ConversionEngine` — no base class, no 

10registration. 

11""" 

12 

13from __future__ import annotations 

14 

15from functools import cached_property 

16from typing import TYPE_CHECKING 

17 

18from .config import ConversionConfig 

19from .formats import FormatPolicy 

20from .options import DoclingOptionsBuilder 

21from .outcome import ConversionOutcome 

22 

23if TYPE_CHECKING: 

24 from docling.document_converter import DocumentConverter 

25 

26 from .engine import ConversionSource 

27 

28 

29class DoclingConverter: 

30 """Source -> ConversionOutcome via docling.""" 

31 

32 def __init__(self, config: ConversionConfig) -> None: 

33 self._config = config 

34 self._options_builder = DoclingOptionsBuilder(config) 

35 self._format_policy = FormatPolicy(config) 

36 

37 @cached_property 

38 def _converter(self) -> DocumentConverter: 

39 """Construct the docling ``DocumentConverter`` once, then reuse it. 

40 

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 

48 

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 ) 

54 

55 def convert(self, source: ConversionSource) -> ConversionOutcome: 

56 """Convert a single source into a typed outcome. 

57 

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)