Skip to content

Commit ecf056b

Browse files
Backport PR #48693 on branch 1.5.x (ENH: Make deprecate_nonkeyword_arguments alter function signature) (#48795)
Backport PR #48693: ENH: Make deprecate_nonkeyword_arguments alter function signature Co-authored-by: Shantanu <[email protected]>
1 parent 9e95b20 commit ecf056b

File tree

3 files changed

+50
-6
lines changed

3 files changed

+50
-6
lines changed

doc/source/whatsnew/v1.5.1.rst

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ Bug fixes
9696

9797
Other
9898
~~~~~
99+
- Avoid showing deprecated signatures when introspecting functions with warnings about arguments becoming keyword-only (:issue:`48692`)
99100
-
100101
-
101102

pandas/tests/util/test_deprecate_nonkeyword_arguments.py

+26
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Tests for the `deprecate_nonkeyword_arguments` decorator
33
"""
44

5+
import inspect
56
import warnings
67

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

1819

20+
def test_f_signature():
21+
assert str(inspect.signature(f)) == "(a, b=0, *, c=0, d=0)"
22+
23+
1924
def test_one_argument():
2025
with tm.assert_produces_warning(None):
2126
assert f(19) == 19
@@ -65,6 +70,10 @@ def g(a, b=0, c=0, d=0):
6570
return a + b + c + d
6671

6772

73+
def test_g_signature():
74+
assert str(inspect.signature(g)) == "(a, *, b=0, c=0, d=0)"
75+
76+
6877
def test_one_and_three_arguments_default_allowed_args():
6978
with tm.assert_produces_warning(None):
7079
assert g(1, b=3, c=3, d=5) == 12
@@ -93,6 +102,10 @@ def h(a=0, b=0, c=0, d=0):
93102
return a + b + c + d
94103

95104

105+
def test_h_signature():
106+
assert str(inspect.signature(h)) == "(*, a=0, b=0, c=0, d=0)"
107+
108+
96109
def test_all_keyword_arguments():
97110
with tm.assert_produces_warning(None):
98111
assert h(a=1, b=2) == 3
@@ -116,12 +129,25 @@ def test_one_positional_argument_with_warning_message_analysis():
116129
)
117130

118131

132+
@deprecate_nonkeyword_arguments(version="1.1")
133+
def i(a=0, /, b=0, *, c=0, d=0):
134+
return a + b + c + d
135+
136+
137+
def test_i_signature():
138+
assert str(inspect.signature(i)) == "(*, a=0, b=0, c=0, d=0)"
139+
140+
119141
class Foo:
120142
@deprecate_nonkeyword_arguments(version=None, allowed_args=["self", "bar"])
121143
def baz(self, bar=None, foobar=None):
122144
...
123145

124146

147+
def test_foo_signature():
148+
assert str(inspect.signature(Foo.baz)) == "(self, bar=None, *, foobar=None)"
149+
150+
125151
def test_class():
126152
msg = (
127153
r"In a future version of pandas all arguments of Foo\.baz "

pandas/util/_decorators.py

+23-6
Original file line numberDiff line numberDiff line change
@@ -290,14 +290,29 @@ def deprecate_nonkeyword_arguments(
290290
"""
291291

292292
def decorate(func):
293+
old_sig = inspect.signature(func)
294+
293295
if allowed_args is not None:
294296
allow_args = allowed_args
295297
else:
296-
spec = inspect.getfullargspec(func)
298+
allow_args = [
299+
p.name
300+
for p in old_sig.parameters.values()
301+
if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD)
302+
and p.default is p.empty
303+
]
297304

298-
# We must have some defaults if we are deprecating default-less
299-
assert spec.defaults is not None # for mypy
300-
allow_args = spec.args[: -len(spec.defaults)]
305+
new_params = [
306+
p.replace(kind=p.KEYWORD_ONLY)
307+
if (
308+
p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD)
309+
and p.name not in allow_args
310+
)
311+
else p
312+
for p in old_sig.parameters.values()
313+
]
314+
new_params.sort(key=lambda p: p.kind)
315+
new_sig = old_sig.replace(parameters=new_params)
301316

302317
num_allow_args = len(allow_args)
303318
msg = (
@@ -307,15 +322,17 @@ def decorate(func):
307322

308323
@wraps(func)
309324
def wrapper(*args, **kwargs):
310-
arguments = _format_argument_list(allow_args)
311325
if len(args) > num_allow_args:
312326
warnings.warn(
313-
msg.format(arguments=arguments),
327+
msg.format(arguments=_format_argument_list(allow_args)),
314328
FutureWarning,
315329
stacklevel=find_stack_level(inspect.currentframe()),
316330
)
317331
return func(*args, **kwargs)
318332

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

321338
return decorate

0 commit comments

Comments
 (0)