Skip to content

Commit 97ab602

Browse files
committed
Add typing for internal helpers
While we don't normally type private API, these are used all over Matplotlib, and it makes sense to type them for internal checking purposes.
1 parent 00afcc0 commit 97ab602

File tree

9 files changed

+189
-41
lines changed

9 files changed

+189
-41
lines changed

lib/matplotlib/_api/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def type_name(tp):
9696
type_name(type(v))))
9797

9898

99-
def check_in_list(values, /, *, _print_supported_values=True, **kwargs):
99+
def check_in_list(values, /, *, _print_supported_values=True, **kwargs):
100100
"""
101101
For each *key, value* pair in *kwargs*, check that *value* is in *values*;
102102
if not, raise an appropriate ValueError.
@@ -378,6 +378,6 @@ def warn_external(message, category=None):
378378
frame.f_globals.get("__name__", "")):
379379
break
380380
frame = frame.f_back
381-
# premetively break reference cycle between locals and the frame
381+
# preemptively break reference cycle between locals and the frame
382382
del frame
383383
warnings.warn(message, category, stacklevel)

lib/matplotlib/_api/__init__.pyi

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from collections.abc import Callable, Generator, Mapping, Sequence
2+
from typing import Any, Iterable, TypeVar, overload
3+
4+
from numpy.typing import NDArray
5+
6+
from .deprecation import ( # noqa: re-exported API
7+
deprecated as deprecated,
8+
warn_deprecated as warn_deprecated,
9+
rename_parameter as rename_parameter,
10+
delete_parameter as delete_parameter,
11+
make_keyword_only as make_keyword_only,
12+
deprecate_method_override as deprecate_method_override,
13+
deprecate_privatize_attribute as deprecate_privatize_attribute,
14+
suppress_matplotlib_deprecation_warning as suppress_matplotlib_deprecation_warning,
15+
MatplotlibDeprecationWarning as MatplotlibDeprecationWarning,
16+
)
17+
18+
_T = TypeVar("_T")
19+
20+
class classproperty(Any):
21+
def __init__(
22+
self,
23+
fget: Callable[[_T], Any],
24+
fset: None = ...,
25+
fdel: None = ...,
26+
doc: str | None = None,
27+
): ...
28+
@overload
29+
def __get__(self, instance: None, owner: None) -> classproperty: ...
30+
@overload
31+
def __get__(self, instance: object, owner: type[object]) -> Any: ...
32+
@property
33+
def fget(self) -> Callable[[_T], Any]: ...
34+
35+
def check_isinstance(
36+
types: type | tuple[type | None, ...], /, **kwargs: Any
37+
) -> None: ...
38+
def check_in_list(
39+
values: Sequence[Any], /, *, _print_supported_values: bool = ..., **kwargs: Any
40+
) -> None: ...
41+
def check_shape(shape: tuple[int | None, ...], /, **kwargs: NDArray) -> None: ...
42+
def check_getitem(mapping: Mapping[Any, Any], /, **kwargs: Any) -> Any: ...
43+
def caching_module_getattr(cls: type) -> Callable[[str], Any]: ...
44+
@overload
45+
def define_aliases(
46+
alias_d: dict[str, list[str]], cls: None = ...
47+
) -> Callable[[type[_T]], type[_T]]: ...
48+
@overload
49+
def define_aliases(alias_d: dict[str, list[str]], cls: type[_T]) -> type[_T]: ...
50+
def select_matching_signature(
51+
funcs: list[Callable], *args: Any, **kwargs: Any
52+
) -> Any: ...
53+
def nargs_error(name: str, takes: int | str, given: int) -> TypeError: ...
54+
def kwarg_error(name: str, kw: str | Iterable[str]) -> TypeError: ...
55+
def recursive_subclasses(cls: type) -> Generator[type, None, None]: ...
56+
def warn_external(
57+
message: str | Warning, category: type[Warning] | None = ...
58+
) -> None: ...

lib/matplotlib/_api/deprecation.pyi

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from collections.abc import Callable
2+
import contextlib
3+
from typing import Any, TypedDict, TypeVar, overload
4+
from typing_extensions import (
5+
ParamSpec, # < Py 3.10
6+
Unpack, # < Py 3.11
7+
)
8+
9+
_P = ParamSpec("_P")
10+
_R = TypeVar("_R")
11+
_T = TypeVar("_T")
12+
13+
class MatplotlibDeprecationWarning(DeprecationWarning): ...
14+
15+
class DeprecationKwargs(TypedDict, total=False):
16+
message: str
17+
alternative: str
18+
pending: bool
19+
obj_type: str
20+
addendum: str
21+
removal: str
22+
23+
class NamedDeprecationKwargs(DeprecationKwargs, total=False):
24+
name: str
25+
26+
def warn_deprecated(since: str, **kwargs: Unpack[NamedDeprecationKwargs]) -> None: ...
27+
def deprecated(
28+
since: str, **kwargs: Unpack[NamedDeprecationKwargs]
29+
) -> Callable[[_T], _T]: ...
30+
31+
class deprecate_privatize_attribute(Any):
32+
def __init__(self, since: str, **kwargs: Unpack[NamedDeprecationKwargs]): ...
33+
def __set_name__(self, owner: type[object], name: str) -> None: ...
34+
35+
DECORATORS: dict[Callable, Callable] = ...
36+
37+
@overload
38+
def rename_parameter(
39+
since: str, old: str, new: str, func: None = ...
40+
) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ...
41+
@overload
42+
def rename_parameter(
43+
since: str, old: str, new: str, func: Callable[_P, _R]
44+
) -> Callable[_P, _R]: ...
45+
46+
class _deprecated_parameter_class: ...
47+
48+
_deprecated_parameter: _deprecated_parameter_class
49+
50+
@overload
51+
def delete_parameter(
52+
since: str, name: str, func: None = ..., **kwargs: Unpack[DeprecationKwargs]
53+
) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ...
54+
@overload
55+
def delete_parameter(
56+
since: str, name: str, func: Callable[_P, _R], **kwargs: Unpack[DeprecationKwargs]
57+
) -> Callable[_P, _R]: ...
58+
@overload
59+
def make_keyword_only(
60+
since: str, name: str, func: None = ...
61+
) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ...
62+
@overload
63+
def make_keyword_only(
64+
since: str, name: str, func: Callable[_P, _R]
65+
) -> Callable[_P, _R]: ...
66+
def deprecate_method_override(
67+
method: Callable[_P, _R],
68+
obj: object | type,
69+
*,
70+
allow_empty: bool = ...,
71+
since: str,
72+
**kwargs: Unpack[NamedDeprecationKwargs]
73+
) -> Callable[_P, _R]: ...
74+
def suppress_matplotlib_deprecation_warning() -> (
75+
contextlib.AbstractContextManager[None]
76+
): ...

lib/matplotlib/artist.pyi

+5-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ from .transforms import (
1313

1414
import numpy as np
1515

16-
from collections.abc import Callable
16+
from collections.abc import Callable, Iterable
1717
from typing import Any, NamedTuple, TextIO, overload
1818
from numpy.typing import ArrayLike
1919

@@ -137,7 +137,6 @@ class Artist:
137137
def format_cursor_data(self, data: Any) -> str: ...
138138
def get_mouseover(self) -> bool: ...
139139
def set_mouseover(self, mouseover: bool) -> None: ...
140-
141140
@property
142141
def mouseover(self) -> bool: ...
143142
@mouseover.setter
@@ -147,7 +146,9 @@ class ArtistInspector:
147146
oorig: Artist | type[Artist]
148147
o: type[Artist]
149148
aliasd: dict[str, set[str]]
150-
def __init__(self, o) -> None: ...
149+
def __init__(
150+
self, o: Artist | type[Artist] | Iterable[Artist | type[Artist]]
151+
) -> None: ...
151152
def get_aliases(self) -> dict[str, set[str]]: ...
152153
def get_valid_values(self, attr: str) -> str | None: ...
153154
def get_setters(self) -> list[str]: ...
@@ -177,4 +178,4 @@ def getp(obj: Artist, property: str | None = ...) -> Any: ...
177178
get = getp
178179

179180
def setp(obj: Artist, *args, file: TextIO | None = ..., **kwargs): ...
180-
def kwdoc(artist: Artist) -> str: ...
181+
def kwdoc(artist: Artist | type[Artist] | Iterable[Artist | type[Artist]]) -> str: ...

lib/matplotlib/cbook.pyi

+11-14
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,18 @@ class CallbackRegistry:
2929
self,
3030
exception_handler: Callable[[Exception], Any] | None = ...,
3131
*,
32-
signals: Iterable[Any] | None = ...
32+
signals: Iterable[Any] | None = ...,
3333
) -> None: ...
3434
def connect(self, signal: Any, func: Callable) -> int: ...
3535
def disconnect(self, cid: int) -> None: ...
3636
def process(self, s: Any, *args, **kwargs) -> None: ...
37-
@contextlib.contextmanager
38-
def blocked(self, *, signal: Any | None = ...): ...
37+
def blocked(
38+
self, *, signal: Any | None = ...
39+
) -> contextlib.AbstractContextManager[None]: ...
3940

4041
class silent_list(list[_T]):
4142
type: str | None
42-
def __init__(self, type, seq: Iterable[_T] | None = ...) -> None: ...
43+
def __init__(self, type: str | None, seq: Iterable[_T] | None = ...) -> None: ...
4344

4445
def strip_math(s: str) -> str: ...
4546
def is_writable_file_like(obj: Any) -> bool: ...
@@ -61,7 +62,7 @@ def to_filehandle(
6162
@overload
6263
def to_filehandle(
6364
fname: str | os.PathLike | IO,
64-
*, # if flag given, will match previous sig
65+
*, # if flag given, will match previous sig
6566
return_opened: Literal[True],
6667
encoding: str | None = ...,
6768
) -> tuple[IO, bool]: ...
@@ -73,24 +74,18 @@ def open_file_cm(
7374
def is_scalar_or_string(val: Any) -> bool: ...
7475
@overload
7576
def get_sample_data(
76-
fname: str | os.PathLike,
77-
asfileobj: Literal[True] = ...,
78-
*,
79-
np_load: Literal[True]
77+
fname: str | os.PathLike, asfileobj: Literal[True] = ..., *, np_load: Literal[True]
8078
) -> np.ndarray: ...
8179
@overload
8280
def get_sample_data(
8381
fname: str | os.PathLike,
8482
asfileobj: Literal[True] = ...,
8583
*,
86-
np_load: Literal[False] = ...
84+
np_load: Literal[False] = ...,
8785
) -> IO: ...
8886
@overload
8987
def get_sample_data(
90-
fname: str | os.PathLike,
91-
asfileobj: Literal[False],
92-
*,
93-
np_load: bool = ...
88+
fname: str | os.PathLike, asfileobj: Literal[False], *, np_load: bool = ...
9489
) -> str: ...
9590
def _get_data_path(*args: Path | str) -> Path: ...
9691
def flatten(
@@ -164,7 +159,9 @@ def normalize_kwargs(
164159
kw: dict[str, Any],
165160
alias_mapping: dict[str, list[str]] | type[Artist] | Artist | None = ...,
166161
) -> dict[str, Any]: ...
162+
def _lock_path(path: str | os.PathLike) -> contextlib.AbstractContextManager[None]: ...
167163
def _str_equal(obj: Any, s: str) -> bool: ...
164+
def _setattr_cm(obj: Any, **kwargs) -> contextlib.AbstractContextManager[None]: ...
168165

169166
class _OrderedSet(collections.abc.MutableSet):
170167
def __init__(self) -> None: ...

lib/matplotlib/lines.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1279,7 +1279,7 @@ def set_xdata(self, x):
12791279
# When deprecation cycle is completed
12801280
# raise RuntimeError('x must be a sequence')
12811281
_api.warn_deprecated(
1282-
since=3.7,
1282+
since="3.7",
12831283
message="Setting data with a non sequence type "
12841284
"is deprecated since %(since)s and will be "
12851285
"remove %(removal)s")
@@ -1300,7 +1300,7 @@ def set_ydata(self, y):
13001300
# When deprecation cycle is completed
13011301
# raise RuntimeError('y must be a sequence')
13021302
_api.warn_deprecated(
1303-
since=3.7,
1303+
since="3.7",
13041304
message="Setting data with a non sequence type "
13051305
"is deprecated since %(since)s and will be "
13061306
"remove %(removal)s")

lib/matplotlib/tests/test_api.py

+31-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
from __future__ import annotations
2+
13
import re
4+
import typing
5+
from typing import Any, Callable, TypeVar
26

37
import numpy as np
48
import pytest
@@ -7,26 +11,33 @@
711
from matplotlib import _api
812

913

14+
if typing.TYPE_CHECKING:
15+
from typing_extensions import Self
16+
17+
T = TypeVar('T')
18+
19+
1020
@pytest.mark.parametrize('target,test_shape',
1121
[((None, ), (1, 3)),
1222
((None, 3), (1,)),
1323
((None, 3), (1, 2)),
1424
((1, 5), (1, 9)),
1525
((None, 2, None), (1, 3, 1))
1626
])
17-
def test_check_shape(target, test_shape):
27+
def test_check_shape(target: tuple[int | None, ...],
28+
test_shape: tuple[int, ...]) -> None:
1829
error_pattern = (f"^'aardvark' must be {len(target)}D.*" +
1930
re.escape(f'has shape {test_shape}'))
2031
data = np.zeros(test_shape)
2132
with pytest.raises(ValueError, match=error_pattern):
2233
_api.check_shape(target, aardvark=data)
2334

2435

25-
def test_classproperty_deprecation():
36+
def test_classproperty_deprecation() -> None:
2637
class A:
2738
@_api.deprecated("0.0.0")
2839
@_api.classproperty
29-
def f(cls):
40+
def f(cls: Self) -> None:
3041
pass
3142
with pytest.warns(mpl.MatplotlibDeprecationWarning):
3243
A.f
@@ -35,12 +46,12 @@ def f(cls):
3546
a.f
3647

3748

38-
def test_deprecate_privatize_attribute():
49+
def test_deprecate_privatize_attribute() -> None:
3950
class C:
40-
def __init__(self): self._attr = 1
41-
def _meth(self, arg): return arg
42-
attr = _api.deprecate_privatize_attribute("0.0")
43-
meth = _api.deprecate_privatize_attribute("0.0")
51+
def __init__(self) -> None: self._attr = 1
52+
def _meth(self, arg: T) -> T: return arg
53+
attr: int = _api.deprecate_privatize_attribute("0.0")
54+
meth: Callable = _api.deprecate_privatize_attribute("0.0")
4455

4556
c = C()
4657
with pytest.warns(mpl.MatplotlibDeprecationWarning):
@@ -53,31 +64,31 @@ def _meth(self, arg): return arg
5364
assert c.meth(42) == 42
5465

5566

56-
def test_delete_parameter():
67+
def test_delete_parameter() -> None:
5768
@_api.delete_parameter("3.0", "foo")
58-
def func1(foo=None):
69+
def func1(foo: Any = None) -> None:
5970
pass
6071

6172
@_api.delete_parameter("3.0", "foo")
62-
def func2(**kwargs):
73+
def func2(**kwargs: Any) -> None:
6374
pass
6475

65-
for func in [func1, func2]:
76+
for func in [func1, func2]: # type: ignore[list-item]
6677
func() # No warning.
6778
with pytest.warns(mpl.MatplotlibDeprecationWarning):
6879
func(foo="bar")
6980

70-
def pyplot_wrapper(foo=_api.deprecation._deprecated_parameter):
81+
def pyplot_wrapper(foo: Any = _api.deprecation._deprecated_parameter) -> None:
7182
func1(foo)
7283

7384
pyplot_wrapper() # No warning.
7485
with pytest.warns(mpl.MatplotlibDeprecationWarning):
7586
func(foo="bar")
7687

7788

78-
def test_make_keyword_only():
89+
def test_make_keyword_only() -> None:
7990
@_api.make_keyword_only("3.0", "arg")
80-
def func(pre, arg, post=None):
91+
def func(pre: Any, arg: Any, post: Any = None) -> None:
8192
pass
8293

8394
func(1, arg=2) # Check that no warning is emitted.
@@ -88,14 +99,16 @@ def func(pre, arg, post=None):
8899
func(1, 2, 3)
89100

90101

91-
def test_deprecation_alternative():
102+
def test_deprecation_alternative() -> None:
92103
alternative = "`.f1`, `f2`, `f3(x) <.f3>` or `f4(x)<f4>`"
93104
@_api.deprecated("1", alternative=alternative)
94-
def f():
105+
def f() -> None:
95106
pass
107+
if f.__doc__ is None:
108+
pytest.skip('Documentation is disabled')
96109
assert alternative in f.__doc__
97110

98111

99-
def test_empty_check_in_list():
112+
def test_empty_check_in_list() -> None:
100113
with pytest.raises(TypeError, match="No argument to check!"):
101114
_api.check_in_list(["a"])

0 commit comments

Comments
 (0)