##// END OF EJS Templates
workign async with
Matthias Bussonnier -
Show More
@@ -1,166 +1,171 b''
1 1 """
2 2 Async helper function that are invalid syntax on Python 3.5 and below.
3 3
4 4 This code is best effort, and may have edge cases not behaving as expected. In
5 5 particular it contain a number of heuristics to detect whether code is
6 6 effectively async and need to run in an event loop or not.
7 7
8 8 Some constructs (like top-level `return`, or `yield`) are taken care of
9 9 explicitly to actually raise a SyntaxError and stay as close as possible to
10 10 Python semantics.
11 11 """
12 12
13 13
14 14 import ast
15 15 import sys
16 import inspect
16 17 from textwrap import dedent, indent
17 18
18 19
19 20 class _AsyncIORunner:
20 21
21 22 def __call__(self, coro):
22 23 """
23 24 Handler for asyncio autoawait
24 25 """
25 26 import asyncio
26 27
27 28 return asyncio.get_event_loop().run_until_complete(coro)
28 29
29 30 def __str__(self):
30 31 return 'asyncio'
31 32
32 33 _asyncio_runner = _AsyncIORunner()
33 34
34 35
35 36 def _curio_runner(coroutine):
36 37 """
37 38 handler for curio autoawait
38 39 """
39 40 import curio
40 41
41 42 return curio.run(coroutine)
42 43
43 44
44 45 def _trio_runner(async_fn):
45 46 import trio
46 47
47 48 async def loc(coro):
48 49 """
49 50 We need the dummy no-op async def to protect from
50 51 trio's internal. See https://github.com/python-trio/trio/issues/89
51 52 """
52 53 return await coro
53 54
54 55 return trio.run(loc, async_fn)
55 56
56 57
57 58 def _pseudo_sync_runner(coro):
58 59 """
59 60 A runner that does not really allow async execution, and just advance the coroutine.
60 61
61 62 See discussion in https://github.com/python-trio/trio/issues/608,
62 63
63 64 Credit to Nathaniel Smith
64 65
65 66 """
66 67 try:
67 68 coro.send(None)
68 69 except StopIteration as exc:
69 70 return exc.value
70 71 else:
71 72 # TODO: do not raise but return an execution result with the right info.
72 73 raise RuntimeError(
73 74 "{coro_name!r} needs a real async loop".format(coro_name=coro.__name__)
74 75 )
75 76
76 77
77 78 def _asyncify(code: str) -> str:
78 79 """wrap code in async def definition.
79 80
80 81 And setup a bit of context to run it later.
81 82 """
82 83 res = dedent(
83 84 """
84 85 async def __wrapper__():
85 86 try:
86 87 {usercode}
87 88 finally:
88 89 locals()
89 90 """
90 91 ).format(usercode=indent(code, " " * 8))
91 92 return res
92 93
93 94
94 95 class _AsyncSyntaxErrorVisitor(ast.NodeVisitor):
95 96 """
96 97 Find syntax errors that would be an error in an async repl, but because
97 98 the implementation involves wrapping the repl in an async function, it
98 99 is erroneously allowed (e.g. yield or return at the top level)
99 100 """
100 101 def __init__(self):
102 if sys.version_info >= (3,8):
103 raise ValueError('DEPRECATED in Python 3.8+')
101 104 self.depth = 0
102 105 super().__init__()
103 106
104 107 def generic_visit(self, node):
105 108 func_types = (ast.FunctionDef, ast.AsyncFunctionDef)
106 109 invalid_types_by_depth = {
107 110 0: (ast.Return, ast.Yield, ast.YieldFrom),
108 111 1: (ast.Nonlocal,)
109 112 }
110 113
111 114 should_traverse = self.depth < max(invalid_types_by_depth.keys())
112 115 if isinstance(node, func_types) and should_traverse:
113 116 self.depth += 1
114 117 super().generic_visit(node)
115 118 self.depth -= 1
116 119 elif isinstance(node, invalid_types_by_depth[self.depth]):
117 120 raise SyntaxError()
118 121 else:
119 122 super().generic_visit(node)
120 123
121 124
122 125 def _async_parse_cell(cell: str) -> ast.AST:
123 126 """
124 127 This is a compatibility shim for pre-3.7 when async outside of a function
125 128 is a syntax error at the parse stage.
126 129
127 130 It will return an abstract syntax tree parsed as if async and await outside
128 131 of a function were not a syntax error.
129 132 """
130 133 if sys.version_info < (3, 7):
131 134 # Prior to 3.7 you need to asyncify before parse
132 135 wrapped_parse_tree = ast.parse(_asyncify(cell))
133 136 return wrapped_parse_tree.body[0].body[0]
134 137 else:
135 138 return ast.parse(cell)
136 139
137 140
138 141 def _should_be_async(cell: str) -> bool:
139 142 """Detect if a block of code need to be wrapped in an `async def`
140 143
141 144 Attempt to parse the block of code, it it compile we're fine.
142 145 Otherwise we wrap if and try to compile.
143 146
144 147 If it works, assume it should be async. Otherwise Return False.
145 148
146 149 Not handled yet: If the block of code has a return statement as the top
147 150 level, it will be seen as async. This is a know limitation.
148 151 """
149 152
150 153 try:
151 154 # we can't limit ourself to ast.parse, as it __accepts__ to parse on
152 155 # 3.7+, but just does not _compile_
153 compile(cell, "<>", "exec")
154 return False
156 code = compile(cell, "<>", "exec", flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
157 return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE
155 158 except SyntaxError:
159 #if sys.version_info > (3, 8):
160 # raise
156 161 try:
157 162 parse_tree = _async_parse_cell(cell)
158 163
159 164 # Raise a SyntaxError if there are top-level return or yields
160 165 v = _AsyncSyntaxErrorVisitor()
161 166 v.visit(parse_tree)
162 167
163 168 except SyntaxError:
164 169 return False
165 170 return True
166 171 return False
General Comments 0
You need to be logged in to leave comments. Login now