""" 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: # 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 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): # 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) 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: # TODO: do not raise but return an execution result with the right info. 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