Skip to content

Commit a711b12

Browse files
committed
Add tool for running stubtest
Walks the ast to find delete_parameter calls and automatically ignores them as their signature is modified at runtime Could potentially be further modified to respect the alias behavior, which is the majority of the current allowlist Makes running stubtest easier as now it is just 'python tools/stubtest.py', rather than a long incantation with multiple arguments
1 parent d60de5e commit a711b12

File tree

4 files changed

+60
-17
lines changed

4 files changed

+60
-17
lines changed

.github/workflows/mypy-stubtest.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@ jobs:
3737
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3838
run: |
3939
set -o pipefail
40-
MPLBACKEND=agg python -m mypy.stubtest \
41-
--mypy-config-file pyproject.toml \
42-
--allowlist ci/mypy-stubtest-allowlist.txt \
43-
matplotlib | \
40+
MPLBACKEND=agg python tools/stubtest.py | \
4441
reviewdog \
4542
-efm '%Eerror: %m' \
4643
-efm '%CStub: in file %f:%l' \

ci/mypy-stubtest-allowlist.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,10 @@ matplotlib.cm.unregister_cmap
5555
matplotlib.collections.PolyCollection.span_where
5656

5757
# 3.8 deprecations
58-
matplotlib.cbook.get_sample_data
5958
matplotlib.contour.ContourSet.allkinds
6059
matplotlib.contour.ContourSet.allsegs
6160
matplotlib.contour.ContourSet.tcolors
6261
matplotlib.contour.ContourSet.tlinewidths
63-
matplotlib.ticker.LogLocator.__init__
64-
matplotlib.ticker.LogLocator.set_params
6562

6663
# positional-only argument name lacking leading underscores
6764
matplotlib.axes._base._AxesBase.axis

doc/devel/contribute.rst

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -425,11 +425,7 @@ Introducing
425425
updated on introduction.
426426
- Items decorated with ``@_api.delete_parameter`` should include a default value hint
427427
for the deleted parameter, even if it did not previously have one (e.g.
428-
``param: <type> = ...``). Even so, the decorator changes the default value to a
429-
sentinel value which should not be included in the type stub. Thus, Mypy Stubtest
430-
needs to be informed of the inconsistency by placing the method into
431-
:file:`ci/mypy-stubtest-allowlist.txt` under a heading indicating the deprecation
432-
version number.
428+
``param: <type> = ...``).
433429

434430
Expiring
435431
~~~~~~~~
@@ -452,11 +448,11 @@ Expiring
452448
will have been updated at introduction, and require no change now.
453449
- Items decorated with ``@_api.delete_parameter`` will need to be updated to the
454450
final signature, in the same way as the ``.py`` file signature is updated.
455-
The entry in :file:`ci/mypy-stubtest-allowlist.txt` should be removed.
456-
- Any other entries in :file:`ci/mypy-stubtest-allowlist.txt` under a version's
457-
deprecations should be double checked, as only ``delete_parameter`` should normally
458-
require that mechanism for deprecation. For removed items that were not in the stub
459-
file, only deleting from the allowlist is required.
451+
- Any entries in :file:`ci/mypy-stubtest-allowlist.txt` which indicate a deprecation
452+
version should be double checked. In most cases this is not needed, though some
453+
items were never type hinted in the first place and were added to this file
454+
instead. For removed items that were not in the stub file, only deleting from the
455+
allowlist is required.
460456

461457
Adding new API
462458
--------------

tools/stubtest.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import ast
2+
import pathlib
3+
import subprocess
4+
import sys
5+
import tempfile
6+
7+
root = pathlib.Path(__file__).parent.parent
8+
9+
lib = root / "lib"
10+
mpl = lib / "matplotlib"
11+
12+
13+
class Visitor(ast.NodeVisitor):
14+
def __init__(self, filepath, output):
15+
self.filepath = filepath
16+
self.context = list(filepath.with_suffix("").relative_to(lib).parts)
17+
self.output = output
18+
19+
def visit_FunctionDef(self, node):
20+
if any("delete_parameter" in ast.unparse(line) for line in node.decorator_list):
21+
parents = []
22+
if hasattr(node, "parent"):
23+
parent = node.parent
24+
while hasattr(parent, "parent") and not isinstance(parent, ast.Module):
25+
parents.append(parent.name)
26+
parent = parent.parent
27+
self.output.write(f"{'.'.join(self.context + parents)}.{node.name}\n")
28+
29+
30+
with tempfile.NamedTemporaryFile("wt") as f:
31+
for path in mpl.glob("**/*.py"):
32+
v = Visitor(path, f)
33+
tree = ast.parse(path.read_text())
34+
35+
# Assign parents to tree so they can be backtraced
36+
for node in ast.walk(tree):
37+
for child in ast.iter_child_nodes(node):
38+
child.parent = node
39+
40+
v.visit(tree)
41+
f.flush()
42+
proc = subprocess.run(
43+
[
44+
"stubtest",
45+
"--mypy-config-file=pyproject.toml",
46+
"--allowlist=ci/mypy-stubtest-allowlist.txt",
47+
f"--allowlist={f.name}",
48+
"matplotlib",
49+
],
50+
cwd=root,
51+
)
52+
53+
sys.exit(proc.returncode)

0 commit comments

Comments
 (0)