matplotlib
103 строки · 3.5 Кб
1import ast
2import os
3import pathlib
4import subprocess
5import sys
6import tempfile
7
8root = pathlib.Path(__file__).parent.parent
9
10lib = root / "lib"
11mpl = lib / "matplotlib"
12
13
14class Visitor(ast.NodeVisitor):
15def __init__(self, filepath, output):
16self.filepath = filepath
17self.context = list(filepath.with_suffix("").relative_to(lib).parts)
18self.output = output
19
20def visit_FunctionDef(self, node):
21# delete_parameter adds a private sentinel value that leaks
22# we do not want that sentinel value in the type hints but it breaks typing
23# Does not apply to variadic arguments (args/kwargs)
24for dec in node.decorator_list:
25if "delete_parameter" in ast.unparse(dec):
26deprecated_arg = dec.args[1].value
27if (
28node.args.vararg is not None
29and node.args.vararg.arg == deprecated_arg
30):
31continue
32if (
33node.args.kwarg is not None
34and node.args.kwarg.arg == deprecated_arg
35):
36continue
37
38parents = []
39if hasattr(node, "parent"):
40parent = node.parent
41while hasattr(parent, "parent") and not isinstance(
42parent, ast.Module
43):
44parents.insert(0, parent.name)
45parent = parent.parent
46self.output.write(f"{'.'.join(self.context + parents)}.{node.name}\n")
47break
48
49def visit_ClassDef(self, node):
50for dec in node.decorator_list:
51if "define_aliases" in ast.unparse(dec):
52parents = []
53if hasattr(node, "parent"):
54parent = node.parent
55while hasattr(parent, "parent") and not isinstance(
56parent, ast.Module
57):
58parents.insert(0, parent.name)
59parent = parent.parent
60aliases = ast.literal_eval(dec.args[0])
61# Written as a regex rather than two lines to avoid unused entries
62# for setters on items with only a getter
63for substitutions in aliases.values():
64parts = self.context + parents + [node.name]
65self.output.write(
66"\n".join(
67f"{'.'.join(parts)}.[gs]et_{a}\n" for a in substitutions
68)
69)
70for child in ast.iter_child_nodes(node):
71self.visit(child)
72
73
74with tempfile.TemporaryDirectory() as d:
75p = pathlib.Path(d) / "allowlist.txt"
76with p.open("wt") as f:
77for path in mpl.glob("**/*.py"):
78v = Visitor(path, f)
79tree = ast.parse(path.read_text())
80
81# Assign parents to tree so they can be backtraced
82for node in ast.walk(tree):
83for child in ast.iter_child_nodes(node):
84child.parent = node
85
86v.visit(tree)
87proc = subprocess.run(
88[
89"stubtest",
90"--mypy-config-file=pyproject.toml",
91"--allowlist=ci/mypy-stubtest-allowlist.txt",
92f"--allowlist={p}",
93"matplotlib",
94],
95cwd=root,
96env=os.environ | {"MPLBACKEND": "agg"},
97)
98try:
99os.unlink(f.name)
100except OSError:
101pass
102
103sys.exit(proc.returncode)
104