Skip to content

Commit

Permalink
Adjusted implementation and docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
carltongibson committed Dec 13, 2022
1 parent c073cf7 commit 5ffba32
Show file tree
Hide file tree
Showing 3 changed files with 15 additions and 17 deletions.
6 changes: 2 additions & 4 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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``.


Expand Down
20 changes: 9 additions & 11 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
6 changes: 4 additions & 2 deletions Lib/test/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,15 +221,17 @@ 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
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
Expand Down

0 comments on commit 5ffba32

Please sign in to comment.