Skip to content

Commit 9feec00

Browse files
committed
Add is_awaitable utility function
This can act as a faster replacement for inspect.isawaitable.
1 parent 85635a2 commit 9feec00

File tree

3 files changed

+129
-0
lines changed

3 files changed

+129
-0
lines changed

src/graphql/pyutils/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from .event_emitter import EventEmitter, EventEmitterAsyncIterator
2222
from .identity_func import identity_func
2323
from .inspect import inspect
24+
from .is_awaitable import is_awaitable
2425
from .is_collection import is_collection
2526
from .is_finite import is_finite
2627
from .is_integer import is_integer
@@ -47,6 +48,7 @@
4748
"EventEmitterAsyncIterator",
4849
"identity_func",
4950
"inspect",
51+
"is_awaitable",
5052
"is_collection",
5153
"is_finite",
5254
"is_integer",

src/graphql/pyutils/is_awaitable.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import inspect
2+
from typing import Any
3+
from types import CoroutineType, GeneratorType
4+
5+
__all__ = ["is_awaitable"]
6+
7+
CO_ITERABLE_COROUTINE = inspect.CO_ITERABLE_COROUTINE
8+
9+
10+
def is_awaitable(value: Any) -> bool:
11+
"""Return true if object can be passed to an ``await`` expression.
12+
13+
Instead of testing if the object is an instance of abc.Awaitable, it checks
14+
the existence of an `__await__` attribute. This is much faster.
15+
"""
16+
return (
17+
# check for coroutine objects
18+
isinstance(value, CoroutineType)
19+
# check for old-style generator based coroutine objects
20+
or isinstance(value, GeneratorType)
21+
and bool(value.gi_code.co_flags & CO_ITERABLE_COROUTINE)
22+
# check for other awaitables (e.g. futures)
23+
or hasattr(value, "__await__")
24+
)

tests/pyutils/test_is_awaitable.py

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import asyncio
2+
from inspect import isawaitable
3+
4+
from pytest import mark # type: ignore
5+
6+
from graphql.pyutils import is_awaitable
7+
8+
9+
def describe_is_awaitable():
10+
def declines_the_none_value():
11+
assert not isawaitable(None)
12+
assert not is_awaitable(None)
13+
14+
def declines_a_boolean_value():
15+
assert not isawaitable(True)
16+
assert not is_awaitable(True)
17+
18+
def declines_an_int_value():
19+
assert not is_awaitable(42)
20+
21+
def declines_a_string_value():
22+
assert not isawaitable("some_string")
23+
assert not is_awaitable("some_string")
24+
25+
def declines_a_dict_value():
26+
assert not isawaitable({})
27+
assert not is_awaitable({})
28+
29+
def declines_an_object_instance():
30+
assert not isawaitable(object())
31+
assert not is_awaitable(object())
32+
33+
def declines_the_type_class():
34+
assert not isawaitable(type)
35+
assert not is_awaitable(type)
36+
37+
def declines_a_lambda_function():
38+
assert not isawaitable(lambda: True) # pragma: no cover
39+
assert not is_awaitable(lambda: True) # pragma: no cover
40+
41+
def declines_a_normal_function():
42+
def some_function():
43+
return True
44+
45+
assert not isawaitable(some_function())
46+
assert not is_awaitable(some_function)
47+
48+
def declines_a_normal_generator_function():
49+
def some_generator():
50+
yield True # pragma: no cover
51+
52+
assert not isawaitable(some_generator)
53+
assert not is_awaitable(some_generator)
54+
55+
def declines_a_normal_generator_object():
56+
def some_generator():
57+
yield True # pragma: no cover
58+
59+
assert not isawaitable(some_generator())
60+
assert not is_awaitable(some_generator())
61+
62+
def declines_a_coroutine_function():
63+
async def some_coroutine():
64+
return True # pragma: no cover
65+
66+
assert not isawaitable(some_coroutine)
67+
assert not is_awaitable(some_coroutine)
68+
69+
@mark.filterwarnings("ignore::RuntimeWarning")
70+
def recognizes_a_coroutine_object():
71+
async def some_coroutine():
72+
return False # pragma: no cover
73+
74+
assert isawaitable(some_coroutine())
75+
assert is_awaitable(some_coroutine())
76+
77+
@mark.filterwarnings("ignore::RuntimeWarning")
78+
@mark.filterwarnings("ignore::DeprecationWarning")
79+
def recognizes_an_old_style_coroutine():
80+
@asyncio.coroutine
81+
def some_old_style_coroutine():
82+
yield False # pragma: no cover
83+
84+
assert is_awaitable(some_old_style_coroutine())
85+
assert is_awaitable(some_old_style_coroutine())
86+
87+
@mark.filterwarnings("ignore::RuntimeWarning")
88+
def recognizes_a_future_object():
89+
async def some_coroutine():
90+
return False # pragma: no cover
91+
92+
some_future = asyncio.ensure_future(some_coroutine())
93+
94+
assert is_awaitable(some_future)
95+
assert is_awaitable(some_future)
96+
97+
@mark.filterwarnings("ignore::RuntimeWarning")
98+
def declines_an_async_generator():
99+
async def some_async_generator():
100+
yield True # pragma: no cover
101+
102+
assert not isawaitable(some_async_generator())
103+
assert not is_awaitable(some_async_generator())

0 commit comments

Comments
 (0)