From 4cf8e98175e501c671c3c43016d3df443def9ffc Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 8 Nov 2022 14:56:51 +0100 Subject: [PATCH 01/15] Added initial test and docs drafts. --- Doc/library/inspect.rst | 14 ++++++++++++++ Lib/test/test_inspect.py | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 44f1ae04c9e39e..48e3d060d00006 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -358,6 +358,20 @@ attributes: wrapped function is a :term:`coroutine function`. +.. function:: markcoroutinefunction(object) + + Decorator to mark a callable as a :term:`coroutine function` if it would not + otherwise be detected by :func:`iscoroutinefunction`. + + This may be of use for sync functions that return an awaitable or objects + implementing an :keyword:`async def` ``__call__``. + + Prefer :keyword:`async def` functions or calling the function and testing + the return with :func:`isawaitable` where feasible. + + .. versionadded:: 3.12 + + .. function:: iscoroutine(object) Return ``True`` if the object is a :term:`coroutine` created by an diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 3f5c299ce681c5..a2ce6d6fcbecc4 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -202,6 +202,25 @@ def test_iscoroutine(self): gen_coroutine_function_example)))) self.assertTrue(inspect.isgenerator(gen_coro)) + # Use subtest initially to see both failures. + with self.subTest("Wrapper not recognised."): + # First case: sync function returning an awaitable. + async def _fn3(): + pass + + def fn3(): + return _fn3() + self.assertTrue(inspect.iscoroutinefunction(fn3)) + + with self.subTest("Awaitable instance not recongnised."): + # Second case: a class with an async def __call__. + # - instance is awaitable. + class Cl: + async def __call__(self): + pass + cl = Cl() + self.assertTrue(inspect.iscoroutinefunction(cl)) + self.assertFalse( inspect.iscoroutinefunction(unittest.mock.Mock())) self.assertTrue( From 6b8fa872c5378cc608b0a0ca01ed350c4193e07d Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 15 Nov 2022 09:06:56 +0100 Subject: [PATCH 02/15] Make first case pass setting code flags. --- Lib/test/test_inspect.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index a2ce6d6fcbecc4..a70d68d293a362 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -210,6 +210,12 @@ async def _fn3(): def fn3(): return _fn3() + + # TODO: Move this to decorator function. + fn3.__code__ = fn3.__code__.replace( + co_flags=fn3.__code__.co_flags | inspect.CO_COROUTINE + ) + self.assertTrue(inspect.iscoroutinefunction(fn3)) with self.subTest("Awaitable instance not recongnised."): From fa22a1631ebaf49f54d45eb36057ac3a49ca26f6 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 15 Nov 2022 09:07:26 +0100 Subject: [PATCH 03/15] Adjust iscoroutinefunction() to look for async __call__. --- Lib/inspect.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index d0015aa202044e..b0312f640785ca 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -396,7 +396,9 @@ def iscoroutinefunction(obj): Coroutine functions are defined with "async def" syntax. """ - return _has_code_flag(obj, CO_COROUTINE) + return _has_code_flag(obj, CO_COROUTINE) or ( + callable(obj) and _has_code_flag(obj.__call__, CO_COROUTINE) + ) def isasyncgenfunction(obj): """Return true if the object is an asynchronous generator function. From 513d3582081efe0d83bd5d4da1f4d9a8c99e3535 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 17 Nov 2022 10:02:19 +0000 Subject: [PATCH 04/15] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2022-11-17-10-02-18.gh-issue-94912.G2aa-E.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-11-17-10-02-18.gh-issue-94912.G2aa-E.rst diff --git a/Misc/NEWS.d/next/Library/2022-11-17-10-02-18.gh-issue-94912.G2aa-E.rst b/Misc/NEWS.d/next/Library/2022-11-17-10-02-18.gh-issue-94912.G2aa-E.rst new file mode 100644 index 00000000000000..0eae0a278539c6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-11-17-10-02-18.gh-issue-94912.G2aa-E.rst @@ -0,0 +1 @@ +Added ``@markcoroutinefunction`` decorator to ``inspect`` module. From 397a975bc331733aafdc843611180759e135f012 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 17 Nov 2022 11:22:30 +0100 Subject: [PATCH 05/15] Moved to decorator; extra tests. --- Lib/inspect.py | 12 +++++++++++- Lib/test/test_inspect.py | 39 +++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index b0312f640785ca..79ba24bada639f 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -125,6 +125,7 @@ "ismodule", "isroutine", "istraceback", + "markcoroutinefunction", "signature", "stack", "trace", @@ -391,13 +392,22 @@ def isgeneratorfunction(obj): See help(isfunction) for a list of attributes.""" return _has_code_flag(obj, CO_GENERATOR) +def markcoroutinefunction(func): + """ + Decorator to ensure callable is recognised as a coroutine function. + """ + func.__code__ = func.__code__.replace( + co_flags=func.__code__.co_flags | CO_COROUTINE + ) + return func + def iscoroutinefunction(obj): """Return true if the object is a coroutine function. Coroutine functions are defined with "async def" syntax. """ return _has_code_flag(obj, CO_COROUTINE) or ( - callable(obj) and _has_code_flag(obj.__call__, CO_COROUTINE) + not isclass(obj) and callable(obj) and _has_code_flag(obj.__call__, CO_COROUTINE) ) def isasyncgenfunction(obj): diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index a70d68d293a362..9f243238a41d69 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -202,30 +202,29 @@ def test_iscoroutine(self): gen_coroutine_function_example)))) self.assertTrue(inspect.isgenerator(gen_coro)) - # Use subtest initially to see both failures. - with self.subTest("Wrapper not recognised."): - # First case: sync function returning an awaitable. - async def _fn3(): - pass + async def _fn3(): + pass - def fn3(): - return _fn3() + @inspect.markcoroutinefunction + def fn3(): + return _fn3() - # TODO: Move this to decorator function. - fn3.__code__ = fn3.__code__.replace( - co_flags=fn3.__code__.co_flags | inspect.CO_COROUTINE - ) + self.assertTrue(inspect.iscoroutinefunction(fn3)) - self.assertTrue(inspect.iscoroutinefunction(fn3)) + class Cl: + async def __call__(self): + pass + + self.assertFalse(inspect.iscoroutinefunction(Cl)) + self.assertTrue(inspect.iscoroutinefunction(Cl())) + + class Cl2: + @inspect.markcoroutinefunction + def __call__(self): + pass - with self.subTest("Awaitable instance not recongnised."): - # Second case: a class with an async def __call__. - # - instance is awaitable. - class Cl: - async def __call__(self): - pass - cl = Cl() - self.assertTrue(inspect.iscoroutinefunction(cl)) + self.assertFalse(inspect.iscoroutinefunction(Cl2)) + self.assertTrue(inspect.iscoroutinefunction(Cl2())) self.assertFalse( inspect.iscoroutinefunction(unittest.mock.Mock())) From d156eab1859aaade6b3e096a25caefdc93731ede Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 23 Nov 2022 15:39:18 +0100 Subject: [PATCH 06/15] Added tests for lambda, @classmethod, and @staticmethod cases. --- Lib/inspect.py | 2 ++ Lib/test/test_inspect.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Lib/inspect.py b/Lib/inspect.py index 79ba24bada639f..76ecdb2d56a7e3 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -396,6 +396,8 @@ def markcoroutinefunction(func): """ Decorator to ensure callable is recognised as a coroutine function. """ + if hasattr(func, '__func__'): + func = func.__func__ func.__code__ = func.__code__.replace( co_flags=func.__code__.co_flags | CO_COROUTINE ) diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 9f243238a41d69..490f34705c6570 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -210,6 +210,11 @@ def fn3(): return _fn3() self.assertTrue(inspect.iscoroutinefunction(fn3)) + self.assertTrue( + inspect.iscoroutinefunction( + inspect.markcoroutinefunction(lambda: _fn3()) + ) + ) class Cl: async def __call__(self): @@ -226,6 +231,20 @@ def __call__(self): self.assertFalse(inspect.iscoroutinefunction(Cl2)) self.assertTrue(inspect.iscoroutinefunction(Cl2())) + class Cl3: + @inspect.markcoroutinefunction + @classmethod + def do_something_classy(cls): + pass + + @inspect.markcoroutinefunction + @staticmethod + def do_something_static(): + pass + + self.assertTrue(inspect.iscoroutinefunction(Cl3.do_something_classy)) + self.assertTrue(inspect.iscoroutinefunction(Cl3.do_something_static)) + self.assertFalse( inspect.iscoroutinefunction(unittest.mock.Mock())) self.assertTrue( From 34859de64ada345fca4a5741825180297bc35438 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 23 Nov 2022 15:41:34 +0100 Subject: [PATCH 07/15] Adjusted docs signature for review comment. --- Doc/library/inspect.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 48e3d060d00006..f963467a5dca9f 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -358,7 +358,7 @@ attributes: wrapped function is a :term:`coroutine function`. -.. function:: markcoroutinefunction(object) +.. function:: markcoroutinefunction(func) Decorator to mark a callable as a :term:`coroutine function` if it would not otherwise be detected by :func:`iscoroutinefunction`. From 47a9fea9653ca539c02e62649bebe8300ad260e9 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 23 Nov 2022 15:53:45 -0800 Subject: [PATCH 08/15] Improve grammar in NEWS file --- .../next/Library/2022-11-17-10-02-18.gh-issue-94912.G2aa-E.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2022-11-17-10-02-18.gh-issue-94912.G2aa-E.rst b/Misc/NEWS.d/next/Library/2022-11-17-10-02-18.gh-issue-94912.G2aa-E.rst index 0eae0a278539c6..ee00f9d8d03f2c 100644 --- a/Misc/NEWS.d/next/Library/2022-11-17-10-02-18.gh-issue-94912.G2aa-E.rst +++ b/Misc/NEWS.d/next/Library/2022-11-17-10-02-18.gh-issue-94912.G2aa-E.rst @@ -1 +1,2 @@ -Added ``@markcoroutinefunction`` decorator to ``inspect`` module. +Add :func:`inspect.markcoroutinefunction` decorator which manually marks +a function as a coroutine for the benefit of :func:`iscoroutinefunction`. From cd6c491359b2424d3ff6b2bac6b8efc12c62ffc0 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 29 Nov 2022 10:05:34 +0100 Subject: [PATCH 09/15] Adjusted docs. --- Doc/library/inspect.rst | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index f963467a5dca9f..0aa31df1c7dcef 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -348,8 +348,10 @@ attributes: .. function:: iscoroutinefunction(object) - Return ``True`` if the object is a :term:`coroutine function` - (a function defined with an :keyword:`async def` syntax). + Return ``True`` if the object is a :term:`coroutine function` (a function + defined with an :keyword:`async def` syntax), a :func:`functools.partial` + wrapping a :term:`coroutine function`, or an instance of a class defining + an :keyword:`async def` ``__call__``. .. versionadded:: 3.5 @@ -357,17 +359,19 @@ attributes: Functions wrapped in :func:`functools.partial` now return ``True`` if the wrapped function is a :term:`coroutine function`. + .. versionchanged:: 3.12 + Instances of classes defining an :keyword:`async def` ``__call__`` now + return ``True``. + .. function:: markcoroutinefunction(func) Decorator to mark a callable as a :term:`coroutine function` if it would not otherwise be detected by :func:`iscoroutinefunction`. - This may be of use for sync functions that return an awaitable or objects - implementing an :keyword:`async def` ``__call__``. - - Prefer :keyword:`async def` functions or calling the function and testing - the return with :func:`isawaitable` where feasible. + This may be of use for sync functions that return a :term:`coroutine`, but + prefer :keyword:`async def` functions, or if necessary calling the function + and testing the return with :func:`iscoroutine` where feasible. .. versionadded:: 3.12 From 629dd8192dc2f5559286028e98f29d58f8cbbf1f Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 30 Nov 2022 10:20:40 +0100 Subject: [PATCH 10/15] Updated for review comments. --- Doc/library/inspect.rst | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 0aa31df1c7dcef..3a527ee76db9bf 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -350,8 +350,9 @@ attributes: Return ``True`` if the object is a :term:`coroutine function` (a function defined with an :keyword:`async def` syntax), a :func:`functools.partial` - wrapping a :term:`coroutine function`, or an instance of a class defining - an :keyword:`async def` ``__call__``. + wrapping a :term:`coroutine function`, an instance of a class defining an + :keyword:`async def` ``__call__``, or a sync function marked with + :func:`markcoroutinefunction`. .. versionadded:: 3.5 @@ -360,8 +361,9 @@ attributes: wrapped function is a :term:`coroutine function`. .. versionchanged:: 3.12 - Instances of classes defining an :keyword:`async def` ``__call__`` now - return ``True``. + Instances of classes defining an :keyword:`async def` ``__call__``, or + sync functions marked with :func:`markcoroutinefunction` now return + ``True``. .. function:: markcoroutinefunction(func) @@ -369,9 +371,12 @@ attributes: Decorator to mark a callable as a :term:`coroutine function` if it would not otherwise be detected by :func:`iscoroutinefunction`. - This may be of use for sync functions that return a :term:`coroutine`, but - prefer :keyword:`async def` functions, or if necessary calling the function - and testing the return with :func:`iscoroutine` where feasible. + This may be of use for sync functions that return a :term:`coroutine`, if + the function is passed to an API that requires :func:`iscoroutinefunction`. + + When possible, using an :keyword:`async def` function is preferred. Also + acceptable is calling the function and testing the return with + :func:`iscoroutine`. .. versionadded:: 3.12 From c6d2b88a1884039c8ea98124401acbf10accf660 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 6 Dec 2022 12:27:37 +0100 Subject: [PATCH 11/15] Use marker for implementation. --- Lib/inspect.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index cd369de02c1bd2..883d0b02bf4423 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -392,22 +392,31 @@ def isgeneratorfunction(obj): See help(isfunction) for a list of attributes.""" return _has_code_flag(obj, CO_GENERATOR) +# A marker for markcoroutinefunction and iscoroutinefunction. +_is_coroutine = object() + def markcoroutinefunction(func): """ Decorator to ensure callable is recognised as a coroutine function. """ if hasattr(func, '__func__'): func = func.__func__ - func.__code__ = func.__code__.replace( - co_flags=func.__code__.co_flags | CO_COROUTINE - ) + func._is_coroutine = _is_coroutine return func def iscoroutinefunction(obj): """Return true if the object is a coroutine function. - Coroutine functions are defined with "async def" syntax. + Coroutine functions are normally defined with "async def" syntax, but may + be marked via markcoroutinefunction. """ + func = getattr(obj, "__func__", obj) + if getattr(func, "_is_coroutine", None) is _is_coroutine: + return True + + if not isclass(obj) and callable(obj) and getattr(obj.__call__, "_is_coroutine", None) is _is_coroutine: + return True + return _has_code_flag(obj, CO_COROUTINE) or ( not isclass(obj) and callable(obj) and _has_code_flag(obj.__call__, CO_COROUTINE) ) From b7249a89b25ab6f5643208f6b8dcd00dd3507593 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 6 Dec 2022 12:36:47 +0100 Subject: [PATCH 12/15] Added "What's New..." entry. --- Doc/whatsnew/3.12.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index c0f98b59ccaf0f..bdb1eb4e2c5e4d 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -221,6 +221,12 @@ asyncio a custom event loop factory. (Contributed by Kumar Aditya in :gh:`99388`.) +inspect +------- + +* Add :func:`inspect.markcoroutinefunction` to mark sync functions that return + a :term:`coroutine` for use with :func:`iscoroutinefunction`. + (Contributed Carlton Gibson in :gh:`99247`.) pathlib ------- From 3bc72e2422ba4f44725525c5eb87989c677c6b34 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 8 Dec 2022 09:49:40 +0100 Subject: [PATCH 13/15] Adjusted implementation. --- Lib/inspect.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index 883d0b02bf4423..3b9bd14a383205 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -410,12 +410,13 @@ def iscoroutinefunction(obj): Coroutine functions are normally defined with "async def" syntax, but may be marked via markcoroutinefunction. """ - func = getattr(obj, "__func__", obj) - if getattr(func, "_is_coroutine", None) is _is_coroutine: - return True - - if not isclass(obj) and callable(obj) and getattr(obj.__call__, "_is_coroutine", None) is _is_coroutine: - return True + if not isclass(obj) and callable(obj): + # Test both the function and the __call__ implementation for the + # _is_coroutine marker. + f = getattr(getattr(obj, "__func__", obj), "_is_coroutine", None) + c = getattr(obj.__call__, "_is_coroutine", None) + if f is _is_coroutine or c is _is_coroutine: + return True return _has_code_flag(obj, CO_COROUTINE) or ( not isclass(obj) and callable(obj) and _has_code_flag(obj.__call__, CO_COROUTINE) From c073cf79867a702dc4de6a0659dd9d16bfe4b44e Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 13 Dec 2022 09:16:44 +0100 Subject: [PATCH 14/15] Renamed to _is_coroutine_marker. --- Lib/inspect.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index 3b9bd14a383205..fec2e319f5a6fa 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -393,7 +393,7 @@ def isgeneratorfunction(obj): return _has_code_flag(obj, CO_GENERATOR) # A marker for markcoroutinefunction and iscoroutinefunction. -_is_coroutine = object() +_is_coroutine_marker = object() def markcoroutinefunction(func): """ @@ -401,7 +401,7 @@ def markcoroutinefunction(func): """ if hasattr(func, '__func__'): func = func.__func__ - func._is_coroutine = _is_coroutine + func._is_coroutine_marker = _is_coroutine_marker return func def iscoroutinefunction(obj): @@ -412,10 +412,10 @@ def iscoroutinefunction(obj): """ if not isclass(obj) and callable(obj): # Test both the function and the __call__ implementation for the - # _is_coroutine marker. - f = getattr(getattr(obj, "__func__", obj), "_is_coroutine", None) - c = getattr(obj.__call__, "_is_coroutine", None) - if f is _is_coroutine or c is _is_coroutine: + # _is_coroutine_marker. + f = getattr(getattr(obj, "__func__", obj), "_is_coroutine_marker", None) + c = getattr(obj.__call__, "_is_coroutine_marker", None) + if f is _is_coroutine_marker or c is _is_coroutine_marker: return True return _has_code_flag(obj, CO_COROUTINE) or ( From 5ffba320051f474cad2796621bc648e0e66890dc Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 13 Dec 2022 09:35:28 +0100 Subject: [PATCH 15/15] Adjusted implementation and docs. --- Doc/library/inspect.rst | 6 ++---- Lib/inspect.py | 20 +++++++++----------- Lib/test/test_inspect.py | 6 ++++-- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 13917c5f7b8518..d07da184280643 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -345,8 +345,7 @@ attributes (see :ref:`import-mod-attrs` for module attributes): Return ``True`` if the object is a :term:`coroutine function` (a function defined with an :keyword:`async def` syntax), a :func:`functools.partial` - wrapping a :term:`coroutine function`, an instance of a class defining an - :keyword:`async def` ``__call__``, or a sync function marked with + wrapping a :term:`coroutine function`, or a sync function marked with :func:`markcoroutinefunction`. .. versionadded:: 3.5 @@ -356,8 +355,7 @@ attributes (see :ref:`import-mod-attrs` for module attributes): wrapped function is a :term:`coroutine function`. .. versionchanged:: 3.12 - Instances of classes defining an :keyword:`async def` ``__call__``, or - sync functions marked with :func:`markcoroutinefunction` now return + Sync functions marked with :func:`markcoroutinefunction` now return ``True``. diff --git a/Lib/inspect.py b/Lib/inspect.py index fec2e319f5a6fa..9b531d174102cb 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -395,6 +395,14 @@ def isgeneratorfunction(obj): # A marker for markcoroutinefunction and iscoroutinefunction. _is_coroutine_marker = object() +def _has_coroutine_mark(f): + while ismethod(f): + f = f.__func__ + f = functools._unwrap_partial(f) + if not (isfunction(f) or _signature_is_functionlike(f)): + return False + return getattr(f, "_is_coroutine_marker", None) is _is_coroutine_marker + def markcoroutinefunction(func): """ Decorator to ensure callable is recognised as a coroutine function. @@ -410,17 +418,7 @@ def iscoroutinefunction(obj): Coroutine functions are normally defined with "async def" syntax, but may be marked via markcoroutinefunction. """ - if not isclass(obj) and callable(obj): - # Test both the function and the __call__ implementation for the - # _is_coroutine_marker. - f = getattr(getattr(obj, "__func__", obj), "_is_coroutine_marker", None) - c = getattr(obj.__call__, "_is_coroutine_marker", None) - if f is _is_coroutine_marker or c is _is_coroutine_marker: - return True - - return _has_code_flag(obj, CO_COROUTINE) or ( - not isclass(obj) and callable(obj) and _has_code_flag(obj.__call__, CO_COROUTINE) - ) + return _has_code_flag(obj, CO_COROUTINE) or _has_coroutine_mark(obj) def isasyncgenfunction(obj): """Return true if the object is an asynchronous generator function. diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 490f34705c6570..f6dfc555e7983a 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -221,7 +221,8 @@ async def __call__(self): pass self.assertFalse(inspect.iscoroutinefunction(Cl)) - self.assertTrue(inspect.iscoroutinefunction(Cl())) + # instances with async def __call__ are NOT recognised. + self.assertFalse(inspect.iscoroutinefunction(Cl())) class Cl2: @inspect.markcoroutinefunction @@ -229,7 +230,8 @@ def __call__(self): pass self.assertFalse(inspect.iscoroutinefunction(Cl2)) - self.assertTrue(inspect.iscoroutinefunction(Cl2())) + # instances with marked __call__ are NOT recognised. + self.assertFalse(inspect.iscoroutinefunction(Cl2())) class Cl3: @inspect.markcoroutinefunction pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy