##// END OF EJS Templates
some more fixes for 3.8
Matthias Bussonnier -
Show More
@@ -1,171 +1,173 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 import inspect
17 from textwrap import dedent, indent
17 from textwrap import dedent, indent
18
18
19
19
20 class _AsyncIORunner:
20 class _AsyncIORunner:
21
21
22 def __call__(self, coro):
22 def __call__(self, coro):
23 """
23 """
24 Handler for asyncio autoawait
24 Handler for asyncio autoawait
25 """
25 """
26 import asyncio
26 import asyncio
27
27
28 return asyncio.get_event_loop().run_until_complete(coro)
28 return asyncio.get_event_loop().run_until_complete(coro)
29
29
30 def __str__(self):
30 def __str__(self):
31 return 'asyncio'
31 return 'asyncio'
32
32
33 _asyncio_runner = _AsyncIORunner()
33 _asyncio_runner = _AsyncIORunner()
34
34
35
35
36 def _curio_runner(coroutine):
36 def _curio_runner(coroutine):
37 """
37 """
38 handler for curio autoawait
38 handler for curio autoawait
39 """
39 """
40 import curio
40 import curio
41
41
42 return curio.run(coroutine)
42 return curio.run(coroutine)
43
43
44
44
45 def _trio_runner(async_fn):
45 def _trio_runner(async_fn):
46 import trio
46 import trio
47
47
48 async def loc(coro):
48 async def loc(coro):
49 """
49 """
50 We need the dummy no-op async def to protect from
50 We need the dummy no-op async def to protect from
51 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
52 """
52 """
53 return await coro
53 return await coro
54
54
55 return trio.run(loc, async_fn)
55 return trio.run(loc, async_fn)
56
56
57
57
58 def _pseudo_sync_runner(coro):
58 def _pseudo_sync_runner(coro):
59 """
59 """
60 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.
61
61
62 See discussion in https://github.com/python-trio/trio/issues/608,
62 See discussion in https://github.com/python-trio/trio/issues/608,
63
63
64 Credit to Nathaniel Smith
64 Credit to Nathaniel Smith
65
65
66 """
66 """
67 try:
67 try:
68 coro.send(None)
68 coro.send(None)
69 except StopIteration as exc:
69 except StopIteration as exc:
70 return exc.value
70 return exc.value
71 else:
71 else:
72 # 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.
73 raise RuntimeError(
73 raise RuntimeError(
74 "{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__)
75 )
75 )
76
76
77
77
78 def _asyncify(code: str) -> str:
78 def _asyncify(code: str) -> str:
79 """wrap code in async def definition.
79 """wrap code in async def definition.
80
80
81 And setup a bit of context to run it later.
81 And setup a bit of context to run it later.
82 """
82 """
83 res = dedent(
83 res = dedent(
84 """
84 """
85 async def __wrapper__():
85 async def __wrapper__():
86 try:
86 try:
87 {usercode}
87 {usercode}
88 finally:
88 finally:
89 locals()
89 locals()
90 """
90 """
91 ).format(usercode=indent(code, " " * 8))
91 ).format(usercode=indent(code, " " * 8))
92 return res
92 return res
93
93
94
94
95 class _AsyncSyntaxErrorVisitor(ast.NodeVisitor):
95 class _AsyncSyntaxErrorVisitor(ast.NodeVisitor):
96 """
96 """
97 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
98 the implementation involves wrapping the repl in an async function, it
98 the implementation involves wrapping the repl in an async function, it
99 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)
100 """
100 """
101 def __init__(self):
101 def __init__(self):
102 if sys.version_info >= (3,8):
102 if sys.version_info >= (3,8):
103 raise ValueError('DEPRECATED in Python 3.8+')
103 raise ValueError('DEPRECATED in Python 3.8+')
104 self.depth = 0
104 self.depth = 0
105 super().__init__()
105 super().__init__()
106
106
107 def generic_visit(self, node):
107 def generic_visit(self, node):
108 func_types = (ast.FunctionDef, ast.AsyncFunctionDef)
108 func_types = (ast.FunctionDef, ast.AsyncFunctionDef)
109 invalid_types_by_depth = {
109 invalid_types_by_depth = {
110 0: (ast.Return, ast.Yield, ast.YieldFrom),
110 0: (ast.Return, ast.Yield, ast.YieldFrom),
111 1: (ast.Nonlocal,)
111 1: (ast.Nonlocal,)
112 }
112 }
113
113
114 should_traverse = self.depth < max(invalid_types_by_depth.keys())
114 should_traverse = self.depth < max(invalid_types_by_depth.keys())
115 if isinstance(node, func_types) and should_traverse:
115 if isinstance(node, func_types) and should_traverse:
116 self.depth += 1
116 self.depth += 1
117 super().generic_visit(node)
117 super().generic_visit(node)
118 self.depth -= 1
118 self.depth -= 1
119 elif isinstance(node, invalid_types_by_depth[self.depth]):
119 elif isinstance(node, invalid_types_by_depth[self.depth]):
120 raise SyntaxError()
120 raise SyntaxError()
121 else:
121 else:
122 super().generic_visit(node)
122 super().generic_visit(node)
123
123
124
124
125 def _async_parse_cell(cell: str) -> ast.AST:
125 def _async_parse_cell(cell: str) -> ast.AST:
126 """
126 """
127 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
128 is a syntax error at the parse stage.
128 is a syntax error at the parse stage.
129
129
130 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
131 of a function were not a syntax error.
131 of a function were not a syntax error.
132 """
132 """
133 if sys.version_info < (3, 7):
133 if sys.version_info < (3, 7):
134 # Prior to 3.7 you need to asyncify before parse
134 # Prior to 3.7 you need to asyncify before parse
135 wrapped_parse_tree = ast.parse(_asyncify(cell))
135 wrapped_parse_tree = ast.parse(_asyncify(cell))
136 return wrapped_parse_tree.body[0].body[0]
136 return wrapped_parse_tree.body[0].body[0]
137 else:
137 else:
138 return ast.parse(cell)
138 return ast.parse(cell)
139
139
140
140
141 def _should_be_async(cell: str) -> bool:
141 def _should_be_async(cell: str) -> bool:
142 """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`
143
143
144 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.
145 Otherwise we wrap if and try to compile.
145 Otherwise we wrap if and try to compile.
146
146
147 If it works, assume it should be async. Otherwise Return False.
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 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.
151 """
151 """
152 if sys.version_info > (3, 8):
152 if sys.version_info > (3, 8):
153 try:
153 code = compile(cell, "<>", "exec", flags=getattr(ast,'PyCF_ALLOW_TOP_LEVEL_AWAIT', 0x0))
154 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 return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE
155
156 except SyntaxError:
157 return False
156 try:
158 try:
157 # we can't limit ourself to ast.parse, as it __accepts__ to parse on
159 # we can't limit ourself to ast.parse, as it __accepts__ to parse on
158 # 3.7+, but just does not _compile_
160 # 3.7+, but just does not _compile_
159 code = compile(cell, "<>", "exec")
161 code = compile(cell, "<>", "exec")
160 except SyntaxError:
162 except SyntaxError:
161 try:
163 try:
162 parse_tree = _async_parse_cell(cell)
164 parse_tree = _async_parse_cell(cell)
163
165
164 # Raise a SyntaxError if there are top-level return or yields
166 # Raise a SyntaxError if there are top-level return or yields
165 v = _AsyncSyntaxErrorVisitor()
167 v = _AsyncSyntaxErrorVisitor()
166 v.visit(parse_tree)
168 v.visit(parse_tree)
167
169
168 except SyntaxError:
170 except SyntaxError:
169 return False
171 return False
170 return True
172 return True
171 return False
173 return False
@@ -1,155 +1,160 b''
1 """Compiler tools with improved interactive support.
1 """Compiler tools with improved interactive support.
2
2
3 Provides compilation machinery similar to codeop, but with caching support so
3 Provides compilation machinery similar to codeop, but with caching support so
4 we can provide interactive tracebacks.
4 we can provide interactive tracebacks.
5
5
6 Authors
6 Authors
7 -------
7 -------
8 * Robert Kern
8 * Robert Kern
9 * Fernando Perez
9 * Fernando Perez
10 * Thomas Kluyver
10 * Thomas Kluyver
11 """
11 """
12
12
13 # Note: though it might be more natural to name this module 'compiler', that
13 # Note: though it might be more natural to name this module 'compiler', that
14 # name is in the stdlib and name collisions with the stdlib tend to produce
14 # name is in the stdlib and name collisions with the stdlib tend to produce
15 # weird problems (often with third-party tools).
15 # weird problems (often with third-party tools).
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Copyright (C) 2010-2011 The IPython Development Team.
18 # Copyright (C) 2010-2011 The IPython Development Team.
19 #
19 #
20 # Distributed under the terms of the BSD License.
20 # Distributed under the terms of the BSD License.
21 #
21 #
22 # The full license is in the file COPYING.txt, distributed with this software.
22 # The full license is in the file COPYING.txt, distributed with this software.
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Imports
26 # Imports
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 # Stdlib imports
29 # Stdlib imports
30 import __future__
30 import __future__
31 from ast import PyCF_ONLY_AST
31 from ast import PyCF_ONLY_AST
32 import codeop
32 import codeop
33 import functools
33 import functools
34 import hashlib
34 import hashlib
35 import linecache
35 import linecache
36 import operator
36 import operator
37 import time
37 import time
38 from contextlib import contextmanager
38 from contextlib import contextmanager
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Constants
41 # Constants
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 # Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h,
44 # Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h,
45 # this is used as a bitmask to extract future-related code flags.
45 # this is used as a bitmask to extract future-related code flags.
46 PyCF_MASK = functools.reduce(operator.or_,
46 PyCF_MASK = functools.reduce(operator.or_,
47 (getattr(__future__, fname).compiler_flag
47 (getattr(__future__, fname).compiler_flag
48 for fname in __future__.all_feature_names))
48 for fname in __future__.all_feature_names))
49
49
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51 # Local utilities
51 # Local utilities
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53
53
54 def code_name(code, number=0):
54 def code_name(code, number=0):
55 """ Compute a (probably) unique name for code for caching.
55 """ Compute a (probably) unique name for code for caching.
56
56
57 This now expects code to be unicode.
57 This now expects code to be unicode.
58 """
58 """
59 hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest()
59 hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest()
60 # Include the number and 12 characters of the hash in the name. It's
60 # Include the number and 12 characters of the hash in the name. It's
61 # pretty much impossible that in a single session we'll have collisions
61 # pretty much impossible that in a single session we'll have collisions
62 # even with truncated hashes, and the full one makes tracebacks too long
62 # even with truncated hashes, and the full one makes tracebacks too long
63 return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12])
63 return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12])
64
64
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66 # Classes and functions
66 # Classes and functions
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68
68
69 class CachingCompiler(codeop.Compile):
69 class CachingCompiler(codeop.Compile):
70 """A compiler that caches code compiled from interactive statements.
70 """A compiler that caches code compiled from interactive statements.
71 """
71 """
72
72
73 def __init__(self):
73 def __init__(self):
74 codeop.Compile.__init__(self)
74 codeop.Compile.__init__(self)
75
75
76 # This is ugly, but it must be done this way to allow multiple
76 # This is ugly, but it must be done this way to allow multiple
77 # simultaneous ipython instances to coexist. Since Python itself
77 # simultaneous ipython instances to coexist. Since Python itself
78 # directly accesses the data structures in the linecache module, and
78 # directly accesses the data structures in the linecache module, and
79 # the cache therein is global, we must work with that data structure.
79 # the cache therein is global, we must work with that data structure.
80 # We must hold a reference to the original checkcache routine and call
80 # We must hold a reference to the original checkcache routine and call
81 # that in our own check_cache() below, but the special IPython cache
81 # that in our own check_cache() below, but the special IPython cache
82 # must also be shared by all IPython instances. If we were to hold
82 # must also be shared by all IPython instances. If we were to hold
83 # separate caches (one in each CachingCompiler instance), any call made
83 # separate caches (one in each CachingCompiler instance), any call made
84 # by Python itself to linecache.checkcache() would obliterate the
84 # by Python itself to linecache.checkcache() would obliterate the
85 # cached data from the other IPython instances.
85 # cached data from the other IPython instances.
86 if not hasattr(linecache, '_ipython_cache'):
86 if not hasattr(linecache, '_ipython_cache'):
87 linecache._ipython_cache = {}
87 linecache._ipython_cache = {}
88 if not hasattr(linecache, '_checkcache_ori'):
88 if not hasattr(linecache, '_checkcache_ori'):
89 linecache._checkcache_ori = linecache.checkcache
89 linecache._checkcache_ori = linecache.checkcache
90 # Now, we must monkeypatch the linecache directly so that parts of the
90 # Now, we must monkeypatch the linecache directly so that parts of the
91 # stdlib that call it outside our control go through our codepath
91 # stdlib that call it outside our control go through our codepath
92 # (otherwise we'd lose our tracebacks).
92 # (otherwise we'd lose our tracebacks).
93 linecache.checkcache = check_linecache_ipython
93 linecache.checkcache = check_linecache_ipython
94
94
95
95
96 def ast_parse(self, source, filename='<unknown>', symbol='exec'):
96 def ast_parse(self, source, filename='<unknown>', symbol='exec'):
97 """Parse code to an AST with the current compiler flags active.
97 """Parse code to an AST with the current compiler flags active.
98
98
99 Arguments are exactly the same as ast.parse (in the standard library),
99 Arguments are exactly the same as ast.parse (in the standard library),
100 and are passed to the built-in compile function."""
100 and are passed to the built-in compile function."""
101 return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1)
101 return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1)
102
102
103 def reset_compiler_flags(self):
103 def reset_compiler_flags(self):
104 """Reset compiler flags to default state."""
104 """Reset compiler flags to default state."""
105 # This value is copied from codeop.Compile.__init__, so if that ever
105 # This value is copied from codeop.Compile.__init__, so if that ever
106 # changes, it will need to be updated.
106 # changes, it will need to be updated.
107 self.flags = codeop.PyCF_DONT_IMPLY_DEDENT
107 self.flags = codeop.PyCF_DONT_IMPLY_DEDENT
108
108
109 @property
109 @property
110 def compiler_flags(self):
110 def compiler_flags(self):
111 """Flags currently active in the compilation process.
111 """Flags currently active in the compilation process.
112 """
112 """
113 return self.flags
113 return self.flags
114
114
115 def cache(self, code, number=0):
115 def cache(self, code, number=0):
116 """Make a name for a block of code, and cache the code.
116 """Make a name for a block of code, and cache the code.
117
117
118 Parameters
118 Parameters
119 ----------
119 ----------
120 code : str
120 code : str
121 The Python source code to cache.
121 The Python source code to cache.
122 number : int
122 number : int
123 A number which forms part of the code's name. Used for the execution
123 A number which forms part of the code's name. Used for the execution
124 counter.
124 counter.
125
125
126 Returns
126 Returns
127 -------
127 -------
128 The name of the cached code (as a string). Pass this as the filename
128 The name of the cached code (as a string). Pass this as the filename
129 argument to compilation, so that tracebacks are correctly hooked up.
129 argument to compilation, so that tracebacks are correctly hooked up.
130 """
130 """
131 name = code_name(code, number)
131 name = code_name(code, number)
132 entry = (len(code), time.time(),
132 entry = (len(code), time.time(),
133 [line+'\n' for line in code.splitlines()], name)
133 [line+'\n' for line in code.splitlines()], name)
134 linecache.cache[name] = entry
134 linecache.cache[name] = entry
135 linecache._ipython_cache[name] = entry
135 linecache._ipython_cache[name] = entry
136 return name
136 return name
137
137
138 @contextmanager
138 @contextmanager
139 def extra_flags(self, flags):
139 def extra_flags(self, flags):
140 old_flags = self.flags
140 ## bits that we'll set to 1
141 turn_on_bits = ~self.flags & flags
142
143
141 self.flags = self.flags | flags
144 self.flags = self.flags | flags
142 try:
145 try:
143 yield
146 yield
144 finally:
147 finally:
145 self.flags = old_flags
148 # turn off only the bits we turned on so that something like
149 # __future__ that set flags stays.
150 self.flags &= ~turn_on_bits
146
151
147
152
148 def check_linecache_ipython(*args):
153 def check_linecache_ipython(*args):
149 """Call linecache.checkcache() safely protecting our cached values.
154 """Call linecache.checkcache() safely protecting our cached values.
150 """
155 """
151 # First call the original checkcache as intended
156 # First call the original checkcache as intended
152 linecache._checkcache_ori(*args)
157 linecache._checkcache_ori(*args)
153 # Then, update back the cache with our data, so that tracebacks related
158 # Then, update back the cache with our data, so that tracebacks related
154 # to our compiled codes can be produced.
159 # to our compiled codes can be produced.
155 linecache.cache.update(linecache._ipython_cache)
160 linecache.cache.update(linecache._ipython_cache)
General Comments 0
You need to be logged in to leave comments. Login now