Skip to content

Commit dd40f48

Browse files
authored
Merge pull request #26367 from QuLogic/api-types
Add typing for internal helpers
2 parents cdf816e + 97ab602 commit dd40f48

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)