Enable check_untyped_defs and make mypy hook blocking

Fix the 13 errors that surfaced under mypy --check-untyped-defs so the
flag can be turned on permanently in pyproject.toml, and drop the
advisory exit-0 wrapper on the mypy pre-commit hook now that the tree is
clean.

- _plugin_manager: rename colliding loop vars (module/name were reused
  with conflicting types) and guard spec/spec.loader from
  spec_from_file_location; call __init__ via the class in __setstate__.
- __main__: pass Verbosity(options.verbose), not a bare int.
- subprocess/_check: widen package to str | Mapping[str, str] to match
  _error_trailer's existing per-platform handling.
- optimize.main: annotate the standalone PdfContext(..., None, None) that
  only ever reads context.options.
This commit is contained in:
James R. Barlow
2026-07-07 15:02:32 -07:00
parent efebe9ca2e
commit 3d291e72c0
6 changed files with 25 additions and 17 deletions
+2 -4
View File
@@ -56,11 +56,9 @@ require_serial = true
[[repos.hooks]] [[repos.hooks]]
id = "mypy" id = "mypy"
name = "mypy (advisory — reports errors, does not fail)" name = "mypy"
language = "system" language = "system"
# Errors are pre-existing debt (87 across 22 files as of 2026-07); reports but entry = "uv run mypy src/ocrmypdf"
# never fails the hook until that debt is fixed and this can be made blocking.
entry = "bash -c 'uv run mypy src/ocrmypdf; exit 0'"
types = ["python"] types = ["python"]
pass_filenames = false pass_filenames = false
require_serial = true require_serial = true
+1
View File
@@ -98,6 +98,7 @@ filterwarnings = [
] ]
[tool.mypy] [tool.mypy]
check_untyped_defs = true
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
module = [ module = [
+1 -1
View File
@@ -49,7 +49,7 @@ def run(args=None):
with suppress(AttributeError, PermissionError): with suppress(AttributeError, PermissionError):
os.nice(5) os.nice(5)
verbosity = options.verbose verbosity = Verbosity(options.verbose)
if not os.isatty(sys.stderr.fileno()): if not os.isatty(sys.stderr.fileno()):
options.progress_bar = False options.progress_bar = False
if options.quiet: if options.quiet:
+12 -8
View File
@@ -76,7 +76,8 @@ class OcrmypdfPluginManager:
return state return state
def __setstate__(self, state): def __setstate__(self, state):
self.__init__( OcrmypdfPluginManager.__init__(
self,
*state['init_args'], *state['init_args'],
plugins=state['plugins'], plugins=state['plugins'],
builtins=state['builtins'], builtins=state['builtins'],
@@ -88,10 +89,10 @@ class OcrmypdfPluginManager:
# 1. Register builtins # 1. Register builtins
if self._builtins: if self._builtins:
for module in sorted( for module_info in sorted(
pkgutil.iter_modules(ocrmypdf.builtin_plugins.__path__) pkgutil.iter_modules(ocrmypdf.builtin_plugins.__path__)
): ):
name = f'ocrmypdf.builtin_plugins.{module.name}' name = f'ocrmypdf.builtin_plugins.{module_info.name}'
module = importlib.import_module(name) module = importlib.import_module(name)
self._pm.register(module) self._pm.register(module)
@@ -99,17 +100,20 @@ class OcrmypdfPluginManager:
self._pm.load_setuptools_entrypoints('ocrmypdf') self._pm.load_setuptools_entrypoints('ocrmypdf')
# 3. Register plugins specified on command line # 3. Register plugins specified on command line
for name in self._plugins: for plugin in self._plugins:
if isinstance(name, Path) or name.endswith('.py'): if isinstance(plugin, Path) or plugin.endswith('.py'):
# Import by filename # Import by filename
module_name = Path(name).stem plugin_path = Path(plugin)
spec = importlib.util.spec_from_file_location(module_name, name) module_name = plugin_path.stem
spec = importlib.util.spec_from_file_location(module_name, plugin_path)
if spec is None or spec.loader is None:
raise ImportError(f'Could not load plugin from {plugin_path}')
module = importlib.util.module_from_spec(spec) module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module sys.modules[module_name] = module
spec.loader.exec_module(module) spec.loader.exec_module(module)
else: else:
# Import by dotted module name # Import by dotted module name
module = importlib.import_module(name) module = importlib.import_module(plugin)
self._pm.register(module) self._pm.register(module)
# ========================================================================= # =========================================================================
+3 -1
View File
@@ -774,7 +774,9 @@ def main(infile, outfile, level, jobs=1):
) )
with TemporaryDirectory() as tmpdir: with TemporaryDirectory() as tmpdir:
context = PdfContext(options, Path(tmpdir), infile, None, None) # optimize() only reads context.options on this standalone path, so
# pdfinfo and plugin_manager are not needed.
context = PdfContext(options, Path(tmpdir), infile, None, None) # type: ignore[arg-type]
tmpout = Path(tmpdir) / 'out.pdf' tmpout = Path(tmpdir) / 'out.pdf'
optimize( optimize(
infile, infile,
+6 -3
View File
@@ -94,7 +94,10 @@ def _error_trailer(program: str, package: str | Mapping[str, str], **kwargs) ->
def _error_missing_program( def _error_missing_program(
program: str, package: str, required_for: str | None, recommended: bool program: str,
package: str | Mapping[str, str],
required_for: str | None,
recommended: bool,
) -> None: ) -> None:
# pylint: disable=unused-argument # pylint: disable=unused-argument
if recommended: if recommended:
@@ -108,7 +111,7 @@ def _error_missing_program(
def _error_old_version( def _error_old_version(
program: str, program: str,
package: str, package: str | Mapping[str, str],
need_version: str, need_version: str,
found_version: str, found_version: str,
required_for: str | None, required_for: str | None,
@@ -124,7 +127,7 @@ def _error_old_version(
def check_external_program( def check_external_program(
*, *,
program: str, program: str,
package: str, package: str | Mapping[str, str],
version_checker: Callable[[], Version], version_checker: Callable[[], Version],
need_version: str | Version, need_version: str | Version,
required_for: str | None = None, required_for: str | None = None,