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

1""" 

2Async helper function that are invalid syntax on Python 3.5 and below. 

3 

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. 

7 

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""" 

12 

13 

14import ast 

15import asyncio 

16import inspect 

17from functools import wraps 

18 

19_asyncio_event_loop = None 

20 

21 

22def get_asyncio_loop(): 

23 """asyncio has deprecated get_event_loop 

24 

25 Replicate it here, with our desired semantics: 

26 

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 

32 

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 

41 

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 

49 

50 

51class _AsyncIORunner: 

52 def __call__(self, coro): 

53 """ 

54 Handler for asyncio autoawait 

55 """ 

56 return get_asyncio_loop().run_until_complete(coro) 

57 

58 def __str__(self): 

59 return "asyncio" 

60 

61 

62_asyncio_runner = _AsyncIORunner() 

63 

64 

65class _AsyncIOProxy: 

66 """Proxy-object for an asyncio 

67 

68 Any coroutine methods will be wrapped in event_loop.run_ 

69 """ 

70 

71 def __init__(self, obj, event_loop): 

72 self._obj = obj 

73 self._event_loop = event_loop 

74 

75 def __repr__(self): 

76 return f"<_AsyncIOProxy({self._obj!r})>" 

77 

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) 

89 

90 return _wrapped 

91 else: 

92 return attr 

93 

94 def __dir__(self): 

95 return dir(self._obj) 

96 

97 

98def _curio_runner(coroutine): 

99 """ 

100 handler for curio autoawait 

101 """ 

102 import curio 

103 

104 return curio.run(coroutine) 

105 

106 

107def _trio_runner(async_fn): 

108 import trio 

109 

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 

116 

117 return trio.run(loc, async_fn) 

118 

119 

120def _pseudo_sync_runner(coro): 

121 """ 

122 A runner that does not really allow async execution, and just advance the coroutine. 

123 

124 See discussion in https://github.com/python-trio/trio/issues/608, 

125 

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 ) 

137 

138 

139def _should_be_async(cell: str) -> bool: 

140 """Detect if a block of code need to be wrapped in an `async def` 

141 

142 Attempt to parse the block of code, it it compile we're fine. 

143 Otherwise we wrap if and try to compile. 

144 

145 If it works, assume it should be async. Otherwise Return False. 

146 

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