Skip to content

Add typing for internal helpers #26367

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 1 commit into from
Jul 28, 2023
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
4 changes: 2 additions & 2 deletions lib/matplotlib/_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def type_name(tp):
type_name(type(v))))


def check_in_list(values, /, *, _print_supported_values=True, **kwargs):
def check_in_list(values, /, *, _print_supported_values=True, **kwargs):
"""
For each *key, value* pair in *kwargs*, check that *value* is in *values*;
if not, raise an appropriate ValueError.
Expand Down Expand Up @@ -378,6 +378,6 @@ def warn_external(message, category=None):
frame.f_globals.get("__name__", "")):
break
frame = frame.f_back
# premetively break reference cycle between locals and the frame
# preemptively break reference cycle between locals and the frame
del frame
warnings.warn(message, category, stacklevel)
58 changes: 58 additions & 0 deletions lib/matplotlib/_api/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from collections.abc import Callable, Generator, Mapping, Sequence
from typing import Any, Iterable, TypeVar, overload

from numpy.typing import NDArray

from .deprecation import ( # noqa: re-exported API
deprecated as deprecated,
warn_deprecated as warn_deprecated,
rename_parameter as rename_parameter,
delete_parameter as delete_parameter,
make_keyword_only as make_keyword_only,
deprecate_method_override as deprecate_method_override,
deprecate_privatize_attribute as deprecate_privatize_attribute,
suppress_matplotlib_deprecation_warning as suppress_matplotlib_deprecation_warning,
MatplotlibDeprecationWarning as MatplotlibDeprecationWarning,
)

_T = TypeVar("_T")

class classproperty(Any):
def __init__(
self,
fget: Callable[[_T], Any],
fset: None = ...,
fdel: None = ...,
doc: str | None = None,
): ...
@overload
def __get__(self, instance: None, owner: None) -> classproperty: ...
@overload
def __get__(self, instance: object, owner: type[object]) -> Any: ...
@property
def fget(self) -> Callable[[_T], Any]: ...

def check_isinstance(
types: type | tuple[type | None, ...], /, **kwargs: Any
) -> None: ...
def check_in_list(
values: Sequence[Any], /, *, _print_supported_values: bool = ..., **kwargs: Any
) -> None: ...
def check_shape(shape: tuple[int | None, ...], /, **kwargs: NDArray) -> None: ...
def check_getitem(mapping: Mapping[Any, Any], /, **kwargs: Any) -> Any: ...
def caching_module_getattr(cls: type) -> Callable[[str], Any]: ...
@overload
def define_aliases(
alias_d: dict[str, list[str]], cls: None = ...
) -> Callable[[type[_T]], type[_T]]: ...
@overload
def define_aliases(alias_d: dict[str, list[str]], cls: type[_T]) -> type[_T]: ...
def select_matching_signature(
funcs: list[Callable], *args: Any, **kwargs: Any
) -> Any: ...
def nargs_error(name: str, takes: int | str, given: int) -> TypeError: ...
def kwarg_error(name: str, kw: str | Iterable[str]) -> TypeError: ...
def recursive_subclasses(cls: type) -> Generator[type, None, None]: ...
def warn_external(
message: str | Warning, category: type[Warning] | None = ...
) -> None: ...
76 changes: 76 additions & 0 deletions lib/matplotlib/_api/deprecation.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from collections.abc import Callable
import contextlib
from typing import Any, TypedDict, TypeVar, overload
from typing_extensions import (
ParamSpec, # < Py 3.10
Unpack, # < Py 3.11
)

_P = ParamSpec("_P")
_R = TypeVar("_R")
_T = TypeVar("_T")

class MatplotlibDeprecationWarning(DeprecationWarning): ...

class DeprecationKwargs(TypedDict, total=False):
message: str
alternative: str
pending: bool
obj_type: str
addendum: str
removal: str

class NamedDeprecationKwargs(DeprecationKwargs, total=False):
name: str

def warn_deprecated(since: str, **kwargs: Unpack[NamedDeprecationKwargs]) -> None: ...
def deprecated(
since: str, **kwargs: Unpack[NamedDeprecationKwargs]
) -> Callable[[_T], _T]: ...

class deprecate_privatize_attribute(Any):
def __init__(self, since: str, **kwargs: Unpack[NamedDeprecationKwargs]): ...
def __set_name__(self, owner: type[object], name: str) -> None: ...

DECORATORS: dict[Callable, Callable] = ...

@overload
def rename_parameter(
since: str, old: str, new: str, func: None = ...
) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ...
@overload
def rename_parameter(
since: str, old: str, new: str, func: Callable[_P, _R]
) -> Callable[_P, _R]: ...

class _deprecated_parameter_class: ...

_deprecated_parameter: _deprecated_parameter_class

@overload
def delete_parameter(
since: str, name: str, func: None = ..., **kwargs: Unpack[DeprecationKwargs]
) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ...
@overload
def delete_parameter(
since: str, name: str, func: Callable[_P, _R], **kwargs: Unpack[DeprecationKwargs]
) -> Callable[_P, _R]: ...
@overload
def make_keyword_only(
since: str, name: str, func: None = ...
) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ...
@overload
def make_keyword_only(
since: str, name: str, func: Callable[_P, _R]
) -> Callable[_P, _R]: ...
def deprecate_method_override(
method: Callable[_P, _R],
obj: object | type,
*,
allow_empty: bool = ...,
since: str,
**kwargs: Unpack[NamedDeprecationKwargs]
) -> Callable[_P, _R]: ...
def suppress_matplotlib_deprecation_warning() -> (
contextlib.AbstractContextManager[None]
): ...
9 changes: 5 additions & 4 deletions lib/matplotlib/artist.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ from .transforms import (

import numpy as np

from collections.abc import Callable
from collections.abc import Callable, Iterable
from typing import Any, NamedTuple, TextIO, overload
from numpy.typing import ArrayLike

Expand Down Expand Up @@ -137,7 +137,6 @@ class Artist:
def format_cursor_data(self, data: Any) -> str: ...
def get_mouseover(self) -> bool: ...
def set_mouseover(self, mouseover: bool) -> None: ...

@property
def mouseover(self) -> bool: ...
@mouseover.setter
Expand All @@ -147,7 +146,9 @@ class ArtistInspector:
oorig: Artist | type[Artist]
o: type[Artist]
aliasd: dict[str, set[str]]
def __init__(self, o) -> None: ...
def __init__(
self, o: Artist | type[Artist] | Iterable[Artist | type[Artist]]
) -> None: ...
def get_aliases(self) -> dict[str, set[str]]: ...
def get_valid_values(self, attr: str) -> str | None: ...
def get_setters(self) -> list[str]: ...
Expand Down Expand Up @@ -177,4 +178,4 @@ def getp(obj: Artist, property: str | None = ...) -> Any: ...
get = getp

def setp(obj: Artist, *args, file: TextIO | None = ..., **kwargs): ...
def kwdoc(artist: Artist) -> str: ...
def kwdoc(artist: Artist | type[Artist] | Iterable[Artist | type[Artist]]) -> str: ...
25 changes: 11 additions & 14 deletions lib/matplotlib/cbook.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,18 @@ class CallbackRegistry:
self,
exception_handler: Callable[[Exception], Any] | None = ...,
*,
signals: Iterable[Any] | None = ...
signals: Iterable[Any] | None = ...,
) -> None: ...
def connect(self, signal: Any, func: Callable) -> int: ...
def disconnect(self, cid: int) -> None: ...
def process(self, s: Any, *args, **kwargs) -> None: ...
@contextlib.contextmanager
def blocked(self, *, signal: Any | None = ...): ...
def blocked(
self, *, signal: Any | None = ...
) -> contextlib.AbstractContextManager[None]: ...

class silent_list(list[_T]):
type: str | None
def __init__(self, type, seq: Iterable[_T] | None = ...) -> None: ...
def __init__(self, type: str | None, seq: Iterable[_T] | None = ...) -> None: ...

def strip_math(s: str) -> str: ...
def is_writable_file_like(obj: Any) -> bool: ...
Expand All @@ -61,7 +62,7 @@ def to_filehandle(
@overload
def to_filehandle(
fname: str | os.PathLike | IO,
*, # if flag given, will match previous sig
*, # if flag given, will match previous sig
return_opened: Literal[True],
encoding: str | None = ...,
) -> tuple[IO, bool]: ...
Expand All @@ -73,24 +74,18 @@ def open_file_cm(
def is_scalar_or_string(val: Any) -> bool: ...
@overload
def get_sample_data(
fname: str | os.PathLike,
asfileobj: Literal[True] = ...,
*,
np_load: Literal[True]
fname: str | os.PathLike, asfileobj: Literal[True] = ..., *, np_load: Literal[True]
) -> np.ndarray: ...
@overload
def get_sample_data(
fname: str | os.PathLike,
asfileobj: Literal[True] = ...,
*,
np_load: Literal[False] = ...
np_load: Literal[False] = ...,
) -> IO: ...
@overload
def get_sample_data(
fname: str | os.PathLike,
asfileobj: Literal[False],
*,
np_load: bool = ...
fname: str | os.PathLike, asfileobj: Literal[False], *, np_load: bool = ...
) -> str: ...
def _get_data_path(*args: Path | str) -> Path: ...
def flatten(
Expand Down Expand Up @@ -164,7 +159,9 @@ def normalize_kwargs(
kw: dict[str, Any],
alias_mapping: dict[str, list[str]] | type[Artist] | Artist | None = ...,
) -> dict[str, Any]: ...
def _lock_path(path: str | os.PathLike) -> contextlib.AbstractContextManager[None]: ...
def _str_equal(obj: Any, s: str) -> bool: ...
def _setattr_cm(obj: Any, **kwargs) -> contextlib.AbstractContextManager[None]: ...

class _OrderedSet(collections.abc.MutableSet):
def __init__(self) -> None: ...
Expand Down
4 changes: 2 additions & 2 deletions lib/matplotlib/lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -1279,7 +1279,7 @@ def set_xdata(self, x):
# When deprecation cycle is completed
# raise RuntimeError('x must be a sequence')
_api.warn_deprecated(
since=3.7,
since="3.7",
message="Setting data with a non sequence type "
"is deprecated since %(since)s and will be "
"remove %(removal)s")
Expand All @@ -1300,7 +1300,7 @@ def set_ydata(self, y):
# When deprecation cycle is completed
# raise RuntimeError('y must be a sequence')
_api.warn_deprecated(
since=3.7,
since="3.7",
message="Setting data with a non sequence type "
"is deprecated since %(since)s and will be "
"remove %(removal)s")
Expand Down
49 changes: 31 additions & 18 deletions lib/matplotlib/tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from __future__ import annotations

import re
import typing
from typing import Any, Callable, TypeVar

import numpy as np
import pytest
Expand All @@ -7,26 +11,33 @@
from matplotlib import _api


if typing.TYPE_CHECKING:
from typing_extensions import Self

T = TypeVar('T')


@pytest.mark.parametrize('target,test_shape',
[((None, ), (1, 3)),
((None, 3), (1,)),
((None, 3), (1, 2)),
((1, 5), (1, 9)),
((None, 2, None), (1, 3, 1))
])
def test_check_shape(target, test_shape):
def test_check_shape(target: tuple[int | None, ...],
test_shape: tuple[int, ...]) -> None:
error_pattern = (f"^'aardvark' must be {len(target)}D.*" +
re.escape(f'has shape {test_shape}'))
data = np.zeros(test_shape)
with pytest.raises(ValueError, match=error_pattern):
_api.check_shape(target, aardvark=data)


def test_classproperty_deprecation():
def test_classproperty_deprecation() -> None:
class A:
@_api.deprecated("0.0.0")
@_api.classproperty
def f(cls):
def f(cls: Self) -> None:
pass
with pytest.warns(mpl.MatplotlibDeprecationWarning):
A.f
Expand All @@ -35,12 +46,12 @@ def f(cls):
a.f


def test_deprecate_privatize_attribute():
def test_deprecate_privatize_attribute() -> None:
class C:
def __init__(self): self._attr = 1
def _meth(self, arg): return arg
attr = _api.deprecate_privatize_attribute("0.0")
meth = _api.deprecate_privatize_attribute("0.0")
def __init__(self) -> None: self._attr = 1
def _meth(self, arg: T) -> T: return arg
attr: int = _api.deprecate_privatize_attribute("0.0")
meth: Callable = _api.deprecate_privatize_attribute("0.0")

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


def test_delete_parameter():
def test_delete_parameter() -> None:
@_api.delete_parameter("3.0", "foo")
def func1(foo=None):
def func1(foo: Any = None) -> None:
pass

@_api.delete_parameter("3.0", "foo")
def func2(**kwargs):
def func2(**kwargs: Any) -> None:
pass

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

def pyplot_wrapper(foo=_api.deprecation._deprecated_parameter):
def pyplot_wrapper(foo: Any = _api.deprecation._deprecated_parameter) -> None:
func1(foo)

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


def test_make_keyword_only():
def test_make_keyword_only() -> None:
@_api.make_keyword_only("3.0", "arg")
def func(pre, arg, post=None):
def func(pre: Any, arg: Any, post: Any = None) -> None:
pass

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


def test_deprecation_alternative():
def test_deprecation_alternative() -> None:
alternative = "`.f1`, `f2`, `f3(x) <.f3>` or `f4(x)<f4>`"
@_api.deprecated("1", alternative=alternative)
def f():
def f() -> None:
pass
if f.__doc__ is None:
pytest.skip('Documentation is disabled')
assert alternative in f.__doc__


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