Skip to content

Commit

Permalink
Backport PR #48693 on branch 1.5.x (ENH: Make deprecate_nonkeyword_ar…
Browse files Browse the repository at this point in the history
…guments alter function signature) (#48795)

Backport PR #48693: ENH: Make deprecate_nonkeyword_arguments alter function signature

Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com>
  • Loading branch information
meeseeksmachine and hauntsaninja committed Sep 27, 2022
1 parent 9e95b20 commit ecf056b
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 6 deletions.
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.5.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ Bug fixes

Other
~~~~~
- Avoid showing deprecated signatures when introspecting functions with warnings about arguments becoming keyword-only (:issue:`48692`)
-
-

Expand Down
26 changes: 26 additions & 0 deletions pandas/tests/util/test_deprecate_nonkeyword_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Tests for the `deprecate_nonkeyword_arguments` decorator
"""

import inspect
import warnings

from pandas.util._decorators import deprecate_nonkeyword_arguments
Expand All @@ -16,6 +17,10 @@ def f(a, b=0, c=0, d=0):
return a + b + c + d


def test_f_signature():
assert str(inspect.signature(f)) == "(a, b=0, *, c=0, d=0)"


def test_one_argument():
with tm.assert_produces_warning(None):
assert f(19) == 19
Expand Down Expand Up @@ -65,6 +70,10 @@ def g(a, b=0, c=0, d=0):
return a + b + c + d


def test_g_signature():
assert str(inspect.signature(g)) == "(a, *, b=0, c=0, d=0)"


def test_one_and_three_arguments_default_allowed_args():
with tm.assert_produces_warning(None):
assert g(1, b=3, c=3, d=5) == 12
Expand Down Expand Up @@ -93,6 +102,10 @@ def h(a=0, b=0, c=0, d=0):
return a + b + c + d


def test_h_signature():
assert str(inspect.signature(h)) == "(*, a=0, b=0, c=0, d=0)"


def test_all_keyword_arguments():
with tm.assert_produces_warning(None):
assert h(a=1, b=2) == 3
Expand All @@ -116,12 +129,25 @@ def test_one_positional_argument_with_warning_message_analysis():
)


@deprecate_nonkeyword_arguments(version="1.1")
def i(a=0, /, b=0, *, c=0, d=0):
return a + b + c + d


def test_i_signature():
assert str(inspect.signature(i)) == "(*, a=0, b=0, c=0, d=0)"


class Foo:
@deprecate_nonkeyword_arguments(version=None, allowed_args=["self", "bar"])
def baz(self, bar=None, foobar=None):
...


def test_foo_signature():
assert str(inspect.signature(Foo.baz)) == "(self, bar=None, *, foobar=None)"


def test_class():
msg = (
r"In a future version of pandas all arguments of Foo\.baz "
Expand Down
29 changes: 23 additions & 6 deletions pandas/util/_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,14 +290,29 @@ def deprecate_nonkeyword_arguments(
"""

def decorate(func):
old_sig = inspect.signature(func)

if allowed_args is not None:
allow_args = allowed_args
else:
spec = inspect.getfullargspec(func)
allow_args = [
p.name
for p in old_sig.parameters.values()
if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD)
and p.default is p.empty
]

# We must have some defaults if we are deprecating default-less
assert spec.defaults is not None # for mypy
allow_args = spec.args[: -len(spec.defaults)]
new_params = [
p.replace(kind=p.KEYWORD_ONLY)
if (
p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD)
and p.name not in allow_args
)
else p
for p in old_sig.parameters.values()
]
new_params.sort(key=lambda p: p.kind)
new_sig = old_sig.replace(parameters=new_params)

num_allow_args = len(allow_args)
msg = (
Expand All @@ -307,15 +322,17 @@ def decorate(func):

@wraps(func)
def wrapper(*args, **kwargs):
arguments = _format_argument_list(allow_args)
if len(args) > num_allow_args:
warnings.warn(
msg.format(arguments=arguments),
msg.format(arguments=_format_argument_list(allow_args)),
FutureWarning,
stacklevel=find_stack_level(inspect.currentframe()),
)
return func(*args, **kwargs)

# error: "Callable[[VarArg(Any), KwArg(Any)], Any]" has no
# attribute "__signature__"
wrapper.__signature__ = new_sig # type: ignore[attr-defined]
return wrapper

return decorate
Expand Down

0 comments on commit ecf056b

Please sign in to comment.