mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-15 14:16:05 +00:00
refactor: rename the application package to docsgpt
The backend import package is now docsgpt, the name it will carry on PyPI; application was far too generic to install into anyone's site-packages. git mv plus a mechanical rewrite of every import, dotted string and path reference: 734 Python files, the compose files, Dockerfile, workflows, docs, setup scripts, devcontainer, k8s manifests, vscode config, pytest and coverage config, .gitignore. Behaviour is unchanged. Kept for one release: - A top-level application package whose meta-path finder resolves application.x.y to the already-imported docsgpt.x.y object, so old imports and entry points (celery -A application.app.celery, uvicorn application.asgi:asgi_app) keep working with a FutureWarning. - Celery registers every application.* task name as an alias of its docsgpt.* task on start-up, so messages queued by the previous release still run. The redbeat key prefix moves to redbeat:docsgpt:v2: so schedule entries the previous release wrote are left unread instead of firing twice. The backend image builds from the repository root (docker build -f docsgpt/Dockerfile .) so it can ship the alias package; a root .dockerignore allow-lists docsgpt/ and application/ and keeps caches, local data, .env files, the sample index files and the Dockerfile out. Compose and the image workflows point at the new context.
This commit is contained in:
1 parent
3e7f1f91b4
commit
574f96341e
984 files changed
+9847
-9682
No files matched your search
@@ -17,11 +17,11 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from application.parser.file.base_parser import BaseParser, DocumentParseError
|
||||
from docsgpt.parser.file.base_parser import BaseParser, DocumentParseError
|
||||
|
||||
anydoc = pytest.importorskip("anydoc")
|
||||
|
||||
from application.parser.file.anydoc_parser import ( # noqa: E402 — after importorskip
|
||||
from docsgpt.parser.file.anydoc_parser import ( # noqa: E402 — after importorskip
|
||||
ANYDOC_SUFFIXES,
|
||||
AnydocParser,
|
||||
anydoc_available,
|
||||
@@ -309,7 +309,7 @@ def test_init_parser_imports_for_real_not_just_find_spec(monkeypatch):
|
||||
"""A wheel whose native extension fails to load has a spec but no importable
|
||||
module; that must surface at init, not as a bare ImportError from parse_file
|
||||
mid-ingest (which load_data does not catch)."""
|
||||
from application.parser.file import anydoc_parser as mod
|
||||
from docsgpt.parser.file import anydoc_parser as mod
|
||||
|
||||
monkeypatch.setattr(mod, "anydoc_available", lambda: True)
|
||||
monkeypatch.setitem(sys.modules, "anydoc", None)
|
||||
@@ -332,7 +332,7 @@ class _FakeDoclingFallback:
|
||||
"""Registered as a DoclingParser subclass so ``_is_docling_backed`` is True."""
|
||||
|
||||
def __new__(cls):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
class _Inner(DoclingParser):
|
||||
def __init__(self):
|
||||
@@ -374,7 +374,7 @@ def test_trust_flagged_pdf_without_docling_keeps_output_and_warns():
|
||||
|
||||
|
||||
def test_trust_check_disabled_stamps_nothing(monkeypatch):
|
||||
from application.parser.file import anydoc_parser as ap
|
||||
from docsgpt.parser.file import anydoc_parser as ap
|
||||
|
||||
monkeypatch.setattr(ap.settings, "PDF_TRUST_CHECK", False)
|
||||
parser = AnydocParser()
|
||||
@@ -431,7 +431,7 @@ def test_trust_check_errors_never_fail_the_parse(monkeypatch, tmp_path):
|
||||
def _explode(path, markdown):
|
||||
raise RuntimeError("scanner bug")
|
||||
|
||||
import application.parser.file.pdf_trust as pt
|
||||
import docsgpt.parser.file.pdf_trust as pt
|
||||
|
||||
monkeypatch.setattr(pt, "verify_pdf_file", _explode)
|
||||
_fake_anydoc(monkeypatch, lambda path: "# converted fine")
|
||||
@@ -442,7 +442,7 @@ def test_trust_check_errors_never_fail_the_parse(monkeypatch, tmp_path):
|
||||
|
||||
|
||||
def test_tableize_applied_when_enabled(monkeypatch, tmp_path):
|
||||
from application.parser.file import anydoc_parser as ap
|
||||
from docsgpt.parser.file import anydoc_parser as ap
|
||||
|
||||
monkeypatch.setattr(ap.settings, "ANYDOC_TABLEIZE", True)
|
||||
monkeypatch.setattr(ap.settings, "PDF_TRUST_CHECK", False)
|
||||
@@ -459,7 +459,7 @@ def test_tableize_applied_when_enabled(monkeypatch, tmp_path):
|
||||
|
||||
|
||||
def test_tableize_disabled_by_default(monkeypatch, tmp_path):
|
||||
from application.parser.file import anydoc_parser as ap
|
||||
from docsgpt.parser.file import anydoc_parser as ap
|
||||
|
||||
monkeypatch.setattr(ap.settings, "PDF_TRUST_CHECK", False)
|
||||
flat = "Cash ..... 1,234 900\nDebt ..... 2,000 1,500\nEquity ..... 900 800"
|
||||
@@ -471,7 +471,7 @@ def test_tableize_disabled_by_default(monkeypatch, tmp_path):
|
||||
|
||||
|
||||
def test_tableize_never_touches_docling_reroute(monkeypatch):
|
||||
from application.parser.file import anydoc_parser as ap
|
||||
from docsgpt.parser.file import anydoc_parser as ap
|
||||
|
||||
monkeypatch.setattr(ap.settings, "ANYDOC_TABLEIZE", True)
|
||||
fallback = _FakeDoclingFallback()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from application.parser.file.audio_parser import AudioParser
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from application.stt.upload_limits import AudioFileTooLargeError
|
||||
from docsgpt.parser.file.audio_parser import AudioParser
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.stt.upload_limits import AudioFileTooLargeError
|
||||
|
||||
|
||||
def test_audio_init_parser():
|
||||
@@ -13,9 +13,9 @@ def test_audio_init_parser():
|
||||
assert parser.parser_config_set
|
||||
|
||||
|
||||
@patch("application.stt.upload_limits.settings")
|
||||
@patch("application.parser.file.audio_parser.STTCreator.create_stt")
|
||||
@patch("application.parser.file.audio_parser.settings")
|
||||
@patch("docsgpt.stt.upload_limits.settings")
|
||||
@patch("docsgpt.parser.file.audio_parser.STTCreator.create_stt")
|
||||
@patch("docsgpt.parser.file.audio_parser.settings")
|
||||
def test_audio_parser_transcribes_file(
|
||||
mock_settings, mock_create_stt, mock_limit_settings, tmp_path
|
||||
):
|
||||
@@ -44,7 +44,7 @@ def test_audio_parser_transcribes_file(
|
||||
)
|
||||
|
||||
|
||||
@patch("application.stt.upload_limits.settings")
|
||||
@patch("docsgpt.stt.upload_limits.settings")
|
||||
def test_audio_parser_rejects_oversized_files(mock_limit_settings, tmp_path):
|
||||
mock_limit_settings.STT_MAX_FILE_SIZE_MB = 1
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Comprehensive tests for application/parser/file/bulk.py
|
||||
"""Comprehensive tests for docsgpt/parser/file/bulk.py
|
||||
|
||||
Covers: SimpleDirectoryReader (init, file discovery, load_data, directory
|
||||
structure building), get_default_file_extractor.
|
||||
@@ -9,7 +9,7 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from application.parser.schema.base import Document
|
||||
from docsgpt.parser.schema.base import Document
|
||||
|
||||
|
||||
# =====================================================================
|
||||
@@ -47,54 +47,54 @@ def temp_dir_with_types(tmp_path):
|
||||
class TestSimpleDirectoryReaderInit:
|
||||
|
||||
def test_init_with_dir(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
reader = SimpleDirectoryReader(input_dir=str(temp_dir))
|
||||
assert len(reader.input_files) >= 2
|
||||
|
||||
def test_init_with_files(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
files = [str(temp_dir / "file1.md")]
|
||||
reader = SimpleDirectoryReader(input_files=files)
|
||||
assert len(reader.input_files) == 1
|
||||
|
||||
def test_init_requires_input(self):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
with pytest.raises(ValueError, match="Must provide"):
|
||||
SimpleDirectoryReader()
|
||||
|
||||
def test_exclude_hidden(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
reader = SimpleDirectoryReader(input_dir=str(temp_dir), exclude_hidden=True)
|
||||
filenames = [f.name for f in reader.input_files]
|
||||
assert ".hidden" not in filenames
|
||||
|
||||
def test_include_hidden(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
reader = SimpleDirectoryReader(input_dir=str(temp_dir), exclude_hidden=False)
|
||||
filenames = [f.name for f in reader.input_files]
|
||||
assert ".hidden" in filenames
|
||||
|
||||
def test_recursive(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
reader = SimpleDirectoryReader(input_dir=str(temp_dir), recursive=True)
|
||||
filenames = [f.name for f in reader.input_files]
|
||||
assert "file3.md" in filenames
|
||||
|
||||
def test_non_recursive(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
reader = SimpleDirectoryReader(input_dir=str(temp_dir), recursive=False)
|
||||
filenames = [f.name for f in reader.input_files]
|
||||
assert "file3.md" not in filenames
|
||||
|
||||
def test_required_exts(self, temp_dir_with_types):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
reader = SimpleDirectoryReader(
|
||||
input_dir=str(temp_dir_with_types), required_exts=[".md"]
|
||||
@@ -105,7 +105,7 @@ class TestSimpleDirectoryReaderInit:
|
||||
assert "notes.txt" not in filenames
|
||||
|
||||
def test_required_exts_case_insensitive(self, tmp_path):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
(tmp_path / "FILE.MD").write_text("content")
|
||||
reader = SimpleDirectoryReader(
|
||||
@@ -114,7 +114,7 @@ class TestSimpleDirectoryReaderInit:
|
||||
assert len(reader.input_files) == 1
|
||||
|
||||
def test_num_files_limit(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
reader = SimpleDirectoryReader(
|
||||
input_dir=str(temp_dir), num_files_limit=1, recursive=False
|
||||
@@ -122,7 +122,7 @@ class TestSimpleDirectoryReaderInit:
|
||||
assert len(reader.input_files) <= 1
|
||||
|
||||
def test_custom_file_extractor(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
mock_parser = MagicMock()
|
||||
reader = SimpleDirectoryReader(
|
||||
@@ -141,7 +141,7 @@ class TestSimpleDirectoryReaderInit:
|
||||
class TestSimpleDirectoryReaderLoadData:
|
||||
|
||||
def test_load_data_returns_documents(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
mock_parser = MagicMock()
|
||||
mock_parser.parser_config_set = True
|
||||
@@ -166,8 +166,8 @@ class TestSimpleDirectoryReaderLoadData:
|
||||
traceback as the document text, so an unguarded loop turns a single
|
||||
corrupt PDF into a failed ingest for the whole zip/folder/sync.
|
||||
"""
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
(tmp_path / "good1.md").write_text("first")
|
||||
(tmp_path / "bad.md").write_text("corrupt")
|
||||
@@ -203,8 +203,8 @@ class TestSimpleDirectoryReaderLoadData:
|
||||
Skipping there would hand ``load_data()[0]`` an empty list and turn a
|
||||
clear "this PDF could not be read" into an IndexError.
|
||||
"""
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
(tmp_path / "bad.pdf").write_text("not really a pdf")
|
||||
|
||||
@@ -224,8 +224,8 @@ class TestSimpleDirectoryReaderLoadData:
|
||||
|
||||
def test_all_files_unparseable_raises(self, tmp_path):
|
||||
"""Nothing parsed is a failed ingest, not an empty success."""
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
(tmp_path / "a.md").write_text("x")
|
||||
(tmp_path / "b.md").write_text("y")
|
||||
@@ -246,8 +246,8 @@ class TestSimpleDirectoryReaderLoadData:
|
||||
|
||||
def test_skipped_file_still_advances_progress(self, tmp_path):
|
||||
"""Progress must not stall on a skipped file."""
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
(tmp_path / "good.md").write_text("ok")
|
||||
(tmp_path / "bad.md").write_text("bad")
|
||||
@@ -273,7 +273,7 @@ class TestSimpleDirectoryReaderLoadData:
|
||||
assert [c[0] for c in calls] == [1, 2]
|
||||
|
||||
def test_load_data_progress_callback_fires_per_file(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
reader = SimpleDirectoryReader(
|
||||
input_dir=str(temp_dir), recursive=False, exclude_hidden=True,
|
||||
@@ -288,7 +288,7 @@ class TestSimpleDirectoryReaderLoadData:
|
||||
assert all(c[1] == total_files for c in calls)
|
||||
|
||||
def test_load_data_progress_callback_errors_swallowed(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
reader = SimpleDirectoryReader(
|
||||
input_dir=str(temp_dir), recursive=False, exclude_hidden=True,
|
||||
@@ -302,7 +302,7 @@ class TestSimpleDirectoryReaderLoadData:
|
||||
assert len(docs) >= 1
|
||||
|
||||
def test_load_data_concatenate(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
mock_parser = MagicMock()
|
||||
mock_parser.parser_config_set = True
|
||||
@@ -319,7 +319,7 @@ class TestSimpleDirectoryReaderLoadData:
|
||||
assert len(docs) == 1
|
||||
|
||||
def test_load_data_with_file_metadata(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
def custom_metadata(filename):
|
||||
return {"custom_key": f"meta_{filename}"}
|
||||
@@ -343,7 +343,7 @@ class TestSimpleDirectoryReaderLoadData:
|
||||
assert "custom_key" in doc.extra_info
|
||||
|
||||
def test_load_data_inits_parser_if_not_set(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
mock_parser = MagicMock()
|
||||
mock_parser.parser_config_set = False
|
||||
@@ -360,7 +360,7 @@ class TestSimpleDirectoryReaderLoadData:
|
||||
mock_parser.init_parser.assert_called()
|
||||
|
||||
def test_load_data_standard_read_for_unknown_ext(self, tmp_path):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
(tmp_path / "file.xyz").write_text("xyz content")
|
||||
reader = SimpleDirectoryReader(
|
||||
@@ -372,7 +372,7 @@ class TestSimpleDirectoryReaderLoadData:
|
||||
assert "xyz content" in docs[0].text
|
||||
|
||||
def test_load_data_list_return_from_parser(self, tmp_path):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
(tmp_path / "multi.md").write_text("content")
|
||||
mock_parser = MagicMock()
|
||||
@@ -388,7 +388,7 @@ class TestSimpleDirectoryReaderLoadData:
|
||||
assert len(docs) == 2
|
||||
|
||||
def test_load_data_tracks_token_counts(self, tmp_path):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
(tmp_path / "test.md").write_text("hello world")
|
||||
mock_parser = MagicMock()
|
||||
@@ -414,7 +414,7 @@ class TestSimpleDirectoryReaderLoadData:
|
||||
class TestBuildDirectoryStructure:
|
||||
|
||||
def test_builds_structure(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
mock_parser = MagicMock()
|
||||
mock_parser.parser_config_set = True
|
||||
@@ -431,7 +431,7 @@ class TestBuildDirectoryStructure:
|
||||
assert isinstance(reader.directory_structure, dict)
|
||||
|
||||
def test_structure_contains_files_and_dirs(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
mock_parser = MagicMock()
|
||||
mock_parser.parser_config_set = True
|
||||
@@ -453,7 +453,7 @@ class TestBuildDirectoryStructure:
|
||||
assert "size_bytes" in val
|
||||
|
||||
def test_structure_excludes_hidden(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
mock_parser = MagicMock()
|
||||
mock_parser.parser_config_set = True
|
||||
@@ -469,7 +469,7 @@ class TestBuildDirectoryStructure:
|
||||
assert ".hidden" not in reader.directory_structure
|
||||
|
||||
def test_no_structure_without_input_dir(self, temp_dir):
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
files = [str(temp_dir / "file1.md")]
|
||||
mock_parser = MagicMock()
|
||||
@@ -494,7 +494,7 @@ class TestBuildDirectoryStructure:
|
||||
class TestGetDefaultFileExtractor:
|
||||
|
||||
def test_returns_dict(self):
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
with patch.dict("sys.modules", {"docling": None, "docling.document_converter": None}):
|
||||
result = get_default_file_extractor()
|
||||
@@ -503,7 +503,7 @@ class TestGetDefaultFileExtractor:
|
||||
|
||||
def test_fallback_parsers_on_import_error(self):
|
||||
with patch(
|
||||
"application.parser.file.bulk.get_default_file_extractor"
|
||||
"docsgpt.parser.file.bulk.get_default_file_extractor"
|
||||
) as mock_fn:
|
||||
mock_fn.return_value = {".pdf": MagicMock(), ".md": MagicMock()}
|
||||
result = mock_fn()
|
||||
@@ -521,7 +521,7 @@ class TestParserEngineSwitch:
|
||||
|
||||
@pytest.fixture
|
||||
def settings(self):
|
||||
from application.core.settings import settings
|
||||
from docsgpt.core.settings import settings
|
||||
|
||||
return settings
|
||||
|
||||
@@ -529,16 +529,16 @@ class TestParserEngineSwitch:
|
||||
def _default_ocr_settings(self, monkeypatch):
|
||||
"""Pin the OCR knobs to their defaults so a developer's ``.env`` (OCR on,
|
||||
native backend) cannot change which parsers the maps hand out here."""
|
||||
from application.core.settings import settings
|
||||
from docsgpt.core.settings import settings
|
||||
|
||||
monkeypatch.setattr(settings, "OCR_ENABLED", False)
|
||||
monkeypatch.setattr(settings, "OCR_BACKEND", "auto")
|
||||
|
||||
def test_default_engine_is_anydoc(self, settings, monkeypatch):
|
||||
pytest.importorskip("anydoc")
|
||||
from application.parser.file.anydoc_parser import ANYDOC_SUFFIXES, AnydocParser
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from application.parser.file.html_parser import HTMLMarkdownParser
|
||||
from docsgpt.parser.file.anydoc_parser import ANYDOC_SUFFIXES, AnydocParser
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.html_parser import HTMLMarkdownParser
|
||||
|
||||
monkeypatch.setattr(settings, "DOC_PARSER_ENGINE", "anydoc")
|
||||
extractor = get_default_file_extractor()
|
||||
@@ -555,7 +555,7 @@ class TestParserEngineSwitch:
|
||||
def test_anydoc_falls_back_to_docling_when_installed(self):
|
||||
pytest.importorskip("anydoc")
|
||||
pytest.importorskip("docling")
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
extractor = get_default_file_extractor(engine="anydoc")
|
||||
|
||||
@@ -568,7 +568,7 @@ class TestParserEngineSwitch:
|
||||
def test_anydoc_fallback_honours_ocr_flag(self):
|
||||
pytest.importorskip("anydoc")
|
||||
pytest.importorskip("docling")
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
extractor = get_default_file_extractor(engine="anydoc", ocr_enabled=True)
|
||||
|
||||
@@ -579,7 +579,7 @@ class TestParserEngineSwitch:
|
||||
pytest.importorskip("anydoc")
|
||||
import sys
|
||||
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
monkeypatch.setitem(sys.modules, "docling", None)
|
||||
extractor = get_default_file_extractor(engine="anydoc")
|
||||
@@ -590,7 +590,7 @@ class TestParserEngineSwitch:
|
||||
|
||||
def test_docling_engine_keeps_docling_map(self):
|
||||
pytest.importorskip("docling")
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
extractor = get_default_file_extractor(engine="docling")
|
||||
|
||||
@@ -599,22 +599,22 @@ class TestParserEngineSwitch:
|
||||
|
||||
def test_setting_selects_docling(self, settings, monkeypatch):
|
||||
pytest.importorskip("docling")
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
monkeypatch.setattr(settings, "DOC_PARSER_ENGINE", "docling")
|
||||
assert type(get_default_file_extractor()[".pdf"]).__name__ == "DoclingPDFParser"
|
||||
|
||||
def test_unknown_engine_uses_anydoc(self):
|
||||
pytest.importorskip("anydoc")
|
||||
from application.parser.file.anydoc_parser import AnydocParser
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.anydoc_parser import AnydocParser
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
assert isinstance(get_default_file_extractor(engine="ghost")[".pdf"], AnydocParser)
|
||||
|
||||
def test_missing_anydoc_degrades_to_base_engine(self, monkeypatch):
|
||||
import sys
|
||||
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
monkeypatch.setitem(sys.modules, "anydoc", None)
|
||||
extractor = get_default_file_extractor(engine="anydoc")
|
||||
@@ -628,7 +628,7 @@ class TestParserEngineSwitch:
|
||||
the first file aborted the whole ingest instead of degrading."""
|
||||
import sys
|
||||
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
monkeypatch.setitem(sys.modules, "docling", None)
|
||||
extractor = get_default_file_extractor(engine="docling")
|
||||
@@ -655,8 +655,8 @@ class TestGainedFormats:
|
||||
(`.txt` is the one deliberate plain-text read.)
|
||||
"""
|
||||
pytest.importorskip("anydoc")
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from application.parser.file.constants import (
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.constants import (
|
||||
SUPPORTED_SOURCE_DOCUMENT_EXTENSIONS,
|
||||
)
|
||||
|
||||
@@ -671,11 +671,11 @@ class TestGainedFormats:
|
||||
|
||||
def test_gained_formats_map_to_anydoc_under_both_engines(self):
|
||||
pytest.importorskip("anydoc")
|
||||
from application.parser.file.anydoc_parser import (
|
||||
from docsgpt.parser.file.anydoc_parser import (
|
||||
ANYDOC_GAINED_SUFFIXES,
|
||||
AnydocParser,
|
||||
)
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
for engine in ("anydoc", "docling"):
|
||||
extractor = get_default_file_extractor(engine=engine)
|
||||
@@ -684,8 +684,8 @@ class TestGainedFormats:
|
||||
|
||||
def test_gained_formats_never_fall_back_to_anydoc_itself(self):
|
||||
pytest.importorskip("anydoc")
|
||||
from application.parser.file.anydoc_parser import AnydocParser
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.anydoc_parser import AnydocParser
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
extractor = get_default_file_extractor(engine="anydoc")
|
||||
assert extractor[".doc"].fallback_parser is None
|
||||
@@ -697,7 +697,7 @@ class TestGainedFormats:
|
||||
def test_gained_entries_absent_without_anydoc(self, monkeypatch):
|
||||
import sys
|
||||
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
monkeypatch.setitem(sys.modules, "anydoc", None)
|
||||
extractor = get_default_file_extractor(engine="docling")
|
||||
@@ -705,7 +705,7 @@ class TestGainedFormats:
|
||||
|
||||
def test_rtf_converts_end_to_end(self, tmp_path):
|
||||
pytest.importorskip("anydoc")
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
path = tmp_path / "note.rtf"
|
||||
path.write_text(r"{\rtf1\ansi Hello {\b bold} world.\par Second paragraph.}")
|
||||
@@ -721,7 +721,7 @@ class TestGainedFormats:
|
||||
pytest.importorskip("anydoc")
|
||||
import zipfile
|
||||
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
path = tmp_path / "doc.odt"
|
||||
with zipfile.ZipFile(path, "w") as z:
|
||||
@@ -753,8 +753,8 @@ class TestGainedFormats:
|
||||
|
||||
def test_gained_suffix_without_a_parser_is_rejected_not_read_as_text(tmp_path):
|
||||
"""Without anydoc an OLE .doc must not be indexed as decoded binary garbage."""
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.bulk import SimpleDirectoryReader
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.bulk import SimpleDirectoryReader
|
||||
|
||||
path = tmp_path / "legacy.doc"
|
||||
path.write_bytes(b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1" + b"\x00" * 64)
|
||||
|
||||
@@ -6,8 +6,8 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from application.parser.file.constants import (
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.constants import (
|
||||
ATTACHMENT_PARSER_EXTENSIONS,
|
||||
attachment_extension,
|
||||
has_attachment_parser,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Comprehensive tests for application/parser/file/docling_parser.py
|
||||
"""Comprehensive tests for docsgpt/parser/file/docling_parser.py
|
||||
|
||||
Covers: DoclingParser (init, _init_parser, OCR engine selection, _export_content,
|
||||
parse_file), subclass initialization, error handling.
|
||||
@@ -21,7 +21,7 @@ import pytest
|
||||
class TestDoclingParserInit:
|
||||
|
||||
def test_default_init(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser()
|
||||
assert parser.ocr_enabled is True
|
||||
@@ -33,7 +33,7 @@ class TestDoclingParserInit:
|
||||
assert parser._converter is None
|
||||
|
||||
def test_custom_init(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser(
|
||||
ocr_enabled=False,
|
||||
@@ -60,7 +60,7 @@ class TestDoclingParserInit:
|
||||
class TestDoclingParserInitParser:
|
||||
|
||||
def test_init_parser_raises_without_docling(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser()
|
||||
|
||||
@@ -72,7 +72,7 @@ class TestDoclingParserInitParser:
|
||||
"""A missing parent package makes find_spec raise; the hint must still show."""
|
||||
import sys
|
||||
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
for name in [m for m in sys.modules if m == "docling" or m.startswith("docling.")]:
|
||||
monkeypatch.delitem(sys.modules, name)
|
||||
@@ -82,7 +82,7 @@ class TestDoclingParserInitParser:
|
||||
DoclingParser()._init_parser()
|
||||
|
||||
def test_init_parser_success(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser()
|
||||
|
||||
@@ -108,14 +108,14 @@ class TestOcrEngineSelection:
|
||||
|
||||
@pytest.fixture
|
||||
def settings(self):
|
||||
from application.core.settings import settings
|
||||
from docsgpt.core.settings import settings
|
||||
|
||||
return settings
|
||||
|
||||
def test_default_setting_is_tesseract(self):
|
||||
# Field defaults, not the live settings: a developer's ``.env`` may
|
||||
# legitimately set other engines/languages.
|
||||
from application.core.settings import Settings
|
||||
from docsgpt.core.settings import Settings
|
||||
|
||||
defaults = Settings.model_construct()
|
||||
assert defaults.OCR_ENGINE == "tesseract"
|
||||
@@ -123,30 +123,30 @@ class TestOcrEngineSelection:
|
||||
assert defaults.OCR_BACKEND == "auto"
|
||||
|
||||
def test_none_reads_setting(self, settings, monkeypatch):
|
||||
from application.parser.file.docling_parser import _resolve_ocr_engine
|
||||
from docsgpt.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
|
||||
from docsgpt.parser.file.docling_parser import _resolve_ocr_engine
|
||||
|
||||
assert _resolve_ocr_engine("easyocr") == "auto"
|
||||
|
||||
def test_tesseract_without_binary_degrades_to_auto(self, monkeypatch):
|
||||
from application.parser.file import docling_parser as dp
|
||||
from docsgpt.parser.file import 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):
|
||||
from application.parser.file import docling_parser as dp
|
||||
from docsgpt.parser.file import 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):
|
||||
from application.parser.file import docling_parser as dp
|
||||
from docsgpt.parser.file import docling_parser as dp
|
||||
|
||||
monkeypatch.setattr(dp.sys, "platform", "linux")
|
||||
assert dp._resolve_ocr_engine("ocrmac") == "auto"
|
||||
@@ -154,25 +154,25 @@ class TestOcrEngineSelection:
|
||||
def test_rapidocr_missing_degrades_to_auto(self, monkeypatch):
|
||||
import sys
|
||||
|
||||
from application.parser.file import docling_parser as dp
|
||||
from docsgpt.parser.file import 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
|
||||
from docsgpt.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
|
||||
from docsgpt.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")
|
||||
import application.parser.file.ocr_parser as op
|
||||
from application.parser.file.docling_parser import _build_ocr_options
|
||||
import docsgpt.parser.file.ocr_parser as op
|
||||
from docsgpt.parser.file.docling_parser import _build_ocr_options
|
||||
|
||||
# Pack inventory unknown: the resolved list is passed through untouched
|
||||
# (a host with tesseract but no chi_sim pack would otherwise drop it).
|
||||
@@ -186,8 +186,8 @@ class TestOcrEngineSelection:
|
||||
|
||||
def test_build_tesseract_explicit_languages_win(self, monkeypatch):
|
||||
pytest.importorskip("docling")
|
||||
import application.parser.file.ocr_parser as op
|
||||
from application.parser.file.docling_parser import _build_ocr_options
|
||||
import docsgpt.parser.file.ocr_parser as op
|
||||
from docsgpt.parser.file.docling_parser import _build_ocr_options
|
||||
|
||||
# Pack inventory unknown: the requested list is passed through untouched.
|
||||
monkeypatch.setattr(op, "tesseract_languages", lambda: None)
|
||||
@@ -196,7 +196,7 @@ class TestOcrEngineSelection:
|
||||
|
||||
def test_build_rapidocr(self):
|
||||
pytest.importorskip("docling")
|
||||
from application.parser.file.docling_parser import _build_ocr_options
|
||||
from docsgpt.parser.file.docling_parser import _build_ocr_options
|
||||
|
||||
options = _build_ocr_options("rapidocr", None, False)
|
||||
assert type(options).__name__ == "RapidOcrOptions"
|
||||
@@ -205,7 +205,7 @@ class TestOcrEngineSelection:
|
||||
def test_build_import_failure_returns_none(self, monkeypatch):
|
||||
import sys
|
||||
|
||||
from application.parser.file.docling_parser import _build_ocr_options
|
||||
from docsgpt.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
|
||||
@@ -215,7 +215,7 @@ class TestOcrEngineSelection:
|
||||
import sys
|
||||
import types
|
||||
|
||||
from application.parser.file.docling_parser import _build_ocr_options
|
||||
from docsgpt.parser.file.docling_parser import _build_ocr_options
|
||||
|
||||
fake = types.ModuleType("docling.datamodel.pipeline_options")
|
||||
|
||||
@@ -236,8 +236,8 @@ class TestDeepseekVlmConverter:
|
||||
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
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
monkeypatch.setattr(settings, "OCR_DEEPSEEK_URL", "http://gpu-host:8000/v1/chat/completions")
|
||||
monkeypatch.setattr(settings, "OCR_DEEPSEEK_MODEL", "deepseek-ocr-x")
|
||||
@@ -267,7 +267,7 @@ class TestDeepseekVlmConverter:
|
||||
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
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
vlm_called = []
|
||||
monkeypatch.setattr(
|
||||
@@ -290,7 +290,7 @@ class TestDeepseekVlmConverter:
|
||||
class TestExportContent:
|
||||
|
||||
def test_export_markdown(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser(export_format="markdown")
|
||||
mock_doc = MagicMock()
|
||||
@@ -302,7 +302,7 @@ class TestExportContent:
|
||||
mock_doc.export_to_markdown.assert_called_once()
|
||||
|
||||
def test_export_html(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser(export_format="html")
|
||||
mock_doc = MagicMock()
|
||||
@@ -313,7 +313,7 @@ class TestExportContent:
|
||||
assert "<h1>" in result
|
||||
|
||||
def test_export_text(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser(export_format="text")
|
||||
mock_doc = MagicMock()
|
||||
@@ -324,7 +324,7 @@ class TestExportContent:
|
||||
assert "Plain text" in result
|
||||
|
||||
def test_fallback_to_texts_on_minimal_content(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser(export_format="markdown")
|
||||
mock_doc = MagicMock()
|
||||
@@ -341,7 +341,7 @@ class TestExportContent:
|
||||
assert "OCR extracted text 2" in result
|
||||
|
||||
def test_no_fallback_for_substantial_content(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser(export_format="markdown")
|
||||
mock_doc = MagicMock()
|
||||
@@ -352,7 +352,7 @@ class TestExportContent:
|
||||
assert result == "A" * 100
|
||||
|
||||
def test_fallback_skipped_when_no_texts(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser(export_format="markdown")
|
||||
mock_doc = MagicMock()
|
||||
@@ -363,7 +363,7 @@ class TestExportContent:
|
||||
assert result == "short"
|
||||
|
||||
def test_fallback_skips_empty_texts(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser(export_format="markdown")
|
||||
mock_doc = MagicMock()
|
||||
@@ -386,7 +386,7 @@ class TestExportContent:
|
||||
class TestDoclingParserParseFile:
|
||||
|
||||
def test_parse_file_success(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser()
|
||||
|
||||
@@ -403,7 +403,7 @@ class TestDoclingParserParseFile:
|
||||
assert "Parsed document content" in result
|
||||
|
||||
def test_parse_file_inits_converter_on_first_call(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser()
|
||||
parser._converter = None
|
||||
@@ -433,8 +433,8 @@ class TestDoclingParserParseFile:
|
||||
as if it were the PDF. ``errors`` controls *decoding* leniency, not
|
||||
"substitute the traceback for the document".
|
||||
"""
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser()
|
||||
mock_converter = MagicMock()
|
||||
@@ -453,8 +453,8 @@ class TestDoclingParserParseFile:
|
||||
|
||||
def test_parse_file_error_ignore_never_returns_a_string(self):
|
||||
"""Belt-and-braces: no code path may hand back error text as content."""
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser()
|
||||
mock_converter = MagicMock()
|
||||
@@ -474,7 +474,7 @@ class TestDoclingParserParseFile:
|
||||
)
|
||||
|
||||
def test_parse_file_error_raise(self):
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
parser = DoclingParser()
|
||||
mock_converter = MagicMock()
|
||||
@@ -494,82 +494,82 @@ class TestDoclingParserParseFile:
|
||||
class TestDoclingSubclasses:
|
||||
|
||||
def test_pdf_parser_init(self):
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
parser = DoclingPDFParser()
|
||||
assert parser.ocr_enabled is True
|
||||
assert parser.export_format == "markdown"
|
||||
|
||||
def test_pdf_parser_custom_ocr(self):
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
parser = DoclingPDFParser(ocr_enabled=False, force_full_page_ocr=True)
|
||||
assert parser.ocr_enabled is False
|
||||
assert parser.force_full_page_ocr is True
|
||||
|
||||
def test_docx_parser_init(self):
|
||||
from application.parser.file.docling_parser import DoclingDocxParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingDocxParser
|
||||
|
||||
parser = DoclingDocxParser()
|
||||
assert parser.export_format == "markdown"
|
||||
|
||||
def test_pptx_parser_init(self):
|
||||
from application.parser.file.docling_parser import DoclingPPTXParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingPPTXParser
|
||||
|
||||
parser = DoclingPPTXParser()
|
||||
assert parser.export_format == "markdown"
|
||||
|
||||
def test_xlsx_parser_init(self):
|
||||
from application.parser.file.docling_parser import DoclingXLSXParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingXLSXParser
|
||||
|
||||
parser = DoclingXLSXParser()
|
||||
assert parser.table_structure is True
|
||||
|
||||
def test_html_parser_init(self):
|
||||
from application.parser.file.docling_parser import DoclingHTMLParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingHTMLParser
|
||||
|
||||
parser = DoclingHTMLParser()
|
||||
assert parser.export_format == "markdown"
|
||||
|
||||
def test_image_parser_init(self):
|
||||
from application.parser.file.docling_parser import DoclingImageParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingImageParser
|
||||
|
||||
parser = DoclingImageParser()
|
||||
assert parser.ocr_enabled is True
|
||||
assert parser.force_full_page_ocr is True
|
||||
|
||||
def test_image_parser_custom(self):
|
||||
from application.parser.file.docling_parser import DoclingImageParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingImageParser
|
||||
|
||||
parser = DoclingImageParser(ocr_enabled=False)
|
||||
assert parser.ocr_enabled is False
|
||||
|
||||
def test_csv_parser_init(self):
|
||||
from application.parser.file.docling_parser import DoclingCSVParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingCSVParser
|
||||
|
||||
parser = DoclingCSVParser()
|
||||
assert parser.table_structure is True
|
||||
|
||||
def test_markdown_parser_init(self):
|
||||
from application.parser.file.docling_parser import DoclingMarkdownParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingMarkdownParser
|
||||
|
||||
parser = DoclingMarkdownParser()
|
||||
assert parser.export_format == "markdown"
|
||||
|
||||
def test_asciidoc_parser_init(self):
|
||||
from application.parser.file.docling_parser import DoclingAsciiDocParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingAsciiDocParser
|
||||
|
||||
parser = DoclingAsciiDocParser()
|
||||
assert parser.export_format == "markdown"
|
||||
|
||||
def test_vtt_parser_init(self):
|
||||
from application.parser.file.docling_parser import DoclingVTTParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingVTTParser
|
||||
|
||||
parser = DoclingVTTParser()
|
||||
assert parser.export_format == "markdown"
|
||||
|
||||
def test_xml_parser_init(self):
|
||||
from application.parser.file.docling_parser import DoclingXMLParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingXMLParser
|
||||
|
||||
parser = DoclingXMLParser()
|
||||
assert parser.export_format == "markdown"
|
||||
@@ -584,7 +584,7 @@ class TestDoclingSubclasses:
|
||||
class TestDoclingParserGaps:
|
||||
def test_csv_parser_init(self):
|
||||
"""Cover line 289: DoclingCSVParser.__init__ calls super."""
|
||||
from application.parser.file.docling_parser import DoclingCSVParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingCSVParser
|
||||
|
||||
parser = DoclingCSVParser()
|
||||
assert parser.export_format == "markdown"
|
||||
@@ -611,13 +611,13 @@ class TestNonOcrParsersLeaveTextAlone:
|
||||
],
|
||||
)
|
||||
def test_ocr_is_off_by_construction(self, name):
|
||||
from application.parser.file import docling_parser as dp
|
||||
from docsgpt.parser.file import docling_parser as dp
|
||||
|
||||
assert getattr(dp, name)().ocr_enabled is False
|
||||
|
||||
def test_cjk_spaces_survive_in_a_docx_export(self, monkeypatch):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.docling_parser import DoclingDocxParser
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.docling_parser import DoclingDocxParser
|
||||
|
||||
monkeypatch.setattr(settings, "OCR_ENGINE", "tesseract")
|
||||
parser = DoclingDocxParser()
|
||||
@@ -628,7 +628,7 @@ class TestNonOcrParsersLeaveTextAlone:
|
||||
assert parser._postprocess_ocr_text(text) == text
|
||||
|
||||
def test_pdf_parser_with_tesseract_still_collapses(self):
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
parser = DoclingPDFParser(ocr_enabled=True)
|
||||
parser._active_ocr_engine = "tesseract"
|
||||
@@ -645,8 +645,8 @@ class TestApplyPipelineCaps:
|
||||
"""_apply_pipeline_caps bounds docling's threaded-pipeline buffering."""
|
||||
|
||||
def test_caps_threaded_pipeline_knobs(self, monkeypatch):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.docling_parser import _apply_pipeline_caps
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.docling_parser import _apply_pipeline_caps
|
||||
|
||||
monkeypatch.setattr(
|
||||
settings, "DOCLING_PIPELINE_QUEUE_MAX_SIZE", 2, raising=False
|
||||
@@ -668,8 +668,8 @@ class TestApplyPipelineCaps:
|
||||
assert opts.ocr_batch_size == 1
|
||||
|
||||
def test_queue_size_is_settings_driven(self, monkeypatch):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.docling_parser import _apply_pipeline_caps
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.docling_parser import _apply_pipeline_caps
|
||||
|
||||
monkeypatch.setattr(
|
||||
settings, "DOCLING_PIPELINE_QUEUE_MAX_SIZE", 6, raising=False
|
||||
@@ -684,8 +684,8 @@ class TestApplyPipelineCaps:
|
||||
|
||||
def test_misconfigured_zero_floors_to_one(self, monkeypatch):
|
||||
"""A 0 queue depth could deadlock the threaded pipeline — floor it."""
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.docling_parser import _apply_pipeline_caps
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.docling_parser import _apply_pipeline_caps
|
||||
|
||||
monkeypatch.setattr(
|
||||
settings, "DOCLING_PIPELINE_QUEUE_MAX_SIZE", 0, raising=False
|
||||
@@ -701,7 +701,7 @@ class TestApplyPipelineCaps:
|
||||
def test_noop_on_docling_without_threaded_pipeline(self):
|
||||
"""Builds predating the threaded pipeline lack the knobs — the cap
|
||||
must be a silent no-op, not an AttributeError."""
|
||||
from application.parser.file.docling_parser import _apply_pipeline_caps
|
||||
from docsgpt.parser.file.docling_parser import _apply_pipeline_caps
|
||||
|
||||
class LegacyOpts:
|
||||
__slots__ = ("do_ocr", "do_table_structure")
|
||||
@@ -738,8 +738,8 @@ class TestDoclingTabularSizeGate:
|
||||
return path
|
||||
|
||||
def test_oversized_csv_delegates_to_plain_csv_parser(self, tmp_path, monkeypatch):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.docling_parser import DoclingCSVParser, DoclingParser
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.docling_parser import DoclingCSVParser, DoclingParser
|
||||
|
||||
monkeypatch.setattr(settings, "DOCLING_TABULAR_MAX_BYTES", 64)
|
||||
docling_parse = MagicMock(name="docling_parse")
|
||||
@@ -755,8 +755,8 @@ class TestDoclingTabularSizeGate:
|
||||
assert out.startswith("0, 0\n1, 2\n")
|
||||
|
||||
def test_small_csv_still_uses_docling(self, tmp_path, monkeypatch):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.docling_parser import DoclingCSVParser, DoclingParser
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.docling_parser import DoclingCSVParser, DoclingParser
|
||||
|
||||
monkeypatch.setattr(settings, "DOCLING_TABULAR_MAX_BYTES", 10_000_000)
|
||||
docling_parse = MagicMock(name="docling_parse", return_value="DOCLING")
|
||||
@@ -768,8 +768,8 @@ class TestDoclingTabularSizeGate:
|
||||
docling_parse.assert_called_once()
|
||||
|
||||
def test_gate_disabled_when_max_bytes_is_zero(self, tmp_path, monkeypatch):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.docling_parser import DoclingCSVParser, DoclingParser
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.docling_parser import DoclingCSVParser, DoclingParser
|
||||
|
||||
monkeypatch.setattr(settings, "DOCLING_TABULAR_MAX_BYTES", 0)
|
||||
docling_parse = MagicMock(name="docling_parse", return_value="DOCLING")
|
||||
@@ -781,9 +781,9 @@ class TestDoclingTabularSizeGate:
|
||||
docling_parse.assert_called_once()
|
||||
|
||||
def test_oversized_xlsx_delegates_to_excel_parser(self, tmp_path, monkeypatch):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.docling_parser import DoclingParser, DoclingXLSXParser
|
||||
from application.parser.file.tabular_parser import ExcelParser
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser, DoclingXLSXParser
|
||||
from docsgpt.parser.file.tabular_parser import ExcelParser
|
||||
|
||||
monkeypatch.setattr(settings, "DOCLING_TABULAR_MAX_BYTES", 64)
|
||||
docling_parse = MagicMock(name="docling_parse")
|
||||
@@ -801,8 +801,8 @@ class TestDoclingTabularSizeGate:
|
||||
excel_parse.assert_called_once()
|
||||
|
||||
def test_small_xlsx_still_uses_docling(self, tmp_path, monkeypatch):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.docling_parser import DoclingParser, DoclingXLSXParser
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser, DoclingXLSXParser
|
||||
|
||||
monkeypatch.setattr(settings, "DOCLING_TABULAR_MAX_BYTES", 10_000_000)
|
||||
docling_parse = MagicMock(name="docling_parse", return_value="DOCLING")
|
||||
@@ -837,7 +837,7 @@ class TestTabularContentSize:
|
||||
(2.44 GB in docling) slips under a byte gate."""
|
||||
|
||||
def test_xlsx_content_size_is_inner_not_ondisk(self, tmp_path):
|
||||
from application.parser.file.docling_parser import _tabular_content_size
|
||||
from docsgpt.parser.file.docling_parser import _tabular_content_size
|
||||
|
||||
path = tmp_path / "data.xlsx"
|
||||
_make_xlsx(path, rows=5000)
|
||||
@@ -847,7 +847,7 @@ class TestTabularContentSize:
|
||||
assert inner > on_disk
|
||||
|
||||
def test_csv_content_size_is_ondisk(self, tmp_path):
|
||||
from application.parser.file.docling_parser import _tabular_content_size
|
||||
from docsgpt.parser.file.docling_parser import _tabular_content_size
|
||||
|
||||
path = tmp_path / "data.csv"
|
||||
path.write_text("a,b\n1,2\n3,4\n")
|
||||
@@ -855,13 +855,13 @@ class TestTabularContentSize:
|
||||
|
||||
def test_compressed_xlsx_over_inner_gate_delegates(self, tmp_path, monkeypatch):
|
||||
"""The regression: on-disk < threshold < inner-uncompressed must gate."""
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.docling_parser import (
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.docling_parser import (
|
||||
DoclingParser,
|
||||
DoclingXLSXParser,
|
||||
_tabular_content_size,
|
||||
)
|
||||
from application.parser.file.tabular_parser import ExcelParser
|
||||
from docsgpt.parser.file.tabular_parser import ExcelParser
|
||||
|
||||
path = tmp_path / "wide.xlsx"
|
||||
_make_xlsx(path, rows=5000)
|
||||
@@ -901,8 +901,8 @@ class TestDoclingMarkupGate:
|
||||
|
||||
@pytest.mark.parametrize("name", ["big.html", "big.vtt"])
|
||||
def test_oversized_markup_parses_truncated_copy(self, tmp_path, monkeypatch, name):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file import docling_parser as dp
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file import docling_parser as dp
|
||||
|
||||
monkeypatch.setattr(settings, "DOCLING_MARKUP_MAX_BYTES", 512)
|
||||
path = self._write(tmp_path, name, 4096)
|
||||
@@ -928,8 +928,8 @@ class TestDoclingMarkupGate:
|
||||
assert path.stat().st_size > 512, "original must be untouched"
|
||||
|
||||
def test_small_markup_parses_original(self, tmp_path, monkeypatch):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file import docling_parser as dp
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file import docling_parser as dp
|
||||
|
||||
monkeypatch.setattr(settings, "DOCLING_MARKUP_MAX_BYTES", 10_000_000)
|
||||
path = self._write(tmp_path, "small.html", 1024)
|
||||
@@ -946,8 +946,8 @@ class TestDoclingMarkupGate:
|
||||
assert seen["path"] == str(path)
|
||||
|
||||
def test_markup_gate_disabled_when_zero(self, tmp_path, monkeypatch):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file import docling_parser as dp
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file import docling_parser as dp
|
||||
|
||||
monkeypatch.setattr(settings, "DOCLING_MARKUP_MAX_BYTES", 0)
|
||||
path = self._write(tmp_path, "big.vtt", 8192)
|
||||
@@ -994,8 +994,8 @@ class TestTabularGateSeam:
|
||||
self, tmp_path, monkeypatch
|
||||
):
|
||||
"""The real incident, end to end: gate trips, blanks do not crash."""
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.docling_parser import (
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.docling_parser import (
|
||||
DoclingParser,
|
||||
DoclingXLSXParser,
|
||||
)
|
||||
@@ -1021,13 +1021,13 @@ class TestTabularGateSeam:
|
||||
``ingest`` and ``store_attachment``, and is the only exception
|
||||
``SimpleDirectoryReader.load_data`` skips rather than propagating.
|
||||
"""
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.docling_parser import (
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.docling_parser import (
|
||||
DoclingParser,
|
||||
DoclingXLSXParser,
|
||||
)
|
||||
from application.parser.file.tabular_parser import ExcelParser
|
||||
from docsgpt.parser.file.tabular_parser import ExcelParser
|
||||
|
||||
monkeypatch.setattr(settings, "DOCLING_TABULAR_MAX_BYTES", 64)
|
||||
monkeypatch.setattr(DoclingParser, "parse_file", MagicMock())
|
||||
@@ -1045,13 +1045,13 @@ class TestTabularGateSeam:
|
||||
def test_oversized_csv_failure_becomes_document_parse_error(
|
||||
self, tmp_path, monkeypatch
|
||||
):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.docling_parser import (
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.docling_parser import (
|
||||
DoclingCSVParser,
|
||||
DoclingParser,
|
||||
)
|
||||
from application.parser.file.tabular_parser import CSVParser
|
||||
from docsgpt.parser.file.tabular_parser import CSVParser
|
||||
|
||||
monkeypatch.setattr(settings, "DOCLING_TABULAR_MAX_BYTES", 8)
|
||||
monkeypatch.setattr(DoclingParser, "parse_file", MagicMock())
|
||||
@@ -1087,7 +1087,7 @@ class TestApplyInferenceSettings:
|
||||
pytest.importorskip("docling")
|
||||
|
||||
def test_disables_torch_compile_by_default(self, monkeypatch):
|
||||
from application.parser.file.docling_parser import _apply_inference_settings
|
||||
from docsgpt.parser.file.docling_parser import _apply_inference_settings
|
||||
|
||||
class Inference:
|
||||
compile_torch_models = True
|
||||
@@ -1105,8 +1105,8 @@ class TestApplyInferenceSettings:
|
||||
assert docling_settings.inference.compile_torch_models is False
|
||||
|
||||
def test_opt_in_reenables_torch_compile(self, monkeypatch):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.docling_parser import _apply_inference_settings
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.docling_parser import _apply_inference_settings
|
||||
|
||||
monkeypatch.setattr(
|
||||
settings, "DOCLING_COMPILE_TORCH_MODELS", True, raising=False
|
||||
@@ -1129,7 +1129,7 @@ class TestApplyInferenceSettings:
|
||||
|
||||
def test_noop_on_docling_without_inference_settings(self, monkeypatch):
|
||||
"""Builds predating the inference settings must be a silent no-op."""
|
||||
from application.parser.file.docling_parser import _apply_inference_settings
|
||||
from docsgpt.parser.file.docling_parser import _apply_inference_settings
|
||||
|
||||
class DoclingSettings:
|
||||
pass
|
||||
@@ -1142,11 +1142,11 @@ class TestApplyInferenceSettings:
|
||||
|
||||
def test_create_converter_applies_inference_settings(self, monkeypatch):
|
||||
"""The cap is worthless unless the converter path actually calls it."""
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
called = []
|
||||
monkeypatch.setattr(
|
||||
"application.parser.file.docling_parser._apply_inference_settings",
|
||||
"docsgpt.parser.file.docling_parser._apply_inference_settings",
|
||||
lambda: called.append(True),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
@@ -1167,11 +1167,11 @@ class TestApplyInferenceSettings:
|
||||
"""
|
||||
import docling.datamodel.pipeline_options as dpo
|
||||
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
events = []
|
||||
monkeypatch.setattr(
|
||||
"application.parser.file.docling_parser._apply_inference_settings",
|
||||
"docsgpt.parser.file.docling_parser._apply_inference_settings",
|
||||
lambda: events.append("settings"),
|
||||
)
|
||||
|
||||
@@ -1210,7 +1210,7 @@ def _mock_conversion(markdown: str, pages: int = 1) -> MagicMock:
|
||||
|
||||
def _set_threshold(monkeypatch, value: int) -> None:
|
||||
"""Point the OCR dropout guard at a specific chars-per-page floor."""
|
||||
from application.core.settings import settings as real_settings
|
||||
from docsgpt.core.settings import settings as real_settings
|
||||
|
||||
class _Stub:
|
||||
def __getattr__(self, name):
|
||||
@@ -1218,7 +1218,7 @@ def _set_threshold(monkeypatch, value: int) -> None:
|
||||
|
||||
stub = _Stub()
|
||||
stub.OCR_MIN_CHARS_PER_PAGE = value
|
||||
monkeypatch.setattr("application.core.settings.settings", stub)
|
||||
monkeypatch.setattr("docsgpt.core.settings.settings", stub)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@@ -1232,7 +1232,7 @@ class TestOCRDropoutGuard:
|
||||
"""
|
||||
|
||||
def test_near_empty_first_pass_recovers_on_fresh_converter(self, caplog):
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
parser = DoclingPDFParser(ocr_enabled=True)
|
||||
degraded = MagicMock()
|
||||
@@ -1274,8 +1274,8 @@ class TestOCRDropoutGuard:
|
||||
assert "Recovered scan.pdf on retry" in messages
|
||||
|
||||
def test_near_empty_both_passes_raises_document_parse_error(self):
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
parser = DoclingPDFParser(ocr_enabled=True)
|
||||
degraded = MagicMock()
|
||||
@@ -1301,11 +1301,11 @@ class TestOCRDropoutGuard:
|
||||
|
||||
def test_dropout_error_reports_an_unread_text_layer(self, monkeypatch):
|
||||
"""A text layer docling ignored points at the pipeline, not the scan."""
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
monkeypatch.setattr(
|
||||
"application.parser.file.docling_parser._pdf_text_layer_probe",
|
||||
"docsgpt.parser.file.docling_parser._pdf_text_layer_probe",
|
||||
lambda file: (12, 48_000),
|
||||
)
|
||||
parser = DoclingPDFParser(ocr_enabled=True)
|
||||
@@ -1319,7 +1319,7 @@ class TestOCRDropoutGuard:
|
||||
assert "The PDF carries a 48000-char text layer" in str(excinfo.value)
|
||||
|
||||
def test_ocr_disabled_returns_near_empty_content_untouched(self):
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
parser = DoclingPDFParser(ocr_enabled=False)
|
||||
converter = MagicMock()
|
||||
@@ -1334,7 +1334,7 @@ class TestOCRDropoutGuard:
|
||||
assert parser._converter is converter
|
||||
|
||||
def test_threshold_zero_disables_the_guard(self, monkeypatch):
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
_set_threshold(monkeypatch, 0)
|
||||
parser = DoclingPDFParser(ocr_enabled=True)
|
||||
@@ -1349,7 +1349,7 @@ class TestOCRDropoutGuard:
|
||||
assert parser._converter is converter
|
||||
|
||||
def test_healthy_ocr_parse_converts_once(self):
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
parser = DoclingPDFParser(ocr_enabled=True)
|
||||
converter = MagicMock()
|
||||
@@ -1368,7 +1368,7 @@ class TestOCRDropoutGuard:
|
||||
|
||||
def test_threshold_is_per_page_not_per_document(self):
|
||||
"""200 chars is healthy for one page and suspicious for a hundred."""
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
content = "x" * 200
|
||||
|
||||
@@ -1388,7 +1388,7 @@ class TestOCRDropoutGuard:
|
||||
with patch.object(
|
||||
long_doc, "_create_converter", return_value=long_doc._converter
|
||||
) as create, patch(
|
||||
"application.parser.file.docling_parser._pdf_text_layer_probe",
|
||||
"docsgpt.parser.file.docling_parser._pdf_text_layer_probe",
|
||||
return_value=(100, 0),
|
||||
):
|
||||
assert long_doc.parse_file(Path("hundred-page.pdf")) == content
|
||||
@@ -1396,7 +1396,7 @@ class TestOCRDropoutGuard:
|
||||
|
||||
def test_guard_does_not_apply_to_non_ocr_formats(self):
|
||||
"""DOCX parsers construct with OCR off: they never OCR anything."""
|
||||
from application.parser.file.docling_parser import DoclingDocxParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingDocxParser
|
||||
|
||||
parser = DoclingDocxParser()
|
||||
assert parser.ocr_enabled is False
|
||||
@@ -1411,7 +1411,7 @@ class TestOCRDropoutGuard:
|
||||
|
||||
def test_guard_applies_to_images(self):
|
||||
"""The retry covers images -- a degraded converter may still recover one."""
|
||||
from application.parser.file.docling_parser import DoclingImageParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingImageParser
|
||||
|
||||
parser = DoclingImageParser(ocr_enabled=True)
|
||||
degraded = MagicMock()
|
||||
@@ -1432,7 +1432,7 @@ class TestOCRDropoutGuard:
|
||||
DocumentParseError is in ``dont_autoretry_for``, so raising here fails a
|
||||
single-file upload permanently.
|
||||
"""
|
||||
from application.parser.file.docling_parser import DoclingImageParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingImageParser
|
||||
|
||||
parser = DoclingImageParser(ocr_enabled=True)
|
||||
converter = MagicMock()
|
||||
@@ -1447,7 +1447,7 @@ class TestOCRDropoutGuard:
|
||||
|
||||
def test_a_text_sparse_scan_indexes_rather_than_failing_the_upload(self):
|
||||
"""20 pages of pictures with a few captions is the document, not a fault."""
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
content = "Fig 1. Fig 2. Fig 3."
|
||||
parser = DoclingPDFParser(ocr_enabled=True)
|
||||
@@ -1458,15 +1458,15 @@ class TestOCRDropoutGuard:
|
||||
with patch.object(
|
||||
parser, "_create_converter", return_value=converter
|
||||
), patch(
|
||||
"application.parser.file.docling_parser._pdf_text_layer_probe",
|
||||
"docsgpt.parser.file.docling_parser._pdf_text_layer_probe",
|
||||
return_value=(20, 0),
|
||||
):
|
||||
assert parser.parse_file(Path("catalog.pdf")) == content
|
||||
|
||||
def test_a_multi_page_zero_char_parse_is_still_a_dropout(self):
|
||||
"""The incident this guard exists for: every page OCR'd to nothing."""
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
parser = DoclingPDFParser(ocr_enabled=True)
|
||||
converter = MagicMock()
|
||||
@@ -1476,7 +1476,7 @@ class TestOCRDropoutGuard:
|
||||
with patch.object(
|
||||
parser, "_create_converter", return_value=converter
|
||||
), patch(
|
||||
"application.parser.file.docling_parser._pdf_text_layer_probe",
|
||||
"docsgpt.parser.file.docling_parser._pdf_text_layer_probe",
|
||||
return_value=(40, 0),
|
||||
):
|
||||
with pytest.raises(DocumentParseError, match="scan.pdf"):
|
||||
@@ -1484,8 +1484,8 @@ class TestOCRDropoutGuard:
|
||||
|
||||
def test_a_pdf_whose_text_layer_was_missed_is_still_a_dropout(self):
|
||||
"""Text docling should have read without OCR at all -- positive evidence."""
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
parser = DoclingPDFParser(ocr_enabled=True)
|
||||
converter = MagicMock()
|
||||
@@ -1495,15 +1495,15 @@ class TestOCRDropoutGuard:
|
||||
with patch.object(
|
||||
parser, "_create_converter", return_value=converter
|
||||
), patch(
|
||||
"application.parser.file.docling_parser._pdf_text_layer_probe",
|
||||
"docsgpt.parser.file.docling_parser._pdf_text_layer_probe",
|
||||
return_value=(20, 5000),
|
||||
):
|
||||
with pytest.raises(DocumentParseError, match="text layer"):
|
||||
parser.parse_file(Path("report.pdf"))
|
||||
|
||||
def test_retry_conversion_failure_is_still_a_parse_error(self):
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
parser = DoclingPDFParser(ocr_enabled=True)
|
||||
parser._converter = MagicMock()
|
||||
@@ -1524,7 +1524,7 @@ class TestOCRDropoutGuard:
|
||||
class TestOCRDropoutHelpers:
|
||||
|
||||
def test_image_placeholders_are_not_text(self):
|
||||
from application.parser.file.docling_parser import _text_char_count
|
||||
from docsgpt.parser.file.docling_parser import _text_char_count
|
||||
|
||||
assert _text_char_count(None) == 0
|
||||
assert _text_char_count("") == 0
|
||||
@@ -1532,20 +1532,20 @@ class TestOCRDropoutHelpers:
|
||||
assert _text_char_count(" <!-- image --> abc ") == 3
|
||||
|
||||
def test_page_count_prefers_document_pages(self):
|
||||
from application.parser.file.docling_parser import _result_page_count
|
||||
from docsgpt.parser.file.docling_parser import _result_page_count
|
||||
|
||||
result = _mock_conversion("x", pages=7)
|
||||
assert _result_page_count(result, Path("a.pdf")) == 7
|
||||
|
||||
def test_page_count_falls_back_to_num_pages(self):
|
||||
from application.parser.file.docling_parser import _result_page_count
|
||||
from docsgpt.parser.file.docling_parser import _result_page_count
|
||||
|
||||
result = _mock_conversion("x", pages=0)
|
||||
result.document.num_pages.return_value = 5
|
||||
assert _result_page_count(result, Path("a.pdf")) == 5
|
||||
|
||||
def test_page_count_falls_back_to_result_pages(self):
|
||||
from application.parser.file.docling_parser import _result_page_count
|
||||
from docsgpt.parser.file.docling_parser import _result_page_count
|
||||
|
||||
result = _mock_conversion("x", pages=0)
|
||||
result.document.num_pages.return_value = None
|
||||
@@ -1553,7 +1553,7 @@ class TestOCRDropoutHelpers:
|
||||
assert _result_page_count(result, Path("a.pdf")) == 2
|
||||
|
||||
def test_page_count_defaults_to_one(self):
|
||||
from application.parser.file.docling_parser import _result_page_count
|
||||
from docsgpt.parser.file.docling_parser import _result_page_count
|
||||
|
||||
result = _mock_conversion("x", pages=0)
|
||||
result.document.num_pages.return_value = None
|
||||
@@ -1561,13 +1561,13 @@ class TestOCRDropoutHelpers:
|
||||
assert _result_page_count(result, Path("a.pdf")) == 1
|
||||
|
||||
def test_images_are_always_one_page(self):
|
||||
from application.parser.file.docling_parser import _result_page_count
|
||||
from docsgpt.parser.file.docling_parser import _result_page_count
|
||||
|
||||
result = _mock_conversion("x", pages=9)
|
||||
assert _result_page_count(result, Path("a.png")) == 1
|
||||
|
||||
def test_threshold_reads_settings_with_a_default(self, monkeypatch):
|
||||
from application.parser.file.docling_parser import _ocr_min_chars_per_page
|
||||
from docsgpt.parser.file.docling_parser import _ocr_min_chars_per_page
|
||||
|
||||
assert _ocr_min_chars_per_page() == 20
|
||||
_set_threshold(monkeypatch, 5)
|
||||
@@ -1576,7 +1576,7 @@ class TestOCRDropoutHelpers:
|
||||
assert _ocr_min_chars_per_page() == 20
|
||||
|
||||
def test_text_layer_probe_survives_an_unreadable_pdf(self, tmp_path):
|
||||
from application.parser.file.docling_parser import _pdf_text_layer_probe
|
||||
from docsgpt.parser.file.docling_parser import _pdf_text_layer_probe
|
||||
|
||||
assert _pdf_text_layer_probe(tmp_path / "missing.pdf") == (0, 0)
|
||||
broken = tmp_path / "broken.pdf"
|
||||
@@ -1597,7 +1597,7 @@ class TestForceFullPageOCRWiring:
|
||||
def _pipeline_options(self, monkeypatch, **kwargs):
|
||||
import docling.datamodel.pipeline_options as dpo
|
||||
|
||||
from application.parser.file.docling_parser import DoclingParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingParser
|
||||
|
||||
built = []
|
||||
real_options = dpo.PdfPipelineOptions
|
||||
@@ -1636,7 +1636,7 @@ class TestDoclingTesseractPostprocessing:
|
||||
"""docling's tesseract path gets the same CJK glyph-space cleanup as the native engine."""
|
||||
|
||||
def _parser_with_export(self, engine, ocr_enabled=True):
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
parser = DoclingPDFParser(ocr_enabled=ocr_enabled)
|
||||
parser._active_ocr_engine = engine
|
||||
@@ -1660,7 +1660,7 @@ class TestDoclingTesseractPostprocessing:
|
||||
@pytest.mark.unit
|
||||
class TestDoclingOcrPages:
|
||||
def test_converts_each_requested_page_with_page_range(self, tmp_path):
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
parser = DoclingPDFParser(ocr_enabled=True)
|
||||
converter = MagicMock()
|
||||
@@ -1683,8 +1683,8 @@ class TestDoclingOcrPages:
|
||||
assert [c.kwargs["page_range"] for c in converter.convert.call_args_list] == [(3, 3), (1, 1)]
|
||||
|
||||
def test_failure_is_a_parse_error(self, tmp_path):
|
||||
from application.parser.file.base_parser import DocumentParseError
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
parser = DoclingPDFParser(ocr_enabled=True)
|
||||
parser._converter = MagicMock()
|
||||
@@ -1699,8 +1699,8 @@ class TestDoclingOcrPages:
|
||||
class TestTesseractLanguageFilter:
|
||||
def test_uninstalled_packs_are_dropped_with_a_warning(self, monkeypatch, caplog):
|
||||
pytest.importorskip("docling")
|
||||
import application.parser.file.ocr_parser as op
|
||||
from application.parser.file.docling_parser import _build_ocr_options
|
||||
import docsgpt.parser.file.ocr_parser as op
|
||||
from docsgpt.parser.file.docling_parser import _build_ocr_options
|
||||
|
||||
monkeypatch.setattr(op, "tesseract_languages", lambda: frozenset({"eng", "osd"}))
|
||||
with caplog.at_level("WARNING"):
|
||||
@@ -1710,16 +1710,16 @@ class TestTesseractLanguageFilter:
|
||||
|
||||
def test_all_packs_missing_falls_back_to_eng(self, monkeypatch):
|
||||
pytest.importorskip("docling")
|
||||
import application.parser.file.ocr_parser as op
|
||||
from application.parser.file.docling_parser import _build_ocr_options
|
||||
import docsgpt.parser.file.ocr_parser as op
|
||||
from docsgpt.parser.file.docling_parser import _build_ocr_options
|
||||
|
||||
monkeypatch.setattr(op, "tesseract_languages", lambda: frozenset({"eng"}))
|
||||
assert _build_ocr_options("tesseract", ["xyz"], False).lang == ["eng"]
|
||||
|
||||
def test_unknown_inventory_keeps_the_list(self, monkeypatch):
|
||||
pytest.importorskip("docling")
|
||||
import application.parser.file.ocr_parser as op
|
||||
from application.parser.file.docling_parser import _build_ocr_options
|
||||
import docsgpt.parser.file.ocr_parser as op
|
||||
from docsgpt.parser.file.docling_parser import _build_ocr_options
|
||||
|
||||
monkeypatch.setattr(op, "tesseract_languages", lambda: None)
|
||||
assert _build_ocr_options("tesseract", ["eng", "deu"], False).lang == ["eng", "deu"]
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Comprehensive tests for application/parser/file/docs_parser.py
|
||||
"""Comprehensive tests for docsgpt/parser/file/docs_parser.py
|
||||
|
||||
Covers: PDFParser (init, parse with pypdf, parse as image, import error),
|
||||
DocxParser (init, parse, import error).
|
||||
@@ -9,7 +9,7 @@ from unittest.mock import MagicMock, patch, mock_open
|
||||
|
||||
import pytest
|
||||
|
||||
from application.parser.file.docs_parser import PDFParser, DocxParser
|
||||
from docsgpt.parser.file.docs_parser import PDFParser, DocxParser
|
||||
|
||||
|
||||
# =====================================================================
|
||||
@@ -44,7 +44,7 @@ class TestPDFParserInit:
|
||||
@pytest.mark.unit
|
||||
class TestPDFParserParse:
|
||||
|
||||
@patch("application.parser.file.docs_parser.settings")
|
||||
@patch("docsgpt.parser.file.docs_parser.settings")
|
||||
def test_parse_with_pypdf(self, mock_settings):
|
||||
mock_settings.PARSE_PDF_AS_IMAGE = False
|
||||
|
||||
@@ -58,7 +58,7 @@ class TestPDFParserParse:
|
||||
mock_reader = MagicMock()
|
||||
mock_reader.pages = [mock_page1, mock_page2]
|
||||
|
||||
with patch("application.parser.file.docs_parser.PdfReader",
|
||||
with patch("docsgpt.parser.file.docs_parser.PdfReader",
|
||||
create=True), \
|
||||
patch("builtins.open", mock_open()):
|
||||
# Need to patch the import inside the function
|
||||
@@ -74,8 +74,8 @@ class TestPDFParserParse:
|
||||
finally:
|
||||
del sys.modules["pypdf"]
|
||||
|
||||
@patch("application.parser.file.docs_parser.settings")
|
||||
@patch("application.parser.file.docs_parser.requests")
|
||||
@patch("docsgpt.parser.file.docs_parser.settings")
|
||||
@patch("docsgpt.parser.file.docs_parser.requests")
|
||||
def test_parse_as_image(self, mock_requests, mock_settings):
|
||||
mock_settings.PARSE_PDF_AS_IMAGE = True
|
||||
|
||||
@@ -89,7 +89,7 @@ class TestPDFParserParse:
|
||||
result = parser.parse_file(Path("test.pdf"))
|
||||
assert result == "# OCR Result"
|
||||
|
||||
@patch("application.parser.file.docs_parser.settings")
|
||||
@patch("docsgpt.parser.file.docs_parser.settings")
|
||||
def test_parse_raises_on_missing_pypdf(self, mock_settings):
|
||||
mock_settings.PARSE_PDF_AS_IMAGE = False
|
||||
|
||||
@@ -196,17 +196,17 @@ class TestBaseParserProperties:
|
||||
class TestDocsParserGaps:
|
||||
def test_pdf_parser_parse_as_image(self, tmp_path):
|
||||
"""Cover lines 33-34: PARSE_PDF_AS_IMAGE sends to external service."""
|
||||
from application.parser.file.docs_parser import PDFParser
|
||||
from docsgpt.parser.file.docs_parser import PDFParser
|
||||
|
||||
pdf_file = tmp_path / "test.pdf"
|
||||
pdf_file.write_bytes(b"%PDF-1.4 fake content")
|
||||
|
||||
with patch(
|
||||
"application.parser.file.docs_parser.settings"
|
||||
"docsgpt.parser.file.docs_parser.settings"
|
||||
) as mock_settings:
|
||||
mock_settings.PARSE_PDF_AS_IMAGE = True
|
||||
with patch(
|
||||
"application.parser.file.docs_parser.requests.post"
|
||||
"docsgpt.parser.file.docs_parser.requests.post"
|
||||
) as mock_post:
|
||||
mock_post.return_value = MagicMock(
|
||||
json=MagicMock(return_value={"markdown": "# Parsed Content"})
|
||||
@@ -218,7 +218,7 @@ class TestDocsParserGaps:
|
||||
|
||||
def test_docx_parser_init_parser(self):
|
||||
"""Cover line 59: DocxParser._init_parser returns empty dict."""
|
||||
from application.parser.file.docs_parser import DocxParser
|
||||
from docsgpt.parser.file.docs_parser import DocxParser
|
||||
|
||||
parser = DocxParser()
|
||||
config = parser._init_parser()
|
||||
@@ -226,7 +226,7 @@ class TestDocsParserGaps:
|
||||
|
||||
def test_docx_parser_import_error(self):
|
||||
"""Cover line 63: ImportError when docx2txt not installed."""
|
||||
from application.parser.file.docs_parser import DocxParser
|
||||
from docsgpt.parser.file.docs_parser import DocxParser
|
||||
|
||||
parser = DocxParser()
|
||||
with patch.dict("sys.modules", {"docx2txt": None}):
|
||||
|
||||
@@ -2,7 +2,7 @@ import pytest
|
||||
import logging
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from application.parser.embedding_pipeline import (
|
||||
from docsgpt.parser.embedding_pipeline import (
|
||||
DEFAULT_EMBEDDINGS_BATCH_SIZE,
|
||||
EmbeddingPipelineError,
|
||||
_resolve_batch_size,
|
||||
@@ -45,7 +45,7 @@ def test_add_text_to_store_with_retry_success():
|
||||
def mock_settings(monkeypatch):
|
||||
mock_settings = MagicMock()
|
||||
monkeypatch.setattr(
|
||||
"application.parser.embedding_pipeline.settings", mock_settings
|
||||
"docsgpt.parser.embedding_pipeline.settings", mock_settings
|
||||
)
|
||||
return mock_settings
|
||||
|
||||
@@ -54,7 +54,7 @@ def mock_settings(monkeypatch):
|
||||
def mock_vector_creator(monkeypatch):
|
||||
mock_creator = MagicMock()
|
||||
monkeypatch.setattr(
|
||||
"application.parser.embedding_pipeline.VectorCreator", mock_creator
|
||||
"docsgpt.parser.embedding_pipeline.VectorCreator", mock_creator
|
||||
)
|
||||
return mock_creator
|
||||
|
||||
@@ -126,7 +126,7 @@ def test_embed_and_store_documents_progress_band(
|
||||
assert currents == sorted(currents)
|
||||
|
||||
|
||||
@patch("application.parser.embedding_pipeline.add_texts_to_store_with_retry")
|
||||
@patch("docsgpt.parser.embedding_pipeline.add_texts_to_store_with_retry")
|
||||
def test_embed_and_store_documents_partial_failure_raises(
|
||||
mock_add_retry, tmp_path, mock_settings, mock_vector_creator, caplog
|
||||
):
|
||||
@@ -170,7 +170,7 @@ def test_embed_and_store_documents_partial_failure_raises(
|
||||
mock_store.save_local.assert_called()
|
||||
|
||||
|
||||
@patch("application.parser.embedding_pipeline.add_texts_to_store_with_retry")
|
||||
@patch("docsgpt.parser.embedding_pipeline.add_texts_to_store_with_retry")
|
||||
def test_embed_and_store_documents_all_chunks_succeed_no_raise(
|
||||
mock_add_retry, tmp_path, mock_settings, mock_vector_creator,
|
||||
):
|
||||
@@ -200,7 +200,7 @@ def test_assert_index_complete_raises_on_partial(monkeypatch):
|
||||
"embedded_chunks": 4, "total_chunks": 10,
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
"application.parser.embedding_pipeline.IngestChunkProgressRepository",
|
||||
"docsgpt.parser.embedding_pipeline.IngestChunkProgressRepository",
|
||||
lambda conn: fake_repo,
|
||||
)
|
||||
from contextlib import contextmanager
|
||||
@@ -210,7 +210,7 @@ def test_assert_index_complete_raises_on_partial(monkeypatch):
|
||||
yield None
|
||||
|
||||
monkeypatch.setattr(
|
||||
"application.parser.embedding_pipeline.db_session", _fake_session,
|
||||
"docsgpt.parser.embedding_pipeline.db_session", _fake_session,
|
||||
)
|
||||
with pytest.raises(EmbeddingPipelineError, match=r"4/10"):
|
||||
assert_index_complete("src-partial")
|
||||
@@ -222,7 +222,7 @@ def test_assert_index_complete_passes_on_full(monkeypatch):
|
||||
"embedded_chunks": 10, "total_chunks": 10,
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
"application.parser.embedding_pipeline.IngestChunkProgressRepository",
|
||||
"docsgpt.parser.embedding_pipeline.IngestChunkProgressRepository",
|
||||
lambda conn: fake_repo,
|
||||
)
|
||||
from contextlib import contextmanager
|
||||
@@ -232,7 +232,7 @@ def test_assert_index_complete_passes_on_full(monkeypatch):
|
||||
yield None
|
||||
|
||||
monkeypatch.setattr(
|
||||
"application.parser.embedding_pipeline.db_session", _fake_session,
|
||||
"docsgpt.parser.embedding_pipeline.db_session", _fake_session,
|
||||
)
|
||||
assert_index_complete("src-full") # no raise
|
||||
|
||||
@@ -242,7 +242,7 @@ def test_assert_index_complete_no_op_when_no_progress_row(monkeypatch):
|
||||
fake_repo = MagicMock()
|
||||
fake_repo.get_progress.return_value = None
|
||||
monkeypatch.setattr(
|
||||
"application.parser.embedding_pipeline.IngestChunkProgressRepository",
|
||||
"docsgpt.parser.embedding_pipeline.IngestChunkProgressRepository",
|
||||
lambda conn: fake_repo,
|
||||
)
|
||||
from contextlib import contextmanager
|
||||
@@ -252,7 +252,7 @@ def test_assert_index_complete_no_op_when_no_progress_row(monkeypatch):
|
||||
yield None
|
||||
|
||||
monkeypatch.setattr(
|
||||
"application.parser.embedding_pipeline.db_session", _fake_session,
|
||||
"docsgpt.parser.embedding_pipeline.db_session", _fake_session,
|
||||
)
|
||||
assert_index_complete("src-missing")
|
||||
|
||||
@@ -270,7 +270,7 @@ def test_assert_index_complete_no_op_when_lookup_fails(monkeypatch, caplog):
|
||||
yield # pragma: no cover
|
||||
|
||||
monkeypatch.setattr(
|
||||
"application.parser.embedding_pipeline.db_session", _broken_session,
|
||||
"docsgpt.parser.embedding_pipeline.db_session", _broken_session,
|
||||
)
|
||||
with caplog.at_level(logging.WARNING, logger="root"):
|
||||
assert_index_complete("src-db-down") # no raise
|
||||
@@ -328,7 +328,7 @@ def test_add_texts_to_store_with_retry_sanitizes_and_skips_empty():
|
||||
|
||||
def test_resolve_batch_size_falls_back_on_bad_setting(monkeypatch):
|
||||
fake = MagicMock() # attribute access yields a MagicMock, not an int
|
||||
monkeypatch.setattr("application.parser.embedding_pipeline.settings", fake)
|
||||
monkeypatch.setattr("docsgpt.parser.embedding_pipeline.settings", fake)
|
||||
assert _resolve_batch_size() == DEFAULT_EMBEDDINGS_BATCH_SIZE
|
||||
|
||||
fake.EMBEDDINGS_BATCH_SIZE = 0
|
||||
@@ -346,7 +346,7 @@ def test_embed_loop_batches_chunks(tmp_path, mock_settings, mock_vector_creator)
|
||||
store = MagicMock()
|
||||
mock_vector_creator.create_vectorstore.return_value = store
|
||||
|
||||
with patch("application.parser.embedding_pipeline._record_progress") as rec:
|
||||
with patch("docsgpt.parser.embedding_pipeline._record_progress") as rec:
|
||||
embed_and_store_documents(
|
||||
docs, str(tmp_path / "s"), "sid", MagicMock(),
|
||||
)
|
||||
@@ -395,10 +395,10 @@ def test_poison_chunk_isolated_by_per_chunk_fallback(
|
||||
raise RuntimeError("input too large")
|
||||
|
||||
with patch(
|
||||
"application.parser.embedding_pipeline.add_texts_to_store_with_retry",
|
||||
"docsgpt.parser.embedding_pipeline.add_texts_to_store_with_retry",
|
||||
side_effect=fake_add,
|
||||
):
|
||||
with patch("application.parser.embedding_pipeline._record_progress") as rec:
|
||||
with patch("docsgpt.parser.embedding_pipeline._record_progress") as rec:
|
||||
with pytest.raises(EmbeddingPipelineError) as exc:
|
||||
embed_and_store_documents(
|
||||
docs, str(tmp_path / "s"), "sid", MagicMock(),
|
||||
@@ -429,7 +429,7 @@ def test_batch_only_failure_recovers_via_fallback(
|
||||
raise RuntimeError("payload too large")
|
||||
|
||||
with patch(
|
||||
"application.parser.embedding_pipeline.add_texts_to_store_with_retry",
|
||||
"docsgpt.parser.embedding_pipeline.add_texts_to_store_with_retry",
|
||||
side_effect=fake_add,
|
||||
):
|
||||
embed_and_store_documents(docs, str(tmp_path / "s"), "sid", MagicMock())
|
||||
|
||||
@@ -4,7 +4,7 @@ from unittest.mock import patch, MagicMock
|
||||
import sys
|
||||
import types
|
||||
|
||||
from application.parser.file.epub_parser import EpubParser
|
||||
from docsgpt.parser.file.epub_parser import EpubParser
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from application.parser.file.html_parser import HTMLParser
|
||||
from docsgpt.parser.file.html_parser import HTMLParser
|
||||
|
||||
|
||||
HTML = (
|
||||
@@ -58,7 +58,7 @@ def test_html_parser_metadata_unreadable_file(tmp_path):
|
||||
|
||||
# --- HTMLMarkdownParser: the anydoc engine's HTML path ---------------------------
|
||||
|
||||
from application.parser.file.html_parser import HTMLMarkdownParser, html_to_markdown # noqa: E402
|
||||
from docsgpt.parser.file.html_parser import HTMLMarkdownParser, html_to_markdown # noqa: E402
|
||||
|
||||
RICH_HTML = """<html><head><title>Doc Title</title>
|
||||
<style>.x { color: red }</style><script>var secret = 1;</script></head>
|
||||
@@ -139,7 +139,7 @@ def test_html_to_markdown_collapses_blank_runs():
|
||||
def test_markdown_parser_head_truncates_oversized_markup(tmp_path, monkeypatch):
|
||||
"""Markup past MARKUP_MAX_BYTES is not parsed: the soup+markdownify tree costs
|
||||
~50x the input, and the upload cap is 100 MB."""
|
||||
from application.core.settings import settings
|
||||
from docsgpt.core.settings import settings
|
||||
|
||||
monkeypatch.setattr(settings, "MARKUP_MAX_BYTES", 600)
|
||||
path = tmp_path / "big.html"
|
||||
@@ -155,7 +155,7 @@ def test_markdown_parser_head_truncates_oversized_markup(tmp_path, monkeypatch):
|
||||
|
||||
|
||||
def test_markdown_parser_gate_disabled_reads_everything(tmp_path, monkeypatch):
|
||||
from application.core.settings import settings
|
||||
from docsgpt.core.settings import settings
|
||||
|
||||
monkeypatch.setattr(settings, "MARKUP_MAX_BYTES", 0)
|
||||
path = tmp_path / "big.html"
|
||||
@@ -187,7 +187,7 @@ def test_markdown_parser_metadata_unreadable_file(tmp_path):
|
||||
def test_markdown_parser_cut_inside_utf8_char_does_not_mojibake_the_page(tmp_path, monkeypatch):
|
||||
"""A byte cut mid-character makes strict UTF-8 fail; BeautifulSoup would then
|
||||
retry as windows-1252, which *succeeds* on umlauts and garbles everything."""
|
||||
from application.core.settings import settings
|
||||
from docsgpt.core.settings import settings
|
||||
|
||||
path = tmp_path / "minified.html"
|
||||
body = "<p>Größe Übermaß schön für</p>" * 400 # no newlines: the cut is arbitrary
|
||||
@@ -203,7 +203,7 @@ def test_markdown_parser_cut_inside_utf8_char_does_not_mojibake_the_page(tmp_pat
|
||||
|
||||
|
||||
def test_trim_torn_utf8_tail():
|
||||
from application.parser.file.html_parser import _trim_torn_utf8_tail as trim
|
||||
from docsgpt.parser.file.html_parser import _trim_torn_utf8_tail as trim
|
||||
|
||||
e_acute, snowman, emoji = "é".encode(), "☃".encode(), "😀".encode()
|
||||
assert trim(b"abc") == b"abc"
|
||||
@@ -218,7 +218,7 @@ def test_trim_torn_utf8_tail():
|
||||
|
||||
def test_markdown_parser_metadata_reuses_last_parse(tmp_path, rich_html_file, monkeypatch):
|
||||
"""The metadata call after parse_file must not build the soup again."""
|
||||
from application.parser.file import html_parser as mod
|
||||
from docsgpt.parser.file import html_parser as mod
|
||||
|
||||
parser = HTMLMarkdownParser()
|
||||
parser.parse_file(rich_html_file)
|
||||
@@ -237,7 +237,7 @@ def test_markdown_parser_metadata_reuses_last_parse(tmp_path, rich_html_file, mo
|
||||
|
||||
|
||||
def test_xml_prolog_does_not_leak_into_markdown(tmp_path):
|
||||
from application.parser.file.html_parser import HTMLMarkdownParser
|
||||
from docsgpt.parser.file.html_parser import HTMLMarkdownParser
|
||||
|
||||
path = tmp_path / "doc.xhtml"
|
||||
path.write_bytes(
|
||||
@@ -264,8 +264,8 @@ def test_data_uris_are_stripped_from_images_and_links():
|
||||
|
||||
|
||||
def test_utf16_head_keeps_an_even_byte_count(tmp_path, monkeypatch):
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.html_parser import HTMLMarkdownParser, read_markup_head
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.html_parser import HTMLMarkdownParser, read_markup_head
|
||||
|
||||
body = "".join(f"<p>Zeile {i} Über Größe</p>\n" for i in range(200))
|
||||
path = tmp_path / "wide.html"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock, mock_open
|
||||
|
||||
from application.parser.file.image_parser import ImageParser
|
||||
from docsgpt.parser.file.image_parser import ImageParser
|
||||
|
||||
|
||||
def test_image_init_parser():
|
||||
@@ -12,7 +12,7 @@ def test_image_init_parser():
|
||||
assert parser.parser_config_set
|
||||
|
||||
|
||||
@patch("application.parser.file.image_parser.settings")
|
||||
@patch("docsgpt.parser.file.image_parser.settings")
|
||||
def test_image_parser_remote_true(mock_settings):
|
||||
mock_settings.PARSE_IMAGE_REMOTE = True
|
||||
parser = ImageParser()
|
||||
@@ -20,7 +20,7 @@ def test_image_parser_remote_true(mock_settings):
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {"markdown": "# From Image"}
|
||||
|
||||
with patch("application.parser.file.image_parser.requests.post", return_value=mock_response) as mock_post:
|
||||
with patch("docsgpt.parser.file.image_parser.requests.post", return_value=mock_response) as mock_post:
|
||||
with patch("builtins.open", mock_open()):
|
||||
result = parser.parse_file(Path("img.png"))
|
||||
|
||||
@@ -28,12 +28,12 @@ def test_image_parser_remote_true(mock_settings):
|
||||
mock_post.assert_called_once()
|
||||
|
||||
|
||||
@patch("application.parser.file.image_parser.settings")
|
||||
@patch("docsgpt.parser.file.image_parser.settings")
|
||||
def test_image_parser_remote_false(mock_settings):
|
||||
mock_settings.PARSE_IMAGE_REMOTE = False
|
||||
parser = ImageParser()
|
||||
|
||||
with patch("application.parser.file.image_parser.requests.post") as mock_post:
|
||||
with patch("docsgpt.parser.file.image_parser.requests.post") as mock_post:
|
||||
result = parser.parse_file(Path("img.png"))
|
||||
|
||||
assert result == ""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, mock_open
|
||||
|
||||
from application.parser.file.json_parser import JSONParser
|
||||
from docsgpt.parser.file.json_parser import JSONParser
|
||||
|
||||
|
||||
def test_json_init_parser():
|
||||
|
||||
@@ -3,8 +3,8 @@ from unittest.mock import mock_open, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from application.parser.file.markdown_parser import MarkdownParser
|
||||
from application import utils
|
||||
from docsgpt.parser.file.markdown_parser import MarkdownParser
|
||||
from docsgpt import utils
|
||||
|
||||
|
||||
class _Enc:
|
||||
|
||||
@@ -18,8 +18,8 @@ pytest.importorskip("pypdfium2")
|
||||
PIL = pytest.importorskip("PIL")
|
||||
from PIL import Image, ImageDraw # noqa: E402
|
||||
|
||||
from application.parser.file.base_parser import DocumentParseError # noqa: E402
|
||||
from application.parser.file import ocr_parser as op # noqa: E402
|
||||
from docsgpt.parser.file.base_parser import DocumentParseError # noqa: E402
|
||||
from docsgpt.parser.file import ocr_parser as op # noqa: E402
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -83,7 +83,7 @@ def _pdf_page_count(path: Path) -> int:
|
||||
|
||||
@pytest.fixture
|
||||
def settings():
|
||||
from application.core.settings import settings
|
||||
from docsgpt.core.settings import settings
|
||||
|
||||
return settings
|
||||
|
||||
@@ -96,7 +96,7 @@ def settings():
|
||||
@pytest.mark.unit
|
||||
class TestSettingsAliases:
|
||||
def test_legacy_docling_names_still_configure_ocr(self, monkeypatch):
|
||||
from application.core.settings import Settings
|
||||
from docsgpt.core.settings import Settings
|
||||
|
||||
monkeypatch.setenv("DOCLING_OCR_ENABLED", "true")
|
||||
monkeypatch.setenv("DOCLING_OCR_ATTACHMENTS_ENABLED", "true")
|
||||
@@ -107,7 +107,7 @@ class TestSettingsAliases:
|
||||
assert loaded.OCR_MIN_CHARS_PER_PAGE == 7
|
||||
|
||||
def test_new_names_and_defaults(self, monkeypatch):
|
||||
from application.core.settings import Settings
|
||||
from docsgpt.core.settings import Settings
|
||||
|
||||
for name in (
|
||||
"OCR_ENABLED",
|
||||
@@ -537,7 +537,7 @@ class TestNativeOcrImageParser:
|
||||
@pytest.mark.unit
|
||||
class TestExtractorWiring:
|
||||
def test_legacy_map_without_ocr_is_unchanged(self):
|
||||
from application.parser.file.bulk import _legacy_file_extractor
|
||||
from docsgpt.parser.file.bulk import _legacy_file_extractor
|
||||
|
||||
extractor = _legacy_file_extractor()
|
||||
assert type(extractor[".pdf"]).__name__ == "PDFParser"
|
||||
@@ -545,7 +545,7 @@ class TestExtractorWiring:
|
||||
assert ".tiff" not in extractor
|
||||
|
||||
def test_legacy_map_with_ocr_uses_native_parsers(self):
|
||||
from application.parser.file.bulk import _legacy_file_extractor
|
||||
from docsgpt.parser.file.bulk import _legacy_file_extractor
|
||||
|
||||
extractor = _legacy_file_extractor(ocr_enabled=True)
|
||||
assert isinstance(extractor[".pdf"], op.NativeOcrPdfParser)
|
||||
@@ -555,8 +555,8 @@ class TestExtractorWiring:
|
||||
|
||||
def test_ocr_without_docling_reaches_native_under_anydoc(self, monkeypatch):
|
||||
pytest.importorskip("anydoc")
|
||||
from application.parser.file.anydoc_parser import AnydocParser
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.anydoc_parser import AnydocParser
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
monkeypatch.setitem(sys.modules, "docling", None)
|
||||
extractor = get_default_file_extractor(engine="anydoc", ocr_enabled=True)
|
||||
@@ -566,7 +566,7 @@ class TestExtractorWiring:
|
||||
assert isinstance(extractor[".png"], op.NativeOcrImageParser)
|
||||
|
||||
def test_ocr_without_docling_reaches_native_under_docling_engine(self, monkeypatch):
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
monkeypatch.setitem(sys.modules, "docling", None)
|
||||
extractor = get_default_file_extractor(engine="docling", ocr_enabled=True)
|
||||
@@ -574,7 +574,7 @@ class TestExtractorWiring:
|
||||
assert isinstance(extractor[".jpg"], op.NativeOcrImageParser)
|
||||
|
||||
def test_ocr_off_without_docling_keeps_legacy(self, monkeypatch):
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
monkeypatch.setitem(sys.modules, "docling", None)
|
||||
extractor = get_default_file_extractor(engine="docling", ocr_enabled=False)
|
||||
@@ -583,8 +583,8 @@ class TestExtractorWiring:
|
||||
|
||||
def test_native_backend_with_docling_installed_wraps_docling_for_text_pdfs(self, monkeypatch, settings):
|
||||
pytest.importorskip("docling")
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from application.parser.file.docling_parser import DoclingPDFParser
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.docling_parser import DoclingPDFParser
|
||||
|
||||
monkeypatch.setattr(settings, "OCR_BACKEND", "native")
|
||||
extractor = get_default_file_extractor(engine="docling", ocr_enabled=True)
|
||||
@@ -600,8 +600,8 @@ class TestExtractorWiring:
|
||||
def test_native_backend_under_anydoc_keeps_docling_reroute_for_trust_check(self, monkeypatch, settings):
|
||||
pytest.importorskip("anydoc")
|
||||
pytest.importorskip("docling")
|
||||
from application.parser.file.anydoc_parser import _is_docling_backed
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.anydoc_parser import _is_docling_backed
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
monkeypatch.setattr(settings, "OCR_BACKEND", "native")
|
||||
extractor = get_default_file_extractor(engine="anydoc", ocr_enabled=True)
|
||||
@@ -612,7 +612,7 @@ class TestExtractorWiring:
|
||||
|
||||
def test_auto_backend_with_docling_installed_is_unchanged(self, monkeypatch, settings):
|
||||
pytest.importorskip("docling")
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
monkeypatch.setattr(settings, "OCR_BACKEND", "auto")
|
||||
extractor = get_default_file_extractor(engine="docling", ocr_enabled=True)
|
||||
@@ -622,7 +622,7 @@ class TestExtractorWiring:
|
||||
|
||||
def test_anydoc_scan_hint_names_new_setting(self, tmp_path):
|
||||
pytest.importorskip("anydoc")
|
||||
from application.parser.file.anydoc_parser import AnydocParser
|
||||
from docsgpt.parser.file.anydoc_parser import AnydocParser
|
||||
|
||||
fallback = MagicMock()
|
||||
fallback.parser_config_set = True
|
||||
@@ -700,7 +700,7 @@ class TestAnydocMixedDocuments:
|
||||
|
||||
def test_scanned_page_is_ocrd_and_appended(self, mixed_pdf, monkeypatch, settings):
|
||||
pytest.importorskip("anydoc")
|
||||
from application.parser.file.anydoc_parser import AnydocParser
|
||||
from docsgpt.parser.file.anydoc_parser import AnydocParser
|
||||
|
||||
monkeypatch.setattr(settings, "OCR_RENDER_DPI", 72)
|
||||
engine = FakeEngine(["SCANNED PAGE WORDS"])
|
||||
@@ -717,8 +717,8 @@ class TestAnydocMixedDocuments:
|
||||
|
||||
def test_no_ocr_fallback_keeps_text_pages_only(self, mixed_pdf):
|
||||
pytest.importorskip("anydoc")
|
||||
from application.parser.file.anydoc_parser import AnydocParser
|
||||
from application.parser.file.docs_parser import PDFParser
|
||||
from docsgpt.parser.file.anydoc_parser import AnydocParser
|
||||
from docsgpt.parser.file.docs_parser import PDFParser
|
||||
|
||||
parser = AnydocParser(fallback_parser=PDFParser())
|
||||
content = parser.parse_file(mixed_pdf)
|
||||
@@ -728,7 +728,7 @@ class TestAnydocMixedDocuments:
|
||||
|
||||
def test_fallback_with_ocr_off_is_not_used(self, mixed_pdf):
|
||||
pytest.importorskip("anydoc")
|
||||
from application.parser.file.anydoc_parser import AnydocParser
|
||||
from docsgpt.parser.file.anydoc_parser import AnydocParser
|
||||
|
||||
fallback = MagicMock()
|
||||
fallback.ocr_enabled = False
|
||||
@@ -738,7 +738,7 @@ class TestAnydocMixedDocuments:
|
||||
|
||||
def test_ocr_failure_keeps_text_pages(self, mixed_pdf, caplog):
|
||||
pytest.importorskip("anydoc")
|
||||
from application.parser.file.anydoc_parser import AnydocParser
|
||||
from docsgpt.parser.file.anydoc_parser import AnydocParser
|
||||
|
||||
fallback = MagicMock()
|
||||
fallback.ocr_enabled = True
|
||||
@@ -755,7 +755,7 @@ class TestAnydocMixedDocuments:
|
||||
|
||||
def test_all_text_document_never_probes_ocr(self, tmp_path):
|
||||
pytest.importorskip("anydoc")
|
||||
from application.parser.file.anydoc_parser import AnydocParser
|
||||
from docsgpt.parser.file.anydoc_parser import AnydocParser
|
||||
|
||||
pdf = _text_pdf(tmp_path / "t.pdf", pages=2, text="Digital text page with enough characters", lines=25)
|
||||
fallback = MagicMock()
|
||||
@@ -797,7 +797,7 @@ class TestRenderBudget:
|
||||
try:
|
||||
page = pdf[0]
|
||||
try:
|
||||
with caplog.at_level(logging.WARNING, logger="application.parser.file.ocr_parser"):
|
||||
with caplog.at_level(logging.WARNING, logger="docsgpt.parser.file.ocr_parser"):
|
||||
image = op._render_page(page, 200)
|
||||
finally:
|
||||
page.close()
|
||||
@@ -906,7 +906,7 @@ class TestImageNormalisation:
|
||||
|
||||
def test_oversized_image_is_downscaled_to_the_budget(self, caplog):
|
||||
big = Image.new("L", (9000, 9000), 255)
|
||||
with caplog.at_level(logging.WARNING, logger="application.parser.file.ocr_parser"):
|
||||
with caplog.at_level(logging.WARNING, logger="docsgpt.parser.file.ocr_parser"):
|
||||
small = op.fit_to_pixel_budget(big)
|
||||
assert small.width * small.height <= op._MAX_RENDER_PIXELS
|
||||
assert "downscaling" in caplog.text
|
||||
@@ -953,7 +953,7 @@ class TestDeepseekErrorShapes:
|
||||
@pytest.mark.unit
|
||||
def test_delegate_parse_lets_setup_errors_through():
|
||||
"""A fallback whose dependency is missing is a deployment problem, not a bad file."""
|
||||
from application.parser.file.base_parser import BaseParser, delegate_parse
|
||||
from docsgpt.parser.file.base_parser import BaseParser, delegate_parse
|
||||
|
||||
class _NeedsLib(BaseParser):
|
||||
def _init_parser(self):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Tests for application.parser.file.openapi3_parser covering lines 7-8, 45."""
|
||||
"""Tests for docsgpt.parser.file.openapi3_parser covering lines 7-8, 45."""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
@@ -9,16 +9,16 @@ class TestOpenAPI3ParserImportFallback:
|
||||
def test_import_fallback_to_base_parser(self):
|
||||
"""Cover lines 7-8: try/except ModuleNotFoundError import fallback."""
|
||||
# The fallback import is a module-level concern. Just verify the class works.
|
||||
with patch("application.parser.file.openapi3_parser.parse"):
|
||||
from application.parser.file.openapi3_parser import OpenAPI3Parser
|
||||
with patch("docsgpt.parser.file.openapi3_parser.parse"):
|
||||
from docsgpt.parser.file.openapi3_parser import OpenAPI3Parser
|
||||
|
||||
parser = OpenAPI3Parser()
|
||||
assert parser is not None
|
||||
|
||||
def test_get_base_urls(self):
|
||||
"""Cover basic URL extraction."""
|
||||
with patch("application.parser.file.openapi3_parser.parse"):
|
||||
from application.parser.file.openapi3_parser import OpenAPI3Parser
|
||||
with patch("docsgpt.parser.file.openapi3_parser.parse"):
|
||||
from docsgpt.parser.file.openapi3_parser import OpenAPI3Parser
|
||||
|
||||
parser = OpenAPI3Parser()
|
||||
urls = parser.get_base_urls([
|
||||
@@ -32,8 +32,8 @@ class TestOpenAPI3ParserImportFallback:
|
||||
|
||||
def test_get_info_from_paths_empty(self):
|
||||
"""Cover path with no operations."""
|
||||
with patch("application.parser.file.openapi3_parser.parse"):
|
||||
from application.parser.file.openapi3_parser import OpenAPI3Parser
|
||||
with patch("docsgpt.parser.file.openapi3_parser.parse"):
|
||||
from docsgpt.parser.file.openapi3_parser import OpenAPI3Parser
|
||||
|
||||
parser = OpenAPI3Parser()
|
||||
mock_path = MagicMock()
|
||||
@@ -43,8 +43,8 @@ class TestOpenAPI3ParserImportFallback:
|
||||
|
||||
def test_parse_file_writes_results(self, tmp_path):
|
||||
"""Cover line 45: parse_file writes to results.txt."""
|
||||
with patch("application.parser.file.openapi3_parser.parse") as mock_parse:
|
||||
from application.parser.file.openapi3_parser import OpenAPI3Parser
|
||||
with patch("docsgpt.parser.file.openapi3_parser.parse") as mock_parse:
|
||||
from docsgpt.parser.file.openapi3_parser import OpenAPI3Parser
|
||||
|
||||
mock_server = MagicMock()
|
||||
mock_server.url = "https://api.example.com"
|
||||
|
||||
@@ -10,7 +10,7 @@ its whole Chinese column in anydoc with no error.
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
from application.parser.file.pdf_trust import (
|
||||
from docsgpt.parser.file.pdf_trust import (
|
||||
check_pdf_fonts,
|
||||
verify_extraction,
|
||||
verify_pdf_file,
|
||||
@@ -134,7 +134,7 @@ class TestRealFixture:
|
||||
def test_flate_bomb_streams_are_capped():
|
||||
"""Flate reaches ~1000:1, so per-stream inflation must be capped — an
|
||||
uncapped decompress of a crafted PDF would OOM the ingest worker."""
|
||||
from application.parser.file.pdf_trust import _decompressed_streams, _STREAM_INFLATE_CAP
|
||||
from docsgpt.parser.file.pdf_trust import _decompressed_streams, _STREAM_INFLATE_CAP
|
||||
|
||||
bomb = _stream(b"\0" * (_STREAM_INFLATE_CAP * 4))
|
||||
chunks = list(_decompressed_streams(bomb))
|
||||
@@ -187,7 +187,7 @@ class TestObjectWalk:
|
||||
"""The forward walk yields exactly what ``\\d+\\s+\\d+\\s+obj(.*?)endobj`` did."""
|
||||
import re
|
||||
|
||||
from application.parser.file.pdf_trust import _object_bodies
|
||||
from docsgpt.parser.file.pdf_trust import _object_bodies
|
||||
|
||||
reference = re.compile(rb"\d+\s+\d+\s+obj(.*?)endobj", re.DOTALL)
|
||||
data = (
|
||||
|
||||
@@ -13,7 +13,7 @@ the fast path must only appear when a caller explicitly asks for it.
|
||||
|
||||
import pytest
|
||||
|
||||
from application.parser.file.base_parser import BaseParser, DocumentParseError
|
||||
from docsgpt.parser.file.base_parser import BaseParser, DocumentParseError
|
||||
|
||||
pypdfium2 = pytest.importorskip("pypdfium2")
|
||||
|
||||
@@ -66,7 +66,7 @@ class _RecordingFallback(BaseParser):
|
||||
|
||||
@pytest.fixture
|
||||
def parser_cls():
|
||||
from application.parser.file.pdfium_parser import PdfiumTextParser
|
||||
from docsgpt.parser.file.pdfium_parser import PdfiumTextParser
|
||||
|
||||
return PdfiumTextParser
|
||||
|
||||
@@ -162,7 +162,7 @@ def docling_engine(monkeypatch):
|
||||
# (docling is an optional extra) the engine degrades to the legacy
|
||||
# parsers and these tests do not apply.
|
||||
pytest.importorskip("docling")
|
||||
from application.core.settings import settings
|
||||
from docsgpt.core.settings import settings
|
||||
|
||||
monkeypatch.setattr(settings, "DOC_PARSER_ENGINE", "docling")
|
||||
# A developer .env with OCR on (and OCR_BACKEND=native) would otherwise
|
||||
@@ -172,7 +172,7 @@ def docling_engine(monkeypatch):
|
||||
|
||||
def test_extractor_defaults_to_docling_for_pdf(docling_engine):
|
||||
"""Sources must be unaffected: no fast path unless explicitly requested."""
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
pdf_parser = get_default_file_extractor()[".pdf"]
|
||||
|
||||
@@ -180,8 +180,8 @@ def test_extractor_defaults_to_docling_for_pdf(docling_engine):
|
||||
|
||||
|
||||
def test_extractor_uses_fast_path_when_requested(docling_engine):
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from application.parser.file.pdfium_parser import PdfiumTextParser
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.pdfium_parser import PdfiumTextParser
|
||||
|
||||
pdf_parser = get_default_file_extractor(pdf_text_fast_path=True)[".pdf"]
|
||||
|
||||
@@ -192,8 +192,8 @@ def test_extractor_uses_fast_path_when_requested(docling_engine):
|
||||
def test_anydoc_engine_ignores_fast_path(monkeypatch):
|
||||
"""anydoc already reads the text layer in milliseconds and keeps structure."""
|
||||
pytest.importorskip("anydoc")
|
||||
from application.core.settings import settings
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.core.settings import settings
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
monkeypatch.setattr(settings, "DOC_PARSER_ENGINE", "anydoc")
|
||||
|
||||
@@ -203,7 +203,7 @@ def test_anydoc_engine_ignores_fast_path(monkeypatch):
|
||||
|
||||
|
||||
def test_fast_path_does_not_change_non_pdf_parsers(docling_engine):
|
||||
from application.parser.file.bulk import get_default_file_extractor
|
||||
from docsgpt.parser.file.bulk import get_default_file_extractor
|
||||
|
||||
plain = get_default_file_extractor()
|
||||
fast = get_default_file_extractor(pdf_text_fast_path=True)
|
||||
|
||||
@@ -2,7 +2,7 @@ import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from application.parser.file.pptx_parser import PPTXParser
|
||||
from docsgpt.parser.file.pptx_parser import PPTXParser
|
||||
|
||||
|
||||
def test_pptx_init_parser():
|
||||
|
||||
@@ -2,7 +2,7 @@ import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, mock_open
|
||||
|
||||
from application.parser.file.rst_parser import RstParser
|
||||
from docsgpt.parser.file.rst_parser import RstParser
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Tests for the dot-leader/whitespace table reconstruction (``ANYDOC_TABLEIZE``)."""
|
||||
from application.parser.file.tableize import tableize
|
||||
from docsgpt.parser.file.tableize import tableize
|
||||
|
||||
DOT_LEADER = """Revenues
|
||||
Insurance premiums ............ 83,431 77,731
|
||||
@@ -76,7 +76,7 @@ def test_non_table_text_passes_through_verbatim():
|
||||
|
||||
def test_single_trailing_number_lines_are_not_a_table():
|
||||
"""Headings, footnotes and version lists look like 'word number' rows; leave them alone."""
|
||||
from application.parser.file.tableize import tableize
|
||||
from docsgpt.parser.file.tableize import tableize
|
||||
|
||||
for block in (
|
||||
"Chapter 1\nChapter 2\nChapter 3",
|
||||
@@ -88,7 +88,7 @@ def test_single_trailing_number_lines_are_not_a_table():
|
||||
|
||||
|
||||
def test_leader_rows_with_one_value_still_convert():
|
||||
from application.parser.file.tableize import tableize
|
||||
from docsgpt.parser.file.tableize import tableize
|
||||
|
||||
block = "Revenue ...... 1,234\nCosts ...... 567\nProfit ...... 667"
|
||||
assert "| --- |" in tableize(block)
|
||||
@@ -2,7 +2,7 @@ import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock, mock_open
|
||||
|
||||
from application.parser.file.tabular_parser import CSVParser, PandasCSVParser, ExcelParser
|
||||
from docsgpt.parser.file.tabular_parser import CSVParser, PandasCSVParser, ExcelParser
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -220,7 +220,7 @@ def test_excel_numeric_headers_do_not_crash(tmp_path):
|
||||
to this parser, so it must handle numeric headers."""
|
||||
from openpyxl import Workbook
|
||||
|
||||
from application.parser.file.tabular_parser import ExcelParser
|
||||
from docsgpt.parser.file.tabular_parser import ExcelParser
|
||||
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
|
||||
@@ -16,7 +16,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from application.parser.file.tabular_parser import (
|
||||
from docsgpt.parser.file.tabular_parser import (
|
||||
ExcelParser,
|
||||
PandasCSVParser,
|
||||
cell_to_text,
|
||||
|
||||
Reference in new issue
Block a user