##// END OF EJS Templates
Add new '%autoreload 3' option...
Spas Kalaydzhisyki -
Show More
@@ -0,0 +1,14 b''
1 Autoreload 3 feature
2 ====================
3
4 Example: When an IPython session is ran with the 'autoreload' extension loaded,
5 you will now have the option '3' to select which means the following:
6
7 1. replicate all functionality from option 2
8 2. autoload all new funcs/classes/enums/globals from the module when they're added
9 3. autoload all newly imported funcs/classes/enums/globals from external modules
10
11 Try ``%autoreload 3`` in an IPython session after running ``%load_ext autoreload``
12
13 For more information please see unit test -
14 extensions/tests/test_autoreload.py : 'test_autoload_newly_added_objects'
@@ -48,6 +48,12 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 (except those excluded by ``%aimport``)
55 every time before executing the Python code typed.
56
51 57 ``%aimport``
52 58
53 59 List modules which are to be automatically imported or not to be imported.
@@ -131,7 +137,10 b' class ModuleReloader(object):'
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)
@@ -242,7 +252,10 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:
@@ -356,7 +369,25 b' class StrongRef(object):'
356 369 return self.obj
357 370
358 371
359 def superreload(module, reload=reload, old_objects=None):
372 def append_obj(module, d, name, obj, autoload=False):
373 not_in_mod = not hasattr(obj, '__module__') or obj.__module__ != module.__name__
374 if autoload:
375 # check needed for module global built-ins (int, str, dict,..)
376 if name.startswith('__') and not_in_mod:
377 return False
378 else:
379 if not_in_mod:
380 return False
381
382 key = (module.__name__, name)
383 try:
384 d.setdefault(key, []).append(weakref.ref(obj))
385 except TypeError:
386 pass
387 return True
388
389
390 def superreload(module, reload=reload, old_objects=None, shell=None):
360 391 """Enhanced version of the builtin reload function.
361 392
362 393 superreload remembers objects previously in the module, and
@@ -371,7 +402,7 b' def superreload(module, reload=reload, old_objects=None):'
371 402
372 403 # collect old objects in the module
373 404 for name, obj in list(module.__dict__.items()):
374 if not hasattr(obj, '__module__') or obj.__module__ != module.__name__:
405 if not append_obj(module, old_objects, name, obj):
375 406 continue
376 407 key = (module.__name__, name)
377 408 try:
@@ -400,7 +431,15 b' def superreload(module, reload=reload, old_objects=None):'
400 431 # iterate over all objects and update functions & classes
401 432 for name, new_obj in list(module.__dict__.items()):
402 433 key = (module.__name__, name)
403 if key not in old_objects: continue
434 if key not in old_objects:
435 # here 'shell' acts both as a flag and as an output var
436 if (
437 shell is None or
438 name == 'Enum' or
439 not append_obj(module, old_objects, name, new_obj, True)
440 ):
441 continue
442 shell.user_ns[name] = new_obj
404 443
405 444 new_refs = []
406 445 for old_ref in old_objects[key]:
@@ -426,8 +465,9 b' from IPython.core.magic import Magics, magics_class, line_magic'
426 465 class AutoreloadMagics(Magics):
427 466 def __init__(self, *a, **kw):
428 467 super(AutoreloadMagics, self).__init__(*a, **kw)
429 self._reloader = ModuleReloader()
468 self._reloader = ModuleReloader(self.shell)
430 469 self._reloader.check_all = False
470 self._reloader.autoload_obj = False
431 471 self.loaded_modules = set(sys.modules)
432 472
433 473 @line_magic
@@ -485,6 +525,11 b' class AutoreloadMagics(Magics):'
485 525 elif parameter_s == '2':
486 526 self._reloader.check_all = True
487 527 self._reloader.enabled = True
528 self._reloader.enabled = True
529 elif parameter_s == '3':
530 self._reloader.check_all = True
531 self._reloader.enabled = True
532 self._reloader.autoload_obj = True
488 533
489 534 @line_magic
490 535 def aimport(self, parameter_s='', stream=None):
@@ -252,6 +252,89 b' class TestAutoreload(Fixture):'
252 252 with nt.assert_raises(AttributeError):
253 253 self.shell.run_code("{object_name}.toto".format(object_name=object_name))
254 254
255 def test_autoload_newly_added_objects(self):
256 self.shell.magic_autoreload("3")
257 mod_code = """
258 def func1(): pass
259 """
260 mod_name, mod_fn = self.new_module(textwrap.dedent(mod_code))
261 self.shell.run_code(f"from {mod_name} import *")
262 self.shell.run_code("func1()")
263 with nt.assert_raises(NameError):
264 self.shell.run_code('func2()')
265 with nt.assert_raises(NameError):
266 self.shell.run_code('t = Test()')
267 with nt.assert_raises(NameError):
268 self.shell.run_code('number')
269
270 # ----------- TEST NEW OBJ LOADED --------------------------
271
272 new_code = """
273 def func1(): pass
274 def func2(): pass
275 class Test: pass
276 number = 0
277 from enum import Enum
278 class TestEnum(Enum):
279 A = 'a'
280 """
281 self.write_file(mod_fn, textwrap.dedent(new_code))
282
283 # test function now exists in shell's namespace namespace
284 self.shell.run_code("func2()")
285 # test function now exists in module's dict
286 self.shell.run_code(f"import sys; sys.modules['{mod_name}'].func2()")
287 # test class now exists
288 self.shell.run_code("t = Test()")
289 # test global built-in var now exists
290 self.shell.run_code('number')
291 # test the enumerations gets loaded succesfully
292 self.shell.run_code("TestEnum.A")
293
294 # ----------- TEST NEW OBJ CAN BE CHANGED --------------------
295
296 new_code = """
297 def func1(): return 'changed'
298 def func2(): return 'changed'
299 class Test:
300 def new_func(self):
301 return 'changed'
302 number = 1
303 from enum import Enum
304 class TestEnum(Enum):
305 A = 'a'
306 B = 'added'
307 """
308 self.write_file(mod_fn, textwrap.dedent(new_code))
309 self.shell.run_code("assert func1() == 'changed'")
310 self.shell.run_code("assert func2() == 'changed'")
311 self.shell.run_code("t = Test(); assert t.new_func() == 'changed'")
312 self.shell.run_code("assert number == 1")
313 self.shell.run_code("assert TestEnum.B.value == 'added'")
314
315 # ----------- TEST IMPORT FROM MODULE --------------------------
316
317 new_mod_code = '''
318 from enum import Enum
319 class Ext(Enum):
320 A = 'ext'
321 def ext_func():
322 return 'ext'
323 class ExtTest:
324 def meth(self):
325 return 'ext'
326 ext_int = 2
327 '''
328 new_mod_name, new_mod_fn = self.new_module(textwrap.dedent(new_mod_code))
329 current_mod_code = f'''
330 from {new_mod_name} import *
331 '''
332 self.write_file(mod_fn, textwrap.dedent(current_mod_code))
333 self.shell.run_code("assert Ext.A.value == 'ext'")
334 self.shell.run_code("assert ext_func() == 'ext'")
335 self.shell.run_code("t = ExtTest(); assert t.meth() == 'ext'")
336 self.shell.run_code("assert ext_int == 2")
337
255 338 def _check_smoketest(self, use_aimport=True):
256 339 """
257 340 Functional test for the automatic reloader using either
General Comments 0
You need to be logged in to leave comments. Login now