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