From 23d130cf0ce3543210e6d005f3d59529d07eceb5 Mon Sep 17 00:00:00 2001 From: Sina Date: Thu, 16 Apr 2026 21:31:42 +0000 Subject: [PATCH 1/3] fix(packaging): lazy-load hf backends for base installs --- src/churro_ocr/providers/builder.py | 43 +++++++++++++---------------- tests/test_package_check.py | 22 +++++++++++++++ 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/churro_ocr/providers/builder.py b/src/churro_ocr/providers/builder.py index 0e20367..deeb899 100644 --- a/src/churro_ocr/providers/builder.py +++ b/src/churro_ocr/providers/builder.py @@ -2,23 +2,11 @@ from __future__ import annotations +from importlib import import_module from typing import TYPE_CHECKING from churro_ocr._internal.litellm import LiteLLMTransport from churro_ocr.errors import ConfigurationError -from churro_ocr.providers.hf import ( - ChandraOCR2OCRBackend, - DeepSeekOCR2OCRBackend, - DotsMOCROCRBackend, - DotsOCR15OCRBackend, - GlmOCROCRBackend, - HuggingFaceVisionOCRBackend, - LFM25VLOCRBackend, - MinerU25OCRBackend, - PaddleOCRVL15OCRBackend, - QianfanOCROCRBackend, - _default_dots_ocr_1_5_model_kwargs, -) from churro_ocr.providers.ocr import ( AzureDocumentIntelligenceOCRBackend, LiteLLMVisionOCRBackend, @@ -40,6 +28,8 @@ from churro_ocr.templates import MINERU2_5_2509_1_2B_MODEL_ID if TYPE_CHECKING: + from types import ModuleType + from churro_ocr.ocr import OCRBackend @@ -137,6 +127,10 @@ def _resolve_model_name(profile: OCRModelProfile, model: str | None, *, fallback return fallback +def _load_huggingface_backends() -> ModuleType: + return import_module("churro_ocr.providers.hf") + + def _build_litellm_backend(spec: OCRBackendSpec, profile: OCRModelProfile) -> OCRBackend: if spec.model is None: message = "OCR provider 'litellm' requires `model`." @@ -191,27 +185,28 @@ def _build_huggingface_backend(spec: OCRBackendSpec, profile: OCRModelProfile) - profile.huggingface, _ensure_options_type(spec.options, HuggingFaceOptions, provider=spec.provider), ) - backend_cls: type[HuggingFaceVisionOCRBackend] = HuggingFaceVisionOCRBackend + hf_backends = _load_huggingface_backends() + backend_cls = hf_backends.HuggingFaceVisionOCRBackend model_kwargs = dict(options.model_kwargs) if options.backend_variant in {"dots-ocr-1.5", "dots-mocr"}: - backend_cls = DotsOCR15OCRBackend + backend_cls = hf_backends.DotsOCR15OCRBackend if options.backend_variant == "dots-mocr": - backend_cls = DotsMOCROCRBackend - model_kwargs = _merge_mapping(_default_dots_ocr_1_5_model_kwargs(), model_kwargs) + backend_cls = hf_backends.DotsMOCROCRBackend + model_kwargs = _merge_mapping(hf_backends._default_dots_ocr_1_5_model_kwargs(), model_kwargs) elif options.backend_variant == "glm-ocr": - backend_cls = GlmOCROCRBackend + backend_cls = hf_backends.GlmOCROCRBackend elif options.backend_variant == "deepseek-ocr-2": - backend_cls = DeepSeekOCR2OCRBackend + backend_cls = hf_backends.DeepSeekOCR2OCRBackend elif options.backend_variant == "chandra-ocr-2": - backend_cls = ChandraOCR2OCRBackend + backend_cls = hf_backends.ChandraOCR2OCRBackend elif options.backend_variant == "mineru2.5": - backend_cls = MinerU25OCRBackend + backend_cls = hf_backends.MinerU25OCRBackend elif options.backend_variant == "paddleocr-vl-1.5": - backend_cls = PaddleOCRVL15OCRBackend + backend_cls = hf_backends.PaddleOCRVL15OCRBackend elif options.backend_variant == "lfm2.5-vl": - backend_cls = LFM25VLOCRBackend + backend_cls = hf_backends.LFM25VLOCRBackend elif options.backend_variant == "qianfan-ocr": - backend_cls = QianfanOCROCRBackend + backend_cls = hf_backends.QianfanOCROCRBackend return backend_cls( model_id=spec.model, template=profile.template, diff --git a/tests/test_package_check.py b/tests/test_package_check.py index ed200fb..b751cd4 100644 --- a/tests/test_package_check.py +++ b/tests/test_package_check.py @@ -1,3 +1,5 @@ +import importlib +import sys from email.message import Message from importlib import metadata as importlib_metadata from importlib.util import module_from_spec, spec_from_file_location @@ -55,3 +57,23 @@ def test_local_runtime_packaging_policy_rejects_direct_torch_runtime_pin() -> No with pytest.raises(RuntimeError, match="must not pin local PyTorch"): package_check._assert_local_runtime_packaging_policy(metadata_message) + + +def test_cli_import_does_not_eagerly_import_hf_backend_module() -> None: + module_names = ( + "churro_ocr.cli", + "churro_ocr.providers.builder", + "churro_ocr.providers.hf", + ) + saved_modules = {name: sys.modules.pop(name, None) for name in module_names} + + try: + cli_module = importlib.import_module("churro_ocr.cli") + assert cli_module.app is not None + assert "churro_ocr.providers.hf" not in sys.modules + finally: + for name in module_names: + sys.modules.pop(name, None) + for name, module in saved_modules.items(): + if module is not None: + sys.modules[name] = module From e3540050be6c5b48473e17a630aaf2bddc1f61e4 Mon Sep 17 00:00:00 2001 From: Sina Date: Thu, 16 Apr 2026 21:32:04 +0000 Subject: [PATCH 2/3] build(packaging): relax tenacity dependency floor --- pixi.lock | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pixi.lock b/pixi.lock index ea306b3..7ba94f4 100644 --- a/pixi.lock +++ b/pixi.lock @@ -784,7 +784,7 @@ packages: - loguru>=0.7.2,<1 - pillow>=10.4.0,<12 - rich>=13.9.2,<14 - - tenacity>=9.1.2,<10 + - tenacity>=9.0.0,<10 - typer>=0.12.3,<1 - google-auth>=2.41.1,<3 ; extra == 'llm' - litellm[caching]==1.82.3 ; extra == 'llm' diff --git a/pyproject.toml b/pyproject.toml index c0ffca8..d6165e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ "loguru>=0.7.2,<1", "Pillow>=10.4.0,<12", "rich>=13.9.2,<14", - "tenacity>=9.1.2,<10", + "tenacity>=9.0.0,<10", "typer>=0.12.3,<1", ] From b89383ec909ac015b69db5010c33657bfb44b357 Mon Sep 17 00:00:00 2001 From: Sina Date: Thu, 16 Apr 2026 21:32:19 +0000 Subject: [PATCH 3/3] chore(release): prepare 0.3.0 release --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ pixi.lock | 4 ++-- pyproject.toml | 4 ++-- 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..452ed10 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +All notable changes to `churro-ocr` will be documented in this file. + +## 0.3.0 + +### Added + +- Added UV-managed runtime installers through `churro-ocr install` for `llm`, `local`, `hf`, `azure`, `mistral`, `pdf`, and `all`. +- Added built-in OCR profiles and provider integrations for Chandra OCR 2, Dots OCR 1.5, dots.mocr, DeepSeek OCR 2, FireRed OCR, GLM-OCR, Infinity-Parser 7B, Liquid LFM2.5-VL 1.6B, MinerU2.5, Nanonets OCR2, olmOCR 2 7B, PaddleOCR-VL 1.5, and Qianfan OCR. +- Added richer prompt, template, and response-processing helpers for OCR backends that emit markdown or HTML. +- Added benchmark leaderboard improvements, including expandable per-language score views and refreshed benchmark coverage across newly supported models. + +### Changed + +- Updated the `hf` extra to `transformers>=5,<6` and moved local PyTorch installation behind the runtime installer workflow. +- Reworked the docs around a CLI-first onboarding path with expanded provider guidance, advanced customization docs, and a more detailed PyPI/README presentation. +- Tightened typing and internal provider boundaries across OCR, page detection, evaluation, and helper modules. + +### Fixed + +- Improved retry handling with a total timeout budget, better transient provider-error handling, and more graceful handling for timed-out OCR pages. +- Hardened LiteLLM request cleanup, unmapped-model handling, and default OCR parsing for providers that return empty or markdown-heavy responses. +- Fixed evaluation and benchmarking stability issues around multiprocessing pools, cached LiteLLM clients, and OCR metadata retention. + +### Breaking Changes + +- Removed the `vllm` extra and the in-process `churro_ocr.providers.vllm` backend. Serve vLLM separately and use the `openai-compatible` backend instead. +- Stopped re-exporting template classes from the top-level `churro_ocr` package. Import `HFChatTemplate`, `OCRPromptTemplate`, and related helpers from `churro_ocr.templates`. diff --git a/pixi.lock b/pixi.lock index 7ba94f4..2b27e42 100644 --- a/pixi.lock +++ b/pixi.lock @@ -778,8 +778,8 @@ packages: requires_python: '>=3.7' - pypi: ./ name: churro-ocr - version: 0.2.0 - sha256: 4aee805c55ac9eadaec8248affc3a728150ad248721bcfa301928bf5c2fd44e3 + version: 0.3.0 + sha256: 61e09ecb31ad134c61047136f8538a746d3c8e4ab961787f13346bc08bb9063f requires_dist: - loguru>=0.7.2,<1 - pillow>=10.4.0,<12 diff --git a/pyproject.toml b/pyproject.toml index d6165e4..5ec3fe6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta" [project] name = "churro-ocr" -version = "0.2.0" -description = "OCR for historical documents" +version = "0.3.0" +description = "OCR and page detection for historical documents" readme = { file = "docs/pypi.md", content-type = "text/markdown" } requires-python = ">=3.12" license = "Apache-2.0"