##// END OF EJS Templates
Again
Again

File last commit:

r27387:ce62a7a4
r27902:33f18f7c
Show More
async_helpers.py
156 lines | 4.2 KiB | text/x-python | PythonLexer
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463 """
Async helper function that are invalid syntax on Python 3.5 and below.
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
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
Prototype async REPL using IPython, take III...
r24463
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
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
Prototype async REPL using IPython, take III...
r24463 """
import ast
Min RK
avoid deprecated asyncio.get_event_loop...
r27192 import asyncio
Matthias Bussonnier
workign async with
r25022 import inspect
Min RK
avoid deprecated get_event_loop...
r27387 from functools import wraps
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463
Min RK
avoid deprecated get_event_loop...
r27387 _asyncio_event_loop = None
def get_asyncio_loop():
"""asyncio has deprecated get_event_loop
Replicate it here, with our desired semantics:
- always returns a valid, not-closed loop
- not thread-local like asyncio's,
because we only want one loop for IPython
- if called from inside a coroutine (e.g. in ipykernel),
return the running loop
.. versionadded:: 8.0
"""
try:
return asyncio.get_running_loop()
except RuntimeError:
# not inside a coroutine,
# track our own global
pass
# not thread-local like asyncio's,
# because we only track one event loop to run for IPython itself,
# always in the main thread.
global _asyncio_event_loop
if _asyncio_event_loop is None or _asyncio_event_loop.is_closed():
_asyncio_event_loop = asyncio.new_event_loop()
return _asyncio_event_loop
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463
Min RK
avoid deprecated asyncio.get_event_loop...
r27192
Min RK
avoid deprecated get_event_loop...
r27387 class _AsyncIORunner:
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
r24490 def __call__(self, coro):
"""
Handler for asyncio autoawait
"""
Min RK
avoid deprecated get_event_loop...
r27387 return get_asyncio_loop().run_until_complete(coro)
Matthias Bussonnier
reformat with black
r24474
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
r24490 def __str__(self):
Matthias Bussonnier
Remove pre-3.8 code path
r27150 return "asyncio"
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
r24490
_asyncio_runner = _AsyncIORunner()
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463
Min RK
avoid deprecated get_event_loop...
r27387 class _AsyncIOProxy:
"""Proxy-object for an asyncio
Any coroutine methods will be wrapped in event_loop.run_
"""
def __init__(self, obj, event_loop):
self._obj = obj
self._event_loop = event_loop
def __repr__(self):
return f"<_AsyncIOProxy({self._obj!r})>"
def __getattr__(self, key):
attr = getattr(self._obj, key)
if inspect.iscoroutinefunction(attr):
# if it's a coroutine method,
# return a threadsafe wrapper onto the _current_ asyncio loop
@wraps(attr)
def _wrapped(*args, **kwargs):
concurrent_future = asyncio.run_coroutine_threadsafe(
attr(*args, **kwargs), self._event_loop
)
return asyncio.wrap_future(concurrent_future)
return _wrapped
else:
return attr
def __dir__(self):
return dir(self._obj)
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463 def _curio_runner(coroutine):
"""
handler for curio autoawait
"""
import curio
Matthias Bussonnier
reformat with black
r24474
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463 return curio.run(coroutine)
Matthias Bussonnier
Add pseudo sync mode
r24481 def _trio_runner(async_fn):
Matthias Bussonnier
att triio runner at top level now that 3.4 drop + docs
r24480 import trio
Matthias Bussonnier
docs cleanup, reformat code, remove dead code.
r24490
Matthias Bussonnier
att triio runner at top level now that 3.4 drop + docs
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
docs cleanup, reformat code, remove dead code.
r24490
Matthias Bussonnier
Add pseudo sync mode
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
docs cleanup, reformat code, remove dead code.
r24490 raise RuntimeError(
"{coro_name!r} needs a real async loop".format(coro_name=coro.__name__)
)
Matthias Bussonnier
Prototype async REPL using IPython, take III...
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
catch `MemoryError` in compiling
r25429 Not handled yet: If the block of code has a return statement as the top
Matthias Bussonnier
Prototype async REPL using IPython, take III...
r24463 level, it will be seen as async. This is a know limitation.
"""
try:
Matthias Bussonnier
Remove pre-3.8 code path
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
catch `MemoryError` in compiling
r25429 except (SyntaxError, MemoryError):
Matthias Bussonnier
Remove pre-3.8 code path
r27150 return False