Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/IPython/core/async_helpers.py: 34%
56 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
1"""
2Async helper function that are invalid syntax on Python 3.5 and below.
4This code is best effort, and may have edge cases not behaving as expected. In
5particular it contain a number of heuristics to detect whether code is
6effectively async and need to run in an event loop or not.
8Some constructs (like top-level `return`, or `yield`) are taken care of
9explicitly to actually raise a SyntaxError and stay as close as possible to
10Python semantics.
11"""
14import ast
15import asyncio
16import inspect
17from functools import wraps
19_asyncio_event_loop = None
22def get_asyncio_loop():
23 """asyncio has deprecated get_event_loop
25 Replicate it here, with our desired semantics:
27 - always returns a valid, not-closed loop
28 - not thread-local like asyncio's,
29 because we only want one loop for IPython
30 - if called from inside a coroutine (e.g. in ipykernel),
31 return the running loop
33 .. versionadded:: 8.0
34 """
35 try:
36 return asyncio.get_running_loop()
37 except RuntimeError:
38 # not inside a coroutine,
39 # track our own global
40 pass
42 # not thread-local like asyncio's,
43 # because we only track one event loop to run for IPython itself,
44 # always in the main thread.
45 global _asyncio_event_loop
46 if _asyncio_event_loop is None or _asyncio_event_loop.is_closed():
47 _asyncio_event_loop = asyncio.new_event_loop()
48 return _asyncio_event_loop
51class _AsyncIORunner:
52 def __call__(self, coro):
53 """
54 Handler for asyncio autoawait
55 """
56 return get_asyncio_loop().run_until_complete(coro)
58 def __str__(self):
59 return "asyncio"
62_asyncio_runner = _AsyncIORunner()
65class _AsyncIOProxy:
66 """Proxy-object for an asyncio
68 Any coroutine methods will be wrapped in event_loop.run_
69 """
71 def __init__(self, obj, event_loop):
72 self._obj = obj
73 self._event_loop = event_loop
75 def __repr__(self):
76 return f"<_AsyncIOProxy({self._obj!r})>"
78 def __getattr__(self, key):
79 attr = getattr(self._obj, key)
80 if inspect.iscoroutinefunction(attr):
81 # if it's a coroutine method,
82 # return a threadsafe wrapper onto the _current_ asyncio loop
83 @wraps(attr)
84 def _wrapped(*args, **kwargs):
85 concurrent_future = asyncio.run_coroutine_threadsafe(
86 attr(*args, **kwargs), self._event_loop
87 )
88 return asyncio.wrap_future(concurrent_future)
90 return _wrapped
91 else:
92 return attr
94 def __dir__(self):
95 return dir(self._obj)
98def _curio_runner(coroutine):
99 """
100 handler for curio autoawait
101 """
102 import curio
104 return curio.run(coroutine)
107def _trio_runner(async_fn):
108 import trio
110 async def loc(coro):
111 """
112 We need the dummy no-op async def to protect from
113 trio's internal. See https://github.com/python-trio/trio/issues/89
114 """
115 return await coro
117 return trio.run(loc, async_fn)
120def _pseudo_sync_runner(coro):
121 """
122 A runner that does not really allow async execution, and just advance the coroutine.
124 See discussion in https://github.com/python-trio/trio/issues/608,
126 Credit to Nathaniel Smith
127 """
128 try:
129 coro.send(None)
130 except StopIteration as exc:
131 return exc.value
132 else:
133 # TODO: do not raise but return an execution result with the right info.
134 raise RuntimeError(
135 "{coro_name!r} needs a real async loop".format(coro_name=coro.__name__)
136 )
139def _should_be_async(cell: str) -> bool:
140 """Detect if a block of code need to be wrapped in an `async def`
142 Attempt to parse the block of code, it it compile we're fine.
143 Otherwise we wrap if and try to compile.
145 If it works, assume it should be async. Otherwise Return False.
147 Not handled yet: If the block of code has a return statement as the top
148 level, it will be seen as async. This is a know limitation.
149 """
150 try:
151 code = compile(
152 cell, "<>", "exec", flags=getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0)
153 )
154 return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE
155 except (SyntaxError, MemoryError):
156 return False