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