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

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**. 

6 

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. 

11 

12Update logic (inherited from :class:`~mafw.devtools.toolchain.ProjectTool`): 

13 

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. 

17 

18Post-update validation (overridden here): 

19 

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. 

24 

25The revert pattern uses ``_revert()`` which restores the raw pyproject.toml 

26content saved before the update. 

27""" 

28 

29from __future__ import annotations 

30 

31import subprocess 

32 

33from mafw.devtools import DevtoolsError 

34from mafw.devtools.toolchain import ProjectTool 

35 

36 

37class MypyTool(ProjectTool): 

38 """Manage the mypy static type checker version in pyproject.toml. 

39 

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. 

46 

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. 

50 

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 """ 

55 

56 @property 

57 def package_name(self) -> str: 

58 """PyPI package name. 

59 

60 :return: ``"mypy"`` 

61 :rtype: str 

62 """ 

63 return 'mypy' 

64 

65 @property 

66 def section_path(self) -> str: 

67 """TOML section path where mypy is declared. 

68 

69 :return: ``"tool.hatch.envs.types.extra-dependencies"`` 

70 :rtype: str 

71 """ 

72 return 'tool.hatch.envs.types.extra-dependencies' 

73 

74 @property 

75 def env_name(self) -> str: 

76 """Hatch environment where mypy is validated. 

77 

78 :return: ``"types"`` 

79 :rtype: str 

80 """ 

81 return 'types' 

82 

83 def post_update(self) -> None: 

84 """Recreate the types environment and run mypy type checking. 

85 

86 Performs the following steps after a successful version update: 

87 

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``. 

93 

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. 

97 

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() 

103 

104 # Step 2: Validate dependency resolution via temporary clone. 

105 self._recreate_environment(self.env_name, py_version) 

106 

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 ) 

115 

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)