Skip to content

Commit

Permalink
[MAINT]: Decorator to warn about changed parameter names & allow for …
Browse files Browse the repository at this point in the history
…deprecation period (#2237)

* Add renamed_kwarg_warning decorator

* Example at solarposition.hour_angle()

* another day, another test

* Flake8 strikes again

* Fix no warning test

Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* Add test to remember removing the deprecation decorator

* test-driven-development for the win: unchanged remain properties

Co-Authored-By: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>

* Update docs

* Update fail_on_pvlib_version test comment

* Rename&deprecate solarposition.sun_rise_set_transit_spa

* flake8

* flake8 no more

* date -> time in solarposition.sun_rise_set_transit_spa

* Change to ..versionchanged, same format.

* Apply suggestions from Adam

Co-authored-by: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com>

* Apply Adam review x2

Co-Authored-By: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com>

* Update solarposition.py

* Update solarposition.py

* Revert solarposition and test_solarposition changes

* Had to use main instead of master xd

* 😭

---------

Co-authored-by: Kevin Anderson <57452607+kandersolar@users.noreply.github.com>
Co-authored-by: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com>
  • Loading branch information
3 people authored Oct 15, 2024
1 parent d9acdba commit b6ac5a1
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 0 deletions.
73 changes: 73 additions & 0 deletions pvlib/_deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
51 changes: 51 additions & 0 deletions pvlib/tests/test__deprecation.py
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit b6ac5a1

Please sign in to comment.