Coverage for /usr/lib/python3/dist-packages/matplotlib/_api/deprecation.py: 69%
173 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
1"""
2Helper functions for deprecating parts of the Matplotlib API.
4This documentation is only relevant for Matplotlib developers, not for users.
6.. warning::
8 This module is for internal use only. Do not use it in your own code.
9 We may change the API at any time with no warning.
11"""
13import contextlib
14import functools
15import inspect
16import math
17import warnings
20class MatplotlibDeprecationWarning(DeprecationWarning):
21 """A class for issuing deprecation warnings for Matplotlib users."""
24def _generate_deprecation_warning(
25 since, message='', name='', alternative='', pending=False, obj_type='',
26 addendum='', *, removal=''):
27 if pending:
28 if removal:
29 raise ValueError(
30 "A pending deprecation cannot have a scheduled removal")
31 else:
32 removal = f"in {removal}" if removal else "two minor releases later"
33 if not message:
34 message = (
35 ("The %(name)s %(obj_type)s" if obj_type else "%(name)s")
36 + (" will be deprecated in a future version"
37 if pending else
38 (" was deprecated in Matplotlib %(since)s"
39 + (" and will be removed %(removal)s" if removal else "")))
40 + "."
41 + (" Use %(alternative)s instead." if alternative else "")
42 + (" %(addendum)s" if addendum else ""))
43 warning_cls = (PendingDeprecationWarning if pending
44 else MatplotlibDeprecationWarning)
45 return warning_cls(message % dict(
46 func=name, name=name, obj_type=obj_type, since=since, removal=removal,
47 alternative=alternative, addendum=addendum))
50def warn_deprecated(
51 since, *, message='', name='', alternative='', pending=False,
52 obj_type='', addendum='', removal=''):
53 """
54 Display a standardized deprecation.
56 Parameters
57 ----------
58 since : str
59 The release at which this API became deprecated.
60 message : str, optional
61 Override the default deprecation message. The ``%(since)s``,
62 ``%(name)s``, ``%(alternative)s``, ``%(obj_type)s``, ``%(addendum)s``,
63 and ``%(removal)s`` format specifiers will be replaced by the values
64 of the respective arguments passed to this function.
65 name : str, optional
66 The name of the deprecated object.
67 alternative : str, optional
68 An alternative API that the user may use in place of the deprecated
69 API. The deprecation warning will tell the user about this alternative
70 if provided.
71 pending : bool, optional
72 If True, uses a PendingDeprecationWarning instead of a
73 DeprecationWarning. Cannot be used together with *removal*.
74 obj_type : str, optional
75 The object type being deprecated.
76 addendum : str, optional
77 Additional text appended directly to the final message.
78 removal : str, optional
79 The expected removal version. With the default (an empty string), a
80 removal version is automatically computed from *since*. Set to other
81 Falsy values to not schedule a removal date. Cannot be used together
82 with *pending*.
84 Examples
85 --------
86 ::
88 # To warn of the deprecation of "matplotlib.name_of_module"
89 warn_deprecated('1.4.0', name='matplotlib.name_of_module',
90 obj_type='module')
91 """
92 warning = _generate_deprecation_warning(
93 since, message, name, alternative, pending, obj_type, addendum,
94 removal=removal)
95 from . import warn_external
96 warn_external(warning, category=MatplotlibDeprecationWarning)
99def deprecated(since, *, message='', name='', alternative='', pending=False,
100 obj_type=None, addendum='', removal=''):
101 """
102 Decorator to mark a function, a class, or a property as deprecated.
104 When deprecating a classmethod, a staticmethod, or a property, the
105 ``@deprecated`` decorator should go *under* ``@classmethod`` and
106 ``@staticmethod`` (i.e., `deprecated` should directly decorate the
107 underlying callable), but *over* ``@property``.
109 When deprecating a class ``C`` intended to be used as a base class in a
110 multiple inheritance hierarchy, ``C`` *must* define an ``__init__`` method
111 (if ``C`` instead inherited its ``__init__`` from its own base class, then
112 ``@deprecated`` would mess up ``__init__`` inheritance when installing its
113 own (deprecation-emitting) ``C.__init__``).
115 Parameters are the same as for `warn_deprecated`, except that *obj_type*
116 defaults to 'class' if decorating a class, 'attribute' if decorating a
117 property, and 'function' otherwise.
119 Examples
120 --------
121 ::
123 @deprecated('1.4.0')
124 def the_function_to_deprecate():
125 pass
126 """
128 def deprecate(obj, message=message, name=name, alternative=alternative,
129 pending=pending, obj_type=obj_type, addendum=addendum):
130 from matplotlib._api import classproperty
132 if isinstance(obj, type):
133 if obj_type is None:
134 obj_type = "class"
135 func = obj.__init__
136 name = name or obj.__name__
137 old_doc = obj.__doc__
139 def finalize(wrapper, new_doc):
140 try:
141 obj.__doc__ = new_doc
142 except AttributeError: # Can't set on some extension objects.
143 pass
144 obj.__init__ = functools.wraps(obj.__init__)(wrapper)
145 return obj
147 elif isinstance(obj, (property, classproperty)):
148 if obj_type is None:
149 obj_type = "attribute"
150 func = None
151 name = name or obj.fget.__name__
152 old_doc = obj.__doc__
154 class _deprecated_property(type(obj)):
155 def __get__(self, instance, owner=None):
156 if instance is not None or owner is not None \
157 and isinstance(self, classproperty):
158 emit_warning()
159 return super().__get__(instance, owner)
161 def __set__(self, instance, value):
162 if instance is not None:
163 emit_warning()
164 return super().__set__(instance, value)
166 def __delete__(self, instance):
167 if instance is not None:
168 emit_warning()
169 return super().__delete__(instance)
171 def __set_name__(self, owner, set_name):
172 nonlocal name
173 if name == "<lambda>":
174 name = set_name
176 def finalize(_, new_doc):
177 return _deprecated_property(
178 fget=obj.fget, fset=obj.fset, fdel=obj.fdel, doc=new_doc)
180 else:
181 if obj_type is None:
182 obj_type = "function"
183 func = obj
184 name = name or obj.__name__
185 old_doc = func.__doc__
187 def finalize(wrapper, new_doc):
188 wrapper = functools.wraps(func)(wrapper)
189 wrapper.__doc__ = new_doc
190 return wrapper
192 def emit_warning():
193 warn_deprecated(
194 since, message=message, name=name, alternative=alternative,
195 pending=pending, obj_type=obj_type, addendum=addendum,
196 removal=removal)
198 def wrapper(*args, **kwargs):
199 emit_warning()
200 return func(*args, **kwargs)
202 old_doc = inspect.cleandoc(old_doc or '').strip('\n')
204 notes_header = '\nNotes\n-----'
205 second_arg = ' '.join([t.strip() for t in
206 (message, f"Use {alternative} instead."
207 if alternative else "", addendum) if t])
208 new_doc = (f"[*Deprecated*] {old_doc}\n"
209 f"{notes_header if notes_header not in old_doc else ''}\n"
210 f".. deprecated:: {since}\n"
211 f" {second_arg}")
213 if not old_doc:
214 # This is to prevent a spurious 'unexpected unindent' warning from
215 # docutils when the original docstring was blank.
216 new_doc += r'\ '
218 return finalize(wrapper, new_doc)
220 return deprecate
223class deprecate_privatize_attribute:
224 """
225 Helper to deprecate public access to an attribute (or method).
227 This helper should only be used at class scope, as follows::
229 class Foo:
230 attr = _deprecate_privatize_attribute(*args, **kwargs)
232 where *all* parameters are forwarded to `deprecated`. This form makes
233 ``attr`` a property which forwards read and write access to ``self._attr``
234 (same name but with a leading underscore), with a deprecation warning.
235 Note that the attribute name is derived from *the name this helper is
236 assigned to*. This helper also works for deprecating methods.
237 """
239 def __init__(self, *args, **kwargs):
240 self.deprecator = deprecated(*args, **kwargs)
242 def __set_name__(self, owner, name):
243 setattr(owner, name, self.deprecator(
244 property(lambda self: getattr(self, f"_{name}"),
245 lambda self, value: setattr(self, f"_{name}", value)),
246 name=name))
249# Used by _copy_docstring_and_deprecators to redecorate pyplot wrappers and
250# boilerplate.py to retrieve original signatures. It may seem natural to store
251# this information as an attribute on the wrapper, but if the wrapper gets
252# itself functools.wraps()ed, then such attributes are silently propagated to
253# the outer wrapper, which is not desired.
254DECORATORS = {}
257def rename_parameter(since, old, new, func=None):
258 """
259 Decorator indicating that parameter *old* of *func* is renamed to *new*.
261 The actual implementation of *func* should use *new*, not *old*. If *old*
262 is passed to *func*, a DeprecationWarning is emitted, and its value is
263 used, even if *new* is also passed by keyword (this is to simplify pyplot
264 wrapper functions, which always pass *new* explicitly to the Axes method).
265 If *new* is also passed but positionally, a TypeError will be raised by the
266 underlying function during argument binding.
268 Examples
269 --------
270 ::
272 @_api.rename_parameter("3.1", "bad_name", "good_name")
273 def func(good_name): ...
274 """
276 decorator = functools.partial(rename_parameter, since, old, new)
278 if func is None:
279 return decorator
281 signature = inspect.signature(func)
282 assert old not in signature.parameters, (
283 f"Matplotlib internal error: {old!r} cannot be a parameter for "
284 f"{func.__name__}()")
285 assert new in signature.parameters, (
286 f"Matplotlib internal error: {new!r} must be a parameter for "
287 f"{func.__name__}()")
289 @functools.wraps(func)
290 def wrapper(*args, **kwargs):
291 if old in kwargs:
292 warn_deprecated(
293 since, message=f"The {old!r} parameter of {func.__name__}() "
294 f"has been renamed {new!r} since Matplotlib {since}; support "
295 f"for the old name will be dropped %(removal)s.")
296 kwargs[new] = kwargs.pop(old)
297 return func(*args, **kwargs)
299 # wrapper() must keep the same documented signature as func(): if we
300 # instead made both *old* and *new* appear in wrapper()'s signature, they
301 # would both show up in the pyplot function for an Axes method as well and
302 # pyplot would explicitly pass both arguments to the Axes method.
304 DECORATORS[wrapper] = decorator
305 return wrapper
308class _deprecated_parameter_class:
309 def __repr__(self):
310 return "<deprecated parameter>"
313_deprecated_parameter = _deprecated_parameter_class()
316def delete_parameter(since, name, func=None, **kwargs):
317 """
318 Decorator indicating that parameter *name* of *func* is being deprecated.
320 The actual implementation of *func* should keep the *name* parameter in its
321 signature, or accept a ``**kwargs`` argument (through which *name* would be
322 passed).
324 Parameters that come after the deprecated parameter effectively become
325 keyword-only (as they cannot be passed positionally without triggering the
326 DeprecationWarning on the deprecated parameter), and should be marked as
327 such after the deprecation period has passed and the deprecated parameter
328 is removed.
330 Parameters other than *since*, *name*, and *func* are keyword-only and
331 forwarded to `.warn_deprecated`.
333 Examples
334 --------
335 ::
337 @_api.delete_parameter("3.1", "unused")
338 def func(used_arg, other_arg, unused, more_args): ...
339 """
341 decorator = functools.partial(delete_parameter, since, name, **kwargs)
343 if func is None:
344 return decorator
346 signature = inspect.signature(func)
347 # Name of `**kwargs` parameter of the decorated function, typically
348 # "kwargs" if such a parameter exists, or None if the decorated function
349 # doesn't accept `**kwargs`.
350 kwargs_name = next((param.name for param in signature.parameters.values()
351 if param.kind == inspect.Parameter.VAR_KEYWORD), None)
352 if name in signature.parameters:
353 kind = signature.parameters[name].kind
354 is_varargs = kind is inspect.Parameter.VAR_POSITIONAL
355 is_varkwargs = kind is inspect.Parameter.VAR_KEYWORD
356 if not is_varargs and not is_varkwargs:
357 name_idx = (
358 # Deprecated parameter can't be passed positionally.
359 math.inf if kind is inspect.Parameter.KEYWORD_ONLY
360 # If call site has no more than this number of parameters, the
361 # deprecated parameter can't have been passed positionally.
362 else [*signature.parameters].index(name))
363 func.__signature__ = signature = signature.replace(parameters=[
364 param.replace(default=_deprecated_parameter)
365 if param.name == name else param
366 for param in signature.parameters.values()])
367 else:
368 name_idx = -1 # Deprecated parameter can always have been passed.
369 else:
370 is_varargs = is_varkwargs = False
371 # Deprecated parameter can't be passed positionally.
372 name_idx = math.inf
373 assert kwargs_name, (
374 f"Matplotlib internal error: {name!r} must be a parameter for "
375 f"{func.__name__}()")
377 addendum = kwargs.pop('addendum', None)
379 @functools.wraps(func)
380 def wrapper(*inner_args, **inner_kwargs):
381 if len(inner_args) <= name_idx and name not in inner_kwargs:
382 # Early return in the simple, non-deprecated case (much faster than
383 # calling bind()).
384 return func(*inner_args, **inner_kwargs)
385 arguments = signature.bind(*inner_args, **inner_kwargs).arguments
386 if is_varargs and arguments.get(name):
387 warn_deprecated(
388 since, message=f"Additional positional arguments to "
389 f"{func.__name__}() are deprecated since %(since)s and "
390 f"support for them will be removed %(removal)s.")
391 elif is_varkwargs and arguments.get(name):
392 warn_deprecated(
393 since, message=f"Additional keyword arguments to "
394 f"{func.__name__}() are deprecated since %(since)s and "
395 f"support for them will be removed %(removal)s.")
396 # We cannot just check `name not in arguments` because the pyplot
397 # wrappers always pass all arguments explicitly.
398 elif any(name in d and d[name] != _deprecated_parameter
399 for d in [arguments, arguments.get(kwargs_name, {})]):
400 deprecation_addendum = (
401 f"If any parameter follows {name!r}, they should be passed as "
402 f"keyword, not positionally.")
403 warn_deprecated(
404 since,
405 name=repr(name),
406 obj_type=f"parameter of {func.__name__}()",
407 addendum=(addendum + " " + deprecation_addendum) if addendum
408 else deprecation_addendum,
409 **kwargs)
410 return func(*inner_args, **inner_kwargs)
412 DECORATORS[wrapper] = decorator
413 return wrapper
416def make_keyword_only(since, name, func=None):
417 """
418 Decorator indicating that passing parameter *name* (or any of the following
419 ones) positionally to *func* is being deprecated.
421 When used on a method that has a pyplot wrapper, this should be the
422 outermost decorator, so that :file:`boilerplate.py` can access the original
423 signature.
424 """
426 decorator = functools.partial(make_keyword_only, since, name)
428 if func is None:
429 return decorator
431 signature = inspect.signature(func)
432 POK = inspect.Parameter.POSITIONAL_OR_KEYWORD
433 KWO = inspect.Parameter.KEYWORD_ONLY
434 assert (name in signature.parameters
435 and signature.parameters[name].kind == POK), (
436 f"Matplotlib internal error: {name!r} must be a positional-or-keyword "
437 f"parameter for {func.__name__}()")
438 names = [*signature.parameters]
439 name_idx = names.index(name)
440 kwonly = [name for name in names[name_idx:]
441 if signature.parameters[name].kind == POK]
443 @functools.wraps(func)
444 def wrapper(*args, **kwargs):
445 # Don't use signature.bind here, as it would fail when stacked with
446 # rename_parameter and an "old" argument name is passed in
447 # (signature.bind would fail, but the actual call would succeed).
448 if len(args) > name_idx:
449 warn_deprecated(
450 since, message="Passing the %(name)s %(obj_type)s "
451 "positionally is deprecated since Matplotlib %(since)s; the "
452 "parameter will become keyword-only %(removal)s.",
453 name=name, obj_type=f"parameter of {func.__name__}()")
454 return func(*args, **kwargs)
456 # Don't modify *func*'s signature, as boilerplate.py needs it.
457 wrapper.__signature__ = signature.replace(parameters=[
458 param.replace(kind=KWO) if param.name in kwonly else param
459 for param in signature.parameters.values()])
460 DECORATORS[wrapper] = decorator
461 return wrapper
464def deprecate_method_override(method, obj, *, allow_empty=False, **kwargs):
465 """
466 Return ``obj.method`` with a deprecation if it was overridden, else None.
468 Parameters
469 ----------
470 method
471 An unbound method, i.e. an expression of the form
472 ``Class.method_name``. Remember that within the body of a method, one
473 can always use ``__class__`` to refer to the class that is currently
474 being defined.
475 obj
476 Either an object of the class where *method* is defined, or a subclass
477 of that class.
478 allow_empty : bool, default: False
479 Whether to allow overrides by "empty" methods without emitting a
480 warning.
481 **kwargs
482 Additional parameters passed to `warn_deprecated` to generate the
483 deprecation warning; must at least include the "since" key.
484 """
486 def empty(): pass
487 def empty_with_docstring(): """doc"""
489 name = method.__name__
490 bound_child = getattr(obj, name)
491 bound_base = (
492 method # If obj is a class, then we need to use unbound methods.
493 if isinstance(bound_child, type(empty)) and isinstance(obj, type)
494 else method.__get__(obj))
495 if (bound_child != bound_base
496 and (not allow_empty
497 or (getattr(getattr(bound_child, "__code__", None),
498 "co_code", None)
499 not in [empty.__code__.co_code,
500 empty_with_docstring.__code__.co_code]))):
501 warn_deprecated(**{"name": name, "obj_type": "method", **kwargs})
502 return bound_child
503 return None
506@contextlib.contextmanager
507def suppress_matplotlib_deprecation_warning():
508 with warnings.catch_warnings():
509 warnings.simplefilter("ignore", MatplotlibDeprecationWarning)
510 yield