8000 Merge "open up async greenlet for third parties" into rel_1_4 · sqlalchemy/sqlalchemy@b13cdd1 · GitHub
[go: up one dir, main page]

Skip to content

Commit b13cdd1

Browse files
zzzeekGerrit Code Review
authored andcommitted
Merge "open up async greenlet for third parties" into rel_1_4
2 parents f844193 + 410a4f0 commit b13cdd1

File tree

2 files changed

+35
-24
lines changed

2 files changed

+35
-24
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.. change::
2+
:tags: usecase, engine
3+
4+
Modified the internal representation used for adapting asyncio calls to
5+
greenlets to allow for duck-typed compatibility with third party libraries
6+
that implement SQLAlchemy's "greenlet-to-asyncio" pattern directly.
7+
Running code within a greenlet that features the attribute
8+
``__sqlalchemy_greenlet_provider__ = True`` will allow calls to
9+
:func:`sqlalchemy.util.await_only` directly.
10+

lib/sqlalchemy/util/_concurrency_py3k.py

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@ def is_exit_exception(e):
3737

3838

3939
class _AsyncIoGreenlet(greenlet.greenlet):
40+
41+
__sqlalchemy_greenlet_provider__ = True
42+
4043
def __init__(self, fn, driver):
4144
greenlet.greenlet.__init__(self, fn, driver)
42-
self.driver = driver
4345
if _has_gr_context:
4446
self.gr_context = driver.gr_context
4547

@@ -55,7 +57,7 @@ def await_only(awaitable: Coroutine) -> Any:
5557
"""
5658
# this is called in the context greenlet while running fn
5759
current = greenlet.getcurrent()
58-
if not isinstance(current, _AsyncIoGreenlet):
60+
if not getattr(current, "__sqlalchemy_greenlet_provider__", False):
5961
raise exc.MissingGreenlet(
6062
"greenlet_spawn has not been called; can't call await_only() "
6163
"here. Was IO attempted in an unexpected place?"
@@ -65,7 +67,7 @@ def await_only(awaitable: Coroutine) -> Any:
6567
# a coroutine to run. Once the awaitable is done, the driver greenlet
6668
# switches back to this greenlet with the result of awaitable that is
6769
# then returned to the caller (or raised as error)
68-
return current.driver.switch(awaitable)
70+
return current.parent.switch(awaitable)
6971

7072

7173
def await_fallback(awaitable: Coroutine) -> Any:
@@ -79,7 +81,7 @@ def await_fallback(awaitable: Coroutine) -> Any:
7981
"""
8082
# this is called in the context greenlet while running fn
8183
current = greenlet.getcurrent()
82-
if not isinstance(current, _AsyncIoGreenlet):
84+
if not getattr(current, "__sqlalchemy_greenlet_provider__", False):
8385
loop = get_event_loop()
8486
if loop.is_running():
8587
raise exc.MissingGreenlet(
@@ -89,7 +91,7 @@ def await_fallback(awaitable: Coroutine) -> Any:
8991
)
9092
return loop.run_until_complete(awaitable)
9193

92-
return current.driver.switch(awaitable)
94+
return current.parent.switch(awaitable)
9395

9496

9597
async def greenlet_spawn(
@@ -111,24 +113,21 @@ async def greenlet_spawn(
111113
# coroutine to wait. If the context is dead the function has
112114 8000
# returned, and its result can be returned.
113115
switch_occurred = False
114-
try:
115-
result = context.switch(*args, **kwargs)
116-
while not context.dead:
117-
switch_occurred = True
118-
try:
119-
# wait for a coroutine from await_only and then return its
120-
# result back to it.
121-
value = await result
122-
except BaseException:
123-
# this allows an exception to be raised within
124-
# the moderated greenlet so that it can continue
125-
# its expected flow.
126-
result = context.throw(*sys.exc_info())
127-
else:
128-
result = context.switch(value)
129-
finally:
130-
# clean up to avoid cycle resolution by gc
131-
del context.driver
116+
result = context.switch(*args, **kwargs)
117+
while not context.dead:
118+
switch_occurred = True
119+
try:
120+
# wait for a coroutine from await_only and then return its
121+
# result back to it.
122+
value = await result
123+
except BaseException:
124+
# this allows an exception to be raised within
125+
# the moderated greenlet so that it can continue
126+
# its expected flow.
127+
result = context.throw(*sys.exc_info())
128+
else:
129+
result = context.switch(value)
130+
132131
if _require_await and not switch_occurred:
133132
raise exc.AwaitRequired(
134133
"The current operation required an async execution but none was "
@@ -175,7 +174,9 @@ def _util_async_run(fn, *args, **kwargs):
175174
return loop.run_until_complete(greenlet_spawn(fn, *args, **kwargs))
176175
else:
177176
# allow for a wrapped test function to call another
178-
assert isinstance(greenlet.getcurrent(), _AsyncIoGreenlet)
177+
assert getattr(
178+
greenlet.getcurrent(), "__sqlalchemy_greenlet_provider__", False
179+
)
179180
return fn(*args, **kwargs)
180181

181182

0 commit comments

Comments
 (0)
0