From a5521acb131fa2e35a9aeccb77975d0d5280f9bc Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 23 Jul 2024 12:06:25 +0200 Subject: [PATCH 1/8] Copy the coroutine status in deprecated --- CHANGELOG.md | 3 +++ src/test_typing_extensions.py | 23 +++++++++++++++++++++++ src/typing_extensions.py | 4 ++++ 3 files changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68c4cf34..81cc718e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ - 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..a1d8dc2d 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -850,6 +850,29 @@ def d(): pass isinstance(cell.cell_contents, deprecated) for cell in d.__closure__ )) + def test_inspect(self): + @deprecated("depr") + def sync(): + pass + + @deprecated("depr") + async def coro(): + pass + + class Cls: + @deprecated("depr") + def sync(self): + pass + + @deprecated("depr") + async def coro(self): + pass + + self.assertFalse(inspect.iscoroutinefunction(sync)) + self.assertTrue(inspect.iscoroutinefunction(coro)) + self.assertFalse(inspect.iscoroutinefunction(Cls.sync)) + 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..ed7cb939 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2899,12 +2899,16 @@ def __init_subclass__(*args, **kwargs): return arg elif callable(arg): import functools + import inspect @functools.wraps(arg) def wrapper(*args, **kwargs): warnings.warn(msg, category=category, stacklevel=stacklevel + 1) return arg(*args, **kwargs) + if inspect.iscoroutinefunction(arg): + wrapper = inspect.markcoroutinefunction(wrapper) + arg.__deprecated__ = wrapper.__deprecated__ = msg return wrapper else: From 93cc91c869177bc35d6b02291fbb3398155d20c8 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 23 Jul 2024 12:14:43 +0200 Subject: [PATCH 2/8] This only works on Python 3.12+ --- CHANGELOG.md | 4 ++-- src/test_typing_extensions.py | 8 ++++++-- src/typing_extensions.py | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81cc718e..1a748283 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,8 @@ `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. + with `@typing_extensions.deprecated` when using Python 3.12 + or above. 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 a1d8dc2d..dd6f60f1 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -869,9 +869,13 @@ async def coro(self): pass self.assertFalse(inspect.iscoroutinefunction(sync)) - self.assertTrue(inspect.iscoroutinefunction(coro)) self.assertFalse(inspect.iscoroutinefunction(Cls.sync)) - self.assertTrue(inspect.iscoroutinefunction(Cls.coro)) + if sys.version_info >= (3, 12): + self.assertTrue(inspect.iscoroutinefunction(coro)) + self.assertTrue(inspect.iscoroutinefunction(Cls.coro)) + else: + self.assertFalse(inspect.iscoroutinefunction(coro)) + self.assertFalse(inspect.iscoroutinefunction(Cls.coro)) class AnyTests(BaseTestCase): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index ed7cb939..8af20ded 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2906,7 +2906,7 @@ def wrapper(*args, **kwargs): warnings.warn(msg, category=category, stacklevel=stacklevel + 1) return arg(*args, **kwargs) - if inspect.iscoroutinefunction(arg): + if sys.version_info >= (3, 12) and inspect.iscoroutinefunction(arg): wrapper = inspect.markcoroutinefunction(wrapper) arg.__deprecated__ = wrapper.__deprecated__ = msg From 6ba9903b82764ab877b452df19a29e0eb497aebd Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 23 Jul 2024 12:37:27 +0200 Subject: [PATCH 3/8] Fix tests for 3.13 betas --- src/test_typing_extensions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index dd6f60f1..ce431271 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -118,6 +118,9 @@ # 3.13 drops support for the keyword argument syntax of TypedDict TYPING_3_13_0 = sys.version_info[:3] >= (3, 13, 0) +# 3.13.0rc1 changes @deprecated behavior +TYPING_3_13_BETA = sys.version_info[:4] == (3, 13, 0, 'beta') + # 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 @@ -870,7 +873,7 @@ async def coro(self): self.assertFalse(inspect.iscoroutinefunction(sync)) self.assertFalse(inspect.iscoroutinefunction(Cls.sync)) - if sys.version_info >= (3, 12): + if sys.version_info >= (3, 12) and not TYPING_3_13_BETA: self.assertTrue(inspect.iscoroutinefunction(coro)) self.assertTrue(inspect.iscoroutinefunction(Cls.coro)) else: From 423a548f038ed720ec96fea2da1f6f2fad4655ad Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 23 Jul 2024 12:49:33 +0200 Subject: [PATCH 4/8] Make it work partly on Python < 3.11 --- CHANGELOG.md | 3 +-- src/test_typing_extensions.py | 6 ++++++ src/typing_extensions.py | 8 ++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a748283..0eafc6b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,7 @@ `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` when using Python 3.12 - or above. Patch by Sebastian Rittau. + 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 ce431271..3782006f 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 @@ -880,6 +881,11 @@ async def coro(self): self.assertFalse(inspect.iscoroutinefunction(coro)) self.assertFalse(inspect.iscoroutinefunction(Cls.coro)) + self.assertFalse(asyncio.coroutines.iscoroutinefunction(sync)) + self.assertFalse(asyncio.coroutines.iscoroutinefunction(Cls.sync)) + self.assertTrue(asyncio.coroutines.iscoroutinefunction(coro)) + self.assertTrue(asyncio.coroutines.iscoroutinefunction(Cls.coro)) + class AnyTests(BaseTestCase): def test_can_subclass(self): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 8af20ded..1adc5823 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2898,6 +2898,7 @@ def __init_subclass__(*args, **kwargs): __init_subclass__.__deprecated__ = msg return arg elif callable(arg): + import asyncio.coroutines import functools import inspect @@ -2906,8 +2907,11 @@ def wrapper(*args, **kwargs): warnings.warn(msg, category=category, stacklevel=stacklevel + 1) return arg(*args, **kwargs) - if sys.version_info >= (3, 12) and inspect.iscoroutinefunction(arg): - wrapper = inspect.markcoroutinefunction(wrapper) + 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 From 5abc0846838642d6b8149da332638d8564133174 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 23 Jul 2024 13:18:15 +0200 Subject: [PATCH 5/8] Split tests per Python version --- src/test_typing_extensions.py | 66 +++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 3782006f..d06b9c8e 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -116,11 +116,14 @@ # 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.0rc1 changes @deprecated behavior -TYPING_3_13_BETA = sys.version_info[:4] == (3, 13, 0, 'beta') +# 3.13.0.rc1 fixes a problem with @deprecated +TYPING_3_13_0_RC = sys.version_info[:3] >= (3, 13, 0) and sys.version_info[:4] != (3, 13, 0, 'beta') # https://github.com/python/cpython/pull/27017 was backported into some 3.9 and 3.10 # versions, but not all @@ -854,35 +857,46 @@ def d(): pass isinstance(cell.cell_contents, deprecated) for cell in d.__closure__ )) - def test_inspect(self): - @deprecated("depr") - def sync(): - pass +@deprecated("depr") +def func(): + pass - @deprecated("depr") - async def coro(): - pass +@deprecated("depr") +async def coro(): + pass - class Cls: - @deprecated("depr") - def sync(self): - pass +class Cls: + @deprecated("depr") + def func(self): + pass - @deprecated("depr") - async def coro(self): - pass + @deprecated("depr") + async def coro(self): + pass - self.assertFalse(inspect.iscoroutinefunction(sync)) - self.assertFalse(inspect.iscoroutinefunction(Cls.sync)) - if sys.version_info >= (3, 12) and not TYPING_3_13_BETA: - self.assertTrue(inspect.iscoroutinefunction(coro)) - self.assertTrue(inspect.iscoroutinefunction(Cls.coro)) - else: - self.assertFalse(inspect.iscoroutinefunction(coro)) - self.assertFalse(inspect.iscoroutinefunction(Cls.coro)) +class DeprecatedCoroTests(BaseTestCase): + @skipUnless(TYPING_3_12_ONLY or TYPING_3_13_0_RC, "@deprecated was added to warnings in Python 3.13") + def test_inspect_py313(self): + self.assertFalse(inspect.iscoroutinefunction(func)) + self.assertFalse(inspect.iscoroutinefunction(Cls.func)) + self.assertTrue(inspect.iscoroutinefunction(coro)) + self.assertTrue(inspect.iscoroutinefunction(Cls.coro)) + + 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)) + + @skipIf(TYPING_3_12_0, "Behavior changed with Python 3.12") + def test_inspect_py311(self): + # This doesn't work in Python < 3.12. + # self.assertTrue(inspect.iscoroutinefunction(func)) + # self.assertTrue(inspect.iscoroutinefunction(Cls.func)) + self.assertFalse(inspect.iscoroutinefunction(coro)) + self.assertFalse(inspect.iscoroutinefunction(Cls.coro)) - self.assertFalse(asyncio.coroutines.iscoroutinefunction(sync)) - self.assertFalse(asyncio.coroutines.iscoroutinefunction(Cls.sync)) + 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)) From 30c03b1aaa973b10f64fcf7486066553665d89e3 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 23 Jul 2024 13:23:57 +0200 Subject: [PATCH 6/8] Change skip message --- src/test_typing_extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index d06b9c8e..bd2fec78 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -875,7 +875,7 @@ async def coro(self): pass class DeprecatedCoroTests(BaseTestCase): - @skipUnless(TYPING_3_12_ONLY or TYPING_3_13_0_RC, "@deprecated was added to warnings in Python 3.13") + @skipUnless(TYPING_3_12_ONLY or TYPING_3_13_0_RC, "inspect.iscoroutinefunction works differently on Python < 3.12") def test_inspect_py313(self): self.assertFalse(inspect.iscoroutinefunction(func)) self.assertFalse(inspect.iscoroutinefunction(Cls.func)) @@ -887,7 +887,7 @@ def test_inspect_py313(self): self.assertTrue(asyncio.coroutines.iscoroutinefunction(coro)) self.assertTrue(asyncio.coroutines.iscoroutinefunction(Cls.coro)) - @skipIf(TYPING_3_12_0, "Behavior changed with Python 3.12") + @skipIf(TYPING_3_12_0, "inspect.iscoroutinefunction works differently on Python 3.12+") def test_inspect_py311(self): # This doesn't work in Python < 3.12. # self.assertTrue(inspect.iscoroutinefunction(func)) From 817fe9fe66c869e52d5b7e18be6ddca27fd5ea11 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 23 Jul 2024 16:37:20 +0200 Subject: [PATCH 7/8] Apply suggestions from code review Co-authored-by: Alex Waygood --- src/test_typing_extensions.py | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index bd2fec78..6437cfe6 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -123,7 +123,7 @@ 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[:3] >= (3, 13, 0) and sys.version_info[:4] != (3, 13, 0, 'beta') +TYPING_3_13_0_RC = sys.version_info[:3] >= (3, 13, 0, "candidate") # https://github.com/python/cpython/pull/27017 was backported into some 3.9 and 3.10 # versions, but not all @@ -875,30 +875,18 @@ async def coro(self): pass class DeprecatedCoroTests(BaseTestCase): - @skipUnless(TYPING_3_12_ONLY or TYPING_3_13_0_RC, "inspect.iscoroutinefunction works differently on Python < 3.12") - def test_inspect_py313(self): - self.assertFalse(inspect.iscoroutinefunction(func)) - self.assertFalse(inspect.iscoroutinefunction(Cls.func)) - self.assertTrue(inspect.iscoroutinefunction(coro)) - self.assertTrue(inspect.iscoroutinefunction(Cls.coro)) - + 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)) - @skipIf(TYPING_3_12_0, "inspect.iscoroutinefunction works differently on Python 3.12+") - def test_inspect_py311(self): - # This doesn't work in Python < 3.12. - # self.assertTrue(inspect.iscoroutinefunction(func)) - # self.assertTrue(inspect.iscoroutinefunction(Cls.func)) - self.assertFalse(inspect.iscoroutinefunction(coro)) - self.assertFalse(inspect.iscoroutinefunction(Cls.coro)) - - 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): From 7ee6ad5f787f597c0f62eb2ce23488dbfae927fc Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Tue, 23 Jul 2024 16:39:47 +0200 Subject: [PATCH 8/8] Fix version check --- src/test_typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 6437cfe6..474c02cc 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -123,7 +123,7 @@ 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[:3] >= (3, 13, 0, "candidate") +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