Start pluggy-based plugin system

This commit is contained in:
James R. Barlow
2020-05-01 02:15:23 -07:00
parent 016dfd420c
commit 82bce463ae
8 changed files with 130 additions and 16 deletions
+9 -4
View File
@@ -15,10 +15,13 @@
# You should have received a copy of the GNU General Public License
# along with OCRmyPDF. If not, see <http://www.gnu.org/licenses/>.
from . import helpers, hocrtransform, leptonica, pdfa, pdfinfo
from ._version import PROGRAM_NAME, __version__
from .api import Verbosity, configure_logging, ocr
from .exceptions import (
from pluggy import HookimplMarker
from ocrmypdf import helpers, hocrtransform, leptonica, pdfa, pdfinfo
from ocrmypdf._version import PROGRAM_NAME, __version__
from ocrmypdf.api import Verbosity, configure_logging, ocr
from ocrmypdf.exceptions import (
BadArgsError,
DpiError,
EncryptedPdfError,
@@ -33,3 +36,5 @@ from .exceptions import (
TesseractConfigError,
UnsupportedImageFormatError,
)
hookimpl = HookimplMarker('ocrmypdf')
+2 -1
View File
@@ -24,11 +24,12 @@ import sys
class PDFContext:
"""Holds our context for a particular run of the pipeline"""
def __init__(self, options, work_folder, origin, pdfinfo):
def __init__(self, options, work_folder, origin, pdfinfo, plugin_manager):
self.options = options
self.work_folder = work_folder
self.origin = origin
self.pdfinfo = pdfinfo
self.plugin_manager = plugin_manager
if options:
self.name = os.path.basename(options.input_file)
else:
+1
View File
@@ -194,6 +194,7 @@ def validate_pdfinfo_options(context):
"form and all filled form fields. The output PDF will be "
"'flattened' and will no longer be fillable."
)
context.plugin_manager.hook.prepare(options=options)
def get_page_dpi(pageinfo, options):
+66
View File
@@ -0,0 +1,66 @@
# © 2020 James R. Barlow: github.com/jbarlow83
#
# This file is part of OCRmyPDF.
#
# OCRmyPDF is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OCRmyPDF is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with OCRmyPDF. If not, see <http://www.gnu.org/licenses/>.
from argparse import Namespace
import pluggy
from PIL import Image
from ocrmypdf.pdfinfo import PdfInfo
hookspec = pluggy.HookspecMarker('ocrmypdf')
# pylint: disable=unused-argument
@hookspec
def prepare(options: Namespace) -> None:
"""Called to notify a plugin that a file will be processed.
The plugin may modify the options. All objects that are in options must
be picklable so they can be marshalled to child worker processes.
Typically, a plugin will call ``registry.register_plugin(__name__)`` to register
all of its public functions with the plugin registry. Functions that are
not intended for registration should be prefixed with an underscore.
Functions that imported from other modules will be ignored by
``.register_plugin()``. For example if you use ``from os import basename``,
``basename`` will not be registered.
"""
@hookspec
def validate(pdfinfo: PdfInfo, options: Namespace) -> None:
"""Called to give a plugin an opportunity to review options and pdfinfo.
options contains the "work order" to process a particular file. pdfinfo
contains information about the input file obtained after loading and
parsing.
The plugin may raise InputFileError or any ExitCodeException to request
normal termination. If the plugin raises another exception type, ocrmypdf
will abort with an error and hold the plugin responsible.
"""
@hookspec
def filter_ocr_image(image: Image) -> Image:
"""Called to filter the image before it is sent to OCR.
This is the image that OCR sees, not what the user sees when they view the
PDF.
"""
+39 -10
View File
@@ -15,6 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with OCRmyPDF. If not, see <http://www.gnu.org/licenses/>.
import importlib
import logging
import logging.handlers
import multiprocessing
@@ -28,12 +29,14 @@ from pathlib import Path
from tempfile import mkdtemp
import PIL
import pluggy
from ._concurrent import exec_progress_pool
from ._graft import OcrGrafter
from ._jobcontext import PDFContext, cleanup_working_files
from ._logging import PageNumberFilter
from ._pipeline import (
from ocrmypdf import _pluginspec
from ocrmypdf._concurrent import exec_progress_pool
from ocrmypdf._graft import OcrGrafter
from ocrmypdf._jobcontext import PDFContext, cleanup_working_files
from ocrmypdf._logging import PageNumberFilter
from ocrmypdf._pipeline import (
convert_to_pdfa,
copy_final,
create_ocr_image,
@@ -58,14 +61,14 @@ from ._pipeline import (
triage,
validate_pdfinfo_options,
)
from ._validation import (
from ocrmypdf._validation import (
check_requested_output_file,
create_input_file,
report_output_file_size,
)
from .exceptions import ExitCode, ExitCodeException
from .helpers import available_cpu_count, check_pdf
from .pdfa import file_claims_pdfa
from ocrmypdf.exceptions import ExitCode, ExitCodeException
from ocrmypdf.helpers import available_cpu_count, check_pdf
from ocrmypdf.pdfa import file_claims_pdfa
log = logging.getLogger(__name__)
@@ -298,6 +301,24 @@ def configure_debug_logging(log_filename, prefix=''):
return log_file_handler
def _load_object_from_module(location):
"""Load a object given a module location
For location=a.b.c, will effectively run "from a.b import c"
Example:
_load_object_from_module("a.b.c")
"""
module_parts = location.split('.')
module_name = '.'.join(module_parts[:-1])
object_name = module_parts[-1]
module = importlib.import_module(module_name)
obj = getattr(module, object_name)
log.debug(f"Loaded object: from {module_name} import {object_name}")
return obj
def run_pipeline(options, api=False):
# Any changes to options will not take effect for options that are already
# bound to function parameters in the pipeline. (For example
@@ -312,6 +333,14 @@ def run_pipeline(options, api=False):
):
debug_log_handler = configure_debug_logging(Path(work_folder) / "debug.log")
pm = pluggy.PluginManager('ocrmypdf')
pm.add_hookspecs(_pluginspec)
for name in options.plugins:
# module = _load_object_from_module(name)
module = importlib.import_module(name)
pm.register(module)
try:
check_requested_output_file(options)
start_input_file, original_filename = create_input_file(options, work_folder)
@@ -331,7 +360,7 @@ def run_pipeline(options, api=False):
max_workers=options.jobs if not options.use_threads else 1, # To help debug
)
context = PDFContext(options, work_folder, origin_pdf, pdfinfo)
context = PDFContext(options, work_folder, origin_pdf, pdfinfo, pm)
# Validate options are okay for this pdf
validate_pdfinfo_options(context)
+6
View File
@@ -480,6 +480,12 @@ advanced.add_argument(
"which do not benefit. If the threshold is 0 it will be apply to all files. "
"Set the threshold very high to disable.",
)
advanced.add_argument(
'--plugins',
action='append',
default=[],
help="Path to a folder than contains plugins.",
)
debugging = parser.add_argument_group(
"Debugging", "Arguments to help with troubleshooting and debugging"
+6
View File
@@ -0,0 +1,6 @@
import ocrmypdf
@ocrmypdf.hookimpl
def prepare(options):
raise ValueError('foo')