From 4d2ec662b10f734dd9cbf58264d8335aa401ff07 Mon Sep 17 00:00:00 2001 From: Martin Kleine <24376955+datenzar@users.noreply.github.com> Date: Sat, 29 Nov 2025 18:41:47 +0100 Subject: [PATCH 1/2] fix: update Python version requirement and improve validation error messages - Changed required Python version from 3.11 to 3.10 in pyproject.toml and CLAUDE.md. - Enhanced validation error messages in EasyOCRParams to provide clearer feedback for unsupported languages. BREAKING CHANGE: Validation logic moved into components as of 2.0.0 --- CLAUDE.md | 2 +- GEMINI.md | 62 +++++++++++++++++++++++++ Makefile | 2 +- pyproject.toml | 15 ++++-- src/ocrbridge/engines/easyocr/models.py | 40 ++++++++-------- tests/__init__.py | 1 - tests/test_models.py | 4 +- 7 files changed, 97 insertions(+), 29 deletions(-) create mode 100644 GEMINI.md delete mode 100644 tests/__init__.py diff --git a/CLAUDE.md b/CLAUDE.md index 17b3886..f93a857 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -92,7 +92,7 @@ Examples: ### Python Style - **Line length**: 100 characters (Ruff configured) -- **Python version**: 3.11+ (uses modern type hints like `list[str]`, `tuple[float, float]`) +- **Python version**: 3.10+ - **Type checking**: Strict mode with pyright - **Linting**: Ruff with rules E, F, I, N, W enabled - Type annotations required on all public functions diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..3a08f29 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,62 @@ +# OCR Bridge - EasyOCR Engine + +## Project Overview +`ocrbridge-easyocr` is a Python-based plugin for the [OCR Bridge](https://github.com/OCRBridge/ocr-service) architecture. It integrates the [EasyOCR](https://github.com/JaidedAI/EasyOCR) library to provide deep learning-based Optical Character Recognition (OCR) capabilities. + +**Key Features:** +* **Plugin Architecture:** Implements the `OCREngine` interface from `ocrbridge-core`. +* **Deep Learning:** Uses EasyOCR (powered by PyTorch) for high-accuracy text recognition. +* **Multilingual:** Supports 80+ languages, with a focus on Asian scripts. +* **GPU Acceleration:** Automatically detects and utilizes CUDA GPUs via PyTorch. +* **Format Support:** Handles images (JPG, PNG, TIFF) and PDFs (via `pdf2image`). +* **Standard Output:** Produces HOCR (HTML-based XML) output with bounding boxes. + +## Development Setup + +### Prerequisites +* **Python:** 3.10+ +* **Package Manager:** `uv` (Unified Python packaging) +* **System Libraries:** + * `poppler-utils` (Required by `pdf2image` for PDF processing) + * CUDA-compatible GPU drivers (Optional, for GPU acceleration) + +### Building and Running +This project uses a `Makefile` to orchestrate common development tasks, wrapping `uv` commands. + +| Command | Description | +| :--- | :--- | +| `make install` | Install dependencies, including dev extras. | +| `make test` | Run the test suite using `pytest`. | +| `make lint` | Run `ruff` for code linting. | +| `make format` | Format code using `ruff`. | +| `make typecheck` | Run static type checking with `pyright`. | +| `make check` | Run all quality checks: `lint`, `typecheck`, and `test`. | +| `make all` | Run `check` and `format`. | +| `uv build` | Build the distribution packages (wheel/sdist). | + +### Architecture +* **Entry Point:** The engine is registered via `project.entry-points` in `pyproject.toml` as `ocrbridge.engines.easyocr:EasyOCREngine`. +* **Core Logic:** `src/ocrbridge/engines/easyocr/engine.py` contains the `EasyOCREngine` class. + * `_create_reader`: Initializes the EasyOCR reader (lazy-loaded). + * `process`: Main method to handle files; routes to `_process_image` or `_process_pdf`. + * `_to_hocr`: Converts EasyOCR's specific output format to standard HOCR XML. +* **Configuration:** `src/ocrbridge/engines/easyocr/models.py` defines `EasyOCRParams` (languages, thresholds) using Pydantic. + +## Development Conventions + +### Code Style +* **Formatting:** Enforced by `ruff`. Line length is 100 characters. +* **Typing:** Strict type checking with `pyright`. All public functions must have type annotations. +* **Imports:** Sorted and organized by `ruff`. + +### Testing +* **Framework:** `pytest`. +* **Location:** Tests are located in the `tests/` directory. +* **Markers:** + * `@pytest.mark.integration`: Tests requiring external binaries (e.g., Tesseract, though this is EasyOCR repo, the marker exists in config). + * `@pytest.mark.slow`: Long-running tests. +* **Samples:** `samples/` directory contains test assets (images, PDFs). + +### Versioning & Release +* **Semantic Versioning:** Managed by `python-semantic-release`. +* **Commits:** Must follow the [Conventional Commits](https://www.conventionalcommits.org/) specification (e.g., `feat:`, `fix:`, `docs:`). This is enforced by `commitlint` (via Node.js hooks) and checked in CI. diff --git a/Makefile b/Makefile index 23a539e..581c9bb 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ format: install $(UV) run $(RUFF) format src tests typecheck: install - $(UV) run $(PYRIGHT) + $(UV) run $(PYRIGHT) --project pyproject.toml test: install $(UV) run pytest diff --git a/pyproject.toml b/pyproject.toml index 6f4b8ef..addd77e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,11 +3,11 @@ name = "ocrbridge-easyocr" version = "1.0.0" description = "EasyOCR engine for OCR Bridge" readme = "README.md" -requires-python = ">=3.11" +requires-python = ">=3.10" license = {text = "MIT"} dependencies = [ - "ocrbridge-core>=0.1.0", + "ocrbridge-core>=2.0.0", "easyocr>=1.7.2", "torch>=2.1.0", "pdf2image>=1.17.0", @@ -27,7 +27,7 @@ dev = [ ] test = [ "pytest~=8.0", - "pytest-mock>=3.15.1", + "pytest-mock>=3.12.0", ] # Entry point for engine discovery @@ -43,7 +43,7 @@ packages = ["src/ocrbridge"] [tool.ruff] line-length = 100 -target-version = "py311" +target-version = "py310" [tool.ruff.lint] select = ["E", "F", "I", "N", "W"] @@ -64,6 +64,10 @@ markers = [ "integration: marks tests as integration tests requiring Tesseract binary", "slow: marks tests as slow running", ] +filterwarnings = [ + "ignore:The argument 'device' of Tensor.pin_memory.*:DeprecationWarning", + "ignore:The argument 'device' of Tensor.is_pinned.*:DeprecationWarning", +] [tool.coverage.run] source = ["src"] @@ -85,3 +89,6 @@ build_command = """ uv build """ version_toml = ["pyproject.toml:project.version"] + +[tool.uv.sources] +ocrbridge-core = { workspace = true } diff --git a/src/ocrbridge/engines/easyocr/models.py b/src/ocrbridge/engines/easyocr/models.py index fd42117..d9fd7fc 100644 --- a/src/ocrbridge/engines/easyocr/models.py +++ b/src/ocrbridge/engines/easyocr/models.py @@ -1,8 +1,13 @@ """EasyOCR engine parameter models.""" -from pydantic import Field, field_validator +from pydantic import Field, ValidationInfo, field_validator from ocrbridge.core.models import OCREngineParams +from ocrbridge.core.validation import ( + validate_list_length, + validate_probability, + validate_whitelist, +) # EasyOCR supported languages (80+ languages) EASYOCR_SUPPORTED_LANGUAGES = { @@ -119,31 +124,26 @@ class EasyOCRParams(OCREngineParams): @classmethod def validate_languages(cls, v: list[str]) -> list[str]: """Validate EasyOCR language codes against supported languages.""" - if not v: - raise ValueError("At least one language required for EasyOCR") + # Use core utilities for common validations + validate_list_length(v, min_length=1, max_length=5, field_name="languages") - if len(v) > 5: - raise ValueError("Maximum 5 languages allowed for EasyOCR") - - # Check all languages are supported - invalid_langs = [lang for lang in v if lang not in EASYOCR_SUPPORTED_LANGUAGES] - - if invalid_langs: + # Engine-specific: Check against EasyOCR whitelist with improved error + try: + validate_whitelist(v, EASYOCR_SUPPORTED_LANGUAGES, field_name="EasyOCR languages") + except ValueError as e: + # Add helpful hint about format difference raise ValueError( - ( - f"Unsupported EasyOCR language codes: {invalid_langs}. " - "Use EasyOCR format (e.g., 'en', 'ch_sim', 'ja'), " - "not Tesseract format ('eng', 'chi_sim')" - ) + f"{e}. Use EasyOCR format (e.g., 'en', 'ch_sim', 'ja'), " + "not Tesseract format ('eng', 'chi_sim')" ) return v @field_validator("text_threshold", "link_threshold") @classmethod - def validate_threshold(cls, v: float) -> float: + def validate_threshold(cls, v: float, info: ValidationInfo) -> float: """Validate threshold is within valid range.""" - if not 0.0 <= v <= 1.0: - raise ValueError("Threshold must be between 0.0 and 1.0") - - return v + # Use core utility for probability validation + # Check if field_name is available (it should be for field validators) + field_name = info.field_name or "threshold" + return validate_probability(v, field_name=field_name) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 3c1664a..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests package placeholder to satisfy linting.""" diff --git a/tests/test_models.py b/tests/test_models.py index dead185..78cc033 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -42,7 +42,7 @@ def test_invalid_language_code(self) -> None: EasyOCRParams(languages=["eng"]) # Tesseract format, not EasyOCR error = exc_info.value.errors()[0] - assert "Unsupported EasyOCR language codes" in error["msg"] + assert "Invalid values for 'EasyOCR languages'" in error["msg"] assert "eng" in error["msg"] def test_invalid_multiple_language_codes(self) -> None: @@ -51,7 +51,7 @@ def test_invalid_multiple_language_codes(self) -> None: EasyOCRParams(languages=["eng", "chi_sim", "invalid"]) error = exc_info.value.errors()[0] - assert "Unsupported EasyOCR language codes" in error["msg"] + assert "Invalid values for 'EasyOCR languages'" in error["msg"] def test_empty_languages_list(self) -> None: """Test EasyOCRParams rejects empty language list.""" From 6b24154fa32010397a3b0b647a3dea81f9071b00 Mon Sep 17 00:00:00 2001 From: Martin Kleine <24376955+datenzar@users.noreply.github.com> Date: Sat, 29 Nov 2025 18:45:42 +0100 Subject: [PATCH 2/2] refactor: remove unused UV source configuration Cleaned up the pyproject.toml by removing the unused ocrbridge-core source configuration to streamline project dependencies. --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index addd77e..8d7b2cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,6 +89,3 @@ build_command = """ uv build """ version_toml = ["pyproject.toml:project.version"] - -[tool.uv.sources] -ocrbridge-core = { workspace = true }