mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-13 12:14:00 +00:00
ocr update
This commit is contained in:
1 parent
d7a7d4d084
commit
d4e92be0ab
27 files changed
+1656
-134
No files matched your search
@@ -1,6 +1,6 @@
|
||||
"""Comprehensive tests for application/parser/file/docling_parser.py
|
||||
|
||||
Covers: DoclingParser (init, _init_parser, _get_ocr_options, _export_content,
|
||||
Covers: DoclingParser (init, _init_parser, OCR engine selection, _export_content,
|
||||
parse_file), subclass initialization, error handling.
|
||||
"""
|
||||
|
||||
@@ -27,8 +27,8 @@ class TestDoclingParserInit:
|
||||
assert parser.ocr_enabled is True
|
||||
assert parser.table_structure is True
|
||||
assert parser.export_format == "markdown"
|
||||
assert parser.use_rapidocr is True
|
||||
assert parser.ocr_languages == ["english"]
|
||||
assert parser.ocr_engine is None # None -> settings.OCR_ENGINE at build
|
||||
assert parser.ocr_languages is None # None -> the engine's own default
|
||||
assert parser.force_full_page_ocr is False
|
||||
assert parser._converter is None
|
||||
|
||||
@@ -39,14 +39,14 @@ class TestDoclingParserInit:
|
||||
ocr_enabled=False,
|
||||
table_structure=False,
|
||||
export_format="text",
|
||||
use_rapidocr=False,
|
||||
ocr_engine="rapidocr",
|
||||
ocr_languages=["german"],
|
||||
force_full_page_ocr=True,
|
||||
)
|
||||
assert parser.ocr_enabled is False
|
||||
assert parser.table_structure is False
|
||||
assert parser.export_format == "text"
|
||||
assert parser.use_rapidocr is False
|
||||
assert parser.ocr_engine == "rapidocr"
|
||||
assert parser.ocr_languages == ["german"]
|
||||
assert parser.force_full_page_ocr is True
|
||||
|
||||
@@ -90,44 +90,169 @@ class TestDoclingParserInitParser:
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestGetOCROptions:
|
||||
class TestOcrEngineSelection:
|
||||
"""``OCR_ENGINE`` resolution and per-engine option building."""
|
||||
|
||||
def test_returns_none_when_rapidocr_disabled(self):
|
||||
@pytest.fixture
|
||||
def settings(self):
|
||||
from application.core.settings import settings
|
||||
|
||||
return settings
|
||||
|
||||
def test_default_setting_is_tesseract(self, settings):
|
||||
assert settings.OCR_ENGINE == "tesseract"
|
||||
assert settings.OCR_LANGS == "eng"
|
||||
|
||||
def test_none_reads_setting(self, settings, monkeypatch):
|
||||
from application.parser.file.docling_parser import _resolve_ocr_engine
|
||||
|
||||
monkeypatch.setattr(settings, "OCR_ENGINE", "auto")
|
||||
assert _resolve_ocr_engine(None) == "auto"
|
||||
|
||||
def test_unknown_engine_degrades_to_auto(self):
|
||||
from application.parser.file.docling_parser import _resolve_ocr_engine
|
||||
|
||||
assert _resolve_ocr_engine("easyocr") == "auto"
|
||||
|
||||
def test_tesseract_without_binary_degrades_to_auto(self, monkeypatch):
|
||||
import application.parser.file.docling_parser as dp
|
||||
|
||||
monkeypatch.setattr(dp.shutil, "which", lambda name: None)
|
||||
assert dp._resolve_ocr_engine("tesseract") == "auto"
|
||||
|
||||
def test_tesseract_with_binary_selected(self, monkeypatch):
|
||||
import application.parser.file.docling_parser as dp
|
||||
|
||||
monkeypatch.setattr(dp.shutil, "which", lambda name: "/usr/bin/tesseract")
|
||||
assert dp._resolve_ocr_engine("tesseract") == "tesseract"
|
||||
|
||||
def test_ocrmac_off_darwin_degrades_to_auto(self, monkeypatch):
|
||||
import application.parser.file.docling_parser as dp
|
||||
|
||||
monkeypatch.setattr(dp.sys, "platform", "linux")
|
||||
assert dp._resolve_ocr_engine("ocrmac") == "auto"
|
||||
|
||||
def test_rapidocr_missing_degrades_to_auto(self, monkeypatch):
|
||||
import sys
|
||||
|
||||
import application.parser.file.docling_parser as dp
|
||||
|
||||
monkeypatch.setitem(sys.modules, "rapidocr", None)
|
||||
assert dp._resolve_ocr_engine("rapidocr") == "auto"
|
||||
|
||||
def test_deepseek_passes_through(self):
|
||||
from application.parser.file.docling_parser import _resolve_ocr_engine
|
||||
|
||||
assert _resolve_ocr_engine("deepseek") == "deepseek"
|
||||
|
||||
def test_build_auto_returns_none(self):
|
||||
from application.parser.file.docling_parser import _build_ocr_options
|
||||
|
||||
assert _build_ocr_options("auto", None, True) is None
|
||||
|
||||
def test_build_tesseract_reads_ocr_langs(self, settings, monkeypatch):
|
||||
pytest.importorskip("docling")
|
||||
from application.parser.file.docling_parser import _build_ocr_options
|
||||
|
||||
monkeypatch.setattr(settings, "OCR_LANGS", "eng+chi_sim")
|
||||
options = _build_ocr_options("tesseract", None, True)
|
||||
|
||||
assert type(options).__name__ == "TesseractCliOcrOptions"
|
||||
assert options.lang == ["eng", "chi_sim"]
|
||||
assert options.force_full_page_ocr is True
|
||||
|
||||
def test_build_tesseract_explicit_languages_win(self):
|
||||
pytest.importorskip("docling")
|
||||
from application.parser.file.docling_parser import _build_ocr_options
|
||||
|
||||
options = _build_ocr_options("tesseract", ["deu"], False)
|
||||
assert options.lang == ["deu"]
|
||||
|
||||
def test_build_rapidocr(self):
|
||||
pytest.importorskip("docling")
|
||||
from application.parser.file.docling_parser import _build_ocr_options
|
||||
|
||||
options = _build_ocr_options("rapidocr", None, False)
|
||||
assert type(options).__name__ == "RapidOcrOptions"
|
||||
assert options.lang == ["english"]
|
||||
|
||||
def test_build_import_failure_returns_none(self, monkeypatch):
|
||||
import sys
|
||||
|
||||
from application.parser.file.docling_parser import _build_ocr_options
|
||||
|
||||
monkeypatch.setitem(sys.modules, "docling.datamodel.pipeline_options", None)
|
||||
assert _build_ocr_options("tesseract", ["eng"], False) is None
|
||||
assert _build_ocr_options("ocrmac", None, False) is None
|
||||
|
||||
def test_build_generic_failure_returns_none(self, monkeypatch):
|
||||
import sys
|
||||
import types
|
||||
|
||||
from application.parser.file.docling_parser import _build_ocr_options
|
||||
|
||||
fake = types.ModuleType("docling.datamodel.pipeline_options")
|
||||
|
||||
def _boom(**kwargs):
|
||||
raise RuntimeError("bad options")
|
||||
|
||||
fake.RapidOcrOptions = _boom
|
||||
monkeypatch.setitem(sys.modules, "docling.datamodel.pipeline_options", fake)
|
||||
assert _build_ocr_options("rapidocr", None, False) is None
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestDeepseekVlmConverter:
|
||||
"""OCR_ENGINE=deepseek swaps the whole pipeline for docling's VLM route."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _requires_docling(self):
|
||||
pytest.importorskip("docling")
|
||||
|
||||
def test_deepseek_builds_vlm_converter(self, monkeypatch):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser(use_rapidocr=False)
|
||||
assert parser._get_ocr_options() is None
|
||||
monkeypatch.setattr(settings, "OCR_DEEPSEEK_URL", "http://gpu-host:8000/v1/chat/completions")
|
||||
monkeypatch.setattr(settings, "OCR_DEEPSEEK_MODEL", "deepseek-ocr-x")
|
||||
|
||||
def test_returns_options_when_available(self):
|
||||
built = {}
|
||||
|
||||
def _capture_converter(format_options):
|
||||
built["format_options"] = format_options
|
||||
return MagicMock()
|
||||
|
||||
monkeypatch.setattr(
|
||||
"docling.document_converter.DocumentConverter", _capture_converter
|
||||
)
|
||||
parser = DoclingParser(ocr_enabled=True, ocr_engine="deepseek")
|
||||
parser._create_converter()
|
||||
|
||||
from docling.datamodel.base_models import InputFormat
|
||||
from docling.pipeline.vlm_pipeline import VlmPipeline
|
||||
|
||||
pdf_option = built["format_options"][InputFormat.PDF]
|
||||
image_option = built["format_options"][InputFormat.IMAGE]
|
||||
assert pdf_option.pipeline_cls is VlmPipeline
|
||||
assert image_option.pipeline_cls is VlmPipeline
|
||||
vlm = pdf_option.pipeline_options.vlm_options
|
||||
assert vlm.url == "http://gpu-host:8000/v1/chat/completions"
|
||||
assert vlm.params["model"] == "deepseek-ocr-x"
|
||||
assert pdf_option.pipeline_options.enable_remote_services is True
|
||||
|
||||
def test_deepseek_ignored_when_ocr_disabled(self, monkeypatch):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser(use_rapidocr=True, ocr_languages=["english"])
|
||||
vlm_called = []
|
||||
monkeypatch.setattr(
|
||||
DoclingParser,
|
||||
"_create_vlm_converter",
|
||||
lambda self: vlm_called.append(True),
|
||||
)
|
||||
monkeypatch.setattr("docling.document_converter.DocumentConverter", MagicMock())
|
||||
DoclingParser(ocr_enabled=False, ocr_engine="deepseek")._create_converter()
|
||||
|
||||
mock_options = MagicMock()
|
||||
with patch(
|
||||
"application.parser.file.docling_parser.DoclingParser._get_ocr_options",
|
||||
return_value=mock_options,
|
||||
):
|
||||
result = parser._get_ocr_options()
|
||||
assert result is mock_options
|
||||
|
||||
def test_returns_none_on_import_error(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser(use_rapidocr=True)
|
||||
|
||||
# Simulate the ImportError path
|
||||
original = parser._get_ocr_options
|
||||
|
||||
def patched_get_ocr():
|
||||
try:
|
||||
raise ImportError("No RapidOcrOptions")
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
parser._get_ocr_options = patched_get_ocr
|
||||
assert parser._get_ocr_options() is None
|
||||
parser._get_ocr_options = original
|
||||
assert vlm_called == []
|
||||
|
||||
|
||||
# =====================================================================
|
||||
@@ -431,31 +556,6 @@ class TestDoclingSubclasses:
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestDoclingParserGaps:
|
||||
def test_get_ocr_options_import_error_returns_none(self):
|
||||
"""Cover lines 148-150: ImportError returns None."""
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser(ocr_enabled=True, use_rapidocr=True)
|
||||
with patch.dict("sys.modules", {"docling.datamodel.pipeline_options": None}):
|
||||
# Force re-import to trigger ImportError
|
||||
with patch(
|
||||
"builtins.__import__", side_effect=ImportError("no module")
|
||||
):
|
||||
result = parser._get_ocr_options()
|
||||
assert result is None
|
||||
|
||||
def test_get_ocr_options_generic_error_returns_none(self):
|
||||
"""Cover lines 151-153: generic Exception returns None."""
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser(ocr_enabled=True, use_rapidocr=True)
|
||||
with patch(
|
||||
"builtins.__import__",
|
||||
side_effect=RuntimeError("unexpected"),
|
||||
):
|
||||
result = parser._get_ocr_options()
|
||||
assert result is None
|
||||
|
||||
def test_csv_parser_init(self):
|
||||
"""Cover line 289: DoclingCSVParser.__init__ calls super."""
|
||||
from application.parser.file.docling_parser import DoclingCSVParser
|
||||
@@ -1442,11 +1542,11 @@ class TestForceFullPageOCRWiring:
|
||||
DoclingParser(**kwargs)._create_converter()
|
||||
return built[0]
|
||||
|
||||
def test_forced_without_rapidocr(self, monkeypatch):
|
||||
def test_forced_with_default_auto_options(self, monkeypatch):
|
||||
options = self._pipeline_options(
|
||||
monkeypatch,
|
||||
ocr_enabled=True,
|
||||
use_rapidocr=False,
|
||||
ocr_engine="auto",
|
||||
force_full_page_ocr=True,
|
||||
)
|
||||
assert options.ocr_options.force_full_page_ocr is True
|
||||
@@ -1455,7 +1555,7 @@ class TestForceFullPageOCRWiring:
|
||||
options = self._pipeline_options(
|
||||
monkeypatch,
|
||||
ocr_enabled=True,
|
||||
use_rapidocr=False,
|
||||
ocr_engine="auto",
|
||||
force_full_page_ocr=False,
|
||||
)
|
||||
assert options.ocr_options.force_full_page_ocr is False
|
||||
Reference in new issue
Block a user