##// END OF EJS Templates
Reformat files that woudl only touch a single line.
M Bussonnier -
Show More
@@ -1,156 +1,155 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
14 import ast
13 import ast
15 import asyncio
14 import asyncio
16 import inspect
15 import inspect
17 from functools import wraps
16 from functools import wraps
18
17
19 _asyncio_event_loop = None
18 _asyncio_event_loop = None
20
19
21
20
22 def get_asyncio_loop():
21 def get_asyncio_loop():
23 """asyncio has deprecated get_event_loop
22 """asyncio has deprecated get_event_loop
24
23
25 Replicate it here, with our desired semantics:
24 Replicate it here, with our desired semantics:
26
25
27 - always returns a valid, not-closed loop
26 - always returns a valid, not-closed loop
28 - not thread-local like asyncio's,
27 - not thread-local like asyncio's,
29 because we only want one loop for IPython
28 because we only want one loop for IPython
30 - if called from inside a coroutine (e.g. in ipykernel),
29 - if called from inside a coroutine (e.g. in ipykernel),
31 return the running loop
30 return the running loop
32
31
33 .. versionadded:: 8.0
32 .. versionadded:: 8.0
34 """
33 """
35 try:
34 try:
36 return asyncio.get_running_loop()
35 return asyncio.get_running_loop()
37 except RuntimeError:
36 except RuntimeError:
38 # not inside a coroutine,
37 # not inside a coroutine,
39 # track our own global
38 # track our own global
40 pass
39 pass
41
40
42 # not thread-local like asyncio's,
41 # not thread-local like asyncio's,
43 # because we only track one event loop to run for IPython itself,
42 # because we only track one event loop to run for IPython itself,
44 # always in the main thread.
43 # always in the main thread.
45 global _asyncio_event_loop
44 global _asyncio_event_loop
46 if _asyncio_event_loop is None or _asyncio_event_loop.is_closed():
45 if _asyncio_event_loop is None or _asyncio_event_loop.is_closed():
47 _asyncio_event_loop = asyncio.new_event_loop()
46 _asyncio_event_loop = asyncio.new_event_loop()
48 return _asyncio_event_loop
47 return _asyncio_event_loop
49
48
50
49
51 class _AsyncIORunner:
50 class _AsyncIORunner:
52 def __call__(self, coro):
51 def __call__(self, coro):
53 """
52 """
54 Handler for asyncio autoawait
53 Handler for asyncio autoawait
55 """
54 """
56 return get_asyncio_loop().run_until_complete(coro)
55 return get_asyncio_loop().run_until_complete(coro)
57
56
58 def __str__(self):
57 def __str__(self):
59 return "asyncio"
58 return "asyncio"
60
59
61
60
62 _asyncio_runner = _AsyncIORunner()
61 _asyncio_runner = _AsyncIORunner()
63
62
64
63
65 class _AsyncIOProxy:
64 class _AsyncIOProxy:
66 """Proxy-object for an asyncio
65 """Proxy-object for an asyncio
67
66
68 Any coroutine methods will be wrapped in event_loop.run_
67 Any coroutine methods will be wrapped in event_loop.run_
69 """
68 """
70
69
71 def __init__(self, obj, event_loop):
70 def __init__(self, obj, event_loop):
72 self._obj = obj
71 self._obj = obj
73 self._event_loop = event_loop
72 self._event_loop = event_loop
74
73
75 def __repr__(self):
74 def __repr__(self):
76 return f"<_AsyncIOProxy({self._obj!r})>"
75 return f"<_AsyncIOProxy({self._obj!r})>"
77
76
78 def __getattr__(self, key):
77 def __getattr__(self, key):
79 attr = getattr(self._obj, key)
78 attr = getattr(self._obj, key)
80 if inspect.iscoroutinefunction(attr):
79 if inspect.iscoroutinefunction(attr):
81 # if it's a coroutine method,
80 # if it's a coroutine method,
82 # return a threadsafe wrapper onto the _current_ asyncio loop
81 # return a threadsafe wrapper onto the _current_ asyncio loop
83 @wraps(attr)
82 @wraps(attr)
84 def _wrapped(*args, **kwargs):
83 def _wrapped(*args, **kwargs):
85 concurrent_future = asyncio.run_coroutine_threadsafe(
84 concurrent_future = asyncio.run_coroutine_threadsafe(
86 attr(*args, **kwargs), self._event_loop
85 attr(*args, **kwargs), self._event_loop
87 )
86 )
88 return asyncio.wrap_future(concurrent_future)
87 return asyncio.wrap_future(concurrent_future)
89
88
90 return _wrapped
89 return _wrapped
91 else:
90 else:
92 return attr
91 return attr
93
92
94 def __dir__(self):
93 def __dir__(self):
95 return dir(self._obj)
94 return dir(self._obj)
96
95
97
96
98 def _curio_runner(coroutine):
97 def _curio_runner(coroutine):
99 """
98 """
100 handler for curio autoawait
99 handler for curio autoawait
101 """
100 """
102 import curio
101 import curio
103
102
104 return curio.run(coroutine)
103 return curio.run(coroutine)
105
104
106
105
107 def _trio_runner(async_fn):
106 def _trio_runner(async_fn):
108 import trio
107 import trio
109
108
110 async def loc(coro):
109 async def loc(coro):
111 """
110 """
112 We need the dummy no-op async def to protect from
111 We need the dummy no-op async def to protect from
113 trio's internal. See https://github.com/python-trio/trio/issues/89
112 trio's internal. See https://github.com/python-trio/trio/issues/89
114 """
113 """
115 return await coro
114 return await coro
116
115
117 return trio.run(loc, async_fn)
116 return trio.run(loc, async_fn)
118
117
119
118
120 def _pseudo_sync_runner(coro):
119 def _pseudo_sync_runner(coro):
121 """
120 """
122 A runner that does not really allow async execution, and just advance the coroutine.
121 A runner that does not really allow async execution, and just advance the coroutine.
123
122
124 See discussion in https://github.com/python-trio/trio/issues/608,
123 See discussion in https://github.com/python-trio/trio/issues/608,
125
124
126 Credit to Nathaniel Smith
125 Credit to Nathaniel Smith
127 """
126 """
128 try:
127 try:
129 coro.send(None)
128 coro.send(None)
130 except StopIteration as exc:
129 except StopIteration as exc:
131 return exc.value
130 return exc.value
132 else:
131 else:
133 # TODO: do not raise but return an execution result with the right info.
132 # TODO: do not raise but return an execution result with the right info.
134 raise RuntimeError(
133 raise RuntimeError(
135 "{coro_name!r} needs a real async loop".format(coro_name=coro.__name__)
134 "{coro_name!r} needs a real async loop".format(coro_name=coro.__name__)
136 )
135 )
137
136
138
137
139 def _should_be_async(cell: str) -> bool:
138 def _should_be_async(cell: str) -> bool:
140 """Detect if a block of code need to be wrapped in an `async def`
139 """Detect if a block of code need to be wrapped in an `async def`
141
140
142 Attempt to parse the block of code, it it compile we're fine.
141 Attempt to parse the block of code, it it compile we're fine.
143 Otherwise we wrap if and try to compile.
142 Otherwise we wrap if and try to compile.
144
143
145 If it works, assume it should be async. Otherwise Return False.
144 If it works, assume it should be async. Otherwise Return False.
146
145
147 Not handled yet: If the block of code has a return statement as the top
146 Not handled yet: If the block of code has a return statement as the top
148 level, it will be seen as async. This is a know limitation.
147 level, it will be seen as async. This is a know limitation.
149 """
148 """
150 try:
149 try:
151 code = compile(
150 code = compile(
152 cell, "<>", "exec", flags=getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0)
151 cell, "<>", "exec", flags=getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0)
153 )
152 )
154 return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE
153 return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE
155 except (SyntaxError, MemoryError):
154 except (SyntaxError, MemoryError):
156 return False
155 return False
@@ -1,67 +1,68 b''
1 """These kinds of tests are less than ideal, but at least they run.
1 """These kinds of tests are less than ideal, but at least they run.
2
2
3 This was an old test that was being run interactively in the top-level tests/
3 This was an old test that was being run interactively in the top-level tests/
4 directory, which we are removing. For now putting this here ensures at least
4 directory, which we are removing. For now putting this here ensures at least
5 we do run the test, though ultimately this functionality should all be tested
5 we do run the test, though ultimately this functionality should all be tested
6 with better-isolated tests that don't rely on the global instance in iptest.
6 with better-isolated tests that don't rely on the global instance in iptest.
7 """
7 """
8
8 from IPython.core.splitinput import LineInfo
9 from IPython.core.splitinput import LineInfo
9 from IPython.core.prefilter import AutocallChecker
10 from IPython.core.prefilter import AutocallChecker
10
11
11
12
12 def doctest_autocall():
13 def doctest_autocall():
13 """
14 """
14 In [1]: def f1(a,b,c):
15 In [1]: def f1(a,b,c):
15 ...: return a+b+c
16 ...: return a+b+c
16 ...:
17 ...:
17
18
18 In [2]: def f2(a):
19 In [2]: def f2(a):
19 ...: return a + a
20 ...: return a + a
20 ...:
21 ...:
21
22
22 In [3]: def r(x):
23 In [3]: def r(x):
23 ...: return True
24 ...: return True
24 ...:
25 ...:
25
26
26 In [4]: ;f2 a b c
27 In [4]: ;f2 a b c
27 Out[4]: 'a b ca b c'
28 Out[4]: 'a b ca b c'
28
29
29 In [5]: assert _ == "a b ca b c"
30 In [5]: assert _ == "a b ca b c"
30
31
31 In [6]: ,f1 a b c
32 In [6]: ,f1 a b c
32 Out[6]: 'abc'
33 Out[6]: 'abc'
33
34
34 In [7]: assert _ == 'abc'
35 In [7]: assert _ == 'abc'
35
36
36 In [8]: print(_)
37 In [8]: print(_)
37 abc
38 abc
38
39
39 In [9]: /f1 1,2,3
40 In [9]: /f1 1,2,3
40 Out[9]: 6
41 Out[9]: 6
41
42
42 In [10]: assert _ == 6
43 In [10]: assert _ == 6
43
44
44 In [11]: /f2 4
45 In [11]: /f2 4
45 Out[11]: 8
46 Out[11]: 8
46
47
47 In [12]: assert _ == 8
48 In [12]: assert _ == 8
48
49
49 In [12]: del f1, f2
50 In [12]: del f1, f2
50
51
51 In [13]: ,r a
52 In [13]: ,r a
52 Out[13]: True
53 Out[13]: True
53
54
54 In [14]: assert _ == True
55 In [14]: assert _ == True
55
56
56 In [15]: r'a'
57 In [15]: r'a'
57 Out[15]: 'a'
58 Out[15]: 'a'
58
59
59 In [16]: assert _ == 'a'
60 In [16]: assert _ == 'a'
60 """
61 """
61
62
62
63
63 def test_autocall_should_ignore_raw_strings():
64 def test_autocall_should_ignore_raw_strings():
64 line_info = LineInfo("r'a'")
65 line_info = LineInfo("r'a'")
65 pm = ip.prefilter_manager
66 pm = ip.prefilter_manager
66 ac = AutocallChecker(shell=pm.shell, prefilter_manager=pm, config=pm.config)
67 ac = AutocallChecker(shell=pm.shell, prefilter_manager=pm, config=pm.config)
67 assert ac.check(line_info) is None
68 assert ac.check(line_info) is None
@@ -1,711 +1,712 b''
1 """Tests for autoreload extension.
1 """Tests for autoreload extension.
2 """
2 """
3
3 # -----------------------------------------------------------------------------
4 # -----------------------------------------------------------------------------
4 # Copyright (c) 2012 IPython Development Team.
5 # Copyright (c) 2012 IPython Development Team.
5 #
6 #
6 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
7 #
8 #
8 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # -----------------------------------------------------------------------------
10 # -----------------------------------------------------------------------------
10
11
11 # -----------------------------------------------------------------------------
12 # -----------------------------------------------------------------------------
12 # Imports
13 # Imports
13 # -----------------------------------------------------------------------------
14 # -----------------------------------------------------------------------------
14
15
15 import os
16 import os
16 import platform
17 import platform
17 import pytest
18 import pytest
18 import sys
19 import sys
19 import tempfile
20 import tempfile
20 import textwrap
21 import textwrap
21 import shutil
22 import shutil
22 import random
23 import random
23 import time
24 import time
24 import traceback
25 import traceback
25 from io import StringIO
26 from io import StringIO
26 from dataclasses import dataclass
27 from dataclasses import dataclass
27
28
28 import IPython.testing.tools as tt
29 import IPython.testing.tools as tt
29
30
30 from unittest import TestCase
31 from unittest import TestCase
31
32
32 from IPython.extensions.autoreload import AutoreloadMagics
33 from IPython.extensions.autoreload import AutoreloadMagics
33 from IPython.core.events import EventManager, pre_run_cell
34 from IPython.core.events import EventManager, pre_run_cell
34 from IPython.testing.decorators import skipif_not_numpy
35 from IPython.testing.decorators import skipif_not_numpy
35 from IPython.core.interactiveshell import ExecutionInfo
36 from IPython.core.interactiveshell import ExecutionInfo
36
37
37 if platform.python_implementation() == "PyPy":
38 if platform.python_implementation() == "PyPy":
38 pytest.skip(
39 pytest.skip(
39 "Current autoreload implementation is extremely slow on PyPy",
40 "Current autoreload implementation is extremely slow on PyPy",
40 allow_module_level=True,
41 allow_module_level=True,
41 )
42 )
42
43
43 # -----------------------------------------------------------------------------
44 # -----------------------------------------------------------------------------
44 # Test fixture
45 # Test fixture
45 # -----------------------------------------------------------------------------
46 # -----------------------------------------------------------------------------
46
47
47 noop = lambda *a, **kw: None
48 noop = lambda *a, **kw: None
48
49
49
50
50 class FakeShell:
51 class FakeShell:
51 def __init__(self):
52 def __init__(self):
52 self.ns = {}
53 self.ns = {}
53 self.user_ns = self.ns
54 self.user_ns = self.ns
54 self.user_ns_hidden = {}
55 self.user_ns_hidden = {}
55 self.events = EventManager(self, {"pre_run_cell", pre_run_cell})
56 self.events = EventManager(self, {"pre_run_cell", pre_run_cell})
56 self.auto_magics = AutoreloadMagics(shell=self)
57 self.auto_magics = AutoreloadMagics(shell=self)
57 self.events.register("pre_run_cell", self.auto_magics.pre_run_cell)
58 self.events.register("pre_run_cell", self.auto_magics.pre_run_cell)
58
59
59 register_magics = set_hook = noop
60 register_magics = set_hook = noop
60
61
61 def showtraceback(
62 def showtraceback(
62 self,
63 self,
63 exc_tuple=None,
64 exc_tuple=None,
64 filename=None,
65 filename=None,
65 tb_offset=None,
66 tb_offset=None,
66 exception_only=False,
67 exception_only=False,
67 running_compiled_code=False,
68 running_compiled_code=False,
68 ):
69 ):
69 traceback.print_exc()
70 traceback.print_exc()
70
71
71 def run_code(self, code):
72 def run_code(self, code):
72 self.events.trigger(
73 self.events.trigger(
73 "pre_run_cell",
74 "pre_run_cell",
74 ExecutionInfo(
75 ExecutionInfo(
75 raw_cell="",
76 raw_cell="",
76 store_history=False,
77 store_history=False,
77 silent=False,
78 silent=False,
78 shell_futures=False,
79 shell_futures=False,
79 cell_id=None,
80 cell_id=None,
80 ),
81 ),
81 )
82 )
82 exec(code, self.user_ns)
83 exec(code, self.user_ns)
83 self.auto_magics.post_execute_hook()
84 self.auto_magics.post_execute_hook()
84
85
85 def push(self, items):
86 def push(self, items):
86 self.ns.update(items)
87 self.ns.update(items)
87
88
88 def magic_autoreload(self, parameter):
89 def magic_autoreload(self, parameter):
89 self.auto_magics.autoreload(parameter)
90 self.auto_magics.autoreload(parameter)
90
91
91 def magic_aimport(self, parameter, stream=None):
92 def magic_aimport(self, parameter, stream=None):
92 self.auto_magics.aimport(parameter, stream=stream)
93 self.auto_magics.aimport(parameter, stream=stream)
93 self.auto_magics.post_execute_hook()
94 self.auto_magics.post_execute_hook()
94
95
95
96
96 class Fixture(TestCase):
97 class Fixture(TestCase):
97 """Fixture for creating test module files"""
98 """Fixture for creating test module files"""
98
99
99 test_dir = None
100 test_dir = None
100 old_sys_path = None
101 old_sys_path = None
101 filename_chars = "abcdefghijklmopqrstuvwxyz0123456789"
102 filename_chars = "abcdefghijklmopqrstuvwxyz0123456789"
102
103
103 def setUp(self):
104 def setUp(self):
104 self.test_dir = tempfile.mkdtemp()
105 self.test_dir = tempfile.mkdtemp()
105 self.old_sys_path = list(sys.path)
106 self.old_sys_path = list(sys.path)
106 sys.path.insert(0, self.test_dir)
107 sys.path.insert(0, self.test_dir)
107 self.shell = FakeShell()
108 self.shell = FakeShell()
108
109
109 def tearDown(self):
110 def tearDown(self):
110 shutil.rmtree(self.test_dir)
111 shutil.rmtree(self.test_dir)
111 sys.path = self.old_sys_path
112 sys.path = self.old_sys_path
112
113
113 self.test_dir = None
114 self.test_dir = None
114 self.old_sys_path = None
115 self.old_sys_path = None
115 self.shell = None
116 self.shell = None
116
117
117 def get_module(self):
118 def get_module(self):
118 module_name = "tmpmod_" + "".join(random.sample(self.filename_chars, 20))
119 module_name = "tmpmod_" + "".join(random.sample(self.filename_chars, 20))
119 if module_name in sys.modules:
120 if module_name in sys.modules:
120 del sys.modules[module_name]
121 del sys.modules[module_name]
121 file_name = os.path.join(self.test_dir, module_name + ".py")
122 file_name = os.path.join(self.test_dir, module_name + ".py")
122 return module_name, file_name
123 return module_name, file_name
123
124
124 def write_file(self, filename, content):
125 def write_file(self, filename, content):
125 """
126 """
126 Write a file, and force a timestamp difference of at least one second
127 Write a file, and force a timestamp difference of at least one second
127
128
128 Notes
129 Notes
129 -----
130 -----
130 Python's .pyc files record the timestamp of their compilation
131 Python's .pyc files record the timestamp of their compilation
131 with a time resolution of one second.
132 with a time resolution of one second.
132
133
133 Therefore, we need to force a timestamp difference between .py
134 Therefore, we need to force a timestamp difference between .py
134 and .pyc, without having the .py file be timestamped in the
135 and .pyc, without having the .py file be timestamped in the
135 future, and without changing the timestamp of the .pyc file
136 future, and without changing the timestamp of the .pyc file
136 (because that is stored in the file). The only reliable way
137 (because that is stored in the file). The only reliable way
137 to achieve this seems to be to sleep.
138 to achieve this seems to be to sleep.
138 """
139 """
139 content = textwrap.dedent(content)
140 content = textwrap.dedent(content)
140 # Sleep one second + eps
141 # Sleep one second + eps
141 time.sleep(1.05)
142 time.sleep(1.05)
142
143
143 # Write
144 # Write
144 with open(filename, "w", encoding="utf-8") as f:
145 with open(filename, "w", encoding="utf-8") as f:
145 f.write(content)
146 f.write(content)
146
147
147 def new_module(self, code):
148 def new_module(self, code):
148 code = textwrap.dedent(code)
149 code = textwrap.dedent(code)
149 mod_name, mod_fn = self.get_module()
150 mod_name, mod_fn = self.get_module()
150 with open(mod_fn, "w", encoding="utf-8") as f:
151 with open(mod_fn, "w", encoding="utf-8") as f:
151 f.write(code)
152 f.write(code)
152 return mod_name, mod_fn
153 return mod_name, mod_fn
153
154
154
155
155 # -----------------------------------------------------------------------------
156 # -----------------------------------------------------------------------------
156 # Test automatic reloading
157 # Test automatic reloading
157 # -----------------------------------------------------------------------------
158 # -----------------------------------------------------------------------------
158
159
159
160
160 def pickle_get_current_class(obj):
161 def pickle_get_current_class(obj):
161 """
162 """
162 Original issue comes from pickle; hence the name.
163 Original issue comes from pickle; hence the name.
163 """
164 """
164 name = obj.__class__.__name__
165 name = obj.__class__.__name__
165 module_name = getattr(obj, "__module__", None)
166 module_name = getattr(obj, "__module__", None)
166 obj2 = sys.modules[module_name]
167 obj2 = sys.modules[module_name]
167 for subpath in name.split("."):
168 for subpath in name.split("."):
168 obj2 = getattr(obj2, subpath)
169 obj2 = getattr(obj2, subpath)
169 return obj2
170 return obj2
170
171
171
172
172 class TestAutoreload(Fixture):
173 class TestAutoreload(Fixture):
173 def test_reload_enums(self):
174 def test_reload_enums(self):
174 mod_name, mod_fn = self.new_module(
175 mod_name, mod_fn = self.new_module(
175 textwrap.dedent(
176 textwrap.dedent(
176 """
177 """
177 from enum import Enum
178 from enum import Enum
178 class MyEnum(Enum):
179 class MyEnum(Enum):
179 A = 'A'
180 A = 'A'
180 B = 'B'
181 B = 'B'
181 """
182 """
182 )
183 )
183 )
184 )
184 self.shell.magic_autoreload("2")
185 self.shell.magic_autoreload("2")
185 self.shell.magic_aimport(mod_name)
186 self.shell.magic_aimport(mod_name)
186 self.write_file(
187 self.write_file(
187 mod_fn,
188 mod_fn,
188 textwrap.dedent(
189 textwrap.dedent(
189 """
190 """
190 from enum import Enum
191 from enum import Enum
191 class MyEnum(Enum):
192 class MyEnum(Enum):
192 A = 'A'
193 A = 'A'
193 B = 'B'
194 B = 'B'
194 C = 'C'
195 C = 'C'
195 """
196 """
196 ),
197 ),
197 )
198 )
198 with tt.AssertNotPrints(
199 with tt.AssertNotPrints(
199 ("[autoreload of %s failed:" % mod_name), channel="stderr"
200 ("[autoreload of %s failed:" % mod_name), channel="stderr"
200 ):
201 ):
201 self.shell.run_code("pass") # trigger another reload
202 self.shell.run_code("pass") # trigger another reload
202
203
203 def test_reload_class_type(self):
204 def test_reload_class_type(self):
204 self.shell.magic_autoreload("2")
205 self.shell.magic_autoreload("2")
205 mod_name, mod_fn = self.new_module(
206 mod_name, mod_fn = self.new_module(
206 """
207 """
207 class Test():
208 class Test():
208 def meth(self):
209 def meth(self):
209 return "old"
210 return "old"
210 """
211 """
211 )
212 )
212 assert "test" not in self.shell.ns
213 assert "test" not in self.shell.ns
213 assert "result" not in self.shell.ns
214 assert "result" not in self.shell.ns
214
215
215 self.shell.run_code("from %s import Test" % mod_name)
216 self.shell.run_code("from %s import Test" % mod_name)
216 self.shell.run_code("test = Test()")
217 self.shell.run_code("test = Test()")
217
218
218 self.write_file(
219 self.write_file(
219 mod_fn,
220 mod_fn,
220 """
221 """
221 class Test():
222 class Test():
222 def meth(self):
223 def meth(self):
223 return "new"
224 return "new"
224 """,
225 """,
225 )
226 )
226
227
227 test_object = self.shell.ns["test"]
228 test_object = self.shell.ns["test"]
228
229
229 # important to trigger autoreload logic !
230 # important to trigger autoreload logic !
230 self.shell.run_code("pass")
231 self.shell.run_code("pass")
231
232
232 test_class = pickle_get_current_class(test_object)
233 test_class = pickle_get_current_class(test_object)
233 assert isinstance(test_object, test_class)
234 assert isinstance(test_object, test_class)
234
235
235 # extra check.
236 # extra check.
236 self.shell.run_code("import pickle")
237 self.shell.run_code("import pickle")
237 self.shell.run_code("p = pickle.dumps(test)")
238 self.shell.run_code("p = pickle.dumps(test)")
238
239
239 def test_reload_class_attributes(self):
240 def test_reload_class_attributes(self):
240 self.shell.magic_autoreload("2")
241 self.shell.magic_autoreload("2")
241 mod_name, mod_fn = self.new_module(
242 mod_name, mod_fn = self.new_module(
242 textwrap.dedent(
243 textwrap.dedent(
243 """
244 """
244 class MyClass:
245 class MyClass:
245
246
246 def __init__(self, a=10):
247 def __init__(self, a=10):
247 self.a = a
248 self.a = a
248 self.b = 22
249 self.b = 22
249 # self.toto = 33
250 # self.toto = 33
250
251
251 def square(self):
252 def square(self):
252 print('compute square')
253 print('compute square')
253 return self.a*self.a
254 return self.a*self.a
254 """
255 """
255 )
256 )
256 )
257 )
257 self.shell.run_code("from %s import MyClass" % mod_name)
258 self.shell.run_code("from %s import MyClass" % mod_name)
258 self.shell.run_code("first = MyClass(5)")
259 self.shell.run_code("first = MyClass(5)")
259 self.shell.run_code("first.square()")
260 self.shell.run_code("first.square()")
260 with self.assertRaises(AttributeError):
261 with self.assertRaises(AttributeError):
261 self.shell.run_code("first.cube()")
262 self.shell.run_code("first.cube()")
262 with self.assertRaises(AttributeError):
263 with self.assertRaises(AttributeError):
263 self.shell.run_code("first.power(5)")
264 self.shell.run_code("first.power(5)")
264 self.shell.run_code("first.b")
265 self.shell.run_code("first.b")
265 with self.assertRaises(AttributeError):
266 with self.assertRaises(AttributeError):
266 self.shell.run_code("first.toto")
267 self.shell.run_code("first.toto")
267
268
268 # remove square, add power
269 # remove square, add power
269
270
270 self.write_file(
271 self.write_file(
271 mod_fn,
272 mod_fn,
272 textwrap.dedent(
273 textwrap.dedent(
273 """
274 """
274 class MyClass:
275 class MyClass:
275
276
276 def __init__(self, a=10):
277 def __init__(self, a=10):
277 self.a = a
278 self.a = a
278 self.b = 11
279 self.b = 11
279
280
280 def power(self, p):
281 def power(self, p):
281 print('compute power '+str(p))
282 print('compute power '+str(p))
282 return self.a**p
283 return self.a**p
283 """
284 """
284 ),
285 ),
285 )
286 )
286
287
287 self.shell.run_code("second = MyClass(5)")
288 self.shell.run_code("second = MyClass(5)")
288
289
289 for object_name in {"first", "second"}:
290 for object_name in {"first", "second"}:
290 self.shell.run_code(f"{object_name}.power(5)")
291 self.shell.run_code(f"{object_name}.power(5)")
291 with self.assertRaises(AttributeError):
292 with self.assertRaises(AttributeError):
292 self.shell.run_code(f"{object_name}.cube()")
293 self.shell.run_code(f"{object_name}.cube()")
293 with self.assertRaises(AttributeError):
294 with self.assertRaises(AttributeError):
294 self.shell.run_code(f"{object_name}.square()")
295 self.shell.run_code(f"{object_name}.square()")
295 self.shell.run_code(f"{object_name}.b")
296 self.shell.run_code(f"{object_name}.b")
296 self.shell.run_code(f"{object_name}.a")
297 self.shell.run_code(f"{object_name}.a")
297 with self.assertRaises(AttributeError):
298 with self.assertRaises(AttributeError):
298 self.shell.run_code(f"{object_name}.toto")
299 self.shell.run_code(f"{object_name}.toto")
299
300
300 @skipif_not_numpy
301 @skipif_not_numpy
301 def test_comparing_numpy_structures(self):
302 def test_comparing_numpy_structures(self):
302 self.shell.magic_autoreload("2")
303 self.shell.magic_autoreload("2")
303 self.shell.run_code("1+1")
304 self.shell.run_code("1+1")
304 mod_name, mod_fn = self.new_module(
305 mod_name, mod_fn = self.new_module(
305 textwrap.dedent(
306 textwrap.dedent(
306 """
307 """
307 import numpy as np
308 import numpy as np
308 class MyClass:
309 class MyClass:
309 a = (np.array((.1, .2)),
310 a = (np.array((.1, .2)),
310 np.array((.2, .3)))
311 np.array((.2, .3)))
311 """
312 """
312 )
313 )
313 )
314 )
314 self.shell.run_code("from %s import MyClass" % mod_name)
315 self.shell.run_code("from %s import MyClass" % mod_name)
315 self.shell.run_code("first = MyClass()")
316 self.shell.run_code("first = MyClass()")
316
317
317 # change property `a`
318 # change property `a`
318 self.write_file(
319 self.write_file(
319 mod_fn,
320 mod_fn,
320 textwrap.dedent(
321 textwrap.dedent(
321 """
322 """
322 import numpy as np
323 import numpy as np
323 class MyClass:
324 class MyClass:
324 a = (np.array((.3, .4)),
325 a = (np.array((.3, .4)),
325 np.array((.5, .6)))
326 np.array((.5, .6)))
326 """
327 """
327 ),
328 ),
328 )
329 )
329
330
330 with tt.AssertNotPrints(
331 with tt.AssertNotPrints(
331 ("[autoreload of %s failed:" % mod_name), channel="stderr"
332 ("[autoreload of %s failed:" % mod_name), channel="stderr"
332 ):
333 ):
333 self.shell.run_code("pass") # trigger another reload
334 self.shell.run_code("pass") # trigger another reload
334
335
335 def test_autoload_newly_added_objects(self):
336 def test_autoload_newly_added_objects(self):
336 # All of these fail with %autoreload 2
337 # All of these fail with %autoreload 2
337 self.shell.magic_autoreload("3")
338 self.shell.magic_autoreload("3")
338 mod_code = """
339 mod_code = """
339 def func1(): pass
340 def func1(): pass
340 """
341 """
341 mod_name, mod_fn = self.new_module(textwrap.dedent(mod_code))
342 mod_name, mod_fn = self.new_module(textwrap.dedent(mod_code))
342 self.shell.run_code(f"from {mod_name} import *")
343 self.shell.run_code(f"from {mod_name} import *")
343 self.shell.run_code("func1()")
344 self.shell.run_code("func1()")
344 with self.assertRaises(NameError):
345 with self.assertRaises(NameError):
345 self.shell.run_code("func2()")
346 self.shell.run_code("func2()")
346 with self.assertRaises(NameError):
347 with self.assertRaises(NameError):
347 self.shell.run_code("t = Test()")
348 self.shell.run_code("t = Test()")
348 with self.assertRaises(NameError):
349 with self.assertRaises(NameError):
349 self.shell.run_code("number")
350 self.shell.run_code("number")
350
351
351 # ----------- TEST NEW OBJ LOADED --------------------------
352 # ----------- TEST NEW OBJ LOADED --------------------------
352
353
353 new_code = """
354 new_code = """
354 def func1(): pass
355 def func1(): pass
355 def func2(): pass
356 def func2(): pass
356 class Test: pass
357 class Test: pass
357 number = 0
358 number = 0
358 from enum import Enum
359 from enum import Enum
359 class TestEnum(Enum):
360 class TestEnum(Enum):
360 A = 'a'
361 A = 'a'
361 """
362 """
362 self.write_file(mod_fn, textwrap.dedent(new_code))
363 self.write_file(mod_fn, textwrap.dedent(new_code))
363
364
364 # test function now exists in shell's namespace namespace
365 # test function now exists in shell's namespace namespace
365 self.shell.run_code("func2()")
366 self.shell.run_code("func2()")
366 # test function now exists in module's dict
367 # test function now exists in module's dict
367 self.shell.run_code(f"import sys; sys.modules['{mod_name}'].func2()")
368 self.shell.run_code(f"import sys; sys.modules['{mod_name}'].func2()")
368 # test class now exists
369 # test class now exists
369 self.shell.run_code("t = Test()")
370 self.shell.run_code("t = Test()")
370 # test global built-in var now exists
371 # test global built-in var now exists
371 self.shell.run_code("number")
372 self.shell.run_code("number")
372 # test the enumerations gets loaded successfully
373 # test the enumerations gets loaded successfully
373 self.shell.run_code("TestEnum.A")
374 self.shell.run_code("TestEnum.A")
374
375
375 # ----------- TEST NEW OBJ CAN BE CHANGED --------------------
376 # ----------- TEST NEW OBJ CAN BE CHANGED --------------------
376
377
377 new_code = """
378 new_code = """
378 def func1(): return 'changed'
379 def func1(): return 'changed'
379 def func2(): return 'changed'
380 def func2(): return 'changed'
380 class Test:
381 class Test:
381 def new_func(self):
382 def new_func(self):
382 return 'changed'
383 return 'changed'
383 number = 1
384 number = 1
384 from enum import Enum
385 from enum import Enum
385 class TestEnum(Enum):
386 class TestEnum(Enum):
386 A = 'a'
387 A = 'a'
387 B = 'added'
388 B = 'added'
388 """
389 """
389 self.write_file(mod_fn, textwrap.dedent(new_code))
390 self.write_file(mod_fn, textwrap.dedent(new_code))
390 self.shell.run_code("assert func1() == 'changed'")
391 self.shell.run_code("assert func1() == 'changed'")
391 self.shell.run_code("assert func2() == 'changed'")
392 self.shell.run_code("assert func2() == 'changed'")
392 self.shell.run_code("t = Test(); assert t.new_func() == 'changed'")
393 self.shell.run_code("t = Test(); assert t.new_func() == 'changed'")
393 self.shell.run_code("assert number == 1")
394 self.shell.run_code("assert number == 1")
394 if sys.version_info < (3, 12):
395 if sys.version_info < (3, 12):
395 self.shell.run_code("assert TestEnum.B.value == 'added'")
396 self.shell.run_code("assert TestEnum.B.value == 'added'")
396
397
397 # ----------- TEST IMPORT FROM MODULE --------------------------
398 # ----------- TEST IMPORT FROM MODULE --------------------------
398
399
399 new_mod_code = """
400 new_mod_code = """
400 from enum import Enum
401 from enum import Enum
401 class Ext(Enum):
402 class Ext(Enum):
402 A = 'ext'
403 A = 'ext'
403 def ext_func():
404 def ext_func():
404 return 'ext'
405 return 'ext'
405 class ExtTest:
406 class ExtTest:
406 def meth(self):
407 def meth(self):
407 return 'ext'
408 return 'ext'
408 ext_int = 2
409 ext_int = 2
409 """
410 """
410 new_mod_name, new_mod_fn = self.new_module(textwrap.dedent(new_mod_code))
411 new_mod_name, new_mod_fn = self.new_module(textwrap.dedent(new_mod_code))
411 current_mod_code = f"""
412 current_mod_code = f"""
412 from {new_mod_name} import *
413 from {new_mod_name} import *
413 """
414 """
414 self.write_file(mod_fn, textwrap.dedent(current_mod_code))
415 self.write_file(mod_fn, textwrap.dedent(current_mod_code))
415 self.shell.run_code("assert Ext.A.value == 'ext'")
416 self.shell.run_code("assert Ext.A.value == 'ext'")
416 self.shell.run_code("assert ext_func() == 'ext'")
417 self.shell.run_code("assert ext_func() == 'ext'")
417 self.shell.run_code("t = ExtTest(); assert t.meth() == 'ext'")
418 self.shell.run_code("t = ExtTest(); assert t.meth() == 'ext'")
418 self.shell.run_code("assert ext_int == 2")
419 self.shell.run_code("assert ext_int == 2")
419
420
420 def test_verbose_names(self):
421 def test_verbose_names(self):
421 # Asserts correspondense between original mode names and their verbose equivalents.
422 # Asserts correspondense between original mode names and their verbose equivalents.
422 @dataclass
423 @dataclass
423 class AutoreloadSettings:
424 class AutoreloadSettings:
424 check_all: bool
425 check_all: bool
425 enabled: bool
426 enabled: bool
426 autoload_obj: bool
427 autoload_obj: bool
427
428
428 def gather_settings(mode):
429 def gather_settings(mode):
429 self.shell.magic_autoreload(mode)
430 self.shell.magic_autoreload(mode)
430 module_reloader = self.shell.auto_magics._reloader
431 module_reloader = self.shell.auto_magics._reloader
431 return AutoreloadSettings(
432 return AutoreloadSettings(
432 module_reloader.check_all,
433 module_reloader.check_all,
433 module_reloader.enabled,
434 module_reloader.enabled,
434 module_reloader.autoload_obj,
435 module_reloader.autoload_obj,
435 )
436 )
436
437
437 assert gather_settings("0") == gather_settings("off")
438 assert gather_settings("0") == gather_settings("off")
438 assert gather_settings("0") == gather_settings("OFF") # Case insensitive
439 assert gather_settings("0") == gather_settings("OFF") # Case insensitive
439 assert gather_settings("1") == gather_settings("explicit")
440 assert gather_settings("1") == gather_settings("explicit")
440 assert gather_settings("2") == gather_settings("all")
441 assert gather_settings("2") == gather_settings("all")
441 assert gather_settings("3") == gather_settings("complete")
442 assert gather_settings("3") == gather_settings("complete")
442
443
443 # And an invalid mode name raises an exception.
444 # And an invalid mode name raises an exception.
444 with self.assertRaises(ValueError):
445 with self.assertRaises(ValueError):
445 self.shell.magic_autoreload("4")
446 self.shell.magic_autoreload("4")
446
447
447 def test_aimport_parsing(self):
448 def test_aimport_parsing(self):
448 # Modules can be included or excluded all in one line.
449 # Modules can be included or excluded all in one line.
449 module_reloader = self.shell.auto_magics._reloader
450 module_reloader = self.shell.auto_magics._reloader
450 self.shell.magic_aimport("os") # import and mark `os` for auto-reload.
451 self.shell.magic_aimport("os") # import and mark `os` for auto-reload.
451 assert module_reloader.modules["os"] is True
452 assert module_reloader.modules["os"] is True
452 assert "os" not in module_reloader.skip_modules.keys()
453 assert "os" not in module_reloader.skip_modules.keys()
453
454
454 self.shell.magic_aimport("-math") # forbid autoreloading of `math`
455 self.shell.magic_aimport("-math") # forbid autoreloading of `math`
455 assert module_reloader.skip_modules["math"] is True
456 assert module_reloader.skip_modules["math"] is True
456 assert "math" not in module_reloader.modules.keys()
457 assert "math" not in module_reloader.modules.keys()
457
458
458 self.shell.magic_aimport(
459 self.shell.magic_aimport(
459 "-os, math"
460 "-os, math"
460 ) # Can do this all in one line; wasn't possible before.
461 ) # Can do this all in one line; wasn't possible before.
461 assert module_reloader.modules["math"] is True
462 assert module_reloader.modules["math"] is True
462 assert "math" not in module_reloader.skip_modules.keys()
463 assert "math" not in module_reloader.skip_modules.keys()
463 assert module_reloader.skip_modules["os"] is True
464 assert module_reloader.skip_modules["os"] is True
464 assert "os" not in module_reloader.modules.keys()
465 assert "os" not in module_reloader.modules.keys()
465
466
466 def test_autoreload_output(self):
467 def test_autoreload_output(self):
467 self.shell.magic_autoreload("complete")
468 self.shell.magic_autoreload("complete")
468 mod_code = """
469 mod_code = """
469 def func1(): pass
470 def func1(): pass
470 """
471 """
471 mod_name, mod_fn = self.new_module(mod_code)
472 mod_name, mod_fn = self.new_module(mod_code)
472 self.shell.run_code(f"import {mod_name}")
473 self.shell.run_code(f"import {mod_name}")
473 with tt.AssertPrints("", channel="stdout"): # no output; this is default
474 with tt.AssertPrints("", channel="stdout"): # no output; this is default
474 self.shell.run_code("pass")
475 self.shell.run_code("pass")
475
476
476 self.shell.magic_autoreload("complete --print")
477 self.shell.magic_autoreload("complete --print")
477 self.write_file(mod_fn, mod_code) # "modify" the module
478 self.write_file(mod_fn, mod_code) # "modify" the module
478 with tt.AssertPrints(
479 with tt.AssertPrints(
479 f"Reloading '{mod_name}'.", channel="stdout"
480 f"Reloading '{mod_name}'.", channel="stdout"
480 ): # see something printed out
481 ): # see something printed out
481 self.shell.run_code("pass")
482 self.shell.run_code("pass")
482
483
483 self.shell.magic_autoreload("complete -p")
484 self.shell.magic_autoreload("complete -p")
484 self.write_file(mod_fn, mod_code) # "modify" the module
485 self.write_file(mod_fn, mod_code) # "modify" the module
485 with tt.AssertPrints(
486 with tt.AssertPrints(
486 f"Reloading '{mod_name}'.", channel="stdout"
487 f"Reloading '{mod_name}'.", channel="stdout"
487 ): # see something printed out
488 ): # see something printed out
488 self.shell.run_code("pass")
489 self.shell.run_code("pass")
489
490
490 self.shell.magic_autoreload("complete --print --log")
491 self.shell.magic_autoreload("complete --print --log")
491 self.write_file(mod_fn, mod_code) # "modify" the module
492 self.write_file(mod_fn, mod_code) # "modify" the module
492 with tt.AssertPrints(
493 with tt.AssertPrints(
493 f"Reloading '{mod_name}'.", channel="stdout"
494 f"Reloading '{mod_name}'.", channel="stdout"
494 ): # see something printed out
495 ): # see something printed out
495 self.shell.run_code("pass")
496 self.shell.run_code("pass")
496
497
497 self.shell.magic_autoreload("complete --print --log")
498 self.shell.magic_autoreload("complete --print --log")
498 self.write_file(mod_fn, mod_code) # "modify" the module
499 self.write_file(mod_fn, mod_code) # "modify" the module
499 with self.assertLogs(logger="autoreload") as lo: # see something printed out
500 with self.assertLogs(logger="autoreload") as lo: # see something printed out
500 self.shell.run_code("pass")
501 self.shell.run_code("pass")
501 assert lo.output == [f"INFO:autoreload:Reloading '{mod_name}'."]
502 assert lo.output == [f"INFO:autoreload:Reloading '{mod_name}'."]
502
503
503 self.shell.magic_autoreload("complete -l")
504 self.shell.magic_autoreload("complete -l")
504 self.write_file(mod_fn, mod_code) # "modify" the module
505 self.write_file(mod_fn, mod_code) # "modify" the module
505 with self.assertLogs(logger="autoreload") as lo: # see something printed out
506 with self.assertLogs(logger="autoreload") as lo: # see something printed out
506 self.shell.run_code("pass")
507 self.shell.run_code("pass")
507 assert lo.output == [f"INFO:autoreload:Reloading '{mod_name}'."]
508 assert lo.output == [f"INFO:autoreload:Reloading '{mod_name}'."]
508
509
509 def _check_smoketest(self, use_aimport=True):
510 def _check_smoketest(self, use_aimport=True):
510 """
511 """
511 Functional test for the automatic reloader using either
512 Functional test for the automatic reloader using either
512 '%autoreload 1' or '%autoreload 2'
513 '%autoreload 1' or '%autoreload 2'
513 """
514 """
514
515
515 mod_name, mod_fn = self.new_module(
516 mod_name, mod_fn = self.new_module(
516 """
517 """
517 x = 9
518 x = 9
518
519
519 z = 123 # this item will be deleted
520 z = 123 # this item will be deleted
520
521
521 def foo(y):
522 def foo(y):
522 return y + 3
523 return y + 3
523
524
524 class Baz(object):
525 class Baz(object):
525 def __init__(self, x):
526 def __init__(self, x):
526 self.x = x
527 self.x = x
527 def bar(self, y):
528 def bar(self, y):
528 return self.x + y
529 return self.x + y
529 @property
530 @property
530 def quux(self):
531 def quux(self):
531 return 42
532 return 42
532 def zzz(self):
533 def zzz(self):
533 '''This method will be deleted below'''
534 '''This method will be deleted below'''
534 return 99
535 return 99
535
536
536 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
537 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
537 def foo(self):
538 def foo(self):
538 return 1
539 return 1
539 """
540 """
540 )
541 )
541
542
542 #
543 #
543 # Import module, and mark for reloading
544 # Import module, and mark for reloading
544 #
545 #
545 if use_aimport:
546 if use_aimport:
546 self.shell.magic_autoreload("1")
547 self.shell.magic_autoreload("1")
547 self.shell.magic_aimport(mod_name)
548 self.shell.magic_aimport(mod_name)
548 stream = StringIO()
549 stream = StringIO()
549 self.shell.magic_aimport("", stream=stream)
550 self.shell.magic_aimport("", stream=stream)
550 self.assertIn(("Modules to reload:\n%s" % mod_name), stream.getvalue())
551 self.assertIn(("Modules to reload:\n%s" % mod_name), stream.getvalue())
551
552
552 with self.assertRaises(ImportError):
553 with self.assertRaises(ImportError):
553 self.shell.magic_aimport("tmpmod_as318989e89ds")
554 self.shell.magic_aimport("tmpmod_as318989e89ds")
554 else:
555 else:
555 self.shell.magic_autoreload("2")
556 self.shell.magic_autoreload("2")
556 self.shell.run_code("import %s" % mod_name)
557 self.shell.run_code("import %s" % mod_name)
557 stream = StringIO()
558 stream = StringIO()
558 self.shell.magic_aimport("", stream=stream)
559 self.shell.magic_aimport("", stream=stream)
559 self.assertTrue(
560 self.assertTrue(
560 "Modules to reload:\nall-except-skipped" in stream.getvalue()
561 "Modules to reload:\nall-except-skipped" in stream.getvalue()
561 )
562 )
562 self.assertIn(mod_name, self.shell.ns)
563 self.assertIn(mod_name, self.shell.ns)
563
564
564 mod = sys.modules[mod_name]
565 mod = sys.modules[mod_name]
565
566
566 #
567 #
567 # Test module contents
568 # Test module contents
568 #
569 #
569 old_foo = mod.foo
570 old_foo = mod.foo
570 old_obj = mod.Baz(9)
571 old_obj = mod.Baz(9)
571 old_obj2 = mod.Bar()
572 old_obj2 = mod.Bar()
572
573
573 def check_module_contents():
574 def check_module_contents():
574 self.assertEqual(mod.x, 9)
575 self.assertEqual(mod.x, 9)
575 self.assertEqual(mod.z, 123)
576 self.assertEqual(mod.z, 123)
576
577
577 self.assertEqual(old_foo(0), 3)
578 self.assertEqual(old_foo(0), 3)
578 self.assertEqual(mod.foo(0), 3)
579 self.assertEqual(mod.foo(0), 3)
579
580
580 obj = mod.Baz(9)
581 obj = mod.Baz(9)
581 self.assertEqual(old_obj.bar(1), 10)
582 self.assertEqual(old_obj.bar(1), 10)
582 self.assertEqual(obj.bar(1), 10)
583 self.assertEqual(obj.bar(1), 10)
583 self.assertEqual(obj.quux, 42)
584 self.assertEqual(obj.quux, 42)
584 self.assertEqual(obj.zzz(), 99)
585 self.assertEqual(obj.zzz(), 99)
585
586
586 obj2 = mod.Bar()
587 obj2 = mod.Bar()
587 self.assertEqual(old_obj2.foo(), 1)
588 self.assertEqual(old_obj2.foo(), 1)
588 self.assertEqual(obj2.foo(), 1)
589 self.assertEqual(obj2.foo(), 1)
589
590
590 check_module_contents()
591 check_module_contents()
591
592
592 #
593 #
593 # Simulate a failed reload: no reload should occur and exactly
594 # Simulate a failed reload: no reload should occur and exactly
594 # one error message should be printed
595 # one error message should be printed
595 #
596 #
596 self.write_file(
597 self.write_file(
597 mod_fn,
598 mod_fn,
598 """
599 """
599 a syntax error
600 a syntax error
600 """,
601 """,
601 )
602 )
602
603
603 with tt.AssertPrints(
604 with tt.AssertPrints(
604 ("[autoreload of %s failed:" % mod_name), channel="stderr"
605 ("[autoreload of %s failed:" % mod_name), channel="stderr"
605 ):
606 ):
606 self.shell.run_code("pass") # trigger reload
607 self.shell.run_code("pass") # trigger reload
607 with tt.AssertNotPrints(
608 with tt.AssertNotPrints(
608 ("[autoreload of %s failed:" % mod_name), channel="stderr"
609 ("[autoreload of %s failed:" % mod_name), channel="stderr"
609 ):
610 ):
610 self.shell.run_code("pass") # trigger another reload
611 self.shell.run_code("pass") # trigger another reload
611 check_module_contents()
612 check_module_contents()
612
613
613 #
614 #
614 # Rewrite module (this time reload should succeed)
615 # Rewrite module (this time reload should succeed)
615 #
616 #
616 self.write_file(
617 self.write_file(
617 mod_fn,
618 mod_fn,
618 """
619 """
619 x = 10
620 x = 10
620
621
621 def foo(y):
622 def foo(y):
622 return y + 4
623 return y + 4
623
624
624 class Baz(object):
625 class Baz(object):
625 def __init__(self, x):
626 def __init__(self, x):
626 self.x = x
627 self.x = x
627 def bar(self, y):
628 def bar(self, y):
628 return self.x + y + 1
629 return self.x + y + 1
629 @property
630 @property
630 def quux(self):
631 def quux(self):
631 return 43
632 return 43
632
633
633 class Bar: # old-style class
634 class Bar: # old-style class
634 def foo(self):
635 def foo(self):
635 return 2
636 return 2
636 """,
637 """,
637 )
638 )
638
639
639 def check_module_contents():
640 def check_module_contents():
640 self.assertEqual(mod.x, 10)
641 self.assertEqual(mod.x, 10)
641 self.assertFalse(hasattr(mod, "z"))
642 self.assertFalse(hasattr(mod, "z"))
642
643
643 self.assertEqual(old_foo(0), 4) # superreload magic!
644 self.assertEqual(old_foo(0), 4) # superreload magic!
644 self.assertEqual(mod.foo(0), 4)
645 self.assertEqual(mod.foo(0), 4)
645
646
646 obj = mod.Baz(9)
647 obj = mod.Baz(9)
647 self.assertEqual(old_obj.bar(1), 11) # superreload magic!
648 self.assertEqual(old_obj.bar(1), 11) # superreload magic!
648 self.assertEqual(obj.bar(1), 11)
649 self.assertEqual(obj.bar(1), 11)
649
650
650 self.assertEqual(old_obj.quux, 43)
651 self.assertEqual(old_obj.quux, 43)
651 self.assertEqual(obj.quux, 43)
652 self.assertEqual(obj.quux, 43)
652
653
653 self.assertFalse(hasattr(old_obj, "zzz"))
654 self.assertFalse(hasattr(old_obj, "zzz"))
654 self.assertFalse(hasattr(obj, "zzz"))
655 self.assertFalse(hasattr(obj, "zzz"))
655
656
656 obj2 = mod.Bar()
657 obj2 = mod.Bar()
657 self.assertEqual(old_obj2.foo(), 2)
658 self.assertEqual(old_obj2.foo(), 2)
658 self.assertEqual(obj2.foo(), 2)
659 self.assertEqual(obj2.foo(), 2)
659
660
660 self.shell.run_code("pass") # trigger reload
661 self.shell.run_code("pass") # trigger reload
661 check_module_contents()
662 check_module_contents()
662
663
663 #
664 #
664 # Another failure case: deleted file (shouldn't reload)
665 # Another failure case: deleted file (shouldn't reload)
665 #
666 #
666 os.unlink(mod_fn)
667 os.unlink(mod_fn)
667
668
668 self.shell.run_code("pass") # trigger reload
669 self.shell.run_code("pass") # trigger reload
669 check_module_contents()
670 check_module_contents()
670
671
671 #
672 #
672 # Disable autoreload and rewrite module: no reload should occur
673 # Disable autoreload and rewrite module: no reload should occur
673 #
674 #
674 if use_aimport:
675 if use_aimport:
675 self.shell.magic_aimport("-" + mod_name)
676 self.shell.magic_aimport("-" + mod_name)
676 stream = StringIO()
677 stream = StringIO()
677 self.shell.magic_aimport("", stream=stream)
678 self.shell.magic_aimport("", stream=stream)
678 self.assertTrue(("Modules to skip:\n%s" % mod_name) in stream.getvalue())
679 self.assertTrue(("Modules to skip:\n%s" % mod_name) in stream.getvalue())
679
680
680 # This should succeed, although no such module exists
681 # This should succeed, although no such module exists
681 self.shell.magic_aimport("-tmpmod_as318989e89ds")
682 self.shell.magic_aimport("-tmpmod_as318989e89ds")
682 else:
683 else:
683 self.shell.magic_autoreload("0")
684 self.shell.magic_autoreload("0")
684
685
685 self.write_file(
686 self.write_file(
686 mod_fn,
687 mod_fn,
687 """
688 """
688 x = -99
689 x = -99
689 """,
690 """,
690 )
691 )
691
692
692 self.shell.run_code("pass") # trigger reload
693 self.shell.run_code("pass") # trigger reload
693 self.shell.run_code("pass")
694 self.shell.run_code("pass")
694 check_module_contents()
695 check_module_contents()
695
696
696 #
697 #
697 # Re-enable autoreload: reload should now occur
698 # Re-enable autoreload: reload should now occur
698 #
699 #
699 if use_aimport:
700 if use_aimport:
700 self.shell.magic_aimport(mod_name)
701 self.shell.magic_aimport(mod_name)
701 else:
702 else:
702 self.shell.magic_autoreload("")
703 self.shell.magic_autoreload("")
703
704
704 self.shell.run_code("pass") # trigger reload
705 self.shell.run_code("pass") # trigger reload
705 self.assertEqual(mod.x, -99)
706 self.assertEqual(mod.x, -99)
706
707
707 def test_smoketest_aimport(self):
708 def test_smoketest_aimport(self):
708 self._check_smoketest(use_aimport=True)
709 self._check_smoketest(use_aimport=True)
709
710
710 def test_smoketest_autoreload(self):
711 def test_smoketest_autoreload(self):
711 self._check_smoketest(use_aimport=False)
712 self._check_smoketest(use_aimport=False)
@@ -1,62 +1,63 b''
1 """
1 """
2 Inputhook for running the original asyncio event loop while we're waiting for
2 Inputhook for running the original asyncio event loop while we're waiting for
3 input.
3 input.
4
4
5 By default, in IPython, we run the prompt with a different asyncio event loop,
5 By default, in IPython, we run the prompt with a different asyncio event loop,
6 because otherwise we risk that people are freezing the prompt by scheduling bad
6 because otherwise we risk that people are freezing the prompt by scheduling bad
7 coroutines. E.g., a coroutine that does a while/true and never yield back
7 coroutines. E.g., a coroutine that does a while/true and never yield back
8 control to the loop. We can't cancel that.
8 control to the loop. We can't cancel that.
9
9
10 However, sometimes we want the asyncio loop to keep running while waiting for
10 However, sometimes we want the asyncio loop to keep running while waiting for
11 a prompt.
11 a prompt.
12
12
13 The following example will print the numbers from 1 to 10 above the prompt,
13 The following example will print the numbers from 1 to 10 above the prompt,
14 while we are waiting for input. (This works also because we use
14 while we are waiting for input. (This works also because we use
15 prompt_toolkit`s `patch_stdout`)::
15 prompt_toolkit`s `patch_stdout`)::
16
16
17 In [1]: import asyncio
17 In [1]: import asyncio
18
18
19 In [2]: %gui asyncio
19 In [2]: %gui asyncio
20
20
21 In [3]: async def f():
21 In [3]: async def f():
22 ...: for i in range(10):
22 ...: for i in range(10):
23 ...: await asyncio.sleep(1)
23 ...: await asyncio.sleep(1)
24 ...: print(i)
24 ...: print(i)
25
25
26
26
27 In [4]: asyncio.ensure_future(f())
27 In [4]: asyncio.ensure_future(f())
28
28
29 """
29 """
30
30 from prompt_toolkit import __version__ as ptk_version
31 from prompt_toolkit import __version__ as ptk_version
31
32
32 from IPython.core.async_helpers import get_asyncio_loop
33 from IPython.core.async_helpers import get_asyncio_loop
33
34
34 PTK3 = ptk_version.startswith("3.")
35 PTK3 = ptk_version.startswith("3.")
35
36
36
37
37 def inputhook(context):
38 def inputhook(context):
38 """
39 """
39 Inputhook for asyncio event loop integration.
40 Inputhook for asyncio event loop integration.
40 """
41 """
41 # For prompt_toolkit 3.0, this input hook literally doesn't do anything.
42 # For prompt_toolkit 3.0, this input hook literally doesn't do anything.
42 # The event loop integration here is implemented in `interactiveshell.py`
43 # The event loop integration here is implemented in `interactiveshell.py`
43 # by running the prompt itself in the current asyncio loop. The main reason
44 # by running the prompt itself in the current asyncio loop. The main reason
44 # for this is that nesting asyncio event loops is unreliable.
45 # for this is that nesting asyncio event loops is unreliable.
45 if PTK3:
46 if PTK3:
46 return
47 return
47
48
48 # For prompt_toolkit 2.0, we can run the current asyncio event loop,
49 # For prompt_toolkit 2.0, we can run the current asyncio event loop,
49 # because prompt_toolkit 2.0 uses a different event loop internally.
50 # because prompt_toolkit 2.0 uses a different event loop internally.
50
51
51 # get the persistent asyncio event loop
52 # get the persistent asyncio event loop
52 loop = get_asyncio_loop()
53 loop = get_asyncio_loop()
53
54
54 def stop():
55 def stop():
55 loop.stop()
56 loop.stop()
56
57
57 fileno = context.fileno()
58 fileno = context.fileno()
58 loop.add_reader(fileno, stop)
59 loop.add_reader(fileno, stop)
59 try:
60 try:
60 loop.run_forever()
61 loop.run_forever()
61 finally:
62 finally:
62 loop.remove_reader(fileno)
63 loop.remove_reader(fileno)
@@ -1,23 +1,22 b''
1 """Emscripten-specific implementation of process utilities.
1 """Emscripten-specific implementation of process utilities.
2
2
3 This file is only meant to be imported by process.py, not by end-users.
3 This file is only meant to be imported by process.py, not by end-users.
4 """
4 """
5
5
6
7 from ._process_common import arg_split
6 from ._process_common import arg_split
8
7
9
8
10 def system(cmd):
9 def system(cmd):
11 raise OSError("Not available")
10 raise OSError("Not available")
12
11
13
12
14 def getoutput(cmd):
13 def getoutput(cmd):
15 raise OSError("Not available")
14 raise OSError("Not available")
16
15
17
16
18 def check_pid(cmd):
17 def check_pid(cmd):
19 raise OSError("Not available")
18 raise OSError("Not available")
20
19
21
20
22 # `arg_split` is still used by magics regardless of whether we are on a posix/windows/emscipten
21 # `arg_split` is still used by magics regardless of whether we are on a posix/windows/emscipten
23 __all__ = ["system", "getoutput", "check_pid", "arg_split"]
22 __all__ = ["system", "getoutput", "check_pid", "arg_split"]
General Comments 0
You need to be logged in to leave comments. Login now