Coverage for /usr/lib/python3/dist-packages/matplotlib/_api/__init__.py: 80%
117 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""
2Helper functions for managing the Matplotlib API.
4This documentation is only relevant for Matplotlib developers, not for users.
6.. warning::
8 This module and its submodules are for internal use only. Do not use them
9 in your own code. We may change the API at any time with no warning.
11"""
13import functools
14import itertools
15import re
16import sys
17import warnings
19from .deprecation import (
20 deprecated, warn_deprecated,
21 rename_parameter, delete_parameter, make_keyword_only,
22 deprecate_method_override, deprecate_privatize_attribute,
23 suppress_matplotlib_deprecation_warning,
24 MatplotlibDeprecationWarning)
27class classproperty:
28 """
29 Like `property`, but also triggers on access via the class, and it is the
30 *class* that's passed as argument.
32 Examples
33 --------
34 ::
36 class C:
37 @classproperty
38 def foo(cls):
39 return cls.__name__
41 assert C.foo == "C"
42 """
44 def __init__(self, fget, fset=None, fdel=None, doc=None):
45 self._fget = fget
46 if fset is not None or fdel is not None:
47 raise ValueError('classproperty only implements fget.')
48 self.fset = fset
49 self.fdel = fdel
50 # docs are ignored for now
51 self._doc = doc
53 def __get__(self, instance, owner):
54 return self._fget(owner)
56 @property
57 def fget(self):
58 return self._fget
61# In the following check_foo() functions, the first parameter starts with an
62# underscore because it is intended to be positional-only (e.g., so that
63# `_api.check_isinstance([...], types=foo)` doesn't fail.
65def check_isinstance(_types, **kwargs):
66 """
67 For each *key, value* pair in *kwargs*, check that *value* is an instance
68 of one of *_types*; if not, raise an appropriate TypeError.
70 As a special case, a ``None`` entry in *_types* is treated as NoneType.
72 Examples
73 --------
74 >>> _api.check_isinstance((SomeClass, None), arg=arg)
75 """
76 types = _types
77 none_type = type(None)
78 types = ((types,) if isinstance(types, type) else
79 (none_type,) if types is None else
80 tuple(none_type if tp is None else tp for tp in types))
82 def type_name(tp):
83 return ("None" if tp is none_type
84 else tp.__qualname__ if tp.__module__ == "builtins"
85 else f"{tp.__module__}.{tp.__qualname__}")
87 for k, v in kwargs.items():
88 if not isinstance(v, types):
89 names = [*map(type_name, types)]
90 if "None" in names: # Move it to the end for better wording.
91 names.remove("None")
92 names.append("None")
93 raise TypeError(
94 "{!r} must be an instance of {}, not a {}".format(
95 k,
96 ", ".join(names[:-1]) + " or " + names[-1]
97 if len(names) > 1 else names[0],
98 type_name(type(v))))
101def check_in_list(_values, *, _print_supported_values=True, **kwargs):
102 """
103 For each *key, value* pair in *kwargs*, check that *value* is in *_values*.
105 Parameters
106 ----------
107 _values : iterable
108 Sequence of values to check on.
109 _print_supported_values : bool, default: True
110 Whether to print *_values* when raising ValueError.
111 **kwargs : dict
112 *key, value* pairs as keyword arguments to find in *_values*.
114 Raises
115 ------
116 ValueError
117 If any *value* in *kwargs* is not found in *_values*.
119 Examples
120 --------
121 >>> _api.check_in_list(["foo", "bar"], arg=arg, other_arg=other_arg)
122 """
123 if not kwargs:
124 raise TypeError("No argument to check!")
125 values = _values
126 for key, val in kwargs.items():
127 if val not in values:
128 msg = f"{val!r} is not a valid value for {key}"
129 if _print_supported_values:
130 msg += f"; supported values are {', '.join(map(repr, values))}"
131 raise ValueError(msg)
134def check_shape(_shape, **kwargs):
135 """
136 For each *key, value* pair in *kwargs*, check that *value* has the shape
137 *_shape*, if not, raise an appropriate ValueError.
139 *None* in the shape is treated as a "free" size that can have any length.
140 e.g. (None, 2) -> (N, 2)
142 The values checked must be numpy arrays.
144 Examples
145 --------
146 To check for (N, 2) shaped arrays
148 >>> _api.check_shape((None, 2), arg=arg, other_arg=other_arg)
149 """
150 target_shape = _shape
151 for k, v in kwargs.items():
152 data_shape = v.shape
154 if len(target_shape) != len(data_shape) or any(
155 t not in [s, None]
156 for t, s in zip(target_shape, data_shape)
157 ):
158 dim_labels = iter(itertools.chain(
159 'MNLIJKLH',
160 (f"D{i}" for i in itertools.count())))
161 text_shape = ", ".join((str(n)
162 if n is not None
163 else next(dim_labels)
164 for n in target_shape))
166 raise ValueError(
167 f"{k!r} must be {len(target_shape)}D "
168 f"with shape ({text_shape}). "
169 f"Your input has shape {v.shape}."
170 )
173def check_getitem(_mapping, **kwargs):
174 """
175 *kwargs* must consist of a single *key, value* pair. If *key* is in
176 *_mapping*, return ``_mapping[value]``; else, raise an appropriate
177 ValueError.
179 Examples
180 --------
181 >>> _api.check_getitem({"foo": "bar"}, arg=arg)
182 """
183 mapping = _mapping
184 if len(kwargs) != 1:
185 raise ValueError("check_getitem takes a single keyword argument")
186 (k, v), = kwargs.items()
187 try:
188 return mapping[v]
189 except KeyError:
190 raise ValueError(
191 "{!r} is not a valid value for {}; supported values are {}"
192 .format(v, k, ', '.join(map(repr, mapping)))) from None
195def caching_module_getattr(cls):
196 """
197 Helper decorator for implementing module-level ``__getattr__`` as a class.
199 This decorator must be used at the module toplevel as follows::
201 @caching_module_getattr
202 class __getattr__: # The class *must* be named ``__getattr__``.
203 @property # Only properties are taken into account.
204 def name(self): ...
206 The ``__getattr__`` class will be replaced by a ``__getattr__``
207 function such that trying to access ``name`` on the module will
208 resolve the corresponding property (which may be decorated e.g. with
209 ``_api.deprecated`` for deprecating module globals). The properties are
210 all implicitly cached. Moreover, a suitable AttributeError is generated
211 and raised if no property with the given name exists.
212 """
214 assert cls.__name__ == "__getattr__"
215 # Don't accidentally export cls dunders.
216 props = {name: prop for name, prop in vars(cls).items()
217 if isinstance(prop, property)}
218 instance = cls()
220 @functools.lru_cache(None)
221 def __getattr__(name):
222 if name in props:
223 return props[name].__get__(instance)
224 raise AttributeError(
225 f"module {cls.__module__!r} has no attribute {name!r}")
227 return __getattr__
230def define_aliases(alias_d, cls=None):
231 """
232 Class decorator for defining property aliases.
234 Use as ::
236 @_api.define_aliases({"property": ["alias", ...], ...})
237 class C: ...
239 For each property, if the corresponding ``get_property`` is defined in the
240 class so far, an alias named ``get_alias`` will be defined; the same will
241 be done for setters. If neither the getter nor the setter exists, an
242 exception will be raised.
244 The alias map is stored as the ``_alias_map`` attribute on the class and
245 can be used by `.normalize_kwargs` (which assumes that higher priority
246 aliases come last).
247 """
248 if cls is None: # Return the actual class decorator.
249 return functools.partial(define_aliases, alias_d)
251 def make_alias(name): # Enforce a closure over *name*.
252 @functools.wraps(getattr(cls, name))
253 def method(self, *args, **kwargs):
254 return getattr(self, name)(*args, **kwargs)
255 return method
257 for prop, aliases in alias_d.items():
258 exists = False
259 for prefix in ["get_", "set_"]:
260 if prefix + prop in vars(cls):
261 exists = True
262 for alias in aliases:
263 method = make_alias(prefix + prop)
264 method.__name__ = prefix + alias
265 method.__doc__ = "Alias for `{}`.".format(prefix + prop)
266 setattr(cls, prefix + alias, method)
267 if not exists:
268 raise ValueError(
269 "Neither getter nor setter exists for {!r}".format(prop))
271 def get_aliased_and_aliases(d):
272 return {*d, *(alias for aliases in d.values() for alias in aliases)}
274 preexisting_aliases = getattr(cls, "_alias_map", {})
275 conflicting = (get_aliased_and_aliases(preexisting_aliases)
276 & get_aliased_and_aliases(alias_d))
277 if conflicting:
278 # Need to decide on conflict resolution policy.
279 raise NotImplementedError(
280 f"Parent class already defines conflicting aliases: {conflicting}")
281 cls._alias_map = {**preexisting_aliases, **alias_d}
282 return cls
285def select_matching_signature(funcs, *args, **kwargs):
286 """
287 Select and call the function that accepts ``*args, **kwargs``.
289 *funcs* is a list of functions which should not raise any exception (other
290 than `TypeError` if the arguments passed do not match their signature).
292 `select_matching_signature` tries to call each of the functions in *funcs*
293 with ``*args, **kwargs`` (in the order in which they are given). Calls
294 that fail with a `TypeError` are silently skipped. As soon as a call
295 succeeds, `select_matching_signature` returns its return value. If no
296 function accepts ``*args, **kwargs``, then the `TypeError` raised by the
297 last failing call is re-raised.
299 Callers should normally make sure that any ``*args, **kwargs`` can only
300 bind a single *func* (to avoid any ambiguity), although this is not checked
301 by `select_matching_signature`.
303 Notes
304 -----
305 `select_matching_signature` is intended to help implementing
306 signature-overloaded functions. In general, such functions should be
307 avoided, except for back-compatibility concerns. A typical use pattern is
308 ::
310 def my_func(*args, **kwargs):
311 params = select_matching_signature(
312 [lambda old1, old2: locals(), lambda new: locals()],
313 *args, **kwargs)
314 if "old1" in params:
315 warn_deprecated(...)
316 old1, old2 = params.values() # note that locals() is ordered.
317 else:
318 new, = params.values()
319 # do things with params
321 which allows *my_func* to be called either with two parameters (*old1* and
322 *old2*) or a single one (*new*). Note that the new signature is given
323 last, so that callers get a `TypeError` corresponding to the new signature
324 if the arguments they passed in do not match any signature.
325 """
326 # Rather than relying on locals() ordering, one could have just used func's
327 # signature (``bound = inspect.signature(func).bind(*args, **kwargs);
328 # bound.apply_defaults(); return bound``) but that is significantly slower.
329 for i, func in enumerate(funcs):
330 try:
331 return func(*args, **kwargs)
332 except TypeError:
333 if i == len(funcs) - 1:
334 raise
337def recursive_subclasses(cls):
338 """Yield *cls* and direct and indirect subclasses of *cls*."""
339 yield cls
340 for subcls in cls.__subclasses__():
341 yield from recursive_subclasses(subcls)
344def warn_external(message, category=None):
345 """
346 `warnings.warn` wrapper that sets *stacklevel* to "outside Matplotlib".
348 The original emitter of the warning can be obtained by patching this
349 function back to `warnings.warn`, i.e. ``_api.warn_external =
350 warnings.warn`` (or ``functools.partial(warnings.warn, stacklevel=2)``,
351 etc.).
352 """
353 frame = sys._getframe()
354 for stacklevel in itertools.count(1): # lgtm[py/unused-loop-variable]
355 if frame is None:
356 # when called in embedded context may hit frame is None
357 break
358 if not re.match(r"\A(matplotlib|mpl_toolkits)(\Z|\.(?!tests\.))",
359 # Work around sphinx-gallery not setting __name__.
360 frame.f_globals.get("__name__", "")):
361 break
362 frame = frame.f_back
363 warnings.warn(message, category, stacklevel)