##// 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 # <full commit hash> # initial black-format
13 # <full commit hash> # initial black-format
14 # <full commit hash> # rename something internal
14 # <full commit hash> # rename something internal
15 6e748726282d1acb9a4f9f264ee679c474c4b8f5 # Apply pygrade --36plus on IPython/core/tests/test_inputtransformer.py.
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 Reload all modules (except those excluded by ``%aimport``) every
48 Reload all modules (except those excluded by ``%aimport``) every
49 time before executing the Python code typed.
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 ``%aimport``
56 ``%aimport``
52
57
53 List modules which are to be automatically imported or not to be imported.
58 List modules which are to be automatically imported or not to be imported.
@@ -124,14 +129,18 b' from imp import reload'
124 # Autoreload functionality
129 # Autoreload functionality
125 #------------------------------------------------------------------------------
130 # ------------------------------------------------------------------------------
126
131
127 class ModuleReloader(object):
132
133 class ModuleReloader:
128 enabled = False
134 enabled = False
129 """Whether this reloader is enabled"""
135 """Whether this reloader is enabled"""
130
136
131 check_all = True
137 check_all = True
132 """Autoreload all modules, not just those listed in 'modules'"""
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 # Modules that failed to reload: {module: mtime-on-failed-reload, ...}
144 # Modules that failed to reload: {module: mtime-on-failed-reload, ...}
136 self.failed = {}
145 self.failed = {}
137 # Modules specially marked as autoreloadable.
146 # Modules specially marked as autoreloadable.
@@ -142,6 +151,7 b' class ModuleReloader(object):'
142 self.old_objects = {}
151 self.old_objects = {}
143 # Module modification timestamps
152 # Module modification timestamps
144 self.modules_mtimes = {}
153 self.modules_mtimes = {}
154 self.shell = shell
145
155
146 # Cache module modification times
156 # Cache module modification times
147 self.check(check_all=True, do_reload=False)
157 self.check(check_all=True, do_reload=False)
@@ -176,22 +186,22 b' class ModuleReloader(object):'
176 self.mark_module_reloadable(module_name)
186 self.mark_module_reloadable(module_name)
177
187
178 import_module(module_name)
188 import_module(module_name)
179 top_name = module_name.split('.')[0]
189 top_name = module_name.split(".")[0]
180 top_module = sys.modules[top_name]
190 top_module = sys.modules[top_name]
181 return top_module, top_name
191 return top_module, top_name
182
192
183 def filename_and_mtime(self, module):
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 return None, None
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 # we cannot reload(__main__) or reload(__mp_main__)
198 # we cannot reload(__main__) or reload(__mp_main__)
189 return None, None
199 return None, None
190
200
191 filename = module.__file__
201 filename = module.__file__
192 path, ext = os.path.splitext(filename)
202 path, ext = os.path.splitext(filename)
193
203
194 if ext.lower() == '.py':
204 if ext.lower() == ".py":
195 py_filename = filename
205 py_filename = filename
196 else:
206 else:
197 try:
207 try:
@@ -242,21 +252,35 b' class ModuleReloader(object):'
242 # If we've reached this point, we should try to reload the module
252 # If we've reached this point, we should try to reload the module
243 if do_reload:
253 if do_reload:
244 try:
254 try:
255 if self.autoload_obj:
256 superreload(m, reload, self.old_objects, self.shell)
257 else:
245 superreload(m, reload, self.old_objects)
258 superreload(m, reload, self.old_objects)
246 if py_filename in self.failed:
259 if py_filename in self.failed:
247 del self.failed[py_filename]
260 del self.failed[py_filename]
248 except:
261 except:
249 print("[autoreload of %s failed: %s]" % (
262 print(
250 modname, traceback.format_exc(10)), file=sys.stderr)
263 "[autoreload of {} failed: {}]".format(
264 modname, traceback.format_exc(10)
265 ),
266 file=sys.stderr,
267 )
251 self.failed[py_filename] = pymtime
268 self.failed[py_filename] = pymtime
252
269
270
253 #------------------------------------------------------------------------------
271 # ------------------------------------------------------------------------------
254 # superreload
272 # superreload
255 #------------------------------------------------------------------------------
273 # ------------------------------------------------------------------------------
256
274
257
275
258 func_attrs = ['__code__', '__defaults__', '__doc__',
276 func_attrs = [
259 '__closure__', '__globals__', '__dict__']
277 "__code__",
278 "__defaults__",
279 "__doc__",
280 "__closure__",
281 "__globals__",
282 "__dict__",
283 ]
260
284
261
285
262 def update_function(old, new):
286 def update_function(old, new):
@@ -299,7 +323,8 b' def update_class(old, new):'
299 pass
323 pass
300 continue
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 try:
329 try:
305 setattr(old, key, getattr(new, key))
330 setattr(old, key, getattr(new, key))
@@ -329,16 +354,18 b' def isinstance2(a, b, typ):'
329
354
330
355
331 UPDATE_RULES = [
356 UPDATE_RULES = [
332 (lambda a, b: isinstance2(a, b, type),
357 (lambda a, b: isinstance2(a, b, type), update_class),
333 update_class),
358 (lambda a, b: isinstance2(a, b, types.FunctionType), update_function),
334 (lambda a, b: isinstance2(a, b, types.FunctionType),
359 (lambda a, b: isinstance2(a, b, property), update_property),
335 update_function),
336 (lambda a, b: isinstance2(a, b, property),
337 update_property),
338 ]
360 ]
339 UPDATE_RULES.extend([(lambda a, b: isinstance2(a, b, types.MethodType),
361 UPDATE_RULES.extend(
340 lambda a, b: update_function(a.__func__, b.__func__)),
362 [
341 ])
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 def update_generic(a, b):
371 def update_generic(a, b):
@@ -349,14 +376,45 b' def update_generic(a, b):'
349 return False
376 return False
350
377
351
378
352 class StrongRef(object):
379 class StrongRef:
353 def __init__(self, obj):
380 def __init__(self, obj):
354 self.obj = obj
381 self.obj = obj
382
355 def __call__(self):
383 def __call__(self):
356 return self.obj
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 """Enhanced version of the builtin reload function.
418 """Enhanced version of the builtin reload function.
361
419
362 superreload remembers objects previously in the module, and
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 # collect old objects in the module
430 # collect old objects in the module
373 for name, obj in list(module.__dict__.items()):
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 continue
433 continue
376 key = (module.__name__, name)
434 key = (module.__name__, name)
377 try:
435 try:
@@ -385,8 +443,8 b' def superreload(module, reload=reload, old_objects=None):'
385 old_dict = module.__dict__.copy()
443 old_dict = module.__dict__.copy()
386 old_name = module.__name__
444 old_name = module.__name__
387 module.__dict__.clear()
445 module.__dict__.clear()
388 module.__dict__['__name__'] = old_name
446 module.__dict__["__name__"] = old_name
389 module.__dict__['__loader__'] = old_dict['__loader__']
447 module.__dict__["__loader__"] = old_dict["__loader__"]
390 except (TypeError, AttributeError, KeyError):
448 except (TypeError, AttributeError, KeyError):
391 pass
449 pass
392
450
@@ -400,12 +458,21 b' def superreload(module, reload=reload, old_objects=None):'
400 # iterate over all objects and update functions & classes
458 # iterate over all objects and update functions & classes
401 for name, new_obj in list(module.__dict__.items()):
459 for name, new_obj in list(module.__dict__.items()):
402 key = (module.__name__, name)
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 new_refs = []
471 new_refs = []
406 for old_ref in old_objects[key]:
472 for old_ref in old_objects[key]:
407 old_obj = old_ref()
473 old_obj = old_ref()
408 if old_obj is None: continue
474 if old_obj is None:
475 continue
409 new_refs.append(old_ref)
476 new_refs.append(old_ref)
410 update_generic(old_obj, new_obj)
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 return module
484 return module
418
485
486
419 #------------------------------------------------------------------------------
487 # ------------------------------------------------------------------------------
420 # IPython connectivity
488 # IPython connectivity
421 #------------------------------------------------------------------------------
489 # ------------------------------------------------------------------------------
422
490
423 from IPython.core.magic import Magics, magics_class, line_magic
491 from IPython.core.magic import Magics, magics_class, line_magic
424
492
493
425 @magics_class
494 @magics_class
426 class AutoreloadMagics(Magics):
495 class AutoreloadMagics(Magics):
427 def __init__(self, *a, **kw):
496 def __init__(self, *a, **kw):
428 super(AutoreloadMagics, self).__init__(*a, **kw)
497 super().__init__(*a, **kw)
429 self._reloader = ModuleReloader()
498 self._reloader = ModuleReloader(self.shell)
430 self._reloader.check_all = False
499 self._reloader.check_all = False
500 self._reloader.autoload_obj = False
431 self.loaded_modules = set(sys.modules)
501 self.loaded_modules = set(sys.modules)
432
502
433 @line_magic
503 @line_magic
434 def autoreload(self, parameter_s=''):
504 def autoreload(self, parameter_s=""):
435 r"""%autoreload => Reload modules automatically
505 r"""%autoreload => Reload modules automatically
436
506
437 %autoreload
507 %autoreload
@@ -475,19 +545,24 b' class AutoreloadMagics(Magics):'
475 autoreloaded.
545 autoreloaded.
476
546
477 """
547 """
478 if parameter_s == '':
548 if parameter_s == "":
479 self._reloader.check(True)
549 self._reloader.check(True)
480 elif parameter_s == '0':
550 elif parameter_s == "0":
481 self._reloader.enabled = False
551 self._reloader.enabled = False
482 elif parameter_s == '1':
552 elif parameter_s == "1":
483 self._reloader.check_all = False
553 self._reloader.check_all = False
484 self._reloader.enabled = True
554 self._reloader.enabled = True
485 elif parameter_s == '2':
555 elif parameter_s == "2":
486 self._reloader.check_all = True
556 self._reloader.check_all = True
487 self._reloader.enabled = True
557 self._reloader.enabled = True
558 self._reloader.enabled = True
559 elif parameter_s == "3":
560 self._reloader.check_all = True
561 self._reloader.enabled = True
562 self._reloader.autoload_obj = True
488
563
489 @line_magic
564 @line_magic
490 def aimport(self, parameter_s='', stream=None):
565 def aimport(self, parameter_s="", stream=None):
491 """%aimport => Import modules for automatic reloading.
566 """%aimport => Import modules for automatic reloading.
492
567
493 %aimport
568 %aimport
@@ -511,13 +586,13 b' class AutoreloadMagics(Magics):'
511 if self._reloader.check_all:
586 if self._reloader.check_all:
512 stream.write("Modules to reload:\nall-except-skipped\n")
587 stream.write("Modules to reload:\nall-except-skipped\n")
513 else:
588 else:
514 stream.write("Modules to reload:\n%s\n" % ' '.join(to_reload))
589 stream.write("Modules to reload:\n%s\n" % " ".join(to_reload))
515 stream.write("\nModules to skip:\n%s\n" % ' '.join(to_skip))
590 stream.write("\nModules to skip:\n%s\n" % " ".join(to_skip))
516 elif modname.startswith('-'):
591 elif modname.startswith("-"):
517 modname = modname[1:]
592 modname = modname[1:]
518 self._reloader.mark_module_skipped(modname)
593 self._reloader.mark_module_skipped(modname)
519 else:
594 else:
520 for _module in ([_.strip() for _ in modname.split(',')]):
595 for _module in [_.strip() for _ in modname.split(",")]:
521 top_module, top_name = self._reloader.aimport_module(_module)
596 top_module, top_name = self._reloader.aimport_module(_module)
522
597
523 # Inject module to user namespace
598 # Inject module to user namespace
@@ -531,8 +606,7 b' class AutoreloadMagics(Magics):'
531 pass
606 pass
532
607
533 def post_execute_hook(self):
608 def post_execute_hook(self):
534 """Cache the modification times of any modules imported in this execution
609 """Cache the modification times of any modules imported in this execution"""
535 """
536 newly_loaded_modules = set(sys.modules) - self.loaded_modules
610 newly_loaded_modules = set(sys.modules) - self.loaded_modules
537 for modname in newly_loaded_modules:
611 for modname in newly_loaded_modules:
538 _, pymtime = self._reloader.filename_and_mtime(sys.modules[modname])
612 _, pymtime = self._reloader.filename_and_mtime(sys.modules[modname])
@@ -546,5 +620,5 b' def load_ipython_extension(ip):'
546 """Load the extension in IPython."""
620 """Load the extension in IPython."""
547 auto_reload = AutoreloadMagics(ip)
621 auto_reload = AutoreloadMagics(ip)
548 ip.register_magics(auto_reload)
622 ip.register_magics(auto_reload)
549 ip.events.register('pre_run_cell', auto_reload.pre_run_cell)
623 ip.events.register("pre_run_cell", auto_reload.pre_run_cell)
550 ip.events.register('post_execute', auto_reload.post_execute_hook)
624 ip.events.register("post_execute", auto_reload.post_execute_hook)
@@ -35,20 +35,20 b' from IPython.core.events import EventManager, pre_run_cell'
35
35
36 noop = lambda *a, **kw: None
36 noop = lambda *a, **kw: None
37
37
38 class FakeShell:
39
38
39 class FakeShell:
40 def __init__(self):
40 def __init__(self):
41 self.ns = {}
41 self.ns = {}
42 self.user_ns = self.ns
42 self.user_ns = self.ns
43 self.user_ns_hidden = {}
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 self.auto_magics = AutoreloadMagics(shell=self)
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 register_magics = set_hook = noop
48 register_magics = set_hook = noop
49
49
50 def run_code(self, code):
50 def run_code(self, code):
51 self.events.trigger('pre_run_cell')
51 self.events.trigger("pre_run_cell")
52 exec(code, self.user_ns)
52 exec(code, self.user_ns)
53 self.auto_magics.post_execute_hook()
53 self.auto_magics.post_execute_hook()
54
54
@@ -111,20 +111,22 b' class Fixture(TestCase):'
111 time.sleep(1.05)
111 time.sleep(1.05)
112
112
113 # Write
113 # Write
114 with open(filename, 'w') as f:
114 with open(filename, "w") as f:
115 f.write(content)
115 f.write(content)
116
116
117 def new_module(self, code):
117 def new_module(self, code):
118 code = textwrap.dedent(code)
118 code = textwrap.dedent(code)
119 mod_name, mod_fn = self.get_module()
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 f.write(code)
121 f.write(code)
122 return mod_name, mod_fn
122 return mod_name, mod_fn
123
123
124
124 #-----------------------------------------------------------------------------
125 # -----------------------------------------------------------------------------
125 # Test automatic reloading
126 # Test automatic reloading
126 #-----------------------------------------------------------------------------
127 # -----------------------------------------------------------------------------
127
128
129
128 def pickle_get_current_class(obj):
130 def pickle_get_current_class(obj):
129 """
131 """
130 Original issue comes from pickle; hence the name.
132 Original issue comes from pickle; hence the name.
@@ -136,25 +138,36 b' def pickle_get_current_class(obj):'
136 obj2 = getattr(obj2, subpath)
138 obj2 = getattr(obj2, subpath)
137 return obj2
139 return obj2
138
140
139 class TestAutoreload(Fixture):
140
141
142 class TestAutoreload(Fixture):
141 def test_reload_enums(self):
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 from enum import Enum
147 from enum import Enum
144 class MyEnum(Enum):
148 class MyEnum(Enum):
145 A = 'A'
149 A = 'A'
146 B = 'B'
150 B = 'B'
147 """))
151 """
152 )
153 )
148 self.shell.magic_autoreload("2")
154 self.shell.magic_autoreload("2")
149 self.shell.magic_aimport(mod_name)
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 from enum import Enum
160 from enum import Enum
152 class MyEnum(Enum):
161 class MyEnum(Enum):
153 A = 'A'
162 A = 'A'
154 B = 'B'
163 B = 'B'
155 C = 'C'
164 C = 'C'
156 """))
165 """
157 with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
166 ),
167 )
168 with tt.AssertNotPrints(
169 ("[autoreload of %s failed:" % mod_name), channel="stderr"
170 ):
158 self.shell.run_code("pass") # trigger another reload
171 self.shell.run_code("pass") # trigger another reload
159
172
160 def test_reload_class_type(self):
173 def test_reload_class_type(self):
@@ -195,7 +208,9 b' class TestAutoreload(Fixture):'
195
208
196 def test_reload_class_attributes(self):
209 def test_reload_class_attributes(self):
197 self.shell.magic_autoreload("2")
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 class MyClass:
214 class MyClass:
200
215
201 def __init__(self, a=10):
216 def __init__(self, a=10):
@@ -241,16 +256,99 b' class TestAutoreload(Fixture):'
241
256
242 self.shell.run_code("second = MyClass(5)")
257 self.shell.run_code("second = MyClass(5)")
243
258
244 for object_name in {'first', 'second'}:
259 for object_name in {"first", "second"}:
245 self.shell.run_code("{object_name}.power(5)".format(object_name=object_name))
260 self.shell.run_code(f"{object_name}.power(5)")
246 with nt.assert_raises(AttributeError):
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 with nt.assert_raises(AttributeError):
263 with nt.assert_raises(AttributeError):
249 self.shell.run_code("{object_name}.square()".format(object_name=object_name))
264 self.shell.run_code(f"{object_name}.square()")
250 self.shell.run_code("{object_name}.b".format(object_name=object_name))
265 self.shell.run_code(f"{object_name}.b")
251 self.shell.run_code("{object_name}.a".format(object_name=object_name))
266 self.shell.run_code(f"{object_name}.a")
252 with nt.assert_raises(AttributeError):
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 def _check_smoketest(self, use_aimport=True):
353 def _check_smoketest(self, use_aimport=True):
256 """
354 """
@@ -258,7 +356,8 b' class TestAutoreload(Fixture):'
258 '%autoreload 1' or '%autoreload 2'
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 x = 9
361 x = 9
263
362
264 z = 123 # this item will be deleted
363 z = 123 # this item will be deleted
@@ -281,7 +380,8 b' class Baz(object):'
281 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
380 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
282 def foo(self):
381 def foo(self):
283 return 1
382 return 1
284 """)
383 """
384 )
285
385
286 #
386 #
287 # Import module, and mark for reloading
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 self.shell.run_code("import %s" % mod_name)
400 self.shell.run_code("import %s" % mod_name)
301 stream = StringIO()
401 stream = StringIO()
302 self.shell.magic_aimport("", stream=stream)
402 self.shell.magic_aimport("", stream=stream)
303 nt.assert_true("Modules to reload:\nall-except-skipped" in
403 nt.assert_true(
304 stream.getvalue())
404 "Modules to reload:\nall-except-skipped" in stream.getvalue()
405 )
305 nt.assert_in(mod_name, self.shell.ns)
406 nt.assert_in(mod_name, self.shell.ns)
306
407
307 mod = sys.modules[mod_name]
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 # Simulate a failed reload: no reload should occur and exactly
437 # Simulate a failed reload: no reload should occur and exactly
337 # one error message should be printed
438 # one error message should be printed
338 #
439 #
339 self.write_file(mod_fn, """
440 self.write_file(
441 mod_fn,
442 """
340 a syntax error
443 a syntax error
341 """)
444 """,
445 )
342
446
343 with tt.AssertPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
447 with tt.AssertPrints(
448 ("[autoreload of %s failed:" % mod_name), channel="stderr"
449 ):
344 self.shell.run_code("pass") # trigger reload
450 self.shell.run_code("pass") # trigger reload
345 with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
451 with tt.AssertNotPrints(
452 ("[autoreload of %s failed:" % mod_name), channel="stderr"
453 ):
346 self.shell.run_code("pass") # trigger another reload
454 self.shell.run_code("pass") # trigger another reload
347 check_module_contents()
455 check_module_contents()
348
456
349 #
457 #
350 # Rewrite module (this time reload should succeed)
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 x = 10
463 x = 10
354
464
355 def foo(y):
465 def foo(y):
@@ -367,11 +477,12 b' class Baz(object):'
367 class Bar: # old-style class
477 class Bar: # old-style class
368 def foo(self):
478 def foo(self):
369 return 2
479 return 2
370 """)
480 """,
481 )
371
482
372 def check_module_contents():
483 def check_module_contents():
373 nt.assert_equal(mod.x, 10)
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 nt.assert_equal(mod.foo(0), 4)
488 nt.assert_equal(mod.foo(0), 4)
@@ -383,8 +494,8 b' class Bar: # old-style class'
383 nt.assert_equal(old_obj.quux, 43)
494 nt.assert_equal(old_obj.quux, 43)
384 nt.assert_equal(obj.quux, 43)
495 nt.assert_equal(obj.quux, 43)
385
496
386 nt.assert_false(hasattr(old_obj, 'zzz'))
497 nt.assert_false(hasattr(old_obj, "zzz"))
387 nt.assert_false(hasattr(obj, 'zzz'))
498 nt.assert_false(hasattr(obj, "zzz"))
388
499
389 obj2 = mod.Bar()
500 obj2 = mod.Bar()
390 nt.assert_equal(old_obj2.foo(), 2)
501 nt.assert_equal(old_obj2.foo(), 2)
@@ -408,17 +519,19 b' class Bar: # old-style class'
408 self.shell.magic_aimport("-" + mod_name)
519 self.shell.magic_aimport("-" + mod_name)
409 stream = StringIO()
520 stream = StringIO()
410 self.shell.magic_aimport("", stream=stream)
521 self.shell.magic_aimport("", stream=stream)
411 nt.assert_true(("Modules to skip:\n%s" % mod_name) in
522 nt.assert_true(("Modules to skip:\n%s" % mod_name) in stream.getvalue())
412 stream.getvalue())
413
523
414 # This should succeed, although no such module exists
524 # This should succeed, although no such module exists
415 self.shell.magic_aimport("-tmpmod_as318989e89ds")
525 self.shell.magic_aimport("-tmpmod_as318989e89ds")
416 else:
526 else:
417 self.shell.magic_autoreload("0")
527 self.shell.magic_autoreload("0")
418
528
419 self.write_file(mod_fn, """
529 self.write_file(
530 mod_fn,
531 """
420 x = -99
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 self.shell.run_code("pass")
537 self.shell.run_code("pass")
@@ -440,8 +553,3 b' x = -99'
440
553
441 def test_smoketest_autoreload(self):
554 def test_smoketest_autoreload(self):
442 self._check_smoketest(use_aimport=False)
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 Previously, "pinfo" or "pinfo2" command had to be used for this purpose.
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 .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT.
173 .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT.
156
174
157 As a reminder, IPython master has diverged from the 7.x branch, thus master may
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