Source code for mafw.devtools.toolchain.tools.mypy
# Copyright 2026 European Union
# Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
# SPDX-License-Identifier: EUPL-1.2
"""
Concrete :class:`~mafw.devtools.toolchain.ProjectTool` implementation for **mypy**.
Mypy is a project tool whose version lower bound is declared in
``tool.hatch.envs.types.extra-dependencies``. This module manages the ``>=``
specifier for mypy and validates updates by running type checking via the
``types`` hatch environment.
Update logic (inherited from :class:`~mafw.devtools.toolchain.ProjectTool`):
1. Query PyPI for the latest stable mypy release.
2. Compare against the current lower bound in pyproject.toml.
3. If a newer version exists, update the ``>=`` specifier.
Post-update validation (overridden here):
4. Recreate the ``types`` hatch environment to force fresh dependency resolution.
5. Determine the highest supported Python version dynamically from pyproject.toml.
6. Run mypy via ``hatch run types.py<version>:check``.
7. If the type check fails, revert pyproject.toml to its previous content.
The revert pattern uses ``_revert()`` which restores the raw pyproject.toml
content saved before the update.
"""
from __future__ import annotations
import subprocess
from mafw.devtools import DevtoolsError
from mafw.devtools.toolchain import ProjectTool
[docs]
class MypyTool(ProjectTool):
"""Manage the mypy static type checker version in pyproject.toml.
Mypy is categorized as a *project* tool because its version is
controlled through the ``types`` hatch environment extra-dependencies
in ``pyproject.toml``. After an update, the ``types`` environment is
recreated and mypy type checking is executed at the highest supported
Python version to validate compatibility. If the check fails,
pyproject.toml is reverted to its pre-update state.
All standard behaviour (version detection, update, verify, bootstrap)
is provided by :class:`~mafw.devtools.toolchain.ProjectTool`.
Only :meth:`post_update` is overridden to run mypy type checking.
:param project_root: Path to the project root directory containing
``pyproject.toml``. Defaults to the current working directory.
:type project_root: Path | None
"""
@property
def package_name(self) -> str:
"""PyPI package name.
:return: ``"mypy"``
:rtype: str
"""
return 'mypy'
@property
def section_path(self) -> str:
"""TOML section path where mypy is declared.
:return: ``"tool.hatch.envs.types.extra-dependencies"``
:rtype: str
"""
return 'tool.hatch.envs.types.extra-dependencies'
@property
def env_name(self) -> str:
"""Hatch environment where mypy is validated.
:return: ``"types"``
:rtype: str
"""
return 'types'
[docs]
def post_update(self) -> None:
"""Recreate the types environment and run mypy type checking.
Performs the following steps after a successful version update:
1. Forces recreation of the ``types`` hatch environment so that the
newly specified dependency versions are resolved and installed.
2. Determines the highest supported Python version dynamically from
``tool.mafw.supported-python`` in pyproject.toml.
3. Runs mypy via ``hatch run types.py<version>:check``.
If environment recreation fails due to a dependency conflict, the
conflicting package name is extracted and reported. If the type check
fails, ``pyproject.toml`` is reverted to its pre-update state.
:raises DevtoolsError: If environment recreation fails or the mypy
check exits with a non-zero exit code.
"""
# Step 1: Determine the highest Python version dynamically.
py_version = self._get_highest_python_version()
# Step 2: Validate dependency resolution via temporary clone.
self._recreate_environment(self.env_name, py_version)
# Step 3: Run mypy type checking at the determined Python version.
result = subprocess.run(
['hatch', 'run', f'types.py{py_version}:check'], # noqa: S603, S607
capture_output=True,
text=True,
check=False,
cwd=self._project_root,
)
if result.returncode != 0:
# Extract conflicting package info from stderr if possible.
conflict = self._parse_resolution_failure(result.stderr)
# Revert pyproject.toml to its pre-update state.
self._revert()
msg = f'mypy type check failed after updating {self.name} — pyproject.toml has been reverted.'
if conflict:
msg += f'\nConflicting package: {conflict}'
msg += f'\n{result.stderr.strip()}'
raise DevtoolsError(msg)