##// END OF EJS Templates
Merge pull request #12734 from skalaydzhiyski/feature-add-autoreload-option-3...
Matthias Bussonnier -
r26472:7e0c8b73 merge
parent child Browse files
Show More
@@ -13,3 +13,9 b''
13 13 # <full commit hash> # initial black-format
14 14 # <full commit hash> # rename something internal
15 15 6e748726282d1acb9a4f9f264ee679c474c4b8f5 # Apply pygrade --36plus on IPython/core/tests/test_inputtransformer.py.
16 0233e65d8086d0ec34acb8685b7a5411633f0899 # apply pyupgrade to IPython/extensions/tests/test_autoreload.py
17 a6a7e4dd7e51b892147895006d3a2a6c34b79ae6 # apply black to IPython/extensions/tests/test_autoreload.py
18 c5ca5a8f25432dfd6b9eccbbe446a8348bf37cfa # apply pyupgrade to IPython/extensions/autoreload.py
19 50624b84ccdece781750f5eb635a9efbf2fe30d6 # apply black to IPython/extensions/autoreload.py
20 b7aaa47412b96379198705955004930c57f9d74a # apply pyupgrade to IPython/extensions/autoreload.py
21 9c7476a88af3e567426b412f1b3c778401d8f6aa # apply black to IPython/extensions/autoreload.py
@@ -48,6 +48,11 b' The following magic commands are provided:'
48 48 Reload all modules (except those excluded by ``%aimport``) every
49 49 time before executing the Python code typed.
50 50
51 ``%autoreload 3``
52
53 Reload all modules AND autoload newly added objects
54 every time before executing the Python code typed.
55
51 56 ``%aimport``
52 57
53 58 List modules which are to be automatically imported or not to be imported.
@@ -94,21 +99,21 b' Some of the known remaining caveats are:'
94 99
95 100 skip_doctest = True
96 101
97 #-----------------------------------------------------------------------------
102 # -----------------------------------------------------------------------------
98 103 # Copyright (C) 2000 Thomas Heller
99 104 # Copyright (C) 2008 Pauli Virtanen <pav@iki.fi>
100 105 # Copyright (C) 2012 The IPython Development Team
101 106 #
102 107 # Distributed under the terms of the BSD License. The full license is in
103 108 # the file COPYING, distributed as part of this software.
104 #-----------------------------------------------------------------------------
109 # -----------------------------------------------------------------------------
105 110 #
106 111 # This IPython module is written by Pauli Virtanen, based on the autoreload
107 112 # code by Thomas Heller.
108 113
109 #-----------------------------------------------------------------------------
114 # -----------------------------------------------------------------------------
110 115 # Imports
111 #-----------------------------------------------------------------------------
116 # -----------------------------------------------------------------------------
112 117
113 118 import os
114 119 import sys
@@ -120,18 +125,22 b' from importlib import import_module'
120 125 from importlib.util import source_from_cache
121 126 from imp import reload
122 127
123 #------------------------------------------------------------------------------
128 # ------------------------------------------------------------------------------
124 129 # Autoreload functionality
125 #------------------------------------------------------------------------------
130 # ------------------------------------------------------------------------------
126 131
127 class ModuleReloader(object):
132
133 class ModuleReloader:
128 134 enabled = False
129 135 """Whether this reloader is enabled"""
130 136
131 137 check_all = True
132 138 """Autoreload all modules, not just those listed in 'modules'"""
133 139
134 def __init__(self):
140 autoload_obj = False
141 """Autoreload all modules AND autoload all new objects"""
142
143 def __init__(self, shell=None):
135 144 # Modules that failed to reload: {module: mtime-on-failed-reload, ...}
136 145 self.failed = {}
137 146 # Modules specially marked as autoreloadable.
@@ -142,6 +151,7 b' class ModuleReloader(object):'
142 151 self.old_objects = {}
143 152 # Module modification timestamps
144 153 self.modules_mtimes = {}
154 self.shell = shell
145 155
146 156 # Cache module modification times
147 157 self.check(check_all=True, do_reload=False)
@@ -176,22 +186,22 b' class ModuleReloader(object):'
176 186 self.mark_module_reloadable(module_name)
177 187
178 188 import_module(module_name)
179 top_name = module_name.split('.')[0]
189 top_name = module_name.split(".")[0]
180 190 top_module = sys.modules[top_name]
181 191 return top_module, top_name
182 192
183 193 def filename_and_mtime(self, module):
184 if not hasattr(module, '__file__') or module.__file__ is None:
194 if not hasattr(module, "__file__") or module.__file__ is None:
185 195 return None, None
186 196
187 if getattr(module, '__name__', None) in [None, '__mp_main__', '__main__']:
197 if getattr(module, "__name__", None) in [None, "__mp_main__", "__main__"]:
188 198 # we cannot reload(__main__) or reload(__mp_main__)
189 199 return None, None
190 200
191 201 filename = module.__file__
192 202 path, ext = os.path.splitext(filename)
193 203
194 if ext.lower() == '.py':
204 if ext.lower() == ".py":
195 205 py_filename = filename
196 206 else:
197 207 try:
@@ -242,21 +252,35 b' class ModuleReloader(object):'
242 252 # If we've reached this point, we should try to reload the module
243 253 if do_reload:
244 254 try:
245 superreload(m, reload, self.old_objects)
255 if self.autoload_obj:
256 superreload(m, reload, self.old_objects, self.shell)
257 else:
258 superreload(m, reload, self.old_objects)
246 259 if py_filename in self.failed:
247 260 del self.failed[py_filename]
248 261 except:
249 print("[autoreload of %s failed: %s]" % (
250 modname, traceback.format_exc(10)), file=sys.stderr)
262 print(
263 "[autoreload of {} failed: {}]".format(
264 modname, traceback.format_exc(10)
265 ),
266 file=sys.stderr,
267 )
251 268 self.failed[py_filename] = pymtime
252 269
253 #------------------------------------------------------------------------------
270
271 # ------------------------------------------------------------------------------
254 272 # superreload
255 #------------------------------------------------------------------------------
273 # ------------------------------------------------------------------------------
256 274
257 275
258 func_attrs = ['__code__', '__defaults__', '__doc__',
259 '__closure__', '__globals__', '__dict__']
276 func_attrs = [
277 "__code__",
278 "__defaults__",
279 "__doc__",
280 "__closure__",
281 "__globals__",
282 "__dict__",
283 ]
260 284
261 285
262 286 def update_function(old, new):
@@ -272,7 +296,7 b' def update_instances(old, new):'
272 296 """Use garbage collector to find all instances that refer to the old
273 297 class definition and update their __class__ to point to the new class
274 298 definition"""
275
299
276 300 refs = gc.get_referrers(old)
277 301
278 302 for ref in refs:
@@ -299,19 +323,20 b' def update_class(old, new):'
299 323 pass
300 324 continue
301 325
302 if update_generic(old_obj, new_obj): continue
326 if update_generic(old_obj, new_obj):
327 continue
303 328
304 329 try:
305 330 setattr(old, key, getattr(new, key))
306 331 except (AttributeError, TypeError):
307 pass # skip non-writable attributes
332 pass # skip non-writable attributes
308 333
309 334 for key in list(new.__dict__.keys()):
310 335 if key not in list(old.__dict__.keys()):
311 336 try:
312 337 setattr(old, key, getattr(new, key))
313 338 except (AttributeError, TypeError):
314 pass # skip non-writable attributes
339 pass # skip non-writable attributes
315 340
316 341 # update all instances of class
317 342 update_instances(old, new)
@@ -329,16 +354,18 b' def isinstance2(a, b, typ):'
329 354
330 355
331 356 UPDATE_RULES = [
332 (lambda a, b: isinstance2(a, b, type),
333 update_class),
334 (lambda a, b: isinstance2(a, b, types.FunctionType),
335 update_function),
336 (lambda a, b: isinstance2(a, b, property),
337 update_property),
357 (lambda a, b: isinstance2(a, b, type), update_class),
358 (lambda a, b: isinstance2(a, b, types.FunctionType), update_function),
359 (lambda a, b: isinstance2(a, b, property), update_property),
338 360 ]
339 UPDATE_RULES.extend([(lambda a, b: isinstance2(a, b, types.MethodType),
340 lambda a, b: update_function(a.__func__, b.__func__)),
341 ])
361 UPDATE_RULES.extend(
362 [
363 (
364 lambda a, b: isinstance2(a, b, types.MethodType),
365 lambda a, b: update_function(a.__func__, b.__func__),
366 ),
367 ]
368 )
342 369
343 370
344 371 def update_generic(a, b):
@@ -349,14 +376,45 b' def update_generic(a, b):'
349 376 return False
350 377
351 378
352 class StrongRef(object):
379 class StrongRef:
353 380 def __init__(self, obj):
354 381 self.obj = obj
382
355 383 def __call__(self):
356 384 return self.obj
357 385
358 386
359 def superreload(module, reload=reload, old_objects=None):
387 mod_attrs = [
388 "__name__",
389 "__doc__",
390 "__package__",
391 "__loader__",
392 "__spec__",
393 "__file__",
394 "__cached__",
395 "__builtins__",
396 ]
397
398
399 def append_obj(module, d, name, obj, autoload=False):
400 in_module = hasattr(obj, "__module__") and obj.__module__ == module.__name__
401 if autoload:
402 # check needed for module global built-ins
403 if not in_module and name in mod_attrs:
404 return False
405 else:
406 if not in_module:
407 return False
408
409 key = (module.__name__, name)
410 try:
411 d.setdefault(key, []).append(weakref.ref(obj))
412 except TypeError:
413 pass
414 return True
415
416
417 def superreload(module, reload=reload, old_objects=None, shell=None):
360 418 """Enhanced version of the builtin reload function.
361 419
362 420 superreload remembers objects previously in the module, and
@@ -371,7 +429,7 b' def superreload(module, reload=reload, old_objects=None):'
371 429
372 430 # collect old objects in the module
373 431 for name, obj in list(module.__dict__.items()):
374 if not hasattr(obj, '__module__') or obj.__module__ != module.__name__:
432 if not append_obj(module, old_objects, name, obj):
375 433 continue
376 434 key = (module.__name__, name)
377 435 try:
@@ -385,8 +443,8 b' def superreload(module, reload=reload, old_objects=None):'
385 443 old_dict = module.__dict__.copy()
386 444 old_name = module.__name__
387 445 module.__dict__.clear()
388 module.__dict__['__name__'] = old_name
389 module.__dict__['__loader__'] = old_dict['__loader__']
446 module.__dict__["__name__"] = old_name
447 module.__dict__["__loader__"] = old_dict["__loader__"]
390 448 except (TypeError, AttributeError, KeyError):
391 449 pass
392 450
@@ -400,12 +458,21 b' def superreload(module, reload=reload, old_objects=None):'
400 458 # iterate over all objects and update functions & classes
401 459 for name, new_obj in list(module.__dict__.items()):
402 460 key = (module.__name__, name)
403 if key not in old_objects: continue
461 if key not in old_objects:
462 # here 'shell' acts both as a flag and as an output var
463 if (
464 shell is None
465 or name == "Enum"
466 or not append_obj(module, old_objects, name, new_obj, True)
467 ):
468 continue
469 shell.user_ns[name] = new_obj
404 470
405 471 new_refs = []
406 472 for old_ref in old_objects[key]:
407 473 old_obj = old_ref()
408 if old_obj is None: continue
474 if old_obj is None:
475 continue
409 476 new_refs.append(old_ref)
410 477 update_generic(old_obj, new_obj)
411 478
@@ -416,22 +483,25 b' def superreload(module, reload=reload, old_objects=None):'
416 483
417 484 return module
418 485
419 #------------------------------------------------------------------------------
486
487 # ------------------------------------------------------------------------------
420 488 # IPython connectivity
421 #------------------------------------------------------------------------------
489 # ------------------------------------------------------------------------------
422 490
423 491 from IPython.core.magic import Magics, magics_class, line_magic
424 492
493
425 494 @magics_class
426 495 class AutoreloadMagics(Magics):
427 496 def __init__(self, *a, **kw):
428 super(AutoreloadMagics, self).__init__(*a, **kw)
429 self._reloader = ModuleReloader()
497 super().__init__(*a, **kw)
498 self._reloader = ModuleReloader(self.shell)
430 499 self._reloader.check_all = False
500 self._reloader.autoload_obj = False
431 501 self.loaded_modules = set(sys.modules)
432 502
433 503 @line_magic
434 def autoreload(self, parameter_s=''):
504 def autoreload(self, parameter_s=""):
435 505 r"""%autoreload => Reload modules automatically
436 506
437 507 %autoreload
@@ -475,19 +545,24 b' class AutoreloadMagics(Magics):'
475 545 autoreloaded.
476 546
477 547 """
478 if parameter_s == '':
548 if parameter_s == "":
479 549 self._reloader.check(True)
480 elif parameter_s == '0':
550 elif parameter_s == "0":
481 551 self._reloader.enabled = False
482 elif parameter_s == '1':
552 elif parameter_s == "1":
483 553 self._reloader.check_all = False
484 554 self._reloader.enabled = True
485 elif parameter_s == '2':
555 elif parameter_s == "2":
556 self._reloader.check_all = True
557 self._reloader.enabled = True
558 self._reloader.enabled = True
559 elif parameter_s == "3":
486 560 self._reloader.check_all = True
487 561 self._reloader.enabled = True
562 self._reloader.autoload_obj = True
488 563
489 564 @line_magic
490 def aimport(self, parameter_s='', stream=None):
565 def aimport(self, parameter_s="", stream=None):
491 566 """%aimport => Import modules for automatic reloading.
492 567
493 568 %aimport
@@ -511,13 +586,13 b' class AutoreloadMagics(Magics):'
511 586 if self._reloader.check_all:
512 587 stream.write("Modules to reload:\nall-except-skipped\n")
513 588 else:
514 stream.write("Modules to reload:\n%s\n" % ' '.join(to_reload))
515 stream.write("\nModules to skip:\n%s\n" % ' '.join(to_skip))
516 elif modname.startswith('-'):
589 stream.write("Modules to reload:\n%s\n" % " ".join(to_reload))
590 stream.write("\nModules to skip:\n%s\n" % " ".join(to_skip))
591 elif modname.startswith("-"):
517 592 modname = modname[1:]
518 593 self._reloader.mark_module_skipped(modname)
519 594 else:
520 for _module in ([_.strip() for _ in modname.split(',')]):
595 for _module in [_.strip() for _ in modname.split(",")]:
521 596 top_module, top_name = self._reloader.aimport_module(_module)
522 597
523 598 # Inject module to user namespace
@@ -531,8 +606,7 b' class AutoreloadMagics(Magics):'
531 606 pass
532 607
533 608 def post_execute_hook(self):
534 """Cache the modification times of any modules imported in this execution
535 """
609 """Cache the modification times of any modules imported in this execution"""
536 610 newly_loaded_modules = set(sys.modules) - self.loaded_modules
537 611 for modname in newly_loaded_modules:
538 612 _, pymtime = self._reloader.filename_and_mtime(sys.modules[modname])
@@ -546,5 +620,5 b' def load_ipython_extension(ip):'
546 620 """Load the extension in IPython."""
547 621 auto_reload = AutoreloadMagics(ip)
548 622 ip.register_magics(auto_reload)
549 ip.events.register('pre_run_cell', auto_reload.pre_run_cell)
550 ip.events.register('post_execute', auto_reload.post_execute_hook)
623 ip.events.register("pre_run_cell", auto_reload.pre_run_cell)
624 ip.events.register("post_execute", auto_reload.post_execute_hook)
@@ -1,16 +1,16 b''
1 1 """Tests for autoreload extension.
2 2 """
3 #-----------------------------------------------------------------------------
3 # -----------------------------------------------------------------------------
4 4 # Copyright (c) 2012 IPython Development Team.
5 5 #
6 6 # Distributed under the terms of the Modified BSD License.
7 7 #
8 8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 # -----------------------------------------------------------------------------
10 10
11 #-----------------------------------------------------------------------------
11 # -----------------------------------------------------------------------------
12 12 # Imports
13 #-----------------------------------------------------------------------------
13 # -----------------------------------------------------------------------------
14 14
15 15 import os
16 16 import sys
@@ -29,26 +29,26 b' from unittest import TestCase'
29 29 from IPython.extensions.autoreload import AutoreloadMagics
30 30 from IPython.core.events import EventManager, pre_run_cell
31 31
32 #-----------------------------------------------------------------------------
32 # -----------------------------------------------------------------------------
33 33 # Test fixture
34 #-----------------------------------------------------------------------------
34 # -----------------------------------------------------------------------------
35 35
36 36 noop = lambda *a, **kw: None
37 37
38 class FakeShell:
39 38
39 class FakeShell:
40 40 def __init__(self):
41 41 self.ns = {}
42 42 self.user_ns = self.ns
43 43 self.user_ns_hidden = {}
44 self.events = EventManager(self, {'pre_run_cell', pre_run_cell})
44 self.events = EventManager(self, {"pre_run_cell", pre_run_cell})
45 45 self.auto_magics = AutoreloadMagics(shell=self)
46 self.events.register('pre_run_cell', self.auto_magics.pre_run_cell)
46 self.events.register("pre_run_cell", self.auto_magics.pre_run_cell)
47 47
48 48 register_magics = set_hook = noop
49 49
50 50 def run_code(self, code):
51 self.events.trigger('pre_run_cell')
51 self.events.trigger("pre_run_cell")
52 52 exec(code, self.user_ns)
53 53 self.auto_magics.post_execute_hook()
54 54
@@ -85,7 +85,7 b' class Fixture(TestCase):'
85 85 self.shell = None
86 86
87 87 def get_module(self):
88 module_name = "tmpmod_" + "".join(random.sample(self.filename_chars,20))
88 module_name = "tmpmod_" + "".join(random.sample(self.filename_chars, 20))
89 89 if module_name in sys.modules:
90 90 del sys.modules[module_name]
91 91 file_name = os.path.join(self.test_dir, module_name + ".py")
@@ -111,19 +111,21 b' class Fixture(TestCase):'
111 111 time.sleep(1.05)
112 112
113 113 # Write
114 with open(filename, 'w') as f:
114 with open(filename, "w") as f:
115 115 f.write(content)
116 116
117 117 def new_module(self, code):
118 118 code = textwrap.dedent(code)
119 119 mod_name, mod_fn = self.get_module()
120 with open(mod_fn, 'w') as f:
120 with open(mod_fn, "w") as f:
121 121 f.write(code)
122 122 return mod_name, mod_fn
123 123
124 #-----------------------------------------------------------------------------
124
125 # -----------------------------------------------------------------------------
125 126 # Test automatic reloading
126 #-----------------------------------------------------------------------------
127 # -----------------------------------------------------------------------------
128
127 129
128 130 def pickle_get_current_class(obj):
129 131 """
@@ -136,25 +138,36 b' def pickle_get_current_class(obj):'
136 138 obj2 = getattr(obj2, subpath)
137 139 return obj2
138 140
139 class TestAutoreload(Fixture):
140 141
142 class TestAutoreload(Fixture):
141 143 def test_reload_enums(self):
142 mod_name, mod_fn = self.new_module(textwrap.dedent("""
144 mod_name, mod_fn = self.new_module(
145 textwrap.dedent(
146 """
143 147 from enum import Enum
144 148 class MyEnum(Enum):
145 149 A = 'A'
146 150 B = 'B'
147 """))
151 """
152 )
153 )
148 154 self.shell.magic_autoreload("2")
149 155 self.shell.magic_aimport(mod_name)
150 self.write_file(mod_fn, textwrap.dedent("""
156 self.write_file(
157 mod_fn,
158 textwrap.dedent(
159 """
151 160 from enum import Enum
152 161 class MyEnum(Enum):
153 162 A = 'A'
154 163 B = 'B'
155 164 C = 'C'
156 """))
157 with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
165 """
166 ),
167 )
168 with tt.AssertNotPrints(
169 ("[autoreload of %s failed:" % mod_name), channel="stderr"
170 ):
158 171 self.shell.run_code("pass") # trigger another reload
159 172
160 173 def test_reload_class_type(self):
@@ -195,7 +208,9 b' class TestAutoreload(Fixture):'
195 208
196 209 def test_reload_class_attributes(self):
197 210 self.shell.magic_autoreload("2")
198 mod_name, mod_fn = self.new_module(textwrap.dedent("""
211 mod_name, mod_fn = self.new_module(
212 textwrap.dedent(
213 """
199 214 class MyClass:
200 215
201 216 def __init__(self, a=10):
@@ -241,16 +256,99 b' class TestAutoreload(Fixture):'
241 256
242 257 self.shell.run_code("second = MyClass(5)")
243 258
244 for object_name in {'first', 'second'}:
245 self.shell.run_code("{object_name}.power(5)".format(object_name=object_name))
259 for object_name in {"first", "second"}:
260 self.shell.run_code(f"{object_name}.power(5)")
246 261 with nt.assert_raises(AttributeError):
247 self.shell.run_code("{object_name}.cube()".format(object_name=object_name))
262 self.shell.run_code(f"{object_name}.cube()")
248 263 with nt.assert_raises(AttributeError):
249 self.shell.run_code("{object_name}.square()".format(object_name=object_name))
250 self.shell.run_code("{object_name}.b".format(object_name=object_name))
251 self.shell.run_code("{object_name}.a".format(object_name=object_name))
264 self.shell.run_code(f"{object_name}.square()")
265 self.shell.run_code(f"{object_name}.b")
266 self.shell.run_code(f"{object_name}.a")
252 267 with nt.assert_raises(AttributeError):
253 self.shell.run_code("{object_name}.toto".format(object_name=object_name))
268 self.shell.run_code(f"{object_name}.toto")
269
270 def test_autoload_newly_added_objects(self):
271 self.shell.magic_autoreload("3")
272 mod_code = """
273 def func1(): pass
274 """
275 mod_name, mod_fn = self.new_module(textwrap.dedent(mod_code))
276 self.shell.run_code(f"from {mod_name} import *")
277 self.shell.run_code("func1()")
278 with nt.assert_raises(NameError):
279 self.shell.run_code("func2()")
280 with nt.assert_raises(NameError):
281 self.shell.run_code("t = Test()")
282 with nt.assert_raises(NameError):
283 self.shell.run_code("number")
284
285 # ----------- TEST NEW OBJ LOADED --------------------------
286
287 new_code = """
288 def func1(): pass
289 def func2(): pass
290 class Test: pass
291 number = 0
292 from enum import Enum
293 class TestEnum(Enum):
294 A = 'a'
295 """
296 self.write_file(mod_fn, textwrap.dedent(new_code))
297
298 # test function now exists in shell's namespace namespace
299 self.shell.run_code("func2()")
300 # test function now exists in module's dict
301 self.shell.run_code(f"import sys; sys.modules['{mod_name}'].func2()")
302 # test class now exists
303 self.shell.run_code("t = Test()")
304 # test global built-in var now exists
305 self.shell.run_code("number")
306 # test the enumerations gets loaded succesfully
307 self.shell.run_code("TestEnum.A")
308
309 # ----------- TEST NEW OBJ CAN BE CHANGED --------------------
310
311 new_code = """
312 def func1(): return 'changed'
313 def func2(): return 'changed'
314 class Test:
315 def new_func(self):
316 return 'changed'
317 number = 1
318 from enum import Enum
319 class TestEnum(Enum):
320 A = 'a'
321 B = 'added'
322 """
323 self.write_file(mod_fn, textwrap.dedent(new_code))
324 self.shell.run_code("assert func1() == 'changed'")
325 self.shell.run_code("assert func2() == 'changed'")
326 self.shell.run_code("t = Test(); assert t.new_func() == 'changed'")
327 self.shell.run_code("assert number == 1")
328 self.shell.run_code("assert TestEnum.B.value == 'added'")
329
330 # ----------- TEST IMPORT FROM MODULE --------------------------
331
332 new_mod_code = """
333 from enum import Enum
334 class Ext(Enum):
335 A = 'ext'
336 def ext_func():
337 return 'ext'
338 class ExtTest:
339 def meth(self):
340 return 'ext'
341 ext_int = 2
342 """
343 new_mod_name, new_mod_fn = self.new_module(textwrap.dedent(new_mod_code))
344 current_mod_code = f"""
345 from {new_mod_name} import *
346 """
347 self.write_file(mod_fn, textwrap.dedent(current_mod_code))
348 self.shell.run_code("assert Ext.A.value == 'ext'")
349 self.shell.run_code("assert ext_func() == 'ext'")
350 self.shell.run_code("t = ExtTest(); assert t.meth() == 'ext'")
351 self.shell.run_code("assert ext_int == 2")
254 352
255 353 def _check_smoketest(self, use_aimport=True):
256 354 """
@@ -258,7 +356,8 b' class TestAutoreload(Fixture):'
258 356 '%autoreload 1' or '%autoreload 2'
259 357 """
260 358
261 mod_name, mod_fn = self.new_module("""
359 mod_name, mod_fn = self.new_module(
360 """
262 361 x = 9
263 362
264 363 z = 123 # this item will be deleted
@@ -281,7 +380,8 b' class Baz(object):'
281 380 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
282 381 def foo(self):
283 382 return 1
284 """)
383 """
384 )
285 385
286 386 #
287 387 # Import module, and mark for reloading
@@ -300,8 +400,9 b" class Bar: # old-style class: weakref doesn't work for it on Python < 2.7"
300 400 self.shell.run_code("import %s" % mod_name)
301 401 stream = StringIO()
302 402 self.shell.magic_aimport("", stream=stream)
303 nt.assert_true("Modules to reload:\nall-except-skipped" in
304 stream.getvalue())
403 nt.assert_true(
404 "Modules to reload:\nall-except-skipped" in stream.getvalue()
405 )
305 406 nt.assert_in(mod_name, self.shell.ns)
306 407
307 408 mod = sys.modules[mod_name]
@@ -336,20 +437,29 b" class Bar: # old-style class: weakref doesn't work for it on Python < 2.7"
336 437 # Simulate a failed reload: no reload should occur and exactly
337 438 # one error message should be printed
338 439 #
339 self.write_file(mod_fn, """
440 self.write_file(
441 mod_fn,
442 """
340 443 a syntax error
341 """)
444 """,
445 )
342 446
343 with tt.AssertPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
344 self.shell.run_code("pass") # trigger reload
345 with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
346 self.shell.run_code("pass") # trigger another reload
447 with tt.AssertPrints(
448 ("[autoreload of %s failed:" % mod_name), channel="stderr"
449 ):
450 self.shell.run_code("pass") # trigger reload
451 with tt.AssertNotPrints(
452 ("[autoreload of %s failed:" % mod_name), channel="stderr"
453 ):
454 self.shell.run_code("pass") # trigger another reload
347 455 check_module_contents()
348 456
349 457 #
350 458 # Rewrite module (this time reload should succeed)
351 459 #
352 self.write_file(mod_fn, """
460 self.write_file(
461 mod_fn,
462 """
353 463 x = 10
354 464
355 465 def foo(y):
@@ -367,30 +477,31 b' class Baz(object):'
367 477 class Bar: # old-style class
368 478 def foo(self):
369 479 return 2
370 """)
480 """,
481 )
371 482
372 483 def check_module_contents():
373 484 nt.assert_equal(mod.x, 10)
374 nt.assert_false(hasattr(mod, 'z'))
485 nt.assert_false(hasattr(mod, "z"))
375 486
376 nt.assert_equal(old_foo(0), 4) # superreload magic!
487 nt.assert_equal(old_foo(0), 4) # superreload magic!
377 488 nt.assert_equal(mod.foo(0), 4)
378 489
379 490 obj = mod.Baz(9)
380 nt.assert_equal(old_obj.bar(1), 11) # superreload magic!
491 nt.assert_equal(old_obj.bar(1), 11) # superreload magic!
381 492 nt.assert_equal(obj.bar(1), 11)
382 493
383 494 nt.assert_equal(old_obj.quux, 43)
384 495 nt.assert_equal(obj.quux, 43)
385 496
386 nt.assert_false(hasattr(old_obj, 'zzz'))
387 nt.assert_false(hasattr(obj, 'zzz'))
497 nt.assert_false(hasattr(old_obj, "zzz"))
498 nt.assert_false(hasattr(obj, "zzz"))
388 499
389 500 obj2 = mod.Bar()
390 501 nt.assert_equal(old_obj2.foo(), 2)
391 502 nt.assert_equal(obj2.foo(), 2)
392 503
393 self.shell.run_code("pass") # trigger reload
504 self.shell.run_code("pass") # trigger reload
394 505 check_module_contents()
395 506
396 507 #
@@ -398,7 +509,7 b' class Bar: # old-style class'
398 509 #
399 510 os.unlink(mod_fn)
400 511
401 self.shell.run_code("pass") # trigger reload
512 self.shell.run_code("pass") # trigger reload
402 513 check_module_contents()
403 514
404 515 #
@@ -408,19 +519,21 b' class Bar: # old-style class'
408 519 self.shell.magic_aimport("-" + mod_name)
409 520 stream = StringIO()
410 521 self.shell.magic_aimport("", stream=stream)
411 nt.assert_true(("Modules to skip:\n%s" % mod_name) in
412 stream.getvalue())
522 nt.assert_true(("Modules to skip:\n%s" % mod_name) in stream.getvalue())
413 523
414 524 # This should succeed, although no such module exists
415 525 self.shell.magic_aimport("-tmpmod_as318989e89ds")
416 526 else:
417 527 self.shell.magic_autoreload("0")
418 528
419 self.write_file(mod_fn, """
529 self.write_file(
530 mod_fn,
531 """
420 532 x = -99
421 """)
533 """,
534 )
422 535
423 self.shell.run_code("pass") # trigger reload
536 self.shell.run_code("pass") # trigger reload
424 537 self.shell.run_code("pass")
425 538 check_module_contents()
426 539
@@ -432,7 +545,7 b' x = -99'
432 545 else:
433 546 self.shell.magic_autoreload("")
434 547
435 self.shell.run_code("pass") # trigger reload
548 self.shell.run_code("pass") # trigger reload
436 549 nt.assert_equal(mod.x, -99)
437 550
438 551 def test_smoketest_aimport(self):
@@ -440,8 +553,3 b' x = -99'
440 553
441 554 def test_smoketest_autoreload(self):
442 555 self._check_smoketest(use_aimport=False)
443
444
445
446
447
@@ -152,6 +152,24 b' and "??", in much the same way it can be done when using the IPython prompt::'
152 152
153 153 Previously, "pinfo" or "pinfo2" command had to be used for this purpose.
154 154
155
156 Autoreload 3 feature
157 ====================
158
159 Example: When an IPython session is ran with the 'autoreload' extension loaded,
160 you will now have the option '3' to select which means the following:
161
162 1. replicate all functionality from option 2
163 2. autoload all new funcs/classes/enums/globals from the module when they're added
164 3. autoload all newly imported funcs/classes/enums/globals from external modules
165
166 Try ``%autoreload 3`` in an IPython session after running ``%load_ext autoreload``
167
168 For more information please see unit test -
169 extensions/tests/test_autoreload.py : 'test_autoload_newly_added_objects'
170
171 =======
172
155 173 .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT.
156 174
157 175 As a reminder, IPython master has diverged from the 7.x branch, thus master may
General Comments 0
You need to be logged in to leave comments. Login now