From 2912585d53130092a868f8109ce2739abf0a7f95 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 25 May 2023 05:46:22 -0700 Subject: [PATCH] Fix @deprecated on classes with only __new__ (#193) --- CHANGELOG.md | 6 ++++-- src/test_typing_extensions.py | 21 +++++++++++++++++++++ src/typing_extensions.py | 6 +++--- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 853c211c..428fcfd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,10 @@ # Unreleased -- Fix regression in version 4.6.1 where comparing a generic class against a +- Fix use of `@deprecated` on classes with `__new__` but no `__init__`. + Patch by Jelle Zijlstra. +- Fix regression in version 4.6.1 where comparing a generic class against a runtime-checkable protocol using `isinstance()` would cause `AttributeError` - to be raised if using Python 3.7 + to be raised if using Python 3.7. # Release 4.6.1 (May 23, 2023) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 736b46b4..fd2a91c3 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -357,6 +357,7 @@ class A: with self.assertRaises(TypeError): A(42) + def test_class_with_init(self): @deprecated("HasInit will go away soon") class HasInit: def __init__(self, x): @@ -366,6 +367,7 @@ def __init__(self, x): instance = HasInit(42) self.assertEqual(instance.x, 42) + def test_class_with_new(self): has_new_called = False @deprecated("HasNew will go away soon") @@ -382,6 +384,8 @@ def __init__(self, x) -> None: instance = HasNew(42) self.assertEqual(instance.x, 42) self.assertTrue(has_new_called) + + def test_class_with_inherited_new(self): new_base_called = False class NewBase: @@ -402,6 +406,23 @@ class HasInheritedNew(NewBase): self.assertEqual(instance.x, 42) self.assertTrue(new_base_called) + def test_class_with_new_but_no_init(self): + new_called = False + + @deprecated("HasNewNoInit will go away soon") + class HasNewNoInit: + def __new__(cls, x): + nonlocal new_called + new_called = True + obj = super().__new__(cls) + obj.x = x + return obj + + with self.assertWarnsRegex(DeprecationWarning, "HasNewNoInit will go away soon"): + instance = HasNewNoInit(42) + self.assertEqual(instance.x, 42) + self.assertTrue(new_called) + def test_function(self): @deprecated("b will go away soon") def b(): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index a3f45daa..9aa84d7e 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2497,11 +2497,11 @@ def decorator(__arg: _T) -> _T: @functools.wraps(original_new) def __new__(cls, *args, **kwargs): warnings.warn(__msg, category=category, stacklevel=stacklevel + 1) - # Mirrors a similar check in object.__new__. - if not has_init and (args or kwargs): - raise TypeError(f"{cls.__name__}() takes no arguments") if original_new is not object.__new__: return original_new(cls, *args, **kwargs) + # Mirrors a similar check in object.__new__. + elif not has_init and (args or kwargs): + raise TypeError(f"{cls.__name__}() takes no arguments") else: return original_new(cls)