Skip to content

Commit e81cb82

Browse files
PEP 702: Runtime warnings (#112)
1 parent 1039bb2 commit e81cb82

File tree

2 files changed

+160
-4
lines changed

2 files changed

+160
-4
lines changed

src/test_typing_extensions.py

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from typing_extensions import NamedTuple
3232
from typing_extensions import override, deprecated
3333
from _typed_dict_test_helper import Foo, FooGeneric
34+
import warnings
3435

3536
# Flags used to mark tests that only apply after a specific
3637
# version of the typing module.
@@ -203,7 +204,7 @@ def static_method_bad_order():
203204

204205

205206
class DeprecatedTests(BaseTestCase):
206-
def test_deprecated(self):
207+
def test_dunder_deprecated(self):
207208
@deprecated("A will go away soon")
208209
class A:
209210
pass
@@ -230,6 +231,123 @@ def h(x):
230231
self.assertEqual(len(overloads), 2)
231232
self.assertEqual(overloads[0].__deprecated__, "no more ints")
232233

234+
def test_class(self):
235+
@deprecated("A will go away soon")
236+
class A:
237+
pass
238+
239+
with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"):
240+
A()
241+
with self.assertRaises(TypeError):
242+
A(42)
243+
244+
@deprecated("HasInit will go away soon")
245+
class HasInit:
246+
def __init__(self, x):
247+
self.x = x
248+
249+
with self.assertWarnsRegex(DeprecationWarning, "HasInit will go away soon"):
250+
instance = HasInit(42)
251+
self.assertEqual(instance.x, 42)
252+
253+
has_new_called = False
254+
255+
@deprecated("HasNew will go away soon")
256+
class HasNew:
257+
def __new__(cls, x):
258+
nonlocal has_new_called
259+
has_new_called = True
260+
return super().__new__(cls)
261+
262+
def __init__(self, x) -> None:
263+
self.x = x
264+
265+
with self.assertWarnsRegex(DeprecationWarning, "HasNew will go away soon"):
266+
instance = HasNew(42)
267+
self.assertEqual(instance.x, 42)
268+
self.assertTrue(has_new_called)
269+
new_base_called = False
270+
271+
class NewBase:
272+
def __new__(cls, x):
273+
nonlocal new_base_called
274+
new_base_called = True
275+
return super().__new__(cls)
276+
277+
def __init__(self, x) -> None:
278+
self.x = x
279+
280+
@deprecated("HasInheritedNew will go away soon")
281+
class HasInheritedNew(NewBase):
282+
pass
283+
284+
with self.assertWarnsRegex(DeprecationWarning, "HasInheritedNew will go away soon"):
285+
instance = HasInheritedNew(42)
286+
self.assertEqual(instance.x, 42)
287+
self.assertTrue(new_base_called)
288+
289+
def test_function(self):
290+
@deprecated("b will go away soon")
291+
def b():
292+
pass
293+
294+
with self.assertWarnsRegex(DeprecationWarning, "b will go away soon"):
295+
b()
296+
297+
def test_method(self):
298+
class Capybara:
299+
@deprecated("x will go away soon")
300+
def x(self):
301+
pass
302+
303+
instance = Capybara()
304+
with self.assertWarnsRegex(DeprecationWarning, "x will go away soon"):
305+
instance.x()
306+
307+
def test_property(self):
308+
class Capybara:
309+
@property
310+
@deprecated("x will go away soon")
311+
def x(self):
312+
pass
313+
314+
@property
315+
def no_more_setting(self):
316+
return 42
317+
318+
@no_more_setting.setter
319+
@deprecated("no more setting")
320+
def no_more_setting(self, value):
321+
pass
322+
323+
instance = Capybara()
324+
with self.assertWarnsRegex(DeprecationWarning, "x will go away soon"):
325+
instance.x
326+
327+
with warnings.catch_warnings():
328+
warnings.simplefilter("error")
329+
self.assertEqual(instance.no_more_setting, 42)
330+
331+
with self.assertWarnsRegex(DeprecationWarning, "no more setting"):
332+
instance.no_more_setting = 42
333+
334+
def test_category(self):
335+
@deprecated("c will go away soon", category=RuntimeWarning)
336+
def c():
337+
pass
338+
339+
with self.assertWarnsRegex(RuntimeWarning, "c will go away soon"):
340+
c()
341+
342+
def test_turn_off_warnings(self):
343+
@deprecated("d will go away soon", category=None)
344+
def d():
345+
pass
346+
347+
with warnings.catch_warnings():
348+
warnings.simplefilter("error")
349+
d()
350+
233351

234352
class AnyTests(BaseTestCase):
235353
def test_can_subclass(self):

src/typing_extensions.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import sys
88
import types as _types
99
import typing
10+
import warnings
1011

1112

1213
__all__ = [
@@ -2135,7 +2136,12 @@ def method(self) -> None:
21352136
else:
21362137
_T = typing.TypeVar("_T")
21372138

2138-
def deprecated(__msg: str) -> typing.Callable[[_T], _T]:
2139+
def deprecated(
2140+
__msg: str,
2141+
*,
2142+
category: typing.Optional[typing.Type[Warning]] = DeprecationWarning,
2143+
stacklevel: int = 1,
2144+
) -> typing.Callable[[_T], _T]:
21392145
"""Indicate that a class, function or overload is deprecated.
21402146
21412147
Usage:
@@ -2167,8 +2173,40 @@ def g(x: str) -> int: ...
21672173
21682174
"""
21692175
def decorator(__arg: _T) -> _T:
2170-
__arg.__deprecated__ = __msg
2171-
return __arg
2176+
if category is None:
2177+
__arg.__deprecated__ = __msg
2178+
return __arg
2179+
elif isinstance(__arg, type):
2180+
original_new = __arg.__new__
2181+
has_init = __arg.__init__ is not object.__init__
2182+
2183+
@functools.wraps(original_new)
2184+
def __new__(cls, *args, **kwargs):
2185+
warnings.warn(__msg, category=category, stacklevel=stacklevel + 1)
2186+
# Mirrors a similar check in object.__new__.
2187+
if not has_init and (args or kwargs):
2188+
raise TypeError(f"{cls.__name__}() takes no arguments")
2189+
if original_new is not object.__new__:
2190+
return original_new(cls, *args, **kwargs)
2191+
else:
2192+
return original_new(cls)
2193+
2194+
__arg.__new__ = staticmethod(__new__)
2195+
__arg.__deprecated__ = __new__.__deprecated__ = __msg
2196+
return __arg
2197+
elif callable(__arg):
2198+
@functools.wraps(__arg)
2199+
def wrapper(*args, **kwargs):
2200+
warnings.warn(__msg, category=category, stacklevel=stacklevel + 1)
2201+
return __arg(*args, **kwargs)
2202+
2203+
__arg.__deprecated__ = wrapper.__deprecated__ = __msg
2204+
return wrapper
2205+
else:
2206+
raise TypeError(
2207+
"@deprecated decorator with non-None category must be applied to "
2208+
f"a class or callable, not {__arg!r}"
2209+
)
21722210

21732211
return decorator
21742212

0 commit comments

Comments
 (0)