diff --git a/pvlib/_deprecation.py b/pvlib/_deprecation.py index 56e704c30..aedb4d509 100644 --- a/pvlib/_deprecation.py +++ b/pvlib/_deprecation.py @@ -316,3 +316,76 @@ def wrapper(*args, **kwargs): return finalize(wrapper, new_doc) return deprecate + + +def renamed_kwarg_warning(since, old_param_name, new_param_name, removal=""): + """ + Decorator to mark a possible keyword argument as deprecated and replaced + with other name. + + Raises a warning when the deprecated argument is used, and replaces the + call with the new argument name. Does not modify the function signature. + + .. warning:: + Ensure ``removal`` date with a ``fail_on_pvlib_version`` decorator in + the test suite. + + .. note:: + Not compatible with positional-only arguments. + + .. note:: + Documentation for the function may updated to reflect the new parameter + name; it is suggested to add a |.. versionchanged::| directive. + + Parameters + ---------- + since : str + The release at which this API became deprecated. + old_param_name : str + The name of the deprecated parameter. + new_param_name : str + The name of the new parameter. + removal : str, optional + The expected removal version, in order to compose the Warning message. + + Examples + -------- + >>> @renamed_kwarg_warning("1.4.0", "old_name", "new_name", "1.6.0") + >>> def some_function(new_name=None): + >>> pass + >>> some_function(old_name=1) + Parameter 'old_name' has been renamed since 1.4.0. and + will be removed in 1.6.0. Please use 'new_name' instead. + + >>> @renamed_kwarg_warning("1.4.0", "old_name", "new_name") + >>> def some_function(new_name=None): + >>> pass + >>> some_function(old_name=1) + Parameter 'old_name' has been renamed since 1.4.0. and + will be removed soon. Please use 'new_name' instead. + """ + + def deprecate(func, old=old_param_name, new=new_param_name, since=since): + def wrapper(*args, **kwargs): + if old in kwargs: + if new in kwargs: + raise ValueError( + f"{func.__name__} received both '{old}' and '{new}', " + "which are mutually exclusive since they refer to the " + f"same parameter. Please remove deprecated '{old}'." + ) + warnings.warn( + f"Parameter '{old}' has been renamed since {since}. " + f"and will be removed " + + (f"in {removal}" if removal else "soon") + + f". Please use '{new}' instead.", + _projectWarning, + stacklevel=2, + ) + kwargs[new] = kwargs.pop(old) + return func(*args, **kwargs) + + wrapper = functools.wraps(func)(wrapper) + return wrapper + + return deprecate diff --git a/pvlib/tests/test__deprecation.py b/pvlib/tests/test__deprecation.py new file mode 100644 index 000000000..177082ca9 --- /dev/null +++ b/pvlib/tests/test__deprecation.py @@ -0,0 +1,51 @@ +""" +Test the _deprecation module. +""" + +import pytest + +from pvlib import _deprecation + +import warnings + + +@pytest.fixture +def renamed_kwarg_func(): + """Returns a function decorated by renamed_kwarg_warning. + This function is called 'func' and has a docstring equal to 'docstring'. + """ + + @_deprecation.renamed_kwarg_warning( + "0.1.0", "old_kwarg", "new_kwarg", "0.2.0" + ) + def func(new_kwarg): + """docstring""" + return new_kwarg + + return func + + +def test_renamed_kwarg_warning(renamed_kwarg_func): + # assert decorated function name and docstring are unchanged + assert renamed_kwarg_func.__name__ == "func" + assert renamed_kwarg_func.__doc__ == "docstring" + + # assert no warning is raised when using the new kwarg + with warnings.catch_warnings(): + warnings.simplefilter("error") + assert renamed_kwarg_func(new_kwarg=1) == 1 # as keyword argument + assert renamed_kwarg_func(1) == 1 # as positional argument + + # assert a warning is raised when using the old kwarg + with pytest.warns(Warning, match="Parameter 'old_kwarg' has been renamed"): + assert renamed_kwarg_func(old_kwarg=1) == 1 + + # assert an error is raised when using both the old and new kwarg + with pytest.raises(ValueError, match="they refer to the same parameter."): + renamed_kwarg_func(old_kwarg=1, new_kwarg=2) + + # assert when not providing any of them + with pytest.raises( + TypeError, match="missing 1 required positional argument" + ): + renamed_kwarg_func()