async_helpers.py
103 lines
| 2.7 KiB
| text/x-python
|
PythonLexer
Matthias Bussonnier
|
r24463 | """ | ||
Async helper function that are invalid syntax on Python 3.5 and below. | ||||
Matthias Bussonnier
|
r24490 | This code is best effort, and may have edge cases not behaving as expected. In | ||
particular it contain a number of heuristics to detect whether code is | ||||
effectively async and need to run in an event loop or not. | ||||
Matthias Bussonnier
|
r24463 | |||
Matthias Bussonnier
|
r24490 | Some constructs (like top-level `return`, or `yield`) are taken care of | ||
explicitly to actually raise a SyntaxError and stay as close as possible to | ||||
Python semantics. | ||||
Matthias Bussonnier
|
r24463 | """ | ||
import ast | ||||
Min RK
|
r27192 | import asyncio | ||
Matthias Bussonnier
|
r25022 | import inspect | ||
Matthias Bussonnier
|
r24463 | |||
Matthias Bussonnier
|
r24490 | class _AsyncIORunner: | ||
Min RK
|
r27192 | def __init__(self): | ||
self._loop = None | ||||
@property | ||||
def loop(self): | ||||
"""Always returns a non-closed event loop""" | ||||
if self._loop is None or self._loop.is_closed(): | ||||
policy = asyncio.get_event_loop_policy() | ||||
self._loop = policy.new_event_loop() | ||||
policy.set_event_loop(self._loop) | ||||
return self._loop | ||||
Matthias Bussonnier
|
r24490 | def __call__(self, coro): | ||
""" | ||||
Handler for asyncio autoawait | ||||
""" | ||||
Min RK
|
r27192 | return self.loop.run_until_complete(coro) | ||
Matthias Bussonnier
|
r24474 | |||
Matthias Bussonnier
|
r24490 | def __str__(self): | ||
Matthias Bussonnier
|
r27150 | return "asyncio" | ||
Matthias Bussonnier
|
r24490 | |||
_asyncio_runner = _AsyncIORunner() | ||||
Matthias Bussonnier
|
r24463 | |||
def _curio_runner(coroutine): | ||||
""" | ||||
handler for curio autoawait | ||||
""" | ||||
import curio | ||||
Matthias Bussonnier
|
r24474 | |||
Matthias Bussonnier
|
r24463 | return curio.run(coroutine) | ||
Matthias Bussonnier
|
r24481 | def _trio_runner(async_fn): | ||
Matthias Bussonnier
|
r24480 | import trio | ||
Matthias Bussonnier
|
r24490 | |||
Matthias Bussonnier
|
r24480 | async def loc(coro): | ||
""" | ||||
We need the dummy no-op async def to protect from | ||||
trio's internal. See https://github.com/python-trio/trio/issues/89 | ||||
""" | ||||
return await coro | ||||
Matthias Bussonnier
|
r24490 | |||
Matthias Bussonnier
|
r24481 | return trio.run(loc, async_fn) | ||
def _pseudo_sync_runner(coro): | ||||
""" | ||||
A runner that does not really allow async execution, and just advance the coroutine. | ||||
See discussion in https://github.com/python-trio/trio/issues/608, | ||||
Credit to Nathaniel Smith | ||||
""" | ||||
try: | ||||
coro.send(None) | ||||
except StopIteration as exc: | ||||
return exc.value | ||||
else: | ||||
# TODO: do not raise but return an execution result with the right info. | ||||
Matthias Bussonnier
|
r24490 | raise RuntimeError( | ||
"{coro_name!r} needs a real async loop".format(coro_name=coro.__name__) | ||||
) | ||||
Matthias Bussonnier
|
r24463 | |||
def _should_be_async(cell: str) -> bool: | ||||
"""Detect if a block of code need to be wrapped in an `async def` | ||||
Attempt to parse the block of code, it it compile we're fine. | ||||
Otherwise we wrap if and try to compile. | ||||
If it works, assume it should be async. Otherwise Return False. | ||||
yangyang
|
r25429 | Not handled yet: If the block of code has a return statement as the top | ||
Matthias Bussonnier
|
r24463 | level, it will be seen as async. This is a know limitation. | ||
""" | ||||
try: | ||||
Matthias Bussonnier
|
r27150 | code = compile( | ||
cell, "<>", "exec", flags=getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0) | ||||
) | ||||
return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE | ||||
yangyang
|
r25429 | except (SyntaxError, MemoryError): | ||
Matthias Bussonnier
|
r27150 | return False | ||