##// 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 Async helper function that are invalid syntax on Python 3.5 and below.
2 Async helper function that are invalid syntax on Python 3.5 and below.
3
3
4 This code is best effort, and may have edge cases not behaving as expected. In
4 This code is best effort, and may have edge cases not behaving as expected. In
5 particular it contain a number of heuristics to detect whether code is
5 particular it contain a number of heuristics to detect whether code is
6 effectively async and need to run in an event loop or not.
6 effectively async and need to run in an event loop or not.
7
7
8 Some constructs (like top-level `return`, or `yield`) are taken care of
8 Some constructs (like top-level `return`, or `yield`) are taken care of
9 explicitly to actually raise a SyntaxError and stay as close as possible to
9 explicitly to actually raise a SyntaxError and stay as close as possible to
10 Python semantics.
10 Python semantics.
11 """
11 """
12
12
13
13
14 import ast
14 import ast
15 import sys
16 import inspect
15 import inspect
17 from textwrap import dedent, indent
18
16
19
17
20 class _AsyncIORunner:
18 class _AsyncIORunner:
21
22 def __call__(self, coro):
19 def __call__(self, coro):
23 """
20 """
24 Handler for asyncio autoawait
21 Handler for asyncio autoawait
25 """
22 """
26 import asyncio
23 import asyncio
27
24
28 return asyncio.get_event_loop_policy().get_event_loop().run_until_complete(coro)
25 return asyncio.get_event_loop_policy().get_event_loop().run_until_complete(coro)
29
26
30 def __str__(self):
27 def __str__(self):
31 return 'asyncio'
28 return "asyncio"
29
32
30
33 _asyncio_runner = _AsyncIORunner()
31 _asyncio_runner = _AsyncIORunner()
34
32
35
33
36 def _curio_runner(coroutine):
34 def _curio_runner(coroutine):
37 """
35 """
38 handler for curio autoawait
36 handler for curio autoawait
39 """
37 """
40 import curio
38 import curio
41
39
42 return curio.run(coroutine)
40 return curio.run(coroutine)
43
41
44
42
45 def _trio_runner(async_fn):
43 def _trio_runner(async_fn):
46 import trio
44 import trio
47
45
48 async def loc(coro):
46 async def loc(coro):
49 """
47 """
50 We need the dummy no-op async def to protect from
48 We need the dummy no-op async def to protect from
51 trio's internal. See https://github.com/python-trio/trio/issues/89
49 trio's internal. See https://github.com/python-trio/trio/issues/89
52 """
50 """
53 return await coro
51 return await coro
54
52
55 return trio.run(loc, async_fn)
53 return trio.run(loc, async_fn)
56
54
57
55
58 def _pseudo_sync_runner(coro):
56 def _pseudo_sync_runner(coro):
59 """
57 """
60 A runner that does not really allow async execution, and just advance the coroutine.
58 A runner that does not really allow async execution, and just advance the coroutine.
61
59
62 See discussion in https://github.com/python-trio/trio/issues/608,
60 See discussion in https://github.com/python-trio/trio/issues/608,
63
61
64 Credit to Nathaniel Smith
62 Credit to Nathaniel Smith
65 """
63 """
66 try:
64 try:
67 coro.send(None)
65 coro.send(None)
68 except StopIteration as exc:
66 except StopIteration as exc:
69 return exc.value
67 return exc.value
70 else:
68 else:
71 # TODO: do not raise but return an execution result with the right info.
69 # TODO: do not raise but return an execution result with the right info.
72 raise RuntimeError(
70 raise RuntimeError(
73 "{coro_name!r} needs a real async loop".format(coro_name=coro.__name__)
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 def _should_be_async(cell: str) -> bool:
75 def _should_be_async(cell: str) -> bool:
141 """Detect if a block of code need to be wrapped in an `async def`
76 """Detect if a block of code need to be wrapped in an `async def`
142
77
143 Attempt to parse the block of code, it it compile we're fine.
78 Attempt to parse the block of code, it it compile we're fine.
144 Otherwise we wrap if and try to compile.
79 Otherwise we wrap if and try to compile.
145
80
146 If it works, assume it should be async. Otherwise Return False.
81 If it works, assume it should be async. Otherwise Return False.
147
82
148 Not handled yet: If the block of code has a return statement as the top
83 Not handled yet: If the block of code has a return statement as the top
149 level, it will be seen as async. This is a know limitation.
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 try:
86 try:
158 # we can't limit ourself to ast.parse, as it __accepts__ to parse on
87 code = compile(
159 # 3.7+, but just does not _compile_
88 cell, "<>", "exec", flags=getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0)
160 code = compile(cell, "<>", "exec")
89 )
90 return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE
161 except (SyntaxError, MemoryError):
91 except (SyntaxError, MemoryError):
162 try:
92 return False
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
General Comments 0
You need to be logged in to leave comments. Login now