##// END OF EJS Templates
Merge pull request #5044 from takluyver/autoreload-nopyc...
Min RK -
r15689:4527ad96 merge
parent child Browse files
Show More
@@ -1,505 +1,536 b''
1 1 """IPython extension to reload modules before executing user code.
2 2
3 3 ``autoreload`` reloads modules automatically before entering the execution of
4 4 code typed at the IPython prompt.
5 5
6 6 This makes for example the following workflow possible:
7 7
8 8 .. sourcecode:: ipython
9 9
10 10 In [1]: %load_ext autoreload
11 11
12 12 In [2]: %autoreload 2
13 13
14 14 In [3]: from foo import some_function
15 15
16 16 In [4]: some_function()
17 17 Out[4]: 42
18 18
19 19 In [5]: # open foo.py in an editor and change some_function to return 43
20 20
21 21 In [6]: some_function()
22 22 Out[6]: 43
23 23
24 24 The module was reloaded without reloading it explicitly, and the object
25 25 imported with ``from foo import ...`` was also updated.
26 26
27 27 Usage
28 28 =====
29 29
30 30 The following magic commands are provided:
31 31
32 32 ``%autoreload``
33 33
34 34 Reload all modules (except those excluded by ``%aimport``)
35 35 automatically now.
36 36
37 37 ``%autoreload 0``
38 38
39 39 Disable automatic reloading.
40 40
41 41 ``%autoreload 1``
42 42
43 43 Reload all modules imported with ``%aimport`` every time before
44 44 executing the Python code typed.
45 45
46 46 ``%autoreload 2``
47 47
48 48 Reload all modules (except those excluded by ``%aimport``) every
49 49 time before executing the Python code typed.
50 50
51 51 ``%aimport``
52 52
53 53 List modules which are to be automatically imported or not to be imported.
54 54
55 55 ``%aimport foo``
56 56
57 57 Import module 'foo' and mark it to be autoreloaded for ``%autoreload 1``
58 58
59 59 ``%aimport -foo``
60 60
61 61 Mark module 'foo' to not be autoreloaded.
62 62
63 63 Caveats
64 64 =======
65 65
66 66 Reloading Python modules in a reliable way is in general difficult,
67 67 and unexpected things may occur. ``%autoreload`` tries to work around
68 68 common pitfalls by replacing function code objects and parts of
69 69 classes previously in the module with new versions. This makes the
70 70 following things to work:
71 71
72 72 - Functions and classes imported via 'from xxx import foo' are upgraded
73 73 to new versions when 'xxx' is reloaded.
74 74
75 75 - Methods and properties of classes are upgraded on reload, so that
76 76 calling 'c.foo()' on an object 'c' created before the reload causes
77 77 the new code for 'foo' to be executed.
78 78
79 79 Some of the known remaining caveats are:
80 80
81 81 - Replacing code objects does not always succeed: changing a @property
82 82 in a class to an ordinary method or a method to a member variable
83 83 can cause problems (but in old objects only).
84 84
85 85 - Functions that are removed (eg. via monkey-patching) from a module
86 86 before it is reloaded are not upgraded.
87 87
88 88 - C extension modules cannot be reloaded, and so cannot be autoreloaded.
89 89 """
90 90 from __future__ import print_function
91 91
92 92 skip_doctest = True
93 93
94 94 #-----------------------------------------------------------------------------
95 95 # Copyright (C) 2000 Thomas Heller
96 96 # Copyright (C) 2008 Pauli Virtanen <pav@iki.fi>
97 97 # Copyright (C) 2012 The IPython Development Team
98 98 #
99 99 # Distributed under the terms of the BSD License. The full license is in
100 100 # the file COPYING, distributed as part of this software.
101 101 #-----------------------------------------------------------------------------
102 102 #
103 103 # This IPython module is written by Pauli Virtanen, based on the autoreload
104 104 # code by Thomas Heller.
105 105
106 106 #-----------------------------------------------------------------------------
107 107 # Imports
108 108 #-----------------------------------------------------------------------------
109 109
110 110 import os
111 111 import sys
112 112 import traceback
113 113 import types
114 114 import weakref
115 115
116 116 try:
117 117 # Reload is not defined by default in Python3.
118 118 reload
119 119 except NameError:
120 120 from imp import reload
121 121
122 122 from IPython.utils import openpy
123 123 from IPython.utils.py3compat import PY3
124 124
125 125 #------------------------------------------------------------------------------
126 126 # Autoreload functionality
127 127 #------------------------------------------------------------------------------
128 128
129 129 class ModuleReloader(object):
130 130 enabled = False
131 131 """Whether this reloader is enabled"""
132 132
133 failed = {}
134 """Modules that failed to reload: {module: mtime-on-failed-reload, ...}"""
135
136 modules = {}
137 """Modules specially marked as autoreloadable."""
138
139 skip_modules = {}
140 """Modules specially marked as not autoreloadable."""
141
142 133 check_all = True
143 134 """Autoreload all modules, not just those listed in 'modules'"""
144 135
145 old_objects = {}
146 """(module-name, name) -> weakref, for replacing old code objects"""
136 def __init__(self):
137 # Modules that failed to reload: {module: mtime-on-failed-reload, ...}
138 self.failed = {}
139 # Modules specially marked as autoreloadable.
140 self.modules = {}
141 # Modules specially marked as not autoreloadable.
142 self.skip_modules = {}
143 # (module-name, name) -> weakref, for replacing old code objects
144 self.old_objects = {}
145 # Module modification timestamps
146 self.modules_mtimes = {}
147
148 # Cache module modification times
149 self.check(check_all=True, do_reload=False)
147 150
148 151 def mark_module_skipped(self, module_name):
149 152 """Skip reloading the named module in the future"""
150 153 try:
151 154 del self.modules[module_name]
152 155 except KeyError:
153 156 pass
154 157 self.skip_modules[module_name] = True
155 158
156 159 def mark_module_reloadable(self, module_name):
157 160 """Reload the named module in the future (if it is imported)"""
158 161 try:
159 162 del self.skip_modules[module_name]
160 163 except KeyError:
161 164 pass
162 165 self.modules[module_name] = True
163 166
164 167 def aimport_module(self, module_name):
165 168 """Import a module, and mark it reloadable
166 169
167 170 Returns
168 171 -------
169 172 top_module : module
170 173 The imported module if it is top-level, or the top-level
171 174 top_name : module
172 175 Name of top_module
173 176
174 177 """
175 178 self.mark_module_reloadable(module_name)
176 179
177 180 __import__(module_name)
178 181 top_name = module_name.split('.')[0]
179 182 top_module = sys.modules[top_name]
180 183 return top_module, top_name
181 184
182 def check(self, check_all=False):
185 def filename_and_mtime(self, module):
186 if not hasattr(module, '__file__'):
187 return None, None
188
189 if module.__name__ == '__main__':
190 # we cannot reload(__main__)
191 return None, None
192
193 filename = module.__file__
194 path, ext = os.path.splitext(filename)
195
196 if ext.lower() == '.py':
197 py_filename = filename
198 else:
199 try:
200 py_filename = openpy.source_from_cache(filename)
201 except ValueError:
202 return None, None
203
204 try:
205 pymtime = os.stat(py_filename).st_mtime
206 except OSError:
207 return None, None
208
209 return py_filename, pymtime
210
211 def check(self, check_all=False, do_reload=True):
183 212 """Check whether some modules need to be reloaded."""
184 213
185 214 if not self.enabled and not check_all:
186 215 return
187 216
188 217 if check_all or self.check_all:
189 218 modules = list(sys.modules.keys())
190 219 else:
191 220 modules = list(self.modules.keys())
192 221
193 222 for modname in modules:
194 223 m = sys.modules.get(modname, None)
195 224
196 225 if modname in self.skip_modules:
197 226 continue
198 227
199 if not hasattr(m, '__file__'):
228 py_filename, pymtime = self.filename_and_mtime(m)
229 if py_filename is None:
200 230 continue
201 231
202 if m.__name__ == '__main__':
203 # we cannot reload(__main__)
204 continue
205
206 filename = m.__file__
207 path, ext = os.path.splitext(filename)
208
209 if ext.lower() == '.py':
210 pyc_filename = openpy.cache_from_source(filename)
211 py_filename = filename
212 else:
213 pyc_filename = filename
214 try:
215 py_filename = openpy.source_from_cache(filename)
216 except ValueError:
217 continue
218
219 232 try:
220 pymtime = os.stat(py_filename).st_mtime
221 if pymtime <= os.stat(pyc_filename).st_mtime:
233 if pymtime <= self.modules_mtimes[modname]:
222 234 continue
235 except KeyError:
236 self.modules_mtimes[modname] = pymtime
237 continue
238 else:
223 239 if self.failed.get(py_filename, None) == pymtime:
224 240 continue
225 except OSError:
226 continue
227 241
228 try:
229 superreload(m, reload, self.old_objects)
230 if py_filename in self.failed:
231 del self.failed[py_filename]
232 except:
233 print("[autoreload of %s failed: %s]" % (
234 modname, traceback.format_exc(1)), file=sys.stderr)
235 self.failed[py_filename] = pymtime
242 self.modules_mtimes[modname] = pymtime
243
244 # If we've reached this point, we should try to reload the module
245 if do_reload:
246 try:
247 superreload(m, reload, self.old_objects)
248 if py_filename in self.failed:
249 del self.failed[py_filename]
250 except:
251 print("[autoreload of %s failed: %s]" % (
252 modname, traceback.format_exc(1)), file=sys.stderr)
253 self.failed[py_filename] = pymtime
236 254
237 255 #------------------------------------------------------------------------------
238 256 # superreload
239 257 #------------------------------------------------------------------------------
240 258
241 259 if PY3:
242 260 func_attrs = ['__code__', '__defaults__', '__doc__',
243 261 '__closure__', '__globals__', '__dict__']
244 262 else:
245 263 func_attrs = ['func_code', 'func_defaults', 'func_doc',
246 264 'func_closure', 'func_globals', 'func_dict']
247 265
248 266
249 267 def update_function(old, new):
250 268 """Upgrade the code object of a function"""
251 269 for name in func_attrs:
252 270 try:
253 271 setattr(old, name, getattr(new, name))
254 272 except (AttributeError, TypeError):
255 273 pass
256 274
257 275
258 276 def update_class(old, new):
259 277 """Replace stuff in the __dict__ of a class, and upgrade
260 278 method code objects"""
261 279 for key in list(old.__dict__.keys()):
262 280 old_obj = getattr(old, key)
263 281
264 282 try:
265 283 new_obj = getattr(new, key)
266 284 except AttributeError:
267 285 # obsolete attribute: remove it
268 286 try:
269 287 delattr(old, key)
270 288 except (AttributeError, TypeError):
271 289 pass
272 290 continue
273 291
274 292 if update_generic(old_obj, new_obj): continue
275 293
276 294 try:
277 295 setattr(old, key, getattr(new, key))
278 296 except (AttributeError, TypeError):
279 297 pass # skip non-writable attributes
280 298
281 299
282 300 def update_property(old, new):
283 301 """Replace get/set/del functions of a property"""
284 302 update_generic(old.fdel, new.fdel)
285 303 update_generic(old.fget, new.fget)
286 304 update_generic(old.fset, new.fset)
287 305
288 306
289 307 def isinstance2(a, b, typ):
290 308 return isinstance(a, typ) and isinstance(b, typ)
291 309
292 310
293 311 UPDATE_RULES = [
294 312 (lambda a, b: isinstance2(a, b, type),
295 313 update_class),
296 314 (lambda a, b: isinstance2(a, b, types.FunctionType),
297 315 update_function),
298 316 (lambda a, b: isinstance2(a, b, property),
299 317 update_property),
300 318 ]
301 319
302 320
303 321 if PY3:
304 322 UPDATE_RULES.extend([(lambda a, b: isinstance2(a, b, types.MethodType),
305 323 lambda a, b: update_function(a.__func__, b.__func__)),
306 324 ])
307 325 else:
308 326 UPDATE_RULES.extend([(lambda a, b: isinstance2(a, b, types.ClassType),
309 327 update_class),
310 328 (lambda a, b: isinstance2(a, b, types.MethodType),
311 329 lambda a, b: update_function(a.__func__, b.__func__)),
312 330 ])
313 331
314 332
315 333 def update_generic(a, b):
316 334 for type_check, update in UPDATE_RULES:
317 335 if type_check(a, b):
318 336 update(a, b)
319 337 return True
320 338 return False
321 339
322 340
323 341 class StrongRef(object):
324 342 def __init__(self, obj):
325 343 self.obj = obj
326 344 def __call__(self):
327 345 return self.obj
328 346
329 347
330 348 def superreload(module, reload=reload, old_objects={}):
331 349 """Enhanced version of the builtin reload function.
332 350
333 351 superreload remembers objects previously in the module, and
334 352
335 353 - upgrades the class dictionary of every old class in the module
336 354 - upgrades the code object of every old function and method
337 355 - clears the module's namespace before reloading
338 356
339 357 """
340 358
341 359 # collect old objects in the module
342 360 for name, obj in list(module.__dict__.items()):
343 361 if not hasattr(obj, '__module__') or obj.__module__ != module.__name__:
344 362 continue
345 363 key = (module.__name__, name)
346 364 try:
347 365 old_objects.setdefault(key, []).append(weakref.ref(obj))
348 366 except TypeError:
349 367 # weakref doesn't work for all types;
350 368 # create strong references for 'important' cases
351 369 if not PY3 and isinstance(obj, types.ClassType):
352 370 old_objects.setdefault(key, []).append(StrongRef(obj))
353 371
354 372 # reload module
355 373 try:
356 374 # clear namespace first from old cruft
357 375 old_dict = module.__dict__.copy()
358 376 old_name = module.__name__
359 377 module.__dict__.clear()
360 378 module.__dict__['__name__'] = old_name
361 379 module.__dict__['__loader__'] = old_dict['__loader__']
362 380 except (TypeError, AttributeError, KeyError):
363 381 pass
364 382
365 383 try:
366 384 module = reload(module)
367 385 except:
368 386 # restore module dictionary on failed reload
369 387 module.__dict__.update(old_dict)
370 388 raise
371 389
372 390 # iterate over all objects and update functions & classes
373 391 for name, new_obj in list(module.__dict__.items()):
374 392 key = (module.__name__, name)
375 393 if key not in old_objects: continue
376 394
377 395 new_refs = []
378 396 for old_ref in old_objects[key]:
379 397 old_obj = old_ref()
380 398 if old_obj is None: continue
381 399 new_refs.append(old_ref)
382 400 update_generic(old_obj, new_obj)
383 401
384 402 if new_refs:
385 403 old_objects[key] = new_refs
386 404 else:
387 405 del old_objects[key]
388 406
389 407 return module
390 408
391 409 #------------------------------------------------------------------------------
392 410 # IPython connectivity
393 411 #------------------------------------------------------------------------------
394 412
395 413 from IPython.core.magic import Magics, magics_class, line_magic
396 414
397 415 @magics_class
398 416 class AutoreloadMagics(Magics):
399 417 def __init__(self, *a, **kw):
400 418 super(AutoreloadMagics, self).__init__(*a, **kw)
401 419 self._reloader = ModuleReloader()
402 420 self._reloader.check_all = False
421 self.loaded_modules = set(sys.modules)
403 422
404 423 @line_magic
405 424 def autoreload(self, parameter_s=''):
406 425 r"""%autoreload => Reload modules automatically
407 426
408 427 %autoreload
409 428 Reload all modules (except those excluded by %aimport) automatically
410 429 now.
411 430
412 431 %autoreload 0
413 432 Disable automatic reloading.
414 433
415 434 %autoreload 1
416 435 Reload all modules imported with %aimport every time before executing
417 436 the Python code typed.
418 437
419 438 %autoreload 2
420 439 Reload all modules (except those excluded by %aimport) every time
421 440 before executing the Python code typed.
422 441
423 442 Reloading Python modules in a reliable way is in general
424 443 difficult, and unexpected things may occur. %autoreload tries to
425 444 work around common pitfalls by replacing function code objects and
426 445 parts of classes previously in the module with new versions. This
427 446 makes the following things to work:
428 447
429 448 - Functions and classes imported via 'from xxx import foo' are upgraded
430 449 to new versions when 'xxx' is reloaded.
431 450
432 451 - Methods and properties of classes are upgraded on reload, so that
433 452 calling 'c.foo()' on an object 'c' created before the reload causes
434 453 the new code for 'foo' to be executed.
435 454
436 455 Some of the known remaining caveats are:
437 456
438 457 - Replacing code objects does not always succeed: changing a @property
439 458 in a class to an ordinary method or a method to a member variable
440 459 can cause problems (but in old objects only).
441 460
442 461 - Functions that are removed (eg. via monkey-patching) from a module
443 462 before it is reloaded are not upgraded.
444 463
445 464 - C extension modules cannot be reloaded, and so cannot be
446 465 autoreloaded.
447 466
448 467 """
449 468 if parameter_s == '':
450 469 self._reloader.check(True)
451 470 elif parameter_s == '0':
452 471 self._reloader.enabled = False
453 472 elif parameter_s == '1':
454 473 self._reloader.check_all = False
455 474 self._reloader.enabled = True
456 475 elif parameter_s == '2':
457 476 self._reloader.check_all = True
458 477 self._reloader.enabled = True
459 478
460 479 @line_magic
461 480 def aimport(self, parameter_s='', stream=None):
462 481 """%aimport => Import modules for automatic reloading.
463 482
464 483 %aimport
465 484 List modules to automatically import and not to import.
466 485
467 486 %aimport foo
468 487 Import module 'foo' and mark it to be autoreloaded for %autoreload 1
469 488
470 489 %aimport -foo
471 490 Mark module 'foo' to not be autoreloaded for %autoreload 1
472 491 """
473 492 modname = parameter_s
474 493 if not modname:
475 494 to_reload = sorted(self._reloader.modules.keys())
476 495 to_skip = sorted(self._reloader.skip_modules.keys())
477 496 if stream is None:
478 497 stream = sys.stdout
479 498 if self._reloader.check_all:
480 499 stream.write("Modules to reload:\nall-except-skipped\n")
481 500 else:
482 501 stream.write("Modules to reload:\n%s\n" % ' '.join(to_reload))
483 502 stream.write("\nModules to skip:\n%s\n" % ' '.join(to_skip))
484 503 elif modname.startswith('-'):
485 504 modname = modname[1:]
486 505 self._reloader.mark_module_skipped(modname)
487 506 else:
488 507 top_module, top_name = self._reloader.aimport_module(modname)
489 508
490 509 # Inject module to user namespace
491 510 self.shell.push({top_name: top_module})
492 511
493 512 def pre_run_cell(self):
494 513 if self._reloader.enabled:
495 514 try:
496 515 self._reloader.check()
497 516 except:
498 517 pass
499 518
519 def post_execute_hook(self):
520 """Cache the modification times of any modules imported in this execution
521 """
522 newly_loaded_modules = set(sys.modules) - self.loaded_modules
523 for modname in newly_loaded_modules:
524 _, pymtime = self._reloader.filename_and_mtime(sys.modules[modname])
525 if pymtime is not None:
526 self._reloader.modules_mtimes[modname] = pymtime
527
528 self.loaded_modules.update(newly_loaded_modules)
529
500 530
501 531 def load_ipython_extension(ip):
502 532 """Load the extension in IPython."""
503 533 auto_reload = AutoreloadMagics(ip)
504 534 ip.register_magics(auto_reload)
505 535 ip.events.register('pre_run_cell', auto_reload.pre_run_cell)
536 ip.events.register('post_execute', auto_reload.post_execute_hook)
@@ -1,321 +1,321 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
17 17 import tempfile
18 18 import shutil
19 19 import random
20 20 import time
21 21
22 22 import nose.tools as nt
23 23 import IPython.testing.tools as tt
24 24
25 25 from IPython.extensions.autoreload import AutoreloadMagics
26 26 from IPython.core.events import EventManager, pre_run_cell
27 27 from IPython.utils.py3compat import PY3
28 28
29 29 if PY3:
30 30 from io import StringIO
31 31 else:
32 32 from StringIO import StringIO
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # Test fixture
36 36 #-----------------------------------------------------------------------------
37 37
38 38 noop = lambda *a, **kw: None
39 39
40 40 class FakeShell(object):
41 41
42 42 def __init__(self):
43 43 self.ns = {}
44 44 self.events = EventManager(self, {'pre_run_cell', pre_run_cell})
45 45 self.auto_magics = AutoreloadMagics(shell=self)
46 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 51 self.events.trigger('pre_run_cell')
52 52 exec(code, self.ns)
53 self.auto_magics.post_execute_hook()
53 54
54 55 def push(self, items):
55 56 self.ns.update(items)
56 57
57 58 def magic_autoreload(self, parameter):
58 59 self.auto_magics.autoreload(parameter)
59 60
60 61 def magic_aimport(self, parameter, stream=None):
61 62 self.auto_magics.aimport(parameter, stream=stream)
63 self.auto_magics.post_execute_hook()
62 64
63 65
64 66 class Fixture(object):
65 67 """Fixture for creating test module files"""
66 68
67 69 test_dir = None
68 70 old_sys_path = None
69 71 filename_chars = "abcdefghijklmopqrstuvwxyz0123456789"
70 72
71 73 def setUp(self):
72 74 self.test_dir = tempfile.mkdtemp()
73 75 self.old_sys_path = list(sys.path)
74 76 sys.path.insert(0, self.test_dir)
75 77 self.shell = FakeShell()
76 78
77 79 def tearDown(self):
78 80 shutil.rmtree(self.test_dir)
79 81 sys.path = self.old_sys_path
80 82
81 83 self.test_dir = None
82 84 self.old_sys_path = None
83 85 self.shell = None
84 86
85 87 def get_module(self):
86 88 module_name = "tmpmod_" + "".join(random.sample(self.filename_chars,20))
87 89 if module_name in sys.modules:
88 90 del sys.modules[module_name]
89 91 file_name = os.path.join(self.test_dir, module_name + ".py")
90 92 return module_name, file_name
91 93
92 94 def write_file(self, filename, content):
93 95 """
94 96 Write a file, and force a timestamp difference of at least one second
95 97
96 98 Notes
97 99 -----
98 100 Python's .pyc files record the timestamp of their compilation
99 101 with a time resolution of one second.
100 102
101 103 Therefore, we need to force a timestamp difference between .py
102 104 and .pyc, without having the .py file be timestamped in the
103 105 future, and without changing the timestamp of the .pyc file
104 106 (because that is stored in the file). The only reliable way
105 107 to achieve this seems to be to sleep.
106 108 """
107 109
108 110 # Sleep one second + eps
109 111 time.sleep(1.05)
110 112
111 113 # Write
112 114 f = open(filename, 'w')
113 115 try:
114 116 f.write(content)
115 117 finally:
116 118 f.close()
117 119
118 120 def new_module(self, code):
119 121 mod_name, mod_fn = self.get_module()
120 122 f = open(mod_fn, 'w')
121 123 try:
122 124 f.write(code)
123 125 finally:
124 126 f.close()
125 127 return mod_name, mod_fn
126 128
127 129 #-----------------------------------------------------------------------------
128 130 # Test automatic reloading
129 131 #-----------------------------------------------------------------------------
130 132
131 133 class TestAutoreload(Fixture):
132 134 def _check_smoketest(self, use_aimport=True):
133 135 """
134 136 Functional test for the automatic reloader using either
135 137 '%autoreload 1' or '%autoreload 2'
136 138 """
137 139
138 140 mod_name, mod_fn = self.new_module("""
139 141 x = 9
140 142
141 143 z = 123 # this item will be deleted
142 144
143 145 def foo(y):
144 146 return y + 3
145 147
146 148 class Baz(object):
147 149 def __init__(self, x):
148 150 self.x = x
149 151 def bar(self, y):
150 152 return self.x + y
151 153 @property
152 154 def quux(self):
153 155 return 42
154 156 def zzz(self):
155 157 '''This method will be deleted below'''
156 158 return 99
157 159
158 160 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
159 161 def foo(self):
160 162 return 1
161 163 """)
162 164
163 165 #
164 166 # Import module, and mark for reloading
165 167 #
166 168 if use_aimport:
167 169 self.shell.magic_autoreload("1")
168 170 self.shell.magic_aimport(mod_name)
169 171 stream = StringIO()
170 172 self.shell.magic_aimport("", stream=stream)
171 nt.assert_true(("Modules to reload:\n%s" % mod_name) in
172 stream.getvalue())
173 nt.assert_in(("Modules to reload:\n%s" % mod_name), stream.getvalue())
173 174
174 nt.assert_raises(
175 ImportError,
176 self.shell.magic_aimport, "tmpmod_as318989e89ds")
175 with nt.assert_raises(ImportError):
176 self.shell.magic_aimport("tmpmod_as318989e89ds")
177 177 else:
178 178 self.shell.magic_autoreload("2")
179 179 self.shell.run_code("import %s" % mod_name)
180 180 stream = StringIO()
181 181 self.shell.magic_aimport("", stream=stream)
182 182 nt.assert_true("Modules to reload:\nall-except-skipped" in
183 183 stream.getvalue())
184 184 nt.assert_in(mod_name, self.shell.ns)
185 185
186 186 mod = sys.modules[mod_name]
187 187
188 188 #
189 189 # Test module contents
190 190 #
191 191 old_foo = mod.foo
192 192 old_obj = mod.Baz(9)
193 193 old_obj2 = mod.Bar()
194 194
195 195 def check_module_contents():
196 196 nt.assert_equal(mod.x, 9)
197 197 nt.assert_equal(mod.z, 123)
198 198
199 199 nt.assert_equal(old_foo(0), 3)
200 200 nt.assert_equal(mod.foo(0), 3)
201 201
202 202 obj = mod.Baz(9)
203 203 nt.assert_equal(old_obj.bar(1), 10)
204 204 nt.assert_equal(obj.bar(1), 10)
205 205 nt.assert_equal(obj.quux, 42)
206 206 nt.assert_equal(obj.zzz(), 99)
207 207
208 208 obj2 = mod.Bar()
209 209 nt.assert_equal(old_obj2.foo(), 1)
210 210 nt.assert_equal(obj2.foo(), 1)
211 211
212 212 check_module_contents()
213 213
214 214 #
215 215 # Simulate a failed reload: no reload should occur and exactly
216 216 # one error message should be printed
217 217 #
218 218 self.write_file(mod_fn, """
219 219 a syntax error
220 220 """)
221 221
222 222 with tt.AssertPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
223 223 self.shell.run_code("pass") # trigger reload
224 224 with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
225 225 self.shell.run_code("pass") # trigger another reload
226 226 check_module_contents()
227 227
228 228 #
229 229 # Rewrite module (this time reload should succeed)
230 230 #
231 231 self.write_file(mod_fn, """
232 232 x = 10
233 233
234 234 def foo(y):
235 235 return y + 4
236 236
237 237 class Baz(object):
238 238 def __init__(self, x):
239 239 self.x = x
240 240 def bar(self, y):
241 241 return self.x + y + 1
242 242 @property
243 243 def quux(self):
244 244 return 43
245 245
246 246 class Bar: # old-style class
247 247 def foo(self):
248 248 return 2
249 249 """)
250 250
251 251 def check_module_contents():
252 252 nt.assert_equal(mod.x, 10)
253 253 nt.assert_false(hasattr(mod, 'z'))
254 254
255 255 nt.assert_equal(old_foo(0), 4) # superreload magic!
256 256 nt.assert_equal(mod.foo(0), 4)
257 257
258 258 obj = mod.Baz(9)
259 259 nt.assert_equal(old_obj.bar(1), 11) # superreload magic!
260 260 nt.assert_equal(obj.bar(1), 11)
261 261
262 262 nt.assert_equal(old_obj.quux, 43)
263 263 nt.assert_equal(obj.quux, 43)
264 264
265 265 nt.assert_false(hasattr(old_obj, 'zzz'))
266 266 nt.assert_false(hasattr(obj, 'zzz'))
267 267
268 268 obj2 = mod.Bar()
269 269 nt.assert_equal(old_obj2.foo(), 2)
270 270 nt.assert_equal(obj2.foo(), 2)
271 271
272 272 self.shell.run_code("pass") # trigger reload
273 273 check_module_contents()
274 274
275 275 #
276 276 # Another failure case: deleted file (shouldn't reload)
277 277 #
278 278 os.unlink(mod_fn)
279 279
280 280 self.shell.run_code("pass") # trigger reload
281 281 check_module_contents()
282 282
283 283 #
284 284 # Disable autoreload and rewrite module: no reload should occur
285 285 #
286 286 if use_aimport:
287 287 self.shell.magic_aimport("-" + mod_name)
288 288 stream = StringIO()
289 289 self.shell.magic_aimport("", stream=stream)
290 290 nt.assert_true(("Modules to skip:\n%s" % mod_name) in
291 291 stream.getvalue())
292 292
293 293 # This should succeed, although no such module exists
294 294 self.shell.magic_aimport("-tmpmod_as318989e89ds")
295 295 else:
296 296 self.shell.magic_autoreload("0")
297 297
298 298 self.write_file(mod_fn, """
299 299 x = -99
300 300 """)
301 301
302 302 self.shell.run_code("pass") # trigger reload
303 303 self.shell.run_code("pass")
304 304 check_module_contents()
305 305
306 306 #
307 307 # Re-enable autoreload: reload should now occur
308 308 #
309 309 if use_aimport:
310 310 self.shell.magic_aimport(mod_name)
311 311 else:
312 312 self.shell.magic_autoreload("")
313 313
314 314 self.shell.run_code("pass") # trigger reload
315 315 nt.assert_equal(mod.x, -99)
316 316
317 317 def test_smoketest_aimport(self):
318 318 self._check_smoketest(use_aimport=True)
319 319
320 320 def test_smoketest_autoreload(self):
321 321 self._check_smoketest(use_aimport=False)
General Comments 0
You need to be logged in to leave comments. Login now