diff --git a/openkb/converter.py b/openkb/converter.py index 710d7a77..2cf72abf 100644 --- a/openkb/converter.py +++ b/openkb/converter.py @@ -11,7 +11,6 @@ from pathlib import Path import pymupdf -from markitdown import MarkItDown from openkb.config import load_config from openkb.images import convert_pdf_with_images, copy_relative_images, extract_base64_images @@ -230,7 +229,15 @@ def convert_document( # Use pymupdf dict-mode for PDFs: text + images inline at correct positions markdown = convert_pdf_with_images(src, doc_name, images_dir) else: - # Non-PDF, non-MD: use markitdown (docx, pptx, html, etc.) + # Non-PDF, non-MD: use markitdown (docx, pptx, html, etc.). + # Imported lazily: markitdown pulls in magika → onnxruntime (tens + # of MB, slow to import) that only this branch needs. Keeping it + # out of module import makes `import openkb` cheap and lets a + # packaged build drop those deps when Office-format ingest isn't + # required. markitdown appends warning filters on import but leaves + # the CLI's front "ignore" filter in place, so no re-suppress here. + from markitdown import MarkItDown + mid = MarkItDown() result = mid.convert(str(src), keep_data_uris=True) markdown = result.text_content diff --git a/tests/test_converter.py b/tests/test_converter.py index 1349b6fb..64adf1b5 100644 --- a/tests/test_converter.py +++ b/tests/test_converter.py @@ -2,6 +2,8 @@ from __future__ import annotations +import subprocess +import sys from unittest.mock import MagicMock, patch from openkb.converter import convert_document, get_pdf_page_count @@ -145,7 +147,7 @@ def test_docx_conversion_enables_keep_data_uris(self, kb_dir, tmp_path): mock_result.text_content = "![](data:image/png;base64,abc123)" with ( - patch("openkb.converter.MarkItDown") as mock_markitdown, + patch("markitdown.MarkItDown") as mock_markitdown, patch( "openkb.converter.extract_base64_images", return_value="converted markdown", @@ -164,6 +166,36 @@ def test_docx_conversion_enables_keep_data_uris(self, kb_dir, tmp_path): assert result.source_path.read_text(encoding="utf-8") == "converted markdown" +# --------------------------------------------------------------------------- +# Lazy markitdown import (guards the deferred-import optimization) +# --------------------------------------------------------------------------- + + +class TestLazyMarkItDownImport: + def test_importing_converter_does_not_load_markitdown(self): + """`import openkb.converter` must not pull in markitdown/magika/onnxruntime. + + markitdown is imported lazily inside convert_document's Office-format + branch so `import openkb` stays cheap and a packaged build can drop + magika/onnxruntime. This runs in a fresh interpreter (a clean + sys.modules) so the assertion is deterministic regardless of what other + tests in this process already imported. + """ + heavy = ("markitdown", "magika", "onnxruntime") + code = ( + "import sys, openkb.converter; " + f"print(','.join(m for m in {heavy!r} if m in sys.modules))" + ) + proc = subprocess.run( + [sys.executable, "-c", code], + capture_output=True, + text=True, + ) + assert proc.returncode == 0, proc.stderr + loaded = proc.stdout.strip() + assert loaded == "", f"import openkb.converter eagerly loaded: {loaded}" + + # --------------------------------------------------------------------------- # _registry_path # ---------------------------------------------------------------------------