##// END OF EJS Templates
Improve async detection mechanism with blacklist...
Paul Ganssle -
Show More
@@ -1,107 +1,145 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 Known limitation and possible improvement.
4 Known limitation and possible improvement.
5
5
6 Top level code that contain a return statement (instead of, or in addition to
6 Top level code that contain a return statement (instead of, or in addition to
7 await) will be detected as requiring being wrapped in async calls. This should
7 await) will be detected as requiring being wrapped in async calls. This should
8 be prevented as early return will not work.
8 be prevented as early return will not work.
9 """
9 """
10
10
11
11
12 import ast
12 import ast
13 import sys
13 import sys
14 import inspect
14 import inspect
15 from textwrap import dedent, indent
15 from textwrap import dedent, indent
16 from types import CodeType
16 from types import CodeType
17
17
18
18
19 def _asyncio_runner(coro):
19 def _asyncio_runner(coro):
20 """
20 """
21 Handler for asyncio autoawait
21 Handler for asyncio autoawait
22 """
22 """
23 import asyncio
23 import asyncio
24
24
25 return asyncio.get_event_loop().run_until_complete(coro)
25 return asyncio.get_event_loop().run_until_complete(coro)
26
26
27
27
28 def _curio_runner(coroutine):
28 def _curio_runner(coroutine):
29 """
29 """
30 handler for curio autoawait
30 handler for curio autoawait
31 """
31 """
32 import curio
32 import curio
33
33
34 return curio.run(coroutine)
34 return curio.run(coroutine)
35
35
36
36
37 def _trio_runner(async_fn):
37 def _trio_runner(async_fn):
38 import trio
38 import trio
39 async def loc(coro):
39 async def loc(coro):
40 """
40 """
41 We need the dummy no-op async def to protect from
41 We need the dummy no-op async def to protect from
42 trio's internal. See https://github.com/python-trio/trio/issues/89
42 trio's internal. See https://github.com/python-trio/trio/issues/89
43 """
43 """
44 return await coro
44 return await coro
45 return trio.run(loc, async_fn)
45 return trio.run(loc, async_fn)
46
46
47
47
48 def _pseudo_sync_runner(coro):
48 def _pseudo_sync_runner(coro):
49 """
49 """
50 A runner that does not really allow async execution, and just advance the coroutine.
50 A runner that does not really allow async execution, and just advance the coroutine.
51
51
52 See discussion in https://github.com/python-trio/trio/issues/608,
52 See discussion in https://github.com/python-trio/trio/issues/608,
53
53
54 Credit to Nathaniel Smith
54 Credit to Nathaniel Smith
55
55
56 """
56 """
57 try:
57 try:
58 coro.send(None)
58 coro.send(None)
59 except StopIteration as exc:
59 except StopIteration as exc:
60 return exc.value
60 return exc.value
61 else:
61 else:
62 # TODO: do not raise but return an execution result with the right info.
62 # TODO: do not raise but return an execution result with the right info.
63 raise RuntimeError("{coro_name!r} needs a real async loop".format(coro_name=coro.__name__))
63 raise RuntimeError("{coro_name!r} needs a real async loop".format(coro_name=coro.__name__))
64
64
65
65
66 def _asyncify(code: str) -> str:
66 def _asyncify(code: str) -> str:
67 """wrap code in async def definition.
67 """wrap code in async def definition.
68
68
69 And setup a bit of context to run it later.
69 And setup a bit of context to run it later.
70 """
70 """
71 res = dedent(
71 res = dedent(
72 """
72 """
73 async def __wrapper__():
73 async def __wrapper__():
74 try:
74 try:
75 {usercode}
75 {usercode}
76 finally:
76 finally:
77 locals()
77 locals()
78 """
78 """
79 ).format(usercode=indent(code, " " * 8)[8:])
79 ).format(usercode=indent(code, " " * 8)[8:])
80 return res
80 return res
81
81
82
82
83 class _AsyncSyntaxErrorVisitor(ast.NodeVisitor):
84 """
85 Find syntax errors that would be an error in an async repl, but because
86 the implementation involves wrapping the repl in an async function, it
87 is erroneously allowed (e.g. yield or return at the top level)
88 """
89 def generic_visit(self, node):
90 func_types = (ast.FunctionDef, ast.AsyncFunctionDef)
91 invalid_types = (ast.Return, ast.Yield, ast.YieldFrom)
92
93 if isinstance(node, func_types):
94 return # Don't recurse into functions
95 elif isinstance(node, invalid_types):
96 raise SyntaxError()
97 else:
98 super().generic_visit(node)
99
100
101 def _async_parse_cell(cell: str) -> ast.AST:
102 """
103 This is a compatibility shim for pre-3.7 when async outside of a function
104 is a syntax error at the parse stage.
105
106 It will return an abstract syntax tree parsed as if async and await outside
107 of a function were not a syntax error.
108 """
109 if sys.version_info < (3, 7):
110 # Prior to 3.7 you need to asyncify before parse
111 wrapped_parse_tree = ast.parse(_asyncify(cell))
112 return wrapped_parse_tree.body[0].body[0]
113 else:
114 return ast.parse(cell)
115
116
83 def _should_be_async(cell: str) -> bool:
117 def _should_be_async(cell: str) -> bool:
84 """Detect if a block of code need to be wrapped in an `async def`
118 """Detect if a block of code need to be wrapped in an `async def`
85
119
86 Attempt to parse the block of code, it it compile we're fine.
120 Attempt to parse the block of code, it it compile we're fine.
87 Otherwise we wrap if and try to compile.
121 Otherwise we wrap if and try to compile.
88
122
89 If it works, assume it should be async. Otherwise Return False.
123 If it works, assume it should be async. Otherwise Return False.
90
124
91 Not handled yet: If the block of code has a return statement as the top
125 Not handled yet: If the block of code has a return statement as the top
92 level, it will be seen as async. This is a know limitation.
126 level, it will be seen as async. This is a know limitation.
93 """
127 """
94
128
95 try:
129 try:
96 # we can't limit ourself to ast.parse, as it __accepts__ to parse on
130 # we can't limit ourself to ast.parse, as it __accepts__ to parse on
97 # 3.7+, but just does not _compile_
131 # 3.7+, but just does not _compile_
98 compile(cell, "<>", "exec")
132 compile(cell, "<>", "exec")
99 return False
133 return False
100 except SyntaxError:
134 except SyntaxError:
101 try:
135 try:
102 ast.parse(_asyncify(cell))
136 parse_tree = _async_parse_cell(cell)
103 # TODO verify ast has not "top level" return or yield.
137
138 # Raise a SyntaxError if there are top-level return or yields
139 v = _AsyncSyntaxErrorVisitor()
140 v.visit(parse_tree)
141
104 except SyntaxError:
142 except SyntaxError:
105 return False
143 return False
106 return True
144 return True
107 return False
145 return False
General Comments 0
You need to be logged in to leave comments. Login now