From 3396beca40899fc36697510d72efc13c253ce4a4 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 24 Sep 2024 06:17:02 -0400 Subject: [PATCH] Remove 'in' from removal substitution for deprecation messages Ever since #27702, the removal will always be a version, and not "two minor/meso releases later". This tends to get messed up by people writing custom `message` arguments, and since there's no reason to add the "to" there instead of in the message any more, move it to the message. Also, fix the autogenerated message to follow what the docstring says (Falsy `removal` leaves out the removal date.) --- lib/matplotlib/_api/deprecation.py | 37 +++++++++++++---------------- lib/matplotlib/_api/deprecation.pyi | 4 ++-- lib/matplotlib/backend_bases.py | 2 +- lib/matplotlib/hatch.py | 2 +- lib/matplotlib/legend.py | 2 +- lib/matplotlib/projections/polar.py | 2 +- lib/matplotlib/tests/test_api.py | 35 +++++++++++++++++++++++++++ 7 files changed, 57 insertions(+), 27 deletions(-) diff --git a/lib/matplotlib/_api/deprecation.py b/lib/matplotlib/_api/deprecation.py index e9722f5d26c4..65a754bbb43d 100644 --- a/lib/matplotlib/_api/deprecation.py +++ b/lib/matplotlib/_api/deprecation.py @@ -26,25 +26,20 @@ def _generate_deprecation_warning( addendum='', *, removal=''): if pending: if removal: - raise ValueError( - "A pending deprecation cannot have a scheduled removal") - else: - if not removal: - macro, meso, *_ = since.split('.') - removal = f'{macro}.{int(meso) + 2}' - removal = f"in {removal}" + raise ValueError("A pending deprecation cannot have a scheduled removal") + elif removal == '': + macro, meso, *_ = since.split('.') + removal = f'{macro}.{int(meso) + 2}' if not message: message = ( - ("The %(name)s %(obj_type)s" if obj_type else "%(name)s") - + (" will be deprecated in a future version" - if pending else - " was deprecated in Matplotlib %(since)s and will be removed %(removal)s" - ) - + "." - + (" Use %(alternative)s instead." if alternative else "") - + (" %(addendum)s" if addendum else "")) - warning_cls = (PendingDeprecationWarning if pending - else MatplotlibDeprecationWarning) + ("The %(name)s %(obj_type)s" if obj_type else "%(name)s") + + (" will be deprecated in a future version" if pending else + (" was deprecated in Matplotlib %(since)s" + + (" and will be removed in %(removal)s" if removal else ""))) + + "." + + (" Use %(alternative)s instead." if alternative else "") + + (" %(addendum)s" if addendum else "")) + warning_cls = PendingDeprecationWarning if pending else MatplotlibDeprecationWarning return warning_cls(message % dict( func=name, name=name, obj_type=obj_type, since=since, removal=removal, alternative=alternative, addendum=addendum)) @@ -295,7 +290,7 @@ def wrapper(*args, **kwargs): warn_deprecated( since, message=f"The {old!r} parameter of {func.__name__}() " f"has been renamed {new!r} since Matplotlib {since}; support " - f"for the old name will be dropped %(removal)s.") + f"for the old name will be dropped in %(removal)s.") kwargs[new] = kwargs.pop(old) return func(*args, **kwargs) @@ -390,12 +385,12 @@ def wrapper(*inner_args, **inner_kwargs): warn_deprecated( since, message=f"Additional positional arguments to " f"{func.__name__}() are deprecated since %(since)s and " - f"support for them will be removed %(removal)s.") + f"support for them will be removed in %(removal)s.") elif is_varkwargs and arguments.get(name): warn_deprecated( since, message=f"Additional keyword arguments to " f"{func.__name__}() are deprecated since %(since)s and " - f"support for them will be removed %(removal)s.") + f"support for them will be removed in %(removal)s.") # We cannot just check `name not in arguments` because the pyplot # wrappers always pass all arguments explicitly. elif any(name in d and d[name] != _deprecated_parameter @@ -453,7 +448,7 @@ def wrapper(*args, **kwargs): warn_deprecated( since, message="Passing the %(name)s %(obj_type)s " "positionally is deprecated since Matplotlib %(since)s; the " - "parameter will become keyword-only %(removal)s.", + "parameter will become keyword-only in %(removal)s.", name=name, obj_type=f"parameter of {func.__name__}()") return func(*args, **kwargs) diff --git a/lib/matplotlib/_api/deprecation.pyi b/lib/matplotlib/_api/deprecation.pyi index d0d04d987410..e050290662d9 100644 --- a/lib/matplotlib/_api/deprecation.pyi +++ b/lib/matplotlib/_api/deprecation.pyi @@ -1,6 +1,6 @@ from collections.abc import Callable import contextlib -from typing import Any, ParamSpec, TypedDict, TypeVar, overload +from typing import Any, Literal, ParamSpec, TypedDict, TypeVar, overload from typing_extensions import ( Unpack, # < Py 3.11 ) @@ -17,7 +17,7 @@ class DeprecationKwargs(TypedDict, total=False): pending: bool obj_type: str addendum: str - removal: str + removal: str | Literal[False] class NamedDeprecationKwargs(DeprecationKwargs, total=False): name: str diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index a8170ce4f6b0..817eb51705fe 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -3333,7 +3333,7 @@ def _get_image_filename(self, tool): _api.warn_deprecated( "3.9", message=f"Loading icon {tool.image!r} from the current " "directory or from Matplotlib's image directory. This behavior " - "is deprecated since %(since)s and will be removed %(removal)s; " + "is deprecated since %(since)s and will be removed in %(removal)s; " "Tool.image should be set to a path relative to the Tool's source " "file, or to an absolute path.") return os.path.abspath(fname) diff --git a/lib/matplotlib/hatch.py b/lib/matplotlib/hatch.py index 7a4b283c1dbe..0cbd042e1628 100644 --- a/lib/matplotlib/hatch.py +++ b/lib/matplotlib/hatch.py @@ -192,7 +192,7 @@ def _validate_hatch_pattern(hatch): message=f'hatch must consist of a string of "{valid}" or ' 'None, but found the following invalid values ' f'"{invalids}". Passing invalid values is deprecated ' - 'since %(since)s and will become an error %(removal)s.' + 'since %(since)s and will become an error in %(removal)s.' ) diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 0d487a48bde7..270757fc298e 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -1337,7 +1337,7 @@ def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs): _api.warn_deprecated("3.9", message=( "You have mixed positional and keyword arguments, some input may " "be discarded. This is deprecated since %(since)s and will " - "become an error %(removal)s.")) + "become an error in %(removal)s.")) if (hasattr(handles, "__len__") and hasattr(labels, "__len__") and diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 325da95105ab..7fe6045039b1 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -21,7 +21,7 @@ def _apply_theta_transforms_warn(): message=( "Passing `apply_theta_transforms=True` (the default) " "is deprecated since Matplotlib %(since)s. " - "Support for this will be removed in Matplotlib %(removal)s. " + "Support for this will be removed in Matplotlib in %(removal)s. " "To prevent this warning, set `apply_theta_transforms=False`, " "and make sure to shift theta values before being passed to " "this transform." diff --git a/lib/matplotlib/tests/test_api.py b/lib/matplotlib/tests/test_api.py index 23d3ec48f31f..f04604c14cce 100644 --- a/lib/matplotlib/tests/test_api.py +++ b/lib/matplotlib/tests/test_api.py @@ -49,6 +49,41 @@ def f(cls: Self) -> None: a.f +def test_warn_deprecated(): + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match=r'foo was deprecated in Matplotlib 3\.10 and will be ' + r'removed in 3\.12\.'): + _api.warn_deprecated('3.10', name='foo') + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match=r'The foo class was deprecated in Matplotlib 3\.10 and ' + r'will be removed in 3\.12\.'): + _api.warn_deprecated('3.10', name='foo', obj_type='class') + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match=r'foo was deprecated in Matplotlib 3\.10 and will be ' + r'removed in 3\.12\. Use bar instead\.'): + _api.warn_deprecated('3.10', name='foo', alternative='bar') + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match=r'foo was deprecated in Matplotlib 3\.10 and will be ' + r'removed in 3\.12\. More information\.'): + _api.warn_deprecated('3.10', name='foo', addendum='More information.') + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match=r'foo was deprecated in Matplotlib 3\.10 and will be ' + r'removed in 4\.0\.'): + _api.warn_deprecated('3.10', name='foo', removal='4.0') + with pytest.warns(mpl.MatplotlibDeprecationWarning, + match=r'foo was deprecated in Matplotlib 3\.10\.'): + _api.warn_deprecated('3.10', name='foo', removal=False) + with pytest.warns(PendingDeprecationWarning, + match=r'foo will be deprecated in a future version'): + _api.warn_deprecated('3.10', name='foo', pending=True) + with pytest.raises(ValueError, match=r'cannot have a scheduled removal'): + _api.warn_deprecated('3.10', name='foo', pending=True, removal='3.12') + with pytest.warns(mpl.MatplotlibDeprecationWarning, match=r'Complete replacement'): + _api.warn_deprecated('3.10', message='Complete replacement', name='foo', + alternative='bar', addendum='More information.', + obj_type='class', removal='4.0') + + def test_deprecate_privatize_attribute() -> None: class C: def __init__(self) -> None: self._attr = 1