Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-119605: Respect follow_wrapped for __init__ and __new__ when getting class signature with inspect.signature #132055

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
aa8865e
Preserve class signature after wrapping with `@warnings.deprecated`
XuehaiPan Jun 3, 2024
d093b57
📜🤖 Added by blurb_it.
blurb-it[bot] Apr 3, 2025
9664211
Add test to class signature
XuehaiPan Apr 3, 2025
a1dd52e
Fix tests
XuehaiPan Apr 3, 2025
668adb3
Merge branch 'main' into deprecated-signature
XuehaiPan Apr 3, 2025
68eebad
Handle potential assignment failure
XuehaiPan Apr 3, 2025
57c61c3
Update tests
XuehaiPan Apr 4, 2025
6e28f0b
Use `__signature__`
XuehaiPan Apr 4, 2025
88666b0
Merge branch 'main' into deprecated-signature
XuehaiPan Apr 4, 2025
43b0977
Remove duplicate in tests
XuehaiPan Apr 4, 2025
4dd8b70
Unwrap decorated __new__ and __init__ in inspect
XuehaiPan Apr 4, 2025
6b7bd71
Unwrap decorated __new__ and __init__ in inspect
XuehaiPan Apr 4, 2025
bb2c39b
Merge branch 'main' into deprecated-signature
XuehaiPan Apr 4, 2025
ca8aa96
Fix tests
XuehaiPan Apr 4, 2025
636c687
Fix tests
XuehaiPan Apr 4, 2025
97a17b2
Merge branch 'main' into deprecated-signature
XuehaiPan Apr 4, 2025
930881f
Merge branch 'main' into deprecated-signature
XuehaiPan Apr 5, 2025
11f8218
Merge branch 'main' into deprecated-signature
XuehaiPan Apr 6, 2025
198f8ee
Revert unneeded changes
XuehaiPan Apr 6, 2025
2c50424
Respect `follow_wrapper_chains`
XuehaiPan Apr 6, 2025
c62184a
Update news entry
XuehaiPan Apr 6, 2025
759d403
Add test for inspect
XuehaiPan Apr 6, 2025
c27ee65
Update news entry
XuehaiPan Apr 6, 2025
c148a7d
Update news entry
XuehaiPan Apr 6, 2025
72c1d5a
Update news entry
XuehaiPan Apr 6, 2025
2b7445b
Update news entry
XuehaiPan Apr 6, 2025
e8a3a1e
Merge branch 'main' into deprecated-signature
XuehaiPan Apr 6, 2025
2517a21
Fix tests
XuehaiPan Apr 6, 2025
44f103f
Simplify branches
XuehaiPan Apr 6, 2025
6693ead
Handle all method descriptor types
XuehaiPan Apr 6, 2025
82ab31e
Merge branch 'main' into deprecated-signature
XuehaiPan Apr 7, 2025
9386d40
Merge branch 'main' into deprecated-signature
XuehaiPan Apr 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions Lib/test/test_warnings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1832,10 +1832,68 @@ async def coro(self):
self.assertFalse(inspect.iscoroutinefunction(Cls.sync))
self.assertTrue(inspect.iscoroutinefunction(Cls.coro))

def test_inspect_class_signature(self):
class Cls1: # no __init__ or __new__
pass

class Cls2: # __new__ only
def __new__(cls, x, y):
return super().__new__(cls)

class Cls3: # __init__ only
def __init__(self, x, y):
pass

class Cls4: # __new__ and __init__
def __new__(cls, x, y):
return super().__new__(cls)

def __init__(self, x, y):
pass

class Cls5(Cls1): # inherits no __init__ or __new__
pass

class Cls6(Cls2): # inherits __new__ only
pass

class Cls7(Cls3): # inherits __init__ only
pass

class Cls8(Cls4): # inherits __new__ and __init__
pass

for cls in (Cls1, Cls2, Cls3, Cls4, Cls5, Cls6, Cls7, Cls8):
with self.subTest(f'class {cls.__name__} signature'):
try:
original_signature = inspect.signature(cls)
except ValueError:
original_signature = None
try:
original_new_signature = inspect.signature(cls.__new__)
except ValueError:
original_new_signature = None

deprecated_cls = deprecated("depr")(cls)

try:
deprecated_signature = inspect.signature(deprecated_cls)
except ValueError:
deprecated_signature = None
self.assertEqual(original_signature, deprecated_signature)

try:
deprecated_new_signature = inspect.signature(deprecated_cls.__new__)
except ValueError:
deprecated_new_signature = None
self.assertEqual(original_new_signature, deprecated_new_signature)


def setUpModule():
py_warnings.onceregistry.clear()
c_warnings.onceregistry.clear()


tearDownModule = setUpModule

if __name__ == "__main__":
Expand Down
48 changes: 45 additions & 3 deletions Lib/warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,17 +590,54 @@ def __call__(self, arg, /):
if category is None:
arg.__deprecated__ = msg
return arg
elif isinstance(arg, type):

def update_signature(original_func):
# Ensure that the signature of the decorated callable matches the original one

def wrapper(func):
import inspect

try:
original_signature = inspect.signature(original_func)
except ValueError:
pass
else:
signature = inspect.signature(func)
if signature != original_signature:
func.__signature__ = original_signature

return func

return wrapper

if isinstance(arg, type):
import functools
from types import MethodType

original_new = arg.__new__
is_object_new = original_new is object.__new__
if is_object_new:
import inspect

try:
arg.__signature__ = inspect.signature(arg)
except (ValueError, AttributeError, TypeError):
pass

def wraps(wrapped):
def identity(func):
return func
return identity

@functools.wraps(original_new)
else:
wraps = functools.wraps

@update_signature(original_new)
@wraps(original_new)
def __new__(cls, *args, **kwargs):
if cls is arg:
warn(msg, category=category, stacklevel=stacklevel + 1)
if original_new is not object.__new__:
if not is_object_new:
return original_new(cls, *args, **kwargs)
# Mirrors a similar check in object.__new__.
elif cls.__init__ is object.__init__ and (args or kwargs):
Expand All @@ -616,15 +653,19 @@ def __new__(cls, *args, **kwargs):
if isinstance(original_init_subclass, MethodType):
original_init_subclass = original_init_subclass.__func__

@update_signature(original_init_subclass)
@functools.wraps(original_init_subclass)
def __init_subclass__(*args, **kwargs):
warn(msg, category=category, stacklevel=stacklevel + 1)
return original_init_subclass(*args, **kwargs)

arg.__init_subclass__ = classmethod(__init_subclass__)

# Or otherwise, which likely means it's a builtin such as
# object's implementation of __init_subclass__.
else:

@update_signature(original_init_subclass)
@functools.wraps(original_init_subclass)
def __init_subclass__(*args, **kwargs):
warn(msg, category=category, stacklevel=stacklevel + 1)
Expand All @@ -639,6 +680,7 @@ def __init_subclass__(*args, **kwargs):
import functools
import inspect

@update_signature(arg)
@functools.wraps(arg)
def wrapper(*args, **kwargs):
warn(msg, category=category, stacklevel=stacklevel + 1)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Preserve class signature after wrapping with :func:`warnings.deprecated`. Patch by Xuehai Pan.
Loading