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