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)