diff --git a/CHANGELOG.md b/CHANGELOG.md index 0917e9a..d10ee6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Removed `infer_interval_breaks` function deprecated in v0.3.0 ([#177](https://github.com/mpytools/mplotutils/pull/177)). +- Finish deprecation of positional arguments started in v0.3.0 ([#178](https://github.com/mpytools/mplotutils/pull/178)). ### Enhancements diff --git a/mplotutils/_cartopy_utils.py b/mplotutils/_cartopy_utils.py index 5d029e4..2b9b2a8 100644 --- a/mplotutils/_cartopy_utils.py +++ b/mplotutils/_cartopy_utils.py @@ -7,7 +7,6 @@ from cartopy.mpl.gridliner import LATITUDE_FORMATTER, LONGITUDE_FORMATTER from mplotutils._colormaps import _get_label_attr -from mplotutils._deprecate import _deprecate_positional_args def sample_data_map(nlons, nlats): @@ -133,7 +132,6 @@ def cyclic_dataarray(obj, coord="lon"): return obj.assign_coords({coord: lon}) -@_deprecate_positional_args("0.3") def ylabel_map(s, *, labelpad=None, size=None, weight=None, y=0.5, ax=None, **kwargs): """add ylabel to cartopy plot @@ -197,7 +195,6 @@ def ylabel_map(s, *, labelpad=None, size=None, weight=None, y=0.5, ax=None, **kw return h -@_deprecate_positional_args("0.3") def xlabel_map(s, *, labelpad=None, size=None, weight=None, x=0.5, ax=None, **kwargs): """add xlabel to cartopy plot @@ -261,7 +258,6 @@ def xlabel_map(s, *, labelpad=None, size=None, weight=None, x=0.5, ax=None, **kw return h -@_deprecate_positional_args("0.3") def yticklabels( y_ticks, *, @@ -362,7 +358,6 @@ def yticklabels( ) -@_deprecate_positional_args("0.3") def xticklabels( x_ticks, *, diff --git a/mplotutils/_colorbar.py b/mplotutils/_colorbar.py index d5b6a92..ab0fcba 100644 --- a/mplotutils/_colorbar.py +++ b/mplotutils/_colorbar.py @@ -5,8 +5,6 @@ import matplotlib.transforms as mtransforms import numpy as np -from mplotutils._deprecate import _deprecate_positional_args - def _deprecate_ax1_ax2(ax, ax2, ax1): if ax is None: @@ -50,7 +48,6 @@ def _deprecate_ax1_ax2(ax, ax2, ax1): return ax -@_deprecate_positional_args("0.3") def colorbar( mappable, ax=None, diff --git a/mplotutils/_deprecate.py b/mplotutils/_deprecate.py index d85f082..336afd4 100644 --- a/mplotutils/_deprecate.py +++ b/mplotutils/_deprecate.py @@ -1,112 +1,4 @@ -# Adapted from scikit-learn https://github.com/scikit-learn/scikit-learn/pull/13311 -# For reference, here is a copy of their copyright notice: - -# BSD 3-Clause License - -# Copyright (c) 2007-2021 The scikit-learn developers. -# All rights reserved. - -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: - -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. - -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. - -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. - -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import inspect import warnings -from functools import wraps - -POSITIONAL_OR_KEYWORD = inspect.Parameter.POSITIONAL_OR_KEYWORD -KEYWORD_ONLY = inspect.Parameter.KEYWORD_ONLY -POSITIONAL_ONLY = inspect.Parameter.POSITIONAL_ONLY -EMPTY = inspect.Parameter.empty - - -def _deprecate_positional_args(version): - """Decorator for methods that issues warnings for positional arguments - Using the keyword-only argument syntax in pep 3102, arguments after the - ``*`` will issue a warning when passed as a positional argument. - - Parameters - ---------- - version : str - version of the library when the positional arguments were deprecated - - Examples - -------- - Deprecate passing `b` as positional argument: - >>> def func(a, b=1): - ... pass - >>> @_deprecate_positional_args("v0.1.0") - ... def func(a, *, b=2): - ... pass - >>> func(1, 2) - - Notes - ----- - This function is adapted from scikit-learn under the terms of its license. See - """ - - def _decorator(func): - signature = inspect.signature(func) - - pos_or_kw_args = [] - kwonly_args = [] - for name, param in signature.parameters.items(): - if param.kind in (POSITIONAL_OR_KEYWORD, POSITIONAL_ONLY): - pos_or_kw_args.append(name) - elif param.kind == KEYWORD_ONLY: - kwonly_args.append(name) - if param.default is EMPTY: - # IMHO `def f(a, *, b):` does not make sense -> disallow it - # if removing this constraint -> need to add these to kwargs as well - raise TypeError("Keyword-only param without default disallowed.") - - @wraps(func) - def inner(*args, **kwargs): - name = func.__name__ - n_extra_args = len(args) - len(pos_or_kw_args) - if n_extra_args > 0: - extra_args = ", ".join(kwonly_args[:n_extra_args]) - - warnings.warn( - f"Passing '{extra_args}' as positional argument(s) to {name} " - f"was deprecated in version {version} and will raise an error two " - "releases later. Please pass them as keyword arguments." - "", - FutureWarning, - stacklevel=2, - ) - - zip_args = zip(kwonly_args[:n_extra_args], args[-n_extra_args:]) - kwargs.update({name: arg for name, arg in zip_args}) - - return func(*args[:-n_extra_args], **kwargs) - - return func(*args, **kwargs) - - return inner - - return _decorator def _module_renamed_warning_init(attr): diff --git a/mplotutils/_map_layout.py b/mplotutils/_map_layout.py index 71e17f0..a0c02bf 100644 --- a/mplotutils/_map_layout.py +++ b/mplotutils/_map_layout.py @@ -5,11 +5,9 @@ import numpy as np from mpl_toolkits.axes_grid1 import AxesGrid -from mplotutils._deprecate import _deprecate_positional_args from mplotutils._mpl import _get_renderer -@_deprecate_positional_args("0.3") def set_map_layout(obj=None, width=17.0, *, nrow=None, ncol=None, axes=None): """set figure height, given width, taking axes' aspect ratio into account diff --git a/mplotutils/tests/test_colorbar.py b/mplotutils/tests/test_colorbar.py index 4bb8ad7..fad7174 100644 --- a/mplotutils/tests/test_colorbar.py +++ b/mplotutils/tests/test_colorbar.py @@ -79,18 +79,6 @@ def test_parse_size_aspect_pad(): # ============================================================================= -@pytest.mark.parametrize("orientation", ("vertical", "horizontal")) -@pytest.mark.filterwarnings("ignore:Passing axes individually") -def test_colorbar_deprecate_positional(orientation): - with subplots_context(1, 2) as (f, axs): - h = axs[0].pcolormesh([[0, 1]]) - - with pytest.warns( - FutureWarning, match="Passing 'orientation' as positional argument" - ): - mpu.colorbar(h, axs[0], axs[0], orientation) - - def test_colorbar_deprecate_ax1_ax2_errors(): h = None diff --git a/mplotutils/tests/test_deprecate.py b/mplotutils/tests/test_deprecate.py deleted file mode 100644 index 6c715d1..0000000 --- a/mplotutils/tests/test_deprecate.py +++ /dev/null @@ -1,140 +0,0 @@ -import pytest - -from mplotutils._deprecate import _deprecate_positional_args - - -def test_deprecate_positional_args_warns_for_function(): - @_deprecate_positional_args("v0.1") - def f1(a, b, *, c="c", d="d"): - return a, b, c, d - - result = f1(1, 2) - assert result == (1, 2, "c", "d") - - result = f1(1, 2, c=3, d=4) - assert result == (1, 2, 3, 4) - - with pytest.warns(FutureWarning, match=r".*v0.1"): - result = f1(1, 2, 3) - assert result == (1, 2, 3, "d") - - with pytest.warns(FutureWarning, match=r"Passing 'c' as positional"): - result = f1(1, 2, 3) - assert result == (1, 2, 3, "d") - - with pytest.warns(FutureWarning, match=r"Passing 'c, d' as positional"): - result = f1(1, 2, 3, 4) - assert result == (1, 2, 3, 4) - - @_deprecate_positional_args("v0.1") - def f2(a="a", *, b="b", c="c", d="d"): - return a, b, c, d - - with pytest.warns(FutureWarning, match=r"Passing 'b' as positional"): - result = f2(1, 2) - assert result == (1, 2, "c", "d") - - @_deprecate_positional_args("v0.1") - def f3(a, *, b="b", **kwargs): - return a, b, kwargs - - with pytest.warns(FutureWarning, match=r"Passing 'b' as positional"): - result = f3(1, 2) - assert result == (1, 2, {}) - - with pytest.warns(FutureWarning, match=r"Passing 'b' as positional"): - result = f3(1, 2, f="f") - assert result == (1, 2, {"f": "f"}) - - # @_deprecate_positional_args("v0.1") - # def f4(a, /, *, b="b", **kwargs): - # return a, b, kwargs - - # result = f4(1) - # assert result == (1, "b", {}) - - # result = f4(1, b=2, f="f") - # assert result == (1, 2, {"f": "f"}) - - # with pytest.warns(FutureWarning, match=r"Passing 'b' as positional"): - # result = f4(1, 2, f="f") - # assert result == (1, 2, {"f": "f"}) - - with pytest.raises(TypeError, match=r"Keyword-only param without default"): - - @_deprecate_positional_args("v0.1") - def f5(a, *, b, c=3, **kwargs): - pass - - -def test_deprecate_positional_args_warns_for_class(): - class A1: - @_deprecate_positional_args("v0.1") - def method(self, a, b, *, c="c", d="d"): - return a, b, c, d - - result = A1().method(1, 2) - assert result == (1, 2, "c", "d") - - result = A1().method(1, 2, c=3, d=4) - assert result == (1, 2, 3, 4) - - with pytest.warns(FutureWarning, match=r".*v0.1"): - result = A1().method(1, 2, 3) - assert result == (1, 2, 3, "d") - - with pytest.warns(FutureWarning, match=r"Passing 'c' as positional"): - result = A1().method(1, 2, 3) - assert result == (1, 2, 3, "d") - - with pytest.warns(FutureWarning, match=r"Passing 'c, d' as positional"): - result = A1().method(1, 2, 3, 4) - assert result == (1, 2, 3, 4) - - class A2: - @_deprecate_positional_args("v0.1") - def method(self, a=1, b=1, *, c="c", d="d"): - return a, b, c, d - - with pytest.warns(FutureWarning, match=r"Passing 'c' as positional"): - result = A2().method(1, 2, 3) - assert result == (1, 2, 3, "d") - - with pytest.warns(FutureWarning, match=r"Passing 'c, d' as positional"): - result = A2().method(1, 2, 3, 4) - assert result == (1, 2, 3, 4) - - class A3: - @_deprecate_positional_args("v0.1") - def method(self, a, *, b="b", **kwargs): - return a, b, kwargs - - with pytest.warns(FutureWarning, match=r"Passing 'b' as positional"): - result = A3().method(1, 2) - assert result == (1, 2, {}) - - with pytest.warns(FutureWarning, match=r"Passing 'b' as positional"): - result = A3().method(1, 2, f="f") - assert result == (1, 2, {"f": "f"}) - - # class A4: - # @_deprecate_positional_args("v0.1") - # def method(self, a, /, *, b="b", **kwargs): - # return a, b, kwargs - - # result = A4().method(1) - # assert result == (1, "b", {}) - - # result = A4().method(1, b=2, f="f") - # assert result == (1, 2, {"f": "f"}) - - # with pytest.warns(FutureWarning, match=r"Passing 'b' as positional"): - # result = A4().method(1, 2, f="f") - # assert result == (1, 2, {"f": "f"}) - - with pytest.raises(TypeError, match=r"Keyword-only param without default"): - - class A5: - @_deprecate_positional_args("v0.1") - def __init__(self, a, *, b, c=3, **kwargs): - pass