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