Coverage for src/mafw/devtools/toolchain/tools/mypy.py: 100%
26 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-26 09:13 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-26 09:13 +0000
1# Copyright 2026 European Union
2# Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
3# SPDX-License-Identifier: EUPL-1.2
4"""
5Concrete :class:`~mafw.devtools.toolchain.ProjectTool` implementation for **mypy**.
7Mypy is a project tool whose version lower bound is declared in
8``tool.hatch.envs.types.extra-dependencies``. This module manages the ``>=``
9specifier for mypy and validates updates by running type checking via the
10``types`` hatch environment.
12Update logic (inherited from :class:`~mafw.devtools.toolchain.ProjectTool`):
141. Query PyPI for the latest stable mypy release.
152. Compare against the current lower bound in pyproject.toml.
163. If a newer version exists, update the ``>=`` specifier.
18Post-update validation (overridden here):
204. Recreate the ``types`` hatch environment to force fresh dependency resolution.
215. Determine the highest supported Python version dynamically from pyproject.toml.
226. Run mypy via ``hatch run types.py<version>:check``.
237. If the type check fails, revert pyproject.toml to its previous content.
25The revert pattern uses ``_revert()`` which restores the raw pyproject.toml
26content saved before the update.
27"""
29from __future__ import annotations
31import subprocess
33from mafw.devtools import DevtoolsError
34from mafw.devtools.toolchain import ProjectTool
37class MypyTool(ProjectTool):
38 """Manage the mypy static type checker version in pyproject.toml.
40 Mypy is categorized as a *project* tool because its version is
41 controlled through the ``types`` hatch environment extra-dependencies
42 in ``pyproject.toml``. After an update, the ``types`` environment is
43 recreated and mypy type checking is executed at the highest supported
44 Python version to validate compatibility. If the check fails,
45 pyproject.toml is reverted to its pre-update state.
47 All standard behaviour (version detection, update, verify, bootstrap)
48 is provided by :class:`~mafw.devtools.toolchain.ProjectTool`.
49 Only :meth:`post_update` is overridden to run mypy type checking.
51 :param project_root: Path to the project root directory containing
52 ``pyproject.toml``. Defaults to the current working directory.
53 :type project_root: Path | None
54 """
56 @property
57 def package_name(self) -> str:
58 """PyPI package name.
60 :return: ``"mypy"``
61 :rtype: str
62 """
63 return 'mypy'
65 @property
66 def section_path(self) -> str:
67 """TOML section path where mypy is declared.
69 :return: ``"tool.hatch.envs.types.extra-dependencies"``
70 :rtype: str
71 """
72 return 'tool.hatch.envs.types.extra-dependencies'
74 @property
75 def env_name(self) -> str:
76 """Hatch environment where mypy is validated.
78 :return: ``"types"``
79 :rtype: str
80 """
81 return 'types'
83 def post_update(self) -> None:
84 """Recreate the types environment and run mypy type checking.
86 Performs the following steps after a successful version update:
88 1. Forces recreation of the ``types`` hatch environment so that the
89 newly specified dependency versions are resolved and installed.
90 2. Determines the highest supported Python version dynamically from
91 ``tool.mafw.supported-python`` in pyproject.toml.
92 3. Runs mypy via ``hatch run types.py<version>:check``.
94 If environment recreation fails due to a dependency conflict, the
95 conflicting package name is extracted and reported. If the type check
96 fails, ``pyproject.toml`` is reverted to its pre-update state.
98 :raises DevtoolsError: If environment recreation fails or the mypy
99 check exits with a non-zero exit code.
100 """
101 # Step 1: Determine the highest Python version dynamically.
102 py_version = self._get_highest_python_version()
104 # Step 2: Validate dependency resolution via temporary clone.
105 self._recreate_environment(self.env_name, py_version)
107 # Step 3: Run mypy type checking at the determined Python version.
108 result = subprocess.run(
109 ['hatch', 'run', f'types.py{py_version}:check'], # noqa: S603, S607
110 capture_output=True,
111 text=True,
112 check=False,
113 cwd=self._project_root,
114 )
116 if result.returncode != 0:
117 # Extract conflicting package info from stderr if possible.
118 conflict = self._parse_resolution_failure(result.stderr)
119 # Revert pyproject.toml to its pre-update state.
120 self._revert()
121 msg = f'mypy type check failed after updating {self.name} — pyproject.toml has been reverted.'
122 if conflict:
123 msg += f'\nConflicting package: {conflict}'
124 msg += f'\n{result.stderr.strip()}'
125 raise DevtoolsError(msg)