Skip to content

Commit ba40ff1

Browse files
committed
feature(extract-python): add a debug cli to extract-python
1 parent 17f323a commit ba40ff1

16 files changed

Lines changed: 233 additions & 32 deletions

File tree

‎extract-core/extract_core/configs.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from abc import ABC, abstractmethod
22
from enum import StrEnum
33
from pathlib import Path
4-
from typing import ClassVar
4+
from typing import ClassVar, Self
55

66
from icij_common.pydantic_utils import icij_config, merge_configs, no_enum_values_config
77
from icij_common.registrable import RegistrableConfig
@@ -29,6 +29,9 @@ class BasePipelineConfig(RegistrableConfig, ABC):
2929
@abstractmethod
3030
def supported_exts(cls) -> set[SupportedExt]: ...
3131

32+
@abstractmethod
33+
def to_device(self) -> Self: ...
34+
3235

3336
class ResultBufferConfig(BaseModel):
3437
max_size: ByteSize = "500MiB"

‎extract-core/extract_core/docling_.py‎

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from collections import defaultdict
44
from copy import deepcopy
55
from functools import cache
6-
from typing import Annotated, Any, ClassVar, get_type_hints
6+
from typing import Annotated, Any, ClassVar, Self, get_type_hints
77

88
from docling.datamodel.backend_options import BackendOptions, BaseBackendOptions
99
from docling.datamodel.base_models import (
@@ -159,7 +159,6 @@ def _table_structure_opts_type_adapter() -> TypeAdapter:
159159
def _resolve_pipeline_options(
160160
pipeline_options: dict[str, Any] | None,
161161
pipeline_cls: type,
162-
device: Device,
163162
) -> PipelineOptions:
164163
option_cls = _find_init_arg_type(pipeline_cls, "pipeline_options")
165164
if pipeline_options is None:
@@ -198,10 +197,6 @@ def _resolve_pipeline_options(
198197
)
199198
pipeline_options["table_structure_options"] = table_structure_opts
200199
pipeline_options = option_cls.model_validate(pipeline_options)
201-
accelerator_opts = getattr(pipeline_options, "accelerator_options", None)
202-
if accelerator_opts is not None:
203-
accelerator_opts.device = device.to_docling()
204-
pipeline_options.accelerator_options = accelerator_opts
205200
return pipeline_options
206201

207202

@@ -218,13 +213,11 @@ class DoclingFormatOption(BaseFormatOption):
218213
pipeline_cls: str
219214
pipeline_options: dict[str, Any] | None = None
220215

221-
def to_docling(self, device: Device) -> BaseFormatOption: # noqa: ANN201
216+
def to_docling(self) -> BaseFormatOption: # noqa: ANN201
222217
from docling.document_converter import FormatOption # noqa: PLC0415
223218

224219
pipeline_cls = _resolve_pipeline_cls(self.pipeline_cls)
225-
pipeline_opts = _resolve_pipeline_options(
226-
self.pipeline_options, pipeline_cls, device
227-
)
220+
pipeline_opts = _resolve_pipeline_options(self.pipeline_options, pipeline_cls)
228221
pipeline_opts = _validate_pipeline_opts(pipeline_opts)
229222
return FormatOption(
230223
pipeline_cls=pipeline_cls,
@@ -448,3 +441,23 @@ def supported_exts(cls) -> set[SupportedExt]:
448441
def with_setting(self, settings: DoclingSettings) -> "DoclingPipelineConfig":
449442
update = {"settings": settings}
450443
return safe_copy(self, update=update)
444+
445+
def to_device(self) -> Self:
446+
device = self.device.to_docling()
447+
new_format_opts = dict()
448+
for fmt, fmt_opts in self.format_options.items():
449+
pipeline_opts = fmt_opts.pipeline_options
450+
if pipeline_opts is None:
451+
pipeline_opts = {"generate_picture_images": True}
452+
accelerator_opts = pipeline_opts.get("accelerator_options", dict())
453+
accelerator_opts["device"] = device
454+
pipeline_opts["accelerator_options"] = accelerator_opts
455+
update = {"pipeline_options": pipeline_opts}
456+
new_format_opts[fmt] = safe_copy(fmt_opts, update=update)
457+
458+
return DoclingPipelineConfig(
459+
device=self.device,
460+
format_options=new_format_opts,
461+
settings=self.settings,
462+
result_buffer=self.result_buffer,
463+
)

‎extract-core/extract_core/marker_.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from copy import deepcopy
12
from functools import cache
2-
from typing import Any, ClassVar
3+
from typing import Any, ClassVar, Self
34

45
from pydantic import Field
56

@@ -38,3 +39,6 @@ def supported_exts(cls) -> set[SupportedExt]:
3839
SupportedExt.GIF,
3940
SupportedExt.TIFF,
4041
}
42+
43+
def to_device(self) -> Self:
44+
return deepcopy(self)

‎extract-core/extract_core/miner_u.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from collections.abc import Callable
2+
from copy import deepcopy
23
from enum import StrEnum
34
from functools import cache
4-
from typing import ClassVar, Literal
5+
from typing import ClassVar, Literal, Self
56

67
from mineru.config import VlmConfig
78
from mineru.types import Tier
@@ -44,3 +45,6 @@ def supported_exts(cls) -> set[SupportedExt]:
4445
SupportedExt.PPTX,
4546
SupportedExt.XLSX,
4647
}
48+
49+
def to_device(self) -> Self:
50+
return deepcopy(self)

‎extract-core/extract_core/pipeline.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
class Pipeline[C: BasePipelineConfig](RegistrableFromConfig, ABC):
1414
def __init__(self, config: C):
15-
self._config = config
15+
self._config = config.to_device()
1616
self._device = self._config.device
1717

1818
@abstractmethod

‎extract-core/tests/test_docling.py‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,18 @@ def test_format_option_ser() -> None:
4747
assert deserialized == config
4848

4949

50-
def test_docling_format_options_should_resolve_gpu_accelerator() -> None:
50+
def test_docling_pipeline_should_resolve_cuda_accelerator() -> None:
5151
# Given
52-
device = Device.CUDA
5352
format_opts = DoclingFormatOption(
54-
backend="PdfDocumentBackend", pipeline_cls="VlmPipeline"
53+
backend="PdfDocumentBackend",
54+
pipeline_cls="VlmPipeline",
55+
)
56+
config = DoclingPipelineConfig(
57+
device=Device.CUDA, format_options={InputFormat.PDF: format_opts}
5558
)
5659
# When
57-
as_docling = format_opts.to_docling(device)
60+
config = config.to_device()
5861
# Then
59-
accelerator_opts = as_docling.pipeline_options.accelerator_options
60-
assert accelerator_opts.device is AcceleratorDevice.CUDA
62+
pdf_fmt_opts = config.format_options[InputFormat.PDF]
63+
device = pdf_fmt_opts.pipeline_options["accelerator_options"]["device"]
64+
assert device is AcceleratorDevice.CUDA

‎extract-core/tests/test_objects.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
)
88
from docling.document_converter import PdfFormatOption
99
from extract_core import DoclingPipelineConfig, PipelineConfig
10-
from extract_core.objects import Device, Status
10+
from extract_core.objects import Status
1111
from pydantic import TypeAdapter
1212

1313

@@ -24,6 +24,7 @@ def test_docling_pipeline_config() -> None:
2424
ta = TypeAdapter(PipelineConfig)
2525
config = {
2626
"pipeline": "docling",
27+
"device": "cpu",
2728
"format_options": {
2829
"pdf": {
2930
"pipeline_cls": "StandardPdfPipeline",
@@ -42,7 +43,7 @@ def test_docling_pipeline_config() -> None:
4243
assert isinstance(pipeline_config, DoclingPipelineConfig)
4344
format_options = pipeline_config.format_options
4445
pdf_options = format_options[InputFormat.PDF]
45-
pdf_pipeline_options = pdf_options.to_docling(Device.CPU)
46+
pdf_pipeline_options = pdf_options.to_docling()
4647

4748
expected_options = PdfFormatOption(
4849
pipeline_options=ThreadedPdfPipelineOptions(

‎extract-python/extract_python/__init__.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,20 @@
55
try:
66
from .docling_ import DOCLING_DEFAULT_ARTIFACTS_PATH, DoclingPipeline
77
except ModuleNotFoundError:
8-
logger.exception("docling is not available, might be optional...")
8+
logger.warning("docling is not available, might be optional...")
99
DOCKING_DEFAULT_ARTIFACTS_PATH, DoclingPipeline = None, None
1010

1111
try:
1212
from .marker_ import MarkerPipeline
1313
except ModuleNotFoundError:
14-
logger.exception("marker is not available, might be optional...")
14+
logger.warning("marker is not available, might be optional...")
1515
MarkerPipeline = None
1616

1717

1818
try:
1919
from .miner_u import MinerUPipeline
2020
except ModuleNotFoundError:
21-
logger.exception("mineru is not available, might be optional...")
21+
logger.warning("mineru is not available, might be optional...")
2222
MinerUPipeline = None
2323

2424

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from .cli import cli_app
2+
3+
4+
def main() -> None:
5+
cli_app()
6+
7+
8+
if __name__ == "__main__":
9+
main()
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import importlib
2+
import logging
3+
import os
4+
from typing import Annotated
5+
6+
import typer
7+
from icij_common.logging_utils import setup_loggers
8+
9+
import extract_python
10+
11+
from .doc import configs_app
12+
from .utils import AsyncTyper
13+
14+
cli_app = AsyncTyper(
15+
context_settings={"help_option_names": ["-h", "--help"]},
16+
pretty_exceptions_enable=False,
17+
)
18+
cli_app.add_typer(configs_app)
19+
20+
21+
def version_callback(value: bool) -> None: # noqa: FBT001
22+
if value:
23+
package_version = importlib.metadata.version(extract_python.__name__)
24+
print(package_version)
25+
raise typer.Exit()
26+
27+
28+
def pretty_exc_callback(value: bool) -> None: # noqa: FBT001
29+
if not value:
30+
os.environ["TYPER_STANDARD_TRACEBACK"] = "1"
31+
32+
33+
@cli_app.callback()
34+
def main(
35+
version: Annotated[ # noqa: ARG001
36+
bool | None,
37+
typer.Option("--version", callback=version_callback, is_eager=True),
38+
] = None,
39+
*,
40+
pretty_exceptions: Annotated[ # noqa: ARG001
41+
bool,
42+
typer.Option(
43+
"--pretty-exceptions", callback=pretty_exc_callback, is_eager=True
44+
),
45+
] = False,
46+
) -> None:
47+
setup_loggers(
48+
["__main__", extract_python.__name__, "docling", "mineru", "marker"],
49+
level=logging.DEBUG,
50+
)

0 commit comments

Comments
 (0)