Reorganize validation to fix exception when Tesseract not installed

The existing logic would call an OCR plugin's get_languages function before
allowing the plugin to check if its dependencies were available. This caused
an exception if Tesseract was installed, when we were supposed to issue
an error message advising the user to install Tesseract.
This commit is contained in:
James R. Barlow
2022-08-01 02:04:09 -07:00
parent c7fcbe9075
commit ba372e5841
2 changed files with 43 additions and 34 deletions
+25 -19
View File
@@ -11,12 +11,14 @@ import logging
import os
import sys
import unicodedata
from argparse import Namespace
from pathlib import Path
from shutil import copyfileobj
from typing import Sequence
import pikepdf
import PIL
from pluggy import PluginManager
from ocrmypdf._exec import unpaper
from ocrmypdf.exceptions import (
@@ -40,7 +42,7 @@ log = logging.getLogger(__name__)
# --------
def check_platform():
def check_platform() -> None:
if os.name == 'nt' and sys.maxsize <= 2**32: # pragma: no cover
# 32-bit interpreter on Windows
log.error(
@@ -49,7 +51,7 @@ def check_platform():
)
def check_options_languages(options, ocr_engine_languages):
def check_options_languages(options: Namespace, ocr_engine_languages: set[str]) -> None:
if not options.languages:
options.languages = {DEFAULT_LANGUAGE}
system_lang = locale.getlocale()[0]
@@ -68,7 +70,7 @@ def check_options_languages(options, ocr_engine_languages):
raise MissingDependencyError(msg)
def check_options_output(options):
def check_options_output(options: Namespace) -> None:
is_latin = options.languages.issubset(HOCR_OK_LANGS)
if options.pdf_renderer.startswith('hocr') and not is_latin:
@@ -104,7 +106,7 @@ def check_options_output(options):
)
def check_options_sidecar(options):
def check_options_sidecar(options: Namespace) -> None:
if options.sidecar == '\0':
if options.output_file == '-':
raise BadArgsError(
@@ -121,7 +123,7 @@ def check_options_sidecar(options):
)
def check_options_preprocessing(options):
def check_options_preprocessing(options: Namespace) -> None:
if options.clean_final:
options.clean = True
if options.unpaper_args and not options.clean:
@@ -182,7 +184,7 @@ def _pages_from_ranges(ranges: str) -> set[int]:
return set(pages)
def check_options_ocr_behavior(options):
def check_options_ocr_behavior(options: Namespace) -> None:
exclusive_options = sum(
(1 if opt else 0)
for opt in (options.force_ocr, options.skip_text, options.redo_ocr)
@@ -193,7 +195,7 @@ def check_options_ocr_behavior(options):
options.pages = _pages_from_ranges(options.pages)
def check_options_advanced(options):
def check_options_advanced(options: Namespace) -> None:
if options.pdfa_image_compression != 'auto' and not options.output_type.startswith(
'pdfa'
):
@@ -203,7 +205,7 @@ def check_options_advanced(options):
)
def check_options_metadata(options):
def check_options_metadata(options: Namespace) -> None:
docinfo = [options.title, options.author, options.keywords, options.subject]
for s in (m for m in docinfo if m):
for char in s:
@@ -216,15 +218,14 @@ def check_options_metadata(options):
)
def check_options_pillow(options):
def check_options_pillow(options: Namespace) -> None:
PIL.Image.MAX_IMAGE_PIXELS = int(options.max_image_mpixels * 1_000_000)
if PIL.Image.MAX_IMAGE_PIXELS == 0:
PIL.Image.MAX_IMAGE_PIXELS = None
def _check_options(options, plugin_manager, ocr_engine_languages):
def _check_plugin_invariant_options(options: Namespace) -> None:
check_platform()
check_options_languages(options, ocr_engine_languages)
check_options_metadata(options)
check_options_output(options)
check_options_sidecar(options)
@@ -232,15 +233,20 @@ def _check_options(options, plugin_manager, ocr_engine_languages):
check_options_ocr_behavior(options)
check_options_advanced(options)
check_options_pillow(options)
def _check_plugin_options(options: Namespace, plugin_manager: PluginManager) -> None:
plugin_manager.hook.check_options(options=options)
def check_options(options, plugin_manager):
ocr_engine_languages = plugin_manager.hook.get_ocr_engine().languages(options)
_check_options(options, plugin_manager, ocr_engine_languages)
check_options_languages(options, ocr_engine_languages)
def create_input_file(options, work_folder: Path) -> tuple[Path, str]:
def check_options(options: Namespace, plugin_manager: PluginManager) -> None:
_check_plugin_invariant_options(options)
_check_plugin_options(options, plugin_manager)
def create_input_file(options: Namespace, work_folder: Path) -> tuple[Path, str]:
if options.input_file == '-':
# stdin
log.info('reading file from standard input')
@@ -275,7 +281,7 @@ def create_input_file(options, work_folder: Path) -> tuple[Path, str]:
raise InputFileError(msg) from e
def check_requested_output_file(options):
def check_requested_output_file(options: Namespace) -> None:
if options.output_file == '-':
if sys.stdout.isatty():
raise BadArgsError(
@@ -293,13 +299,13 @@ def check_requested_output_file(options):
def report_output_file_size(
options,
options: Namespace,
input_file: Path,
output_file: Path,
optimize_messages: Sequence[str] | None = None,
file_overhead: int = 4000,
page_overhead: int = 3000,
):
) -> None:
if optimize_messages is None:
optimize_messages = []
try:
+18 -15
View File
@@ -44,27 +44,24 @@ def make_opts(*args, **kwargs):
def test_hocr_notlatin_warning(caplog):
# Bypass the test to see if the language is installed; we just want to pretend
# that a non-Latin language is installed
vd._check_options(
vd.check_options(
*make_opts_pm(language='chi_sim', pdf_renderer='hocr', output_type='pdfa'),
{'chi_sim'},
)
assert 'PDF renderer is known to cause' in caplog.text
def test_old_ghostscript(caplog):
with patch('ocrmypdf._exec.ghostscript.version', return_value='9.19'):
vd._check_options(
*make_opts_pm(language='chi_sim', output_type='pdfa'), {'chi_sim'}
)
vd.check_options(*make_opts_pm(language='chi_sim', output_type='pdfa'))
assert 'does not work correctly' in caplog.text
with patch('ocrmypdf._exec.ghostscript.version', return_value='9.18'):
with pytest.raises(MissingDependencyError):
vd._check_options(*make_opts_pm(output_type='pdfa-3'), set())
vd.check_options(*make_opts_pm(output_type='pdfa-3'))
with patch('ocrmypdf._exec.ghostscript.version', return_value='9.24'):
with pytest.raises(MissingDependencyError):
vd._check_options(*make_opts_pm(), set())
vd.check_options(*make_opts_pm())
def test_old_tesseract_error():
@@ -72,7 +69,7 @@ def test_old_tesseract_error():
with pytest.raises(MissingDependencyError):
opts = make_opts(pdf_renderer='sandwich', language='eng')
plugin_manager = get_plugin_manager(opts.plugins)
vd._check_options(opts, plugin_manager, {'eng'})
vd.check_options(opts, plugin_manager)
def test_lossless_redo():
@@ -92,7 +89,7 @@ def test_mutex_options():
def test_optimizing(caplog):
opts = make_opts(optimize=0, jbig2_lossy=True, png_quality=18, jpeg_quality=10)
plugin_manager = get_plugin_manager(opts.plugins)
vd._check_options(opts, plugin_manager, set())
vd.check_options(opts, plugin_manager)
assert 'will be ignored because' in caplog.text
@@ -100,7 +97,7 @@ def test_user_words(caplog):
with patch('ocrmypdf._exec.tesseract.has_user_words', return_value=False):
opts = make_opts(user_words='foo')
plugin_manager = get_plugin_manager(opts.plugins)
vd._check_options(opts, plugin_manager, set())
vd.check_options(opts, plugin_manager)
assert (
'Tesseract 4.0 (which you have installed) ignores --user-words'
in caplog.text
@@ -109,7 +106,7 @@ def test_user_words(caplog):
with patch('ocrmypdf._exec.tesseract.has_user_words', return_value=True):
opts = make_opts(user_patterns='foo')
plugin_manager = get_plugin_manager(opts.plugins)
vd._check_options(opts, plugin_manager, set())
vd.check_options(opts, plugin_manager)
assert (
'Tesseract 4.0 (which you have installed) ignores --user-words'
not in caplog.text
@@ -169,7 +166,7 @@ def test_no_progress_bar(progress_bar, resources):
opts = make_opts(progress_bar=progress_bar, input_file=(resources / 'trivial.pdf'))
plugin_manager = get_plugin_manager(opts.plugins)
vd._check_options(opts, plugin_manager, set())
vd.check_options(opts, plugin_manager)
pbar_disabled = None
@@ -290,13 +287,19 @@ def test_optional_program_recommended(caplog):
def test_pagesegmode_warning(caplog):
opts = make_opts(tesseract_pagesegmode='0')
plugin_manager = get_plugin_manager(opts.plugins)
vd._check_options(opts, plugin_manager, set())
vd.check_options(opts, plugin_manager)
assert 'disable OCR' in caplog.text
def test_two_languages():
vd._check_options(
*make_opts_pm(language='fakelang1+fakelang2'), {'fakelang1', 'fakelang2'}
vd.check_options_languages(
create_options(
input_file='a.pdf',
output_file='b.pdf',
parser=get_parser(),
language='fakelang1+fakelang2',
),
{'fakelang1', 'fakelang2'},
)