| | """ |
| | 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 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. |
| | |
| | Not handled yet: If the block of code has a return statement as the top |
| | level, it will be seen as async. This is a know limitation. |
| | """ |
| | 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, MemoryError): |
| | return False |
| |
|