diff --git a/src/ocrmypdf/__main__.py b/src/ocrmypdf/__main__.py index b1514a86..74ffb3e6 100755 --- a/src/ocrmypdf/__main__.py +++ b/src/ocrmypdf/__main__.py @@ -16,8 +16,8 @@ from contextlib import suppress from ocrmypdf import __version__ from ocrmypdf._pipelines.ocr import run_pipeline_cli from ocrmypdf._validation import check_options -from ocrmypdf.cli import get_options_and_plugins from ocrmypdf.api import Verbosity, configure_logging +from ocrmypdf.cli import get_options_and_plugins from ocrmypdf.exceptions import ( BadArgsError, ExitCode, diff --git a/src/ocrmypdf/_options.py b/src/ocrmypdf/_options.py index 1353abab..04d012c3 100644 --- a/src/ocrmypdf/_options.py +++ b/src/ocrmypdf/_options.py @@ -164,7 +164,6 @@ class OCROptions(BaseModel): default_factory=dict, exclude=True, alias='_extra_attrs' ) - @field_validator('languages') @classmethod def validate_languages(cls, v): diff --git a/src/ocrmypdf/_plugin_manager.py b/src/ocrmypdf/_plugin_manager.py index 146704fc..f0c2bc0d 100644 --- a/src/ocrmypdf/_plugin_manager.py +++ b/src/ocrmypdf/_plugin_manager.py @@ -5,7 +5,6 @@ from __future__ import annotations -import argparse import importlib import importlib.util import pkgutil @@ -17,7 +16,6 @@ import pluggy import ocrmypdf.builtin_plugins from ocrmypdf import pluginspec -from ocrmypdf.cli import get_parser class OcrmypdfPluginManager(pluggy.PluginManager): diff --git a/src/ocrmypdf/cli.py b/src/ocrmypdf/cli.py index 025edbe5..0d61523e 100644 --- a/src/ocrmypdf/cli.py +++ b/src/ocrmypdf/cli.py @@ -7,16 +7,16 @@ from __future__ import annotations import argparse from collections.abc import Callable, Mapping -from typing import Any, TypeVar, TYPE_CHECKING +from typing import Any, TypeVar -from ocrmypdf._defaults import DEFAULT_LANGUAGE, DEFAULT_ROTATE_PAGES_THRESHOLD +import pluggy + +from ocrmypdf._defaults import DEFAULT_ROTATE_PAGES_THRESHOLD from ocrmypdf._defaults import PROGRAM_NAME as _PROGRAM_NAME +from ocrmypdf._options import OCROptions +from ocrmypdf._plugin_manager import get_plugin_manager from ocrmypdf._version import __version__ as _VERSION -if TYPE_CHECKING: - import pluggy - from ocrmypdf._options import OCROptions - T = TypeVar('T', int, float) @@ -100,7 +100,7 @@ class LanguageSetAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): """Add a language to the set.""" dest = getattr(namespace, self.dest) - if '+' in values: + if isinstance(values, str) and '+' in values: [dest.append(lang) for lang in values.split('+')] else: dest.append(values) @@ -471,14 +471,12 @@ plugins_only_parser.add_argument( ) -def namespace_to_options(ns) -> 'OCROptions': +def namespace_to_options(ns) -> OCROptions: """Convert argparse.Namespace to OCROptions. - + This function encapsulates CLI-specific knowledge of how command line arguments map to our internal options model. """ - from ocrmypdf._options import OCROptions - # Extract known fields known_fields = {} extra_attrs = {} @@ -504,22 +502,19 @@ def namespace_to_options(ns) -> 'OCROptions': def get_options_and_plugins( args=None, -) -> tuple['OCROptions', 'pluggy.PluginManager']: +) -> tuple[OCROptions, pluggy.PluginManager]: """Parse command line arguments and return OCROptions and plugin manager. - + This is the main entry point for CLI argument processing. It handles plugin discovery, argument parsing, and conversion to our internal options model. - + Args: args: Command line arguments. If None, uses sys.argv. - + Returns: Tuple of (OCROptions, PluginManager) """ - import pluggy - from ocrmypdf._plugin_manager import get_plugin_manager - # First pass: get plugins so we can register their options pre_options, _unused = plugins_only_parser.parse_known_args(args=args) plugin_manager = get_plugin_manager(pre_options.plugins) @@ -531,8 +526,8 @@ def get_options_and_plugins( # Parse all arguments namespace = parser.parse_args(args=args) - + # Convert to OCROptions options = namespace_to_options(namespace) - + return options, plugin_manager diff --git a/tests/test_json_serialization.py b/tests/test_json_serialization.py index c2402ff8..56b1e8cb 100644 --- a/tests/test_json_serialization.py +++ b/tests/test_json_serialization.py @@ -25,6 +25,7 @@ def worker_function(options_json: str) -> str: # Return as JSON string import json + return json.dumps(result) @@ -68,6 +69,7 @@ def test_json_serialization_multiprocessing(): # Verify results from worker processes import json + for result_json in results: result = json.loads(result_json) assert result['input_file'] == '/test/input.pdf' @@ -122,7 +124,9 @@ def test_json_serialization_with_none_values(): # Verify None values are preserved (check actual defaults from model) assert reconstructed.tesseract_timeout == 0.0 # Default value, not None assert reconstructed.fast_web_view == 1.0 # Default value, not None - assert reconstructed.color_conversion_strategy == "LeaveColorUnchanged" # Default value + assert ( + reconstructed.color_conversion_strategy == "LeaveColorUnchanged" + ) # Default value assert reconstructed.pdfa_image_compression is None # This one is actually None # Verify non-None values are preserved diff --git a/tests/test_unpaper.py b/tests/test_unpaper.py index 9319a97e..31425cba 100644 --- a/tests/test_unpaper.py +++ b/tests/test_unpaper.py @@ -11,8 +11,8 @@ import pytest from packaging.version import Version from ocrmypdf._exec import unpaper -from ocrmypdf.cli import get_options_and_plugins from ocrmypdf._validation import check_options +from ocrmypdf.cli import get_options_and_plugins from ocrmypdf.exceptions import BadArgsError, ExitCode, MissingDependencyError from .conftest import check_ocrmypdf, have_unpaper, run_ocrmypdf_api diff --git a/tests/test_validation.py b/tests/test_validation.py index aaf2bb7d..d829905b 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -44,11 +44,7 @@ def make_opts(*args, **kwargs): def make_ocr_opts(input_file='a.pdf', output_file='b.pdf', **kwargs): """Create OCROptions directly for testing Pydantic validation.""" - return OCROptions( - input_file=input_file, - output_file=output_file, - **kwargs - ) + return OCROptions(input_file=input_file, output_file=output_file, **kwargs) def test_old_tesseract_error(): @@ -78,11 +74,17 @@ def test_lossless_redo(): def test_mutex_options(): - with pytest.raises(ValueError, match="Choose only one of --force-ocr, --skip-text, --redo-ocr"): + with pytest.raises( + ValueError, match="Choose only one of --force-ocr, --skip-text, --redo-ocr" + ): make_ocr_opts(force_ocr=True, skip_text=True) - with pytest.raises(ValueError, match="Choose only one of --force-ocr, --skip-text, --redo-ocr"): + with pytest.raises( + ValueError, match="Choose only one of --force-ocr, --skip-text, --redo-ocr" + ): make_ocr_opts(redo_ocr=True, skip_text=True) - with pytest.raises(ValueError, match="Choose only one of --force-ocr, --skip-text, --redo-ocr"): + with pytest.raises( + ValueError, match="Choose only one of --force-ocr, --skip-text, --redo-ocr" + ): make_ocr_opts(redo_ocr=True, force_ocr=True)