Require plugin model registration for namespace access in OCROptions

- Update __getattr__ docstring to clarify that plugin models must be
  registered for namespace access (e.g., options.tesseract.timeout)
- Update test_json_serialization.py to properly register TesseractOptions
  before accessing plugin namespaces
- Worker processes now register plugin models for multiprocessing tests
- Exclude plugin cache keys from extra_attrs comparison in tests
This commit is contained in:
James R. Barlow
2025-12-22 15:09:55 -08:00
parent be425e7405
commit aec995aced
2 changed files with 35 additions and 5 deletions
+4
View File
@@ -511,6 +511,10 @@ class OCROptions(BaseModel):
options.tesseract.timeout
options.optimize.level
Plugin models must be registered via register_plugin_models() for
namespace access to work. Built-in plugins register their models
during initialization.
Args:
name: Attribute name
+31 -5
View File
@@ -4,23 +4,44 @@ import multiprocessing
from io import BytesIO
from pathlib import Path
import pytest
from ocrmypdf._options import OCROptions
from ocrmypdf.builtin_plugins.tesseract_ocr import TesseractOptions
@pytest.fixture(autouse=True)
def register_plugin_models():
"""Register plugin models for tests."""
OCROptions.register_plugin_models({'tesseract': TesseractOptions})
yield
# Clean up after test (optional, but good practice)
def worker_function(options_json: str) -> str:
"""Worker function that deserializes OCROptions from JSON and returns a result."""
# Register plugin models in worker process
from ocrmypdf._options import OCROptions
from ocrmypdf.builtin_plugins.tesseract_ocr import TesseractOptions
OCROptions.register_plugin_models({'tesseract': TesseractOptions})
# Reconstruct OCROptions from JSON in worker process
options = OCROptions.model_validate_json_safe(options_json)
# Verify we can access various option types
# Count only user-added extra_attrs (exclude plugin cache keys starting with '_')
user_attrs_count = len(
[k for k in options.extra_attrs.keys() if not k.startswith('_')]
)
result = {
'input_file': str(options.input_file),
'output_file': str(options.output_file),
'languages': options.languages,
'optimize': options.optimize,
'tesseract_timeout': options.tesseract_timeout,
'tesseract_timeout': options.tesseract.timeout,
'fast_web_view': options.fast_web_view,
'extra_attrs_count': len(options.extra_attrs),
'extra_attrs_count': user_attrs_count,
}
# Return as JSON string
@@ -56,11 +77,16 @@ def test_json_serialization_multiprocessing():
assert reconstructed.output_file == options.output_file
assert reconstructed.languages == options.languages
assert reconstructed.optimize == options.optimize
assert reconstructed.tesseract_timeout == options.tesseract_timeout
assert reconstructed.tesseract_timeout == options.tesseract.timeout
assert reconstructed.fast_web_view == options.fast_web_view
assert reconstructed.deskew == options.deskew
assert reconstructed.clean == options.clean
assert reconstructed.extra_attrs == options.extra_attrs
# Compare user-added extra_attrs (excluding plugin cache keys)
user_attrs = {k: v for k, v in options.extra_attrs.items() if not k.startswith('_')}
reconstructed_attrs = {
k: v for k, v in reconstructed.extra_attrs.items() if not k.startswith('_')
}
assert reconstructed_attrs == user_attrs
# Test multiprocessing with JSON serialization
with multiprocessing.Pool(processes=2) as pool:
@@ -78,7 +104,7 @@ def test_json_serialization_multiprocessing():
assert result['optimize'] == 2
assert result['tesseract_timeout'] == 120.0
assert result['fast_web_view'] == 2.5
assert result['extra_attrs_count'] == 2 # Includes lossless_reconstruction
assert result['extra_attrs_count'] == 2 # custom_field and numeric_field
def test_json_serialization_with_streams():