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