Skip to content

Commit 5e48662

Browse files
authored
[3.13] pythongh-133403: Check generate_stdlib_module_names and check_extension_modules with mypy (pythonGH-137546) (python#137691)
(cherry picked from commit 68a61b0)
1 parent 3019491 commit 5e48662

File tree

4 files changed

+65
-44
lines changed

4 files changed

+65
-44
lines changed

.github/workflows/mypy.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ on:
1313
- "Lib/test/libregrtest/**"
1414
- "Lib/tomllib/**"
1515
- "Misc/mypy/**"
16+
- "Tools/build/check_extension_modules.py"
1617
- "Tools/build/compute-changes.py"
1718
- "Tools/build/deepfreeze.py"
1819
- "Tools/build/generate_sbom.py"
20+
- "Tools/build/generate_stdlib_module_names.py"
1921
- "Tools/build/verify_ensurepip_wheels.py"
2022
- "Tools/build/update_file.py"
2123
- "Tools/build/umarshal.py"

Tools/build/check_extension_modules.py

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
1818
See --help for more information
1919
"""
20+
21+
from __future__ import annotations
22+
23+
import _imp
2024
import argparse
21-
import collections
2225
import enum
2326
import logging
2427
import os
@@ -27,12 +30,12 @@
2730
import sys
2831
import sysconfig
2932
import warnings
30-
import _imp
3133

32-
from importlib._bootstrap import _load as bootstrap_load
34+
from collections.abc import Iterable
35+
from importlib._bootstrap import _load as bootstrap_load # type: ignore[attr-defined]
3336
from importlib.machinery import BuiltinImporter, ExtensionFileLoader, ModuleSpec
3437
from importlib.util import spec_from_file_location, spec_from_loader
35-
from typing import Iterable
38+
from typing import NamedTuple
3639

3740
SRC_DIR = pathlib.Path(__file__).parent.parent.parent
3841

@@ -109,6 +112,7 @@
109112
)
110113

111114

115+
@enum.unique
112116
class ModuleState(enum.Enum):
113117
# Makefile state "yes"
114118
BUILTIN = "builtin"
@@ -120,21 +124,23 @@ class ModuleState(enum.Enum):
120124
# disabled by Setup / makesetup rule
121125
DISABLED_SETUP = "disabled_setup"
122126

123-
def __bool__(self):
127+
def __bool__(self) -> bool:
124128
return self.value in {"builtin", "shared"}
125129

126130

127-
ModuleInfo = collections.namedtuple("ModuleInfo", "name state")
131+
class ModuleInfo(NamedTuple):
132+
name: str
133+
state: ModuleState
128134

129135

130136
class ModuleChecker:
131137
pybuilddir_txt = "pybuilddir.txt"
132138

133139
setup_files = (
134140
# see end of configure.ac
135-
"Modules/Setup.local",
136-
"Modules/Setup.stdlib",
137-
"Modules/Setup.bootstrap",
141+
pathlib.Path("Modules/Setup.local"),
142+
pathlib.Path("Modules/Setup.stdlib"),
143+
pathlib.Path("Modules/Setup.bootstrap"),
138144
SRC_DIR / "Modules/Setup",
139145
)
140146

@@ -146,15 +152,15 @@ def __init__(self, cross_compiling: bool = False, strict: bool = False):
146152
self.builddir = self.get_builddir()
147153
self.modules = self.get_modules()
148154

149-
self.builtin_ok = []
150-
self.shared_ok = []
151-
self.failed_on_import = []
152-
self.missing = []
153-
self.disabled_configure = []
154-
self.disabled_setup = []
155-
self.notavailable = []
155+
self.builtin_ok: list[ModuleInfo] = []
156+
self.shared_ok: list[ModuleInfo] = []
157+
self.failed_on_import: list[ModuleInfo] = []
158+
self.missing: list[ModuleInfo] = []
159+
self.disabled_configure: list[ModuleInfo] = []
160+
self.disabled_setup: list[ModuleInfo] = []
161+
self.notavailable: list[ModuleInfo] = []
156162

157-
def check(self):
163+
def check(self) -> None:
158164
if not hasattr(_imp, 'create_dynamic'):
159165
logger.warning(
160166
('Dynamic extensions not supported '
@@ -186,10 +192,10 @@ def check(self):
186192
assert modinfo.state == ModuleState.SHARED
187193
self.shared_ok.append(modinfo)
188194

189-
def summary(self, *, verbose: bool = False):
195+
def summary(self, *, verbose: bool = False) -> None:
190196
longest = max([len(e.name) for e in self.modules], default=0)
191197

192-
def print_three_column(modinfos: list[ModuleInfo]):
198+
def print_three_column(modinfos: list[ModuleInfo]) -> None:
193199
names = [modinfo.name for modinfo in modinfos]
194200
names.sort(key=str.lower)
195201
# guarantee zip() doesn't drop anything
@@ -259,12 +265,12 @@ def print_three_column(modinfos: list[ModuleInfo]):
259265
f"{len(self.failed_on_import)} failed on import)"
260266
)
261267

262-
def check_strict_build(self):
268+
def check_strict_build(self) -> None:
263269
"""Fail if modules are missing and it's a strict build"""
264270
if self.strict_extensions_build and (self.failed_on_import or self.missing):
265271
raise RuntimeError("Failed to build some stdlib modules")
266272

267-
def list_module_names(self, *, all: bool = False) -> set:
273+
def list_module_names(self, *, all: bool = False) -> set[str]:
268274
names = {modinfo.name for modinfo in self.modules}
269275
if all:
270276
names.update(WINDOWS_MODULES)
@@ -277,9 +283,9 @@ def get_builddir(self) -> pathlib.Path:
277283
except FileNotFoundError:
278284
logger.error("%s must be run from the top build directory", __file__)
279285
raise
280-
builddir = pathlib.Path(builddir)
281-
logger.debug("%s: %s", self.pybuilddir_txt, builddir)
282-
return builddir
286+
builddir_path = pathlib.Path(builddir)
287+
logger.debug("%s: %s", self.pybuilddir_txt, builddir_path)
288+
return builddir_path
283289

284290
def get_modules(self) -> list[ModuleInfo]:
285291
"""Get module info from sysconfig and Modules/Setup* files"""
@@ -364,7 +370,7 @@ def parse_setup_file(self, setup_file: pathlib.Path) -> Iterable[ModuleInfo]:
364370
case ["*disabled*"]:
365371
state = ModuleState.DISABLED
366372
case ["*noconfig*"]:
367-
state = None
373+
continue
368374
case [*items]:
369375
if state == ModuleState.DISABLED:
370376
# *disabled* can disable multiple modules per line
@@ -381,34 +387,41 @@ def parse_setup_file(self, setup_file: pathlib.Path) -> Iterable[ModuleInfo]:
381387
def get_spec(self, modinfo: ModuleInfo) -> ModuleSpec:
382388
"""Get ModuleSpec for builtin or extension module"""
383389
if modinfo.state == ModuleState.SHARED:
384-
location = os.fspath(self.get_location(modinfo))
390+
mod_location = self.get_location(modinfo)
391+
assert mod_location is not None
392+
location = os.fspath(mod_location)
385393
loader = ExtensionFileLoader(modinfo.name, location)
386-
return spec_from_file_location(modinfo.name, location, loader=loader)
394+
spec = spec_from_file_location(modinfo.name, location, loader=loader)
395+
assert spec is not None
396+
return spec
387397
elif modinfo.state == ModuleState.BUILTIN:
388-
return spec_from_loader(modinfo.name, loader=BuiltinImporter)
398+
spec = spec_from_loader(modinfo.name, loader=BuiltinImporter)
399+
assert spec is not None
400+
return spec
389401
else:
390402
raise ValueError(modinfo)
391403

392-
def get_location(self, modinfo: ModuleInfo) -> pathlib.Path:
404+
def get_location(self, modinfo: ModuleInfo) -> pathlib.Path | None:
393405
"""Get shared library location in build directory"""
394406
if modinfo.state == ModuleState.SHARED:
395407
return self.builddir / f"{modinfo.name}{self.ext_suffix}"
396408
else:
397409
return None
398410

399-
def _check_file(self, modinfo: ModuleInfo, spec: ModuleSpec):
411+
def _check_file(self, modinfo: ModuleInfo, spec: ModuleSpec) -> None:
400412
"""Check that the module file is present and not empty"""
401-
if spec.loader is BuiltinImporter:
413+
if spec.loader is BuiltinImporter: # type: ignore[comparison-overlap]
402414
return
403415
try:
416+
assert spec.origin is not None
404417
st = os.stat(spec.origin)
405418
except FileNotFoundError:
406419
logger.error("%s (%s) is missing", modinfo.name, spec.origin)
407420
raise
408421
if not st.st_size:
409422
raise ImportError(f"{spec.origin} is an empty file")
410423

411-
def check_module_import(self, modinfo: ModuleInfo):
424+
def check_module_import(self, modinfo: ModuleInfo) -> None:
412425
"""Attempt to import module and report errors"""
413426
spec = self.get_spec(modinfo)
414427
self._check_file(modinfo, spec)
@@ -427,7 +440,7 @@ def check_module_import(self, modinfo: ModuleInfo):
427440
logger.exception("Importing extension '%s' failed!", modinfo.name)
428441
raise
429442

430-
def check_module_cross(self, modinfo: ModuleInfo):
443+
def check_module_cross(self, modinfo: ModuleInfo) -> None:
431444
"""Sanity check for cross compiling"""
432445
spec = self.get_spec(modinfo)
433446
self._check_file(modinfo, spec)
@@ -440,6 +453,7 @@ def rename_module(self, modinfo: ModuleInfo) -> None:
440453

441454
failed_name = f"{modinfo.name}_failed{self.ext_suffix}"
442455
builddir_path = self.get_location(modinfo)
456+
assert builddir_path is not None
443457
if builddir_path.is_symlink():
444458
symlink = builddir_path
445459
module_path = builddir_path.resolve().relative_to(os.getcwd())
@@ -463,7 +477,7 @@ def rename_module(self, modinfo: ModuleInfo) -> None:
463477
logger.debug("Rename '%s' -> '%s'", module_path, failed_path)
464478

465479

466-
def main():
480+
def main() -> None:
467481
args = parser.parse_args()
468482
if args.debug:
469483
args.verbose = True

Tools/build/generate_stdlib_module_names.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# This script lists the names of standard library modules
22
# to update Python/stdlib_module_names.h
3+
from __future__ import annotations
4+
35
import _imp
46
import os.path
57
import sys
68
import sysconfig
9+
from typing import TextIO
710

811
from check_extension_modules import ModuleChecker
912

@@ -50,12 +53,12 @@
5053
}
5154

5255
# Built-in modules
53-
def list_builtin_modules(names):
56+
def list_builtin_modules(names: set[str]) -> None:
5457
names |= set(sys.builtin_module_names)
5558

5659

5760
# Pure Python modules (Lib/*.py)
58-
def list_python_modules(names):
61+
def list_python_modules(names: set[str]) -> None:
5962
for filename in os.listdir(STDLIB_PATH):
6063
if not filename.endswith(".py"):
6164
continue
@@ -64,7 +67,7 @@ def list_python_modules(names):
6467

6568

6669
# Packages in Lib/
67-
def list_packages(names):
70+
def list_packages(names: set[str]) -> None:
6871
for name in os.listdir(STDLIB_PATH):
6972
if name in IGNORE:
7073
continue
@@ -78,16 +81,16 @@ def list_packages(names):
7881

7982
# Built-in and extension modules built by Modules/Setup*
8083
# includes Windows and macOS extensions.
81-
def list_modules_setup_extensions(names):
84+
def list_modules_setup_extensions(names: set[str]) -> None:
8285
checker = ModuleChecker()
8386
names.update(checker.list_module_names(all=True))
8487

8588

8689
# List frozen modules of the PyImport_FrozenModules list (Python/frozen.c).
8790
# Use the "./Programs/_testembed list_frozen" command.
88-
def list_frozen(names):
91+
def list_frozen(names: set[str]) -> None:
8992
submodules = set()
90-
for name in _imp._frozen_module_names():
93+
for name in _imp._frozen_module_names(): # type: ignore[attr-defined]
9194
# To skip __hello__, __hello_alias__ and etc.
9295
if name.startswith('__'):
9396
continue
@@ -103,8 +106,8 @@ def list_frozen(names):
103106
raise Exception(f'unexpected frozen submodules: {sorted(submodules)}')
104107

105108

106-
def list_modules():
107-
names = set()
109+
def list_modules() -> set[str]:
110+
names: set[str] = set()
108111

109112
list_builtin_modules(names)
110113
list_modules_setup_extensions(names)
@@ -129,7 +132,7 @@ def list_modules():
129132
return names
130133

131134

132-
def write_modules(fp, names):
135+
def write_modules(fp: TextIO, names: set[str]) -> None:
133136
print(f"// Auto-generated by {SCRIPT_NAME}.",
134137
file=fp)
135138
print("// List used to create sys.stdlib_module_names.", file=fp)
@@ -140,7 +143,7 @@ def write_modules(fp, names):
140143
print("};", file=fp)
141144

142145

143-
def main():
146+
def main() -> None:
144147
if not sysconfig.is_python_build():
145148
print(f"ERROR: {sys.executable} is not a Python build",
146149
file=sys.stderr)

Tools/build/mypy.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
# Please, when adding new files here, also add them to:
44
# .github/workflows/mypy.yml
55
files =
6+
Tools/build/check_extension_modules.py,
67
Tools/build/compute-changes.py,
78
Tools/build/deepfreeze.py,
89
Tools/build/generate_sbom.py,
10+
Tools/build/generate_stdlib_module_names.py,
911
Tools/build/verify_ensurepip_wheels.py,
1012
Tools/build/update_file.py,
1113
Tools/build/umarshal.py

0 commit comments

Comments
 (0)