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