From b1f2d257e2a0c1f38aeaec2279c3847e8b5fec3c Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sat, 9 Jul 2022 02:24:19 -0700 Subject: [PATCH] Typing improvements --- pyproject.toml | 3 +- setup.cfg | 2 ++ src/ocrmypdf/_sync.py | 2 +- src/ocrmypdf/_version.py | 4 +-- src/ocrmypdf/pdfa.py | 4 +-- src/ocrmypdf/pdfinfo/info.py | 2 +- src/ocrmypdf/subprocess/__init__.py | 55 +++++++++++++++++++++-------- 7 files changed, 50 insertions(+), 22 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0ee491cd..f2b15188 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,7 +94,8 @@ module = [ 'pdfminer.*', 'reportlab.*', 'fitz', - 'libxmp.utils' + 'libxmp.utils', + 'importlib_metadata' ] ignore_missing_imports = true diff --git a/setup.cfg b/setup.cfg index 5bcf9f53..894488e3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -86,6 +86,8 @@ test = pytest-cov>=2.11.1 pytest-xdist>=2.2.0 python-xmp-toolkit==2.0.1 # also requires apt-get install libexempi3 + types-humanfriendly + types-Pillow watcher = watchdog>=1.0.2 webservice = diff --git a/src/ocrmypdf/_sync.py b/src/ocrmypdf/_sync.py index f3a54195..d40c2190 100644 --- a/src/ocrmypdf/_sync.py +++ b/src/ocrmypdf/_sync.py @@ -302,7 +302,7 @@ def exec_concurrent(context: PdfContext, executor: Executor) -> Sequence[str]: # Merge layers to one single pdf pdf = ocrgraft.finalize() - messages: List[str] = [] + messages: Sequence[str] = [] if options.output_type != 'none': # PDF/A and metadata log.info("Postprocessing...") diff --git a/src/ocrmypdf/_version.py b/src/ocrmypdf/_version.py index 2925dbb4..48369435 100644 --- a/src/ocrmypdf/_version.py +++ b/src/ocrmypdf/_version.py @@ -10,9 +10,9 @@ OCRmyPDF uses setuptools_scm to derive version from git tags. """ try: - from importlib_metadata import version as _package_version -except ImportError: from importlib.metadata import version as _package_version +except ImportError: + from importlib_metadata import version as _package_version # type: ignore PROGRAM_NAME = 'ocrmypdf' diff --git a/src/ocrmypdf/pdfa.py b/src/ocrmypdf/pdfa.py index 23ef7ab9..5f3b9aaa 100644 --- a/src/ocrmypdf/pdfa.py +++ b/src/ocrmypdf/pdfa.py @@ -14,9 +14,9 @@ from pathlib import Path from typing import Dict, Iterator, Union try: - from importlib_resources import files as package_files -except ImportError: from importlib.resources import files as package_files +except ImportError: + from importlib_resources import files as package_files # type: ignore import pikepdf diff --git a/src/ocrmypdf/pdfinfo/info.py b/src/ocrmypdf/pdfinfo/info.py index 132f9033..c4adbe29 100644 --- a/src/ocrmypdf/pdfinfo/info.py +++ b/src/ocrmypdf/pdfinfo/info.py @@ -400,7 +400,7 @@ class ImageInfo: self._comp = 4 else: self._comp = 3 - except UnsupportedImageTypeError as ex: + except (AttributeError, UnsupportedImageTypeError) as ex: self._comp = None logger.warning( f"An image with a corrupt or unreadable ICC profile was found. " diff --git a/src/ocrmypdf/subprocess/__init__.py b/src/ocrmypdf/subprocess/__init__.py index 74994414..f12d8d13 100644 --- a/src/ocrmypdf/subprocess/__init__.py +++ b/src/ocrmypdf/subprocess/__init__.py @@ -14,9 +14,10 @@ import sys from collections.abc import Mapping from contextlib import suppress from functools import lru_cache +from pathlib import Path from subprocess import PIPE, STDOUT, CalledProcessError, CompletedProcess, Popen from subprocess import run as subprocess_run -from typing import Callable, Optional, Type, Union +from typing import Callable, Optional, Sequence, Tuple, Type, Union from packaging.version import Version @@ -26,11 +27,14 @@ from ocrmypdf.exceptions import MissingDependencyError log = logging.getLogger(__name__) +Args = Sequence[Union[Path, str]] +OsEnviron = os._Environ # pylint: disable=protected-access + def run( - args, + args: Args, *, - env=None, + env: Optional[OsEnviron] = None, logs_errors_to_stdout: bool = False, check: bool = False, **kwargs, @@ -73,7 +77,12 @@ def run( def run_polling_stderr( - args, *, callback: Callable[[str], None], check: bool = False, env=None, **kwargs + args: Args, + *, + callback: Callable[[str], None], + check: bool = False, + env: Optional[OsEnviron] = None, + **kwargs, ) -> CompletedProcess: """Run a process like ``ocrmypdf.subprocess.run``, and poll stderr. @@ -106,7 +115,9 @@ def run_polling_stderr( return CompletedProcess(args, proc.returncode, None, stderr=stderr) -def _fix_process_args(args, env, kwargs): +def _fix_process_args( + args: Args, env: Optional[OsEnviron], kwargs +) -> Tuple[Args, OsEnviron, logging.Logger, bool]: assert 'universal_newlines' not in kwargs, "Use text= instead of universal_newlines" if not env: @@ -123,14 +134,18 @@ def _fix_process_args(args, env, kwargs): log.debug("Running: %s", args) process_log = log.getChild(os.path.basename(program)) - text = kwargs.get('text', False) + text = bool(kwargs.get('text', False)) return args, env, process_log, text @lru_cache(maxsize=None) def get_version( - program: str, *, version_arg: str = '--version', regex=r'(\d+(\.\d+)*)', env=None + program: str, + *, + version_arg: str = '--version', + regex=r'(\d+(\.\d+)*)', + env: Optional[OsEnviron] = None, ) -> str: """Get the version of the specified program @@ -229,7 +244,7 @@ a command prompt to install the missing package: ''' -def _get_platform(): +def _get_platform() -> str: if sys.platform.startswith('freebsd'): return 'freebsd' elif sys.platform.startswith('linux'): @@ -239,7 +254,9 @@ def _get_platform(): return sys.platform -def _error_trailer(program, package, **kwargs): +def _error_trailer( + program: str, package: Union[str, Mapping[str, str]], **kwargs +) -> None: del kwargs if isinstance(package, Mapping): package = package.get(_get_platform(), program) @@ -252,7 +269,9 @@ def _error_trailer(program, package, **kwargs): log.info(WINDOWS_INSTALL_ADVICE.format(**locals())) -def _error_missing_program(program, package, required_for, recommended): +def _error_missing_program( + program: str, package: str, required_for: Optional[str], recommended: bool +) -> None: # pylint: disable=unused-argument if recommended: log.warning(MISSING_RECOMMEND_PROGRAM.format(**locals())) @@ -263,7 +282,13 @@ def _error_missing_program(program, package, required_for, recommended): _error_trailer(**locals()) -def _error_old_version(program, package, need_version, found_version, required_for): +def _error_old_version( + program: str, + package: str, + need_version: str, + found_version: str, + required_for: Optional[str], +) -> None: # pylint: disable=unused-argument if required_for: log.error(OLD_VERSION_REQUIRED_FOR.format(**locals())) @@ -272,7 +297,7 @@ def _error_old_version(program, package, need_version, found_version, required_f _error_trailer(**locals()) -def _remove_leading_v(s): +def _remove_leading_v(s: str) -> str: if sys.version_info >= (3, 9): return s.removeprefix('v') @@ -285,12 +310,12 @@ def check_external_program( *, program: str, package: str, - version_checker: Callable, + version_checker: Callable[[], str], need_version: str, required_for: Optional[str] = None, - recommended=False, + recommended: bool = False, version_parser: Type[Version] = Version, -): +) -> None: """Check for required version of external program and raise exception if not. Args: