From d9509f902c20e3d51c62b8abe522809b4760c0ff Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Thu, 29 Aug 2024 17:51:20 +0200 Subject: [PATCH] Copy the coroutine status in deprecated (#438) Co-authored-by: Alex Waygood --- CHANGELOG.md | 2 ++ src/test_typing_extensions.py | 38 +++++++++++++++++++++++++++++++++++ src/typing_extensions.py | 8 ++++++++ 3 files changed, 48 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68c4cf34..0eafc6b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - Add `typing_extensions.get_annotations`, a backport of `inspect.get_annotations` that adds features specified by PEP 649. Patches by Jelle Zijlstra and Alex Waygood. +- Copy the coroutine status of functions and methods wrapped + with `@typing_extensions.deprecated`. Patch by Sebastian Rittau. # Release 4.12.2 (June 7, 2024) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 868e7938..474c02cc 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -1,4 +1,5 @@ import abc +import asyncio import collections import collections.abc import contextlib @@ -115,9 +116,15 @@ # and adds PEP 695 to CPython's grammar TYPING_3_12_0 = sys.version_info[:3] >= (3, 12, 0) +# @deprecated works differently in Python 3.12 +TYPING_3_12_ONLY = (3, 12) <= sys.version_info < (3, 13) + # 3.13 drops support for the keyword argument syntax of TypedDict TYPING_3_13_0 = sys.version_info[:3] >= (3, 13, 0) +# 3.13.0.rc1 fixes a problem with @deprecated +TYPING_3_13_0_RC = sys.version_info[:4] >= (3, 13, 0, "candidate") + # https://github.com/python/cpython/pull/27017 was backported into some 3.9 and 3.10 # versions, but not all HAS_FORWARD_MODULE = "module" in inspect.signature(typing._type_check).parameters @@ -850,6 +857,37 @@ def d(): pass isinstance(cell.cell_contents, deprecated) for cell in d.__closure__ )) +@deprecated("depr") +def func(): + pass + +@deprecated("depr") +async def coro(): + pass + +class Cls: + @deprecated("depr") + def func(self): + pass + + @deprecated("depr") + async def coro(self): + pass + +class DeprecatedCoroTests(BaseTestCase): + def test_asyncio_iscoroutinefunction(self): + self.assertFalse(asyncio.coroutines.iscoroutinefunction(func)) + self.assertFalse(asyncio.coroutines.iscoroutinefunction(Cls.func)) + self.assertTrue(asyncio.coroutines.iscoroutinefunction(coro)) + self.assertTrue(asyncio.coroutines.iscoroutinefunction(Cls.coro)) + + @skipUnless(TYPING_3_12_ONLY or TYPING_3_13_0_RC, "inspect.iscoroutinefunction works differently on Python < 3.12") + def test_inspect_iscoroutinefunction(self): + self.assertFalse(inspect.iscoroutinefunction(func)) + self.assertFalse(inspect.iscoroutinefunction(Cls.func)) + self.assertTrue(inspect.iscoroutinefunction(coro)) + self.assertTrue(inspect.iscoroutinefunction(Cls.coro)) + class AnyTests(BaseTestCase): def test_can_subclass(self): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 8046dae1..1adc5823 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2898,13 +2898,21 @@ def __init_subclass__(*args, **kwargs): __init_subclass__.__deprecated__ = msg return arg elif callable(arg): + import asyncio.coroutines import functools + import inspect @functools.wraps(arg) def wrapper(*args, **kwargs): warnings.warn(msg, category=category, stacklevel=stacklevel + 1) return arg(*args, **kwargs) + if asyncio.coroutines.iscoroutinefunction(arg): + if sys.version_info >= (3, 12): + wrapper = inspect.markcoroutinefunction(wrapper) + else: + wrapper._is_coroutine = asyncio.coroutines._is_coroutine + arg.__deprecated__ = wrapper.__deprecated__ = msg return wrapper else: