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