| """ |
| Async helper function that are invalid syntax on Python 3.5 and below. |
| |
| 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. |
| |
| 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. |
| """ |
|
|
| import ast |
| import asyncio |
| import inspect |
| from functools import wraps |
|
|
| _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: |
| |
| |
| pass |
|
|
| |
| |
| |
| 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 |
|
|
|
|
| class _AsyncIORunner: |
| def __call__(self, coro): |
| """ |
| Handler for asyncio autoawait |
| """ |
| return get_asyncio_loop().run_until_complete(coro) |
|
|
| def __str__(self): |
| return "asyncio" |
|
|
|
|
| _asyncio_runner = _AsyncIORunner() |
|
|
|
|
| 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): |
| |
| |
| @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) |
|
|
|
|
| def _curio_runner(coroutine): |
| """ |
| handler for curio autoawait |
| """ |
| import curio |
|
|
| return curio.run(coroutine) |
|
|
|
|
| def _trio_runner(async_fn): |
| import trio |
|
|
| 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 |
|
|
| 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: |
| |
| raise RuntimeError( |
| "{coro_name!r} needs a real async loop".format(coro_name=coro.__name__) |
| ) |
|
|
|
|
| def _should_be_async(cell: str) -> bool: |
| """Detect if a block of code needs to be wrapped in an `async def` |
| |
| If the code block has a top-level return statement or is otherwise |
| invalid, `False` will be returned. |
| """ |
| try: |
| code = compile( |
| cell, "<>", "exec", flags=getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0) |
| ) |
| return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE |
| except (SyntaxError, ValueError, MemoryError): |
| return False |
|
|