diff --git a/src/ocrmypdf/api.py b/src/ocrmypdf/api.py index d4680a76..16978899 100644 --- a/src/ocrmypdf/api.py +++ b/src/ocrmypdf/api.py @@ -70,6 +70,49 @@ PathOrIO = BinaryIO | StrPath _api_lock = threading.Lock() +def setup_plugin_infrastructure( + plugins: Sequence[Path | str] | None = None, + plugin_manager: pluggy.PluginManager | None = None, +) -> pluggy.PluginManager: + """Set up plugin infrastructure with proper initialization. + + This function handles: + 1. Creating or validating the plugin manager + 2. Calling plugin initialization hooks + 3. Setting up any future plugin registries (Phase 2) + + Args: + plugins: List of plugin paths/names to load + plugin_manager: Existing plugin manager (if any) + + Returns: + Properly initialized plugin manager + + Raises: + ValueError: If both plugins and plugin_manager are provided + """ + if plugins and plugin_manager: + raise ValueError("plugins= and plugin_manager are mutually exclusive") + + if not plugins: + plugins = [] + elif isinstance(plugins, (str, Path)): + plugins = [plugins] + else: + plugins = list(plugins) + + # Create plugin manager if not provided + if not plugin_manager: + plugin_manager = get_plugin_manager(plugins) + + # Initialize plugins (this was missing in the API path) + plugin_manager.hook.initialize(plugin_manager=plugin_manager) # pylint: disable=no-member + + # Future: Initialize plugin option registry here (Phase 2) + + return plugin_manager + + class Verbosity(IntEnum): """Verbosity level for configure_logging.""" @@ -363,8 +406,14 @@ def ocr( # noqa: D417 parser = get_parser() with _api_lock: - if not plugin_manager: - plugin_manager = get_plugin_manager(plugins) + # Set up plugin infrastructure with proper initialization + plugin_manager = setup_plugin_infrastructure( + plugins=plugins, + plugin_manager=plugin_manager + ) + + # Get parser and let plugins add their options + parser = get_parser() plugin_manager.hook.add_options(parser=parser) # pylint: disable=no-member if 'verbose' in kwargs: @@ -490,8 +539,12 @@ def _pdf_to_hocr( # noqa: D417 extra_attrs[key] = options_kwargs.pop(key) with _api_lock: - if not plugin_manager: - plugin_manager = get_plugin_manager(plugins) + # Set up plugin infrastructure with proper initialization + plugin_manager = setup_plugin_infrastructure( + plugins=plugins, + plugin_manager=plugin_manager + ) + plugin_manager.hook.add_options(parser=get_parser()) # pylint: disable=no-member # Create OCROptions directly @@ -589,8 +642,12 @@ def _hocr_to_ocr_pdf( # noqa: D417 extra_attrs[key] = options_kwargs.pop(key) with _api_lock: - if not plugin_manager: - plugin_manager = get_plugin_manager(plugins) + # Set up plugin infrastructure with proper initialization + plugin_manager = setup_plugin_infrastructure( + plugins=plugins, + plugin_manager=plugin_manager + ) + plugin_manager.hook.add_options(parser=get_parser()) # pylint: disable=no-member # Create OCROptions directly @@ -620,4 +677,5 @@ __all__ = [ 'ocr', 'run_pipeline', 'run_pipeline_cli', + 'setup_plugin_infrastructure', ] diff --git a/src/ocrmypdf/cli.py b/src/ocrmypdf/cli.py index f0d4430a..f7552177 100644 --- a/src/ocrmypdf/cli.py +++ b/src/ocrmypdf/cli.py @@ -483,13 +483,17 @@ def get_options_and_plugins( Returns: Tuple of (OCROptions, PluginManager) """ + # Import here to avoid circular imports + from ocrmypdf.api import setup_plugin_infrastructure + # 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) + + # Set up plugin infrastructure with proper initialization + plugin_manager = setup_plugin_infrastructure(plugins=pre_options.plugins) # Get parser and let plugins add their options parser = get_parser() - plugin_manager.hook.initialize(plugin_manager=plugin_manager) # pylint: disable=no-member plugin_manager.hook.add_options(parser=parser) # pylint: disable=no-member # Parse all arguments diff --git a/tests/conftest.py b/tests/conftest.py index 31b5941f..5c03cd04 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,6 +12,7 @@ import pytest from ocrmypdf import api, pdfinfo from ocrmypdf._exec import unpaper +from ocrmypdf.api import setup_plugin_infrastructure from ocrmypdf.cli import get_options_and_plugins from ocrmypdf.exceptions import ExitCode @@ -164,3 +165,8 @@ def pytest_collection_modifyitems(config, items): for item in items: if "slow" in item.keywords: item.add_marker(skip_slow) + + +def get_test_plugin_manager(plugins=None): + """Get a properly initialized plugin manager for testing.""" + return setup_plugin_infrastructure(plugins=plugins or []) diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 0092b602..6f31b2b7 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -16,6 +16,7 @@ from ocrmypdf._jobcontext import PdfContext from ocrmypdf._metadata import metadata_fixup from ocrmypdf._pipeline import convert_to_pdfa from ocrmypdf._plugin_manager import get_plugin_manager +from ocrmypdf.api import setup_plugin_infrastructure from ocrmypdf.cli import get_options_and_plugins from ocrmypdf.exceptions import ExitCode from ocrmypdf.pdfa import file_claims_pdfa, generate_pdfa_ps @@ -330,8 +331,10 @@ def test_metadata_fixup_warning(resources, outdir, caplog): copyfile(resources / 'graph.pdf', outdir / 'graph.pdf') + # Use the new setup function instead of get_plugin_manager directly + plugin_manager = setup_plugin_infrastructure([]) context = PdfContext( - options, outdir, outdir / 'graph.pdf', None, get_plugin_manager([]) + options, outdir, outdir / 'graph.pdf', None, plugin_manager ) metadata_fixup( working_file=outdir / 'graph.pdf', context=context, pdf_save_settings={} @@ -346,7 +349,7 @@ def test_metadata_fixup_warning(resources, outdir, caplog): graph.save(outdir / 'graph_mod.pdf') context = PdfContext( - options, outdir, outdir / 'graph_mod.pdf', None, get_plugin_manager([]) + options, outdir, outdir / 'graph_mod.pdf', None, plugin_manager ) metadata_fixup( working_file=outdir / 'graph.pdf', context=context, pdf_save_settings={} @@ -379,8 +382,11 @@ def test_prevent_gs_invalid_xml(resources, outdir): ] ) pdfinfo = PdfInfo(outdir / 'layers.rendered.pdf') + + # Use the new setup function + plugin_manager = setup_plugin_infrastructure([]) context = PdfContext( - options, outdir, outdir / 'layers.rendered.pdf', pdfinfo, get_plugin_manager([]) + options, outdir, outdir / 'layers.rendered.pdf', pdfinfo, plugin_manager ) convert_to_pdfa( diff --git a/tests/test_unpaper.py b/tests/test_unpaper.py index 31425cba..83da7478 100644 --- a/tests/test_unpaper.py +++ b/tests/test_unpaper.py @@ -12,6 +12,7 @@ from packaging.version import Version from ocrmypdf._exec import unpaper from ocrmypdf._validation import check_options +from ocrmypdf.api import setup_plugin_infrastructure from ocrmypdf.cli import get_options_and_plugins from ocrmypdf.exceptions import BadArgsError, ExitCode, MissingDependencyError