8000 Merge pull request #26397 from QuLogic/testing-types · matplotlib/matplotlib@cdf816e · GitHub
[go: up one dir, main page]

Skip to content

Commit cdf816e

Browse files
authored
Merge pull request #26397 from QuLogic/testing-types
TYP: Add type hints to testing module
2 parents c12be12 + 8057fa4 commit cdf816e

14 files changed

+180
-43
lines changed

ci/mypy-stubtest-allowlist.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ matplotlib._.*
66
matplotlib.rcsetup._listify_validator
77
matplotlib.rcsetup._validate_linestyle
88
matplotlib.ft2font.*
9-
matplotlib.testing.*
9+
matplotlib.testing.jpl_units.*
1010
matplotlib.sphinxext.*
1111

1212
# set methods have heavy dynamic usage of **kwargs, with differences for subclasses

lib/matplotlib/testing/__init__.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,9 @@ def setup():
5050
set_reproducibility_for_testing()
5151

5252

53-
def subprocess_run_for_testing(
54-
command: "list[str]",
55-
env: "dict[str, str]" = None,
56-
timeout: float = None,
57-
stdout=None,
58-
stderr=None,
59-
check: bool = False,
60-
text: bool = True,
61-
capture_output: bool = False
62-
) -> "subprocess.Popen":
53+
def subprocess_run_for_testing(command, env=None, timeout=None, stdout=None,
54+
stderr=None, check=False, text=True,
55+
capture_output=False):
6356
"""
6457
Create and run a subprocess.
6558

lib/matplotlib/testing/__init__.pyi

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from collections.abc import Callable
2+
import subprocess
3+
from typing import Any, IO, Literal, overload
4+
5+
def set_font_settings_for_testing() -> None: ...
6+
def set_reproducibility_for_testing() -> None: ...
7+
def setup() -> None: ...
8+
@overload
9+
def subprocess_run_for_testing(
10+
command: list[str],
11+
env: dict[str, str] | None = ...,
12+
timeout: float | None = ...,
13+
stdout: int | IO[Any] | None = ...,
14+
stderr: int | IO[Any] | None = ...,
15+
check: bool = ...,
16+
*,
17+
text: Literal[True],
18+
capture_output: bool = ...,
19+
) -> subprocess.CompletedProcess[str]: ...
20+
@overload
21+
def subprocess_run_for_testing(
22+
command: list[str],
23+
env: dict[str, str] | None = ...,
24+
timeout: float | None = ...,
25+
stdout: int | IO[Any] | None = ...,
26+
stderr: int | IO[Any] | None = ...,
27+
check: bool = ...,
28+
text: Literal[False] = ...,
29+
capture_output: bool = ...,
30+
) -> subprocess.CompletedProcess[bytes]: ...
31+
@overload
32+
def subprocess_run_for_testing(
33+
command: list[str],
34+
env: dict[str, str] | None = ...,
35+
timeout: float | None = ...,
36+
stdout: int | IO[Any] | None = ...,
37+
stderr: int | IO[Any] | None = ...,
38+
check: bool = ...,
39+
text: bool = ...,
40+
capture_output: bool = ...,
41+
) -> subprocess.CompletedProcess[bytes] | subprocess.CompletedProcess[str]: ...
42+
def subprocess_run_helper(
43+
func: Callable[[], None],
44+
*args: Any,
45+
timeout: float,
46+
extra_env: dict[str, str] | None = ...,
47+
) -> subprocess.CompletedProcess[str]: ...
48+
def _check_for_pgf(texsystem: str) -> bool: ...
49+
def _has_tex_package(package: str) -> bool: ...

lib/matplotlib/testing/_markers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
_log = logging.getLogger(__name__)
1616

1717

18-
def _checkdep_usetex():
18+
def _checkdep_usetex() -> bool:
1919
if not shutil.which("tex"):
2020
_log.warning("usetex mode requires TeX.")
2121
return False

lib/matplotlib/testing/compare.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,8 @@ def __call__(self, orig, dest):
108108
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
109109
try:
110110
self._read_until(b"\nGS")
111-
except _ConverterError as err:
112-
raise OSError(
113-
"Failed to start Ghostscript:\n\n" + err.args[0]) from None
111+
except _ConverterError as e:
112+
raise OSError(f"Failed to start Ghostscript:\n\n{e.args[0]}") from None
114113

115114
def encode_and_escape(name):
116115
return (os.fsencode(name)
@@ -244,10 +243,8 @@ def _update_converter():
244243
converter['svg'] = _SVGConverter()
245244

246245

247-
#: A dictionary that maps filename extensions to functions which
248-
#: themselves map arguments `old` and `new` (filenames) to a list of strings.
249-
#: The list can then be passed to Popen to convert files with that
250-
#: extension to png format.
246+
#: A dictionary that maps filename extensions to functions which themselves
247+
#: convert between arguments `old` and `new` (filenames).
251248
converter = {}
252249
_update_converter()
253250
_svg_with_matplotlib_fonts_converter = _SVGWithMatplotlibFontsConverter()

lib/matplotlib/testing/compare.pyi

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from collections.abc import Callable
2+
from typing import Literal, overload
3+
4+
from numpy.typing import NDArray
5+
6+
__all__ = ["calculate_rms", "comparable_formats", "compare_images"]
7+
8+
def make_test_filename(fname: str, purpose: str) -> str: ...
9+
def get_cache_dir() -> str: ...
10+
def get_file_hash(path: str, block_size: int = ...) -> str: ...
11+
12+
converter: dict[str, Callable[[str, str], None]] = {}
13+
14+
def comparable_formats() -> list[str]: ...
15+
def convert(filename: str, cache: bool) -> str: ...
16+
def crop_to_same(
17+
actual_path: str, actual_image: NDArray, expected_path: str, expected_image: NDArray
18+
) -> tuple[NDArray, NDArray]: ...
19+
def calculate_rms(expected_image: NDArray, actual_image: NDArray) -> float: ...
20+
@overload
21+
def compare_images(
22+
expected: str, actual: str, tol: float, in_decorator: Literal[True]
23+
) -> None | dict[str, float | str]: ...
24+
@overload
25+
def compare_images(
26+
expected: str, actual: str, tol: float, in_decorator: Literal[False]
27+
) -> None | str: ...
28+
@overload
29+
def compare_images(
30+
expected: str, actual: str, tol: float, in_decorator: bool = ...
31+
) -> None | str | dict[str, float | str]: ...
32+
def save_diff_image(expected: str, actual: str, output: str) -> None: ...

lib/matplotlib/testing/conftest.pyi

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from types import ModuleType
2+
3+
import pytest
4+
5+
def pytest_configure(config: pytest.Config) -> None: ...
6+
def pytest_unconfigure(config: pytest.Config) -> None: ...
7+
@pytest.fixture
8+
def mpl_test_settings(request: pytest.FixtureRequest) -> None: ...
9+
@pytest.fixture
10+
def pd() -> ModuleType: ...
11+
@pytest.fixture
12+
def xr() -> ModuleType: ...

lib/matplotlib/testing/decorators.pyi

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from collections.abc import Callable, Sequence
2+
from pathlib import Path
3+
from typing import Any, TypeVar
4+
from typing_extensions import ParamSpec
5+
6+
from matplotlib.figure import Figure
7+
from matplotlib.typing import RcStyleType
8+
9+
_P = ParamSpec("_P")
10+
_R = TypeVar("_R")
11+
12+
def remove_ticks_and_titles(figure: Figure) -> None: ...
13+
def image_comparison(
14+
baseline_images: list[str] | None,
15+
extensions: list[str] | None = ...,
16+
tol: float = ...,
17+
freetype_version: tuple[str, str] | str | None = ...,
18+
remove_text: bool = ...,
19+
savefig_kwarg: dict[str, Any] | None = ...,
20+
style: RcStyleType = ...,
21+
) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ...
22+
def check_figures_equal(
23+
*, extensions: Sequence[str] = ..., tol: float = ...
24+
) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ...
25+
def _image_directories(func: Callable) -> tuple[Path, Path]: ...

lib/matplotlib/testing/widgets.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
See also :mod:`matplotlib.tests.test_widgets`.
77
"""
88

9-
import matplotlib.pyplot as plt
109
from unittest import mock
1110

11+
import matplotlib.pyplot as plt
12+
1213

1314
def get_ax():
1415
"""Create a plot and return its axes."""
@@ -34,9 +35,9 @@ def mock_event(ax, button=1, xdata=0, ydata=0, key=None, step=1):
3435
----------
3536
ax : `~matplotlib.axes.Axes`
3637
The axes the event will be in.
37-
xdata : int
38+
xdata : float
3839
x coord of mouse in data coords.
39-
ydata : int
40+
ydata : float
4041
y coord of mouse in data coords.
4142
button : None or `MouseButton` or {'up', 'down'}
4243
The mouse button pressed in this event (see also `.MouseEvent`).
@@ -70,12 +71,12 @@ def do_event(tool, etype, button=1, xdata=0, ydata=0, key=None, step=1):
7071
7172
Parameters
7273
----------
73-
tool : matplotlib.widgets.RectangleSelector
74+
tool : matplotlib.widgets.AxesWidget
7475
etype : str
7576
The event to trigger.
76-
xdata : int
77+
xdata : float
7778
x coord of mouse in data coords.
78-
ydata : int
79+
ydata : float
7980
y coord of mouse in data coords.
8081
button : None or `MouseButton` or {'up', 'down'}
8182
The mouse button pressed in this event (see also `.MouseEvent`).

lib/matplotlib/testing/widgets.pyi

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import Any, Literal
2+
3+
from matplotlib.axes import Axes
4+
from matplotlib.backend_bases import Event, MouseButton
5+
from matplotlib.widgets import AxesWidget, Widget
6+
7+
def get_ax() -> Axes: ...
8+
def noop(*args: Any, **kwargs: Any) -> None: ...
9+
def mock_event(
10+
ax: Axes,
11+
button: MouseButton | int | Literal["up", "down"] | None = ...,
12+
xdata: float = ...,
13+
ydata: float = ...,
14+
key: str | None = ...,
15+
step: int = ...,
16+
) -> Event: ...
17+
def do_event(
18+
tool: AxesWidget,
19+
etype: str,
20+
button: MouseButton | int | Literal["up", "do 10000 wn"] | None = ...,
21+
xdata: float = ...,
22+
ydata: float = ...,
23+
key: str | None = ...,
24+
step: int = ...,
25+
) -> None: ...
26+
def click_and_drag(
27+
tool: Widget,
28+
start: tuple[float, float],
29+
end: tuple[float, float],
30+
key: str | None = ...,
31+
) -> None: ...

lib/matplotlib/tests/test_backend_pgf.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
import matplotlib as mpl
1111
import matplotlib.pyplot as plt
1212
from matplotlib.testing import _has_tex_package, _check_for_pgf
13-
from matplotlib.testing.compare import compare_images, ImageComparisonFailure
13+
from matplotlib.testing.exceptions import ImageComparisonFailure
14+
from matplotlib.testing.compare import compare_images
1415
from matplotlib.backends.backend_pgf import PdfPages
1516
from matplotlib.testing.decorators import (
1617
_image_directories, check_figures_equal, image_comparison)

lib/matplotlib/tests/test_widgets.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -875,8 +875,8 @@ def test_span_selector_bound(direction):
875875
bound = x_bound if direction == 'horizontal' else y_bound
876876
assert tool._edge_handles.positions == list(bound)
877877

878-
press_data = [10.5, 11.5]
879-
move_data = [11, 13] # Updating selector is done in onmove
878+
press_data = (10.5, 11.5)
879+
move_data = (11, 13) # Updating selector is done in onmove
880880
release_data = move_data
881881
click_and_drag(tool, start=press_data, end=move_data)
882882

@@ -1053,28 +1053,28 @@ def test_check_radio_buttons_image():
10531053
fig = ax.figure
10 10000 541054
fig.subplots_adjust(left=0.3)
10551055

1056-
rax1 = fig.add_axes([0.05, 0.7, 0.2, 0.15])
1056+
rax1 = fig.add_axes((0.05, 0.7, 0.2, 0.15))
10571057
rb1 = widgets.RadioButtons(rax1, ('Radio 1', 'Radio 2', 'Radio 3'))
10581058
with pytest.warns(DeprecationWarning,
10591059
match='The circles attribute was deprecated'):
10601060
rb1.circles # Trigger the old-style elliptic radiobuttons.
10611061

1062-
rax2 = fig.add_axes([0.05, 0.5, 0.2, 0.15])
1062+
rax2 = fig.add_axes((0.05, 0.5, 0.2, 0.15))
10631063
cb1 = widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'),
10641064
(False, True, True))
10651065
with pytest.warns(DeprecationWarning,
10661066
match='The rectangles attribute was deprecated'):
10671067
cb1.rectangles # Trigger old-style Rectangle check boxes
10681068

1069-
rax3 = fig.add_axes([0.05, 0.3, 0.2, 0.15])
1069+
rax3 = fig.add_axes((0.05, 0.3, 0.2, 0.15))
10701070
rb3 = widgets.RadioButtons(
10711071
rax3, ('Radio 1', 'Radio 2', 'Radio 3'),
10721072
label_props={'fontsize': [8, 12, 16],
10731073
'color': ['red', 'green', 'blue']},
10741074
radio_props={'edgecolor': ['red', 'green', 'blue'],
10751075
'facecolor': ['mistyrose', 'palegreen', 'lightblue']})
10761076

1077-
rax4 = fig.add_axes([0.05, 0.1, 0.2, 0.15])
1077+
rax4 = fig.add_axes((0.05, 0.1, 0.2, 0.15))
10781078
cb4 = widgets.CheckButtons(
10791079
rax4, ('Check 1', 'Check 2', 'Check 3'), (False, True, True),
10801080
label_props={'fontsize': [8, 12, 16],

lib/matplotlib/typing.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
# RGBColorType includes the (str, float) tuple, even for RGBA strings
2929
tuple[RGBColorType, float],
3030
# (4-tuple, float) is odd, but accepted as the outer float overriding A of 4-tuple
31-
tuple[tuple[float, float, float, float], float]
31+
tuple[tuple[float, float, float, float], float],
3232
]
3333

3434
ColorType = Union[RGBColorType, RGBAColorType]
@@ -40,14 +40,7 @@
4040
LineStyleType = Union[str, tuple[float, Sequence[float]]]
4141
DrawStyleType = Literal["default", "steps", "steps-pre", "steps-mid", "steps-post"]
4242
MarkEveryType = Union[
43-
None,
44-
int,
45-
tuple[int, int],
46-
slice,
47-
list[int],
48-
float,
49-
tuple[float, float],
50-
list[bool]
43+
None, int, tuple[int, int], slice, list[int], float, tuple[float, float], list[bool]
5144
]
5245

5346
MarkerType = Union[str, path.Path, MarkerStyle]
@@ -56,7 +49,10 @@
5649
CapStyleType = Union[CapStyle, Literal["butt", "projecting", "round"]]
5750

5851
RcStyleType = Union[
59-
str, dict[str, Any], pathlib.Path, list[Union[str, pathlib.Path, dict[str, Any]]]
52+
str,
53+
dict[str, Any],
54+
pathlib.Path,
55+
Sequence[Union[str, pathlib.Path, dict[str, Any]]],
6056
]
6157

6258
HashableList = list[Union[Hashable, "HashableList"]]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ convention = "numpy"
130130

131131
[tool.mypy]
132132
exclude = [
133-
".*/matplotlib/(sphinxext|backends|testing)",
133+
".*/matplotlib/(sphinxext|backends|testing/jpl_units)",
134134
".*/mpl_toolkits",
135135
# tinypages is used for testing the sphinx ext,
136136
# stubtest will import and run, opening a figure if not excluded

0 commit comments

Comments
 (0)
0