214 lines
6.6 KiB
Python
214 lines
6.6 KiB
Python
"""
|
|
Helper functions for managing the Matplotlib API.
|
|
|
|
This documentation is only relevant for Matplotlib developers, not for users.
|
|
|
|
.. warning:
|
|
|
|
This module and its submodules are for internal use only. Do not use them
|
|
in your own code. We may change the API at any time with no warning.
|
|
|
|
"""
|
|
|
|
import itertools
|
|
import re
|
|
import sys
|
|
import warnings
|
|
|
|
from .deprecation import (
|
|
deprecated, warn_deprecated,
|
|
rename_parameter, delete_parameter, make_keyword_only,
|
|
deprecate_method_override, deprecate_privatize_attribute,
|
|
suppress_matplotlib_deprecation_warning,
|
|
MatplotlibDeprecationWarning)
|
|
|
|
|
|
class classproperty:
|
|
"""
|
|
Like `property`, but also triggers on access via the class, and it is the
|
|
*class* that's passed as argument.
|
|
|
|
Examples
|
|
--------
|
|
::
|
|
|
|
class C:
|
|
@classproperty
|
|
def foo(cls):
|
|
return cls.__name__
|
|
|
|
assert C.foo == "C"
|
|
"""
|
|
|
|
def __init__(self, fget, fset=None, fdel=None, doc=None):
|
|
self._fget = fget
|
|
if fset is not None or fdel is not None:
|
|
raise ValueError('classproperty only implements fget.')
|
|
self.fset = fset
|
|
self.fdel = fdel
|
|
# docs are ignored for now
|
|
self._doc = doc
|
|
|
|
def __get__(self, instance, owner):
|
|
return self._fget(owner)
|
|
|
|
@property
|
|
def fget(self):
|
|
return self._fget
|
|
|
|
|
|
# In the following check_foo() functions, the first parameter starts with an
|
|
# underscore because it is intended to be positional-only (e.g., so that
|
|
# `_api.check_isinstance([...], types=foo)` doesn't fail.
|
|
|
|
def check_isinstance(_types, **kwargs):
|
|
"""
|
|
For each *key, value* pair in *kwargs*, check that *value* is an instance
|
|
of one of *_types*; if not, raise an appropriate TypeError.
|
|
|
|
As a special case, a ``None`` entry in *_types* is treated as NoneType.
|
|
|
|
Examples
|
|
--------
|
|
>>> _api.check_isinstance((SomeClass, None), arg=arg)
|
|
"""
|
|
types = _types
|
|
none_type = type(None)
|
|
types = ((types,) if isinstance(types, type) else
|
|
(none_type,) if types is None else
|
|
tuple(none_type if tp is None else tp for tp in types))
|
|
|
|
def type_name(tp):
|
|
return ("None" if tp is none_type
|
|
else tp.__qualname__ if tp.__module__ == "builtins"
|
|
else f"{tp.__module__}.{tp.__qualname__}")
|
|
|
|
for k, v in kwargs.items():
|
|
if not isinstance(v, types):
|
|
names = [*map(type_name, types)]
|
|
if "None" in names: # Move it to the end for better wording.
|
|
names.remove("None")
|
|
names.append("None")
|
|
raise TypeError(
|
|
"{!r} must be an instance of {}, not a {}".format(
|
|
k,
|
|
", ".join(names[:-1]) + " or " + names[-1]
|
|
if len(names) > 1 else names[0],
|
|
type_name(type(v))))
|
|
|
|
|
|
def check_in_list(_values, *, _print_supported_values=True, **kwargs):
|
|
"""
|
|
For each *key, value* pair in *kwargs*, check that *value* is in *_values*.
|
|
|
|
Parameters
|
|
----------
|
|
_values : iterable
|
|
Sequence of values to check on.
|
|
_print_supported_values : bool, default: True
|
|
Whether to print *_values* when raising ValueError.
|
|
**kwargs : dict
|
|
*key, value* pairs as keyword arguments to find in *_values*.
|
|
|
|
Raises
|
|
------
|
|
ValueError
|
|
If any *value* in *kwargs* is not found in *_values*.
|
|
|
|
Examples
|
|
--------
|
|
>>> _api.check_in_list(["foo", "bar"], arg=arg, other_arg=other_arg)
|
|
"""
|
|
values = _values
|
|
for key, val in kwargs.items():
|
|
if val not in values:
|
|
if _print_supported_values:
|
|
raise ValueError(
|
|
f"{val!r} is not a valid value for {key}; "
|
|
f"supported values are {', '.join(map(repr, values))}")
|
|
else:
|
|
raise ValueError(f"{val!r} is not a valid value for {key}")
|
|
|
|
|
|
def check_shape(_shape, **kwargs):
|
|
"""
|
|
For each *key, value* pair in *kwargs*, check that *value* has the shape
|
|
*_shape*, if not, raise an appropriate ValueError.
|
|
|
|
*None* in the shape is treated as a "free" size that can have any length.
|
|
e.g. (None, 2) -> (N, 2)
|
|
|
|
The values checked must be numpy arrays.
|
|
|
|
Examples
|
|
--------
|
|
To check for (N, 2) shaped arrays
|
|
|
|
>>> _api.check_shape((None, 2), arg=arg, other_arg=other_arg)
|
|
"""
|
|
target_shape = _shape
|
|
for k, v in kwargs.items():
|
|
data_shape = v.shape
|
|
|
|
if len(target_shape) != len(data_shape) or any(
|
|
t not in [s, None]
|
|
for t, s in zip(target_shape, data_shape)
|
|
):
|
|
dim_labels = iter(itertools.chain(
|
|
'MNLIJKLH',
|
|
(f"D{i}" for i in itertools.count())))
|
|
text_shape = ", ".join((str(n)
|
|
if n is not None
|
|
else next(dim_labels)
|
|
for n in target_shape))
|
|
|
|
raise ValueError(
|
|
f"{k!r} must be {len(target_shape)}D "
|
|
f"with shape ({text_shape}). "
|
|
f"Your input has shape {v.shape}."
|
|
)
|
|
|
|
|
|
def check_getitem(_mapping, **kwargs):
|
|
"""
|
|
*kwargs* must consist of a single *key, value* pair. If *key* is in
|
|
*_mapping*, return ``_mapping[value]``; else, raise an appropriate
|
|
ValueError.
|
|
|
|
Examples
|
|
--------
|
|
>>> _api.check_getitem({"foo": "bar"}, arg=arg)
|
|
"""
|
|
mapping = _mapping
|
|
if len(kwargs) != 1:
|
|
raise ValueError("check_getitem takes a single keyword argument")
|
|
(k, v), = kwargs.items()
|
|
try:
|
|
return mapping[v]
|
|
except KeyError:
|
|
raise ValueError(
|
|
"{!r} is not a valid value for {}; supported values are {}"
|
|
.format(v, k, ', '.join(map(repr, mapping)))) from None
|
|
|
|
|
|
def warn_external(message, category=None):
|
|
"""
|
|
`warnings.warn` wrapper that sets *stacklevel* to "outside Matplotlib".
|
|
|
|
The original emitter of the warning can be obtained by patching this
|
|
function back to `warnings.warn`, i.e. ``_api.warn_external =
|
|
warnings.warn`` (or ``functools.partial(warnings.warn, stacklevel=2)``,
|
|
etc.).
|
|
"""
|
|
frame = sys._getframe()
|
|
for stacklevel in itertools.count(1): # lgtm[py/unused-loop-variable]
|
|
if frame is None:
|
|
# when called in embedded context may hit frame is None
|
|
break
|
|
if not re.match(r"\A(matplotlib|mpl_toolkits)(\Z|\.(?!tests\.))",
|
|
# Work around sphinx-gallery not setting __name__.
|
|
frame.f_globals.get("__name__", "")):
|
|
break
|
|
frame = frame.f_back
|
|
warnings.warn(message, category, stacklevel)
|