Skip to content

Fix minor issues in stubtest wrapper #28897

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 28 additions & 28 deletions ci/mypy-stubtest-allowlist.txt
Original file line number Diff line number Diff line change
@@ -1,51 +1,51 @@
# Non-typed (and private) modules/functions
matplotlib.backends.*
matplotlib.tests.*
matplotlib.pylab.*
matplotlib._.*
matplotlib.rcsetup._listify_validator
matplotlib.rcsetup._validate_linestyle
matplotlib.ft2font.Glyph
matplotlib.testing.jpl_units.*
matplotlib.sphinxext.*
matplotlib\.backends\..*
matplotlib\.tests(\..*)?
matplotlib\.pylab\..*
matplotlib\._.*
matplotlib\.rcsetup\._listify_validator
matplotlib\.rcsetup\._validate_linestyle
matplotlib\.ft2font\.Glyph
matplotlib\.testing\.jpl_units\..*
matplotlib\.sphinxext(\..*)?

# set methods have heavy dynamic usage of **kwargs, with differences for subclasses
# which results in technically inconsistent signatures, but not actually a problem
matplotlib.*\.set$
matplotlib\..*\.set$

# Typed inline, inconsistencies largely due to imports
matplotlib.pyplot.*
matplotlib.typing.*
matplotlib\.pyplot\..*
matplotlib\.typing\..*

# Other decorator modifying signature
# Backcompat decorator which does not modify runtime reported signature
matplotlib.offsetbox.*Offset[Bb]ox.get_offset
matplotlib\.offsetbox\..*Offset[Bb]ox\.get_offset

# Inconsistent super/sub class parameter name (maybe rename for consistency)
matplotlib.projections.polar.RadialLocator.nonsingular
matplotlib.ticker.LogLocator.nonsingular
matplotlib.ticker.LogitLocator.nonsingular
matplotlib\.projections\.polar\.RadialLocator\.nonsingular
matplotlib\.ticker\.LogLocator\.nonsingular
matplotlib\.ticker\.LogitLocator\.nonsingular

# Stdlib/Enum considered inconsistent (no fault of ours, I don't think)
matplotlib.backend_bases._Mode.__new__
matplotlib.units.Number.__hash__
matplotlib\.backend_bases\._Mode\.__new__
matplotlib\.units\.Number\.__hash__

# 3.6 Pending deprecations
matplotlib.figure.Figure.set_constrained_layout
matplotlib.figure.Figure.set_constrained_layout_pads
matplotlib.figure.Figure.set_tight_layout
matplotlib\.figure\.Figure\.set_constrained_layout
matplotlib\.figure\.Figure\.set_constrained_layout_pads
matplotlib\.figure\.Figure\.set_tight_layout

# Maybe should be abstractmethods, required for subclasses, stubs define once
matplotlib.tri.*TriInterpolator.__call__
matplotlib.tri.*TriInterpolator.gradient
matplotlib\.tri\..*TriInterpolator\.__call__
matplotlib\.tri\..*TriInterpolator\.gradient

# TypeVar used only in type hints
matplotlib.backend_bases.FigureCanvasBase._T
matplotlib.backend_managers.ToolManager._T
matplotlib.spines.Spine._T
matplotlib\.backend_bases\.FigureCanvasBase\._T
matplotlib\.backend_managers\.ToolManager\._T
matplotlib\.spines\.Spine\._T

# Parameter inconsistency due to 3.10 deprecation
matplotlib.figure.FigureBase.get_figure
matplotlib\.figure\.FigureBase\.get_figure

# getitem method only exists for 3.10 deprecation backcompatability
matplotlib.inset.InsetIndicator.__getitem__
matplotlib\.inset\.InsetIndicator\.__getitem__
36 changes: 28 additions & 8 deletions tools/stubtest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ast
import os
import pathlib
import re
import subprocess
import sys
import tempfile
Expand All @@ -12,10 +13,19 @@


class Visitor(ast.NodeVisitor):
def __init__(self, filepath, output):
def __init__(self, filepath, output, existing_allowed):
self.filepath = filepath
self.context = list(filepath.with_suffix("").relative_to(lib).parts)
self.output = output
self.existing_allowed = existing_allowed

def _is_already_allowed(self, parts):
# Skip outputting a path if it's already allowed before.
candidates = ['.'.join(parts[:s]) for s in range(1, len(parts))]
for allow in self.existing_allowed:
if any(allow.fullmatch(path) for path in candidates):
return True
return False

def visit_FunctionDef(self, node):
# delete_parameter adds a private sentinel value that leaks
Expand Down Expand Up @@ -43,7 +53,9 @@ def visit_FunctionDef(self, node):
):
parents.insert(0, parent.name)
parent = parent.parent
self.output.write(f"{'.'.join(self.context + parents)}.{node.name}\n")
parts = [*self.context, *parents, node.name]
if not self._is_already_allowed(parts):
self.output.write("\\.".join(parts) + "\n")
break

def visit_ClassDef(self, node):
Expand All @@ -62,20 +74,28 @@ def visit_ClassDef(self, node):
# for setters on items with only a getter
for substitutions in aliases.values():
parts = self.context + parents + [node.name]
self.output.write(
"\n".join(
f"{'.'.join(parts)}.[gs]et_{a}\n" for a in substitutions
)
)
for a in substitutions:
if not (self._is_already_allowed([*parts, f"get_{a}"]) and
self._is_already_allowed([*parts, f"set_{a}"])):
self.output.write("\\.".join([*parts, f"[gs]et_{a}\n"]))
for child in ast.iter_child_nodes(node):
self.visit(child)


existing_allowed = []
with (root / 'ci/mypy-stubtest-allowlist.txt').open() as f:
for line in f:
line, _, _ = line.partition('#')
line = line.strip()
if line:
existing_allowed.append(re.compile(line))


with tempfile.TemporaryDirectory() as d:
p = pathlib.Path(d) / "allowlist.txt"
with p.open("wt") as f:
for path in mpl.glob("**/*.py"):
v = Visitor(path, f)
v = Visitor(path, f, existing_allowed)
tree = ast.parse(path.read_text())

# Assign parents to tree so they can be backtraced
Expand Down
Loading