##// END OF EJS Templates
autoreload: explicitly check for True when updating attributes. Fixes #11558
Sanyam Agarwal -
Show More
@@ -1,532 +1,534 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 importlib.util import source_from_cache
120 120 from imp import reload
121 121
122 122 #------------------------------------------------------------------------------
123 123 # Autoreload functionality
124 124 #------------------------------------------------------------------------------
125 125
126 126 class ModuleReloader(object):
127 127 enabled = False
128 128 """Whether this reloader is enabled"""
129 129
130 130 check_all = True
131 131 """Autoreload all modules, not just those listed in 'modules'"""
132 132
133 133 def __init__(self):
134 134 # Modules that failed to reload: {module: mtime-on-failed-reload, ...}
135 135 self.failed = {}
136 136 # Modules specially marked as autoreloadable.
137 137 self.modules = {}
138 138 # Modules specially marked as not autoreloadable.
139 139 self.skip_modules = {}
140 140 # (module-name, name) -> weakref, for replacing old code objects
141 141 self.old_objects = {}
142 142 # Module modification timestamps
143 143 self.modules_mtimes = {}
144 144
145 145 # Cache module modification times
146 146 self.check(check_all=True, do_reload=False)
147 147
148 148 def mark_module_skipped(self, module_name):
149 149 """Skip reloading the named module in the future"""
150 150 try:
151 151 del self.modules[module_name]
152 152 except KeyError:
153 153 pass
154 154 self.skip_modules[module_name] = True
155 155
156 156 def mark_module_reloadable(self, module_name):
157 157 """Reload the named module in the future (if it is imported)"""
158 158 try:
159 159 del self.skip_modules[module_name]
160 160 except KeyError:
161 161 pass
162 162 self.modules[module_name] = True
163 163
164 164 def aimport_module(self, module_name):
165 165 """Import a module, and mark it reloadable
166 166
167 167 Returns
168 168 -------
169 169 top_module : module
170 170 The imported module if it is top-level, or the top-level
171 171 top_name : module
172 172 Name of top_module
173 173
174 174 """
175 175 self.mark_module_reloadable(module_name)
176 176
177 177 import_module(module_name)
178 178 top_name = module_name.split('.')[0]
179 179 top_module = sys.modules[top_name]
180 180 return top_module, top_name
181 181
182 182 def filename_and_mtime(self, module):
183 183 if not hasattr(module, '__file__') or module.__file__ is None:
184 184 return None, None
185 185
186 186 if getattr(module, '__name__', None) in [None, '__mp_main__', '__main__']:
187 187 # we cannot reload(__main__) or reload(__mp_main__)
188 188 return None, None
189 189
190 190 filename = module.__file__
191 191 path, ext = os.path.splitext(filename)
192 192
193 193 if ext.lower() == '.py':
194 194 py_filename = filename
195 195 else:
196 196 try:
197 197 py_filename = source_from_cache(filename)
198 198 except ValueError:
199 199 return None, None
200 200
201 201 try:
202 202 pymtime = os.stat(py_filename).st_mtime
203 203 except OSError:
204 204 return None, None
205 205
206 206 return py_filename, pymtime
207 207
208 208 def check(self, check_all=False, do_reload=True):
209 209 """Check whether some modules need to be reloaded."""
210 210
211 211 if not self.enabled and not check_all:
212 212 return
213 213
214 214 if check_all or self.check_all:
215 215 modules = list(sys.modules.keys())
216 216 else:
217 217 modules = list(self.modules.keys())
218 218
219 219 for modname in modules:
220 220 m = sys.modules.get(modname, None)
221 221
222 222 if modname in self.skip_modules:
223 223 continue
224 224
225 225 py_filename, pymtime = self.filename_and_mtime(m)
226 226 if py_filename is None:
227 227 continue
228 228
229 229 try:
230 230 if pymtime <= self.modules_mtimes[modname]:
231 231 continue
232 232 except KeyError:
233 233 self.modules_mtimes[modname] = pymtime
234 234 continue
235 235 else:
236 236 if self.failed.get(py_filename, None) == pymtime:
237 237 continue
238 238
239 239 self.modules_mtimes[modname] = pymtime
240 240
241 241 # If we've reached this point, we should try to reload the module
242 242 if do_reload:
243 243 try:
244 244 superreload(m, reload, self.old_objects)
245 245 if py_filename in self.failed:
246 246 del self.failed[py_filename]
247 247 except:
248 248 print("[autoreload of %s failed: %s]" % (
249 249 modname, traceback.format_exc(10)), file=sys.stderr)
250 250 self.failed[py_filename] = pymtime
251 251
252 252 #------------------------------------------------------------------------------
253 253 # superreload
254 254 #------------------------------------------------------------------------------
255 255
256 256
257 257 func_attrs = ['__code__', '__defaults__', '__doc__',
258 258 '__closure__', '__globals__', '__dict__']
259 259
260 260
261 261 def update_function(old, new):
262 262 """Upgrade the code object of a function"""
263 263 for name in func_attrs:
264 264 try:
265 265 setattr(old, name, getattr(new, name))
266 266 except (AttributeError, TypeError):
267 267 pass
268 268
269 269
270 270 def update_class(old, new):
271 271 """Replace stuff in the __dict__ of a class, and upgrade
272 272 method code objects, and add new methods, if any"""
273 273 for key in list(old.__dict__.keys()):
274 274 old_obj = getattr(old, key)
275 275 try:
276 276 new_obj = getattr(new, key)
277 if old_obj == new_obj:
277 # explicitly checking that comparison returns True to handle
278 # cases where `==` doesn't return a boolean.
279 if (old_obj == new_obj) is True:
278 280 continue
279 281 except AttributeError:
280 282 # obsolete attribute: remove it
281 283 try:
282 284 delattr(old, key)
283 285 except (AttributeError, TypeError):
284 286 pass
285 287 continue
286 288
287 289 if update_generic(old_obj, new_obj): continue
288 290
289 291 try:
290 292 setattr(old, key, getattr(new, key))
291 293 except (AttributeError, TypeError):
292 294 pass # skip non-writable attributes
293 295
294 296 for key in list(new.__dict__.keys()):
295 297 if key not in list(old.__dict__.keys()):
296 298 try:
297 299 setattr(old, key, getattr(new, key))
298 300 except (AttributeError, TypeError):
299 301 pass # skip non-writable attributes
300 302
301 303
302 304 def update_property(old, new):
303 305 """Replace get/set/del functions of a property"""
304 306 update_generic(old.fdel, new.fdel)
305 307 update_generic(old.fget, new.fget)
306 308 update_generic(old.fset, new.fset)
307 309
308 310
309 311 def isinstance2(a, b, typ):
310 312 return isinstance(a, typ) and isinstance(b, typ)
311 313
312 314
313 315 UPDATE_RULES = [
314 316 (lambda a, b: isinstance2(a, b, type),
315 317 update_class),
316 318 (lambda a, b: isinstance2(a, b, types.FunctionType),
317 319 update_function),
318 320 (lambda a, b: isinstance2(a, b, property),
319 321 update_property),
320 322 ]
321 323 UPDATE_RULES.extend([(lambda a, b: isinstance2(a, b, types.MethodType),
322 324 lambda a, b: update_function(a.__func__, b.__func__)),
323 325 ])
324 326
325 327
326 328 def update_generic(a, b):
327 329 for type_check, update in UPDATE_RULES:
328 330 if type_check(a, b):
329 331 update(a, b)
330 332 return True
331 333 return False
332 334
333 335
334 336 class StrongRef(object):
335 337 def __init__(self, obj):
336 338 self.obj = obj
337 339 def __call__(self):
338 340 return self.obj
339 341
340 342
341 343 def superreload(module, reload=reload, old_objects=None):
342 344 """Enhanced version of the builtin reload function.
343 345
344 346 superreload remembers objects previously in the module, and
345 347
346 348 - upgrades the class dictionary of every old class in the module
347 349 - upgrades the code object of every old function and method
348 350 - clears the module's namespace before reloading
349 351
350 352 """
351 353 if old_objects is None:
352 354 old_objects = {}
353 355
354 356 # collect old objects in the module
355 357 for name, obj in list(module.__dict__.items()):
356 358 if not hasattr(obj, '__module__') or obj.__module__ != module.__name__:
357 359 continue
358 360 key = (module.__name__, name)
359 361 try:
360 362 old_objects.setdefault(key, []).append(weakref.ref(obj))
361 363 except TypeError:
362 364 pass
363 365
364 366 # reload module
365 367 try:
366 368 # clear namespace first from old cruft
367 369 old_dict = module.__dict__.copy()
368 370 old_name = module.__name__
369 371 module.__dict__.clear()
370 372 module.__dict__['__name__'] = old_name
371 373 module.__dict__['__loader__'] = old_dict['__loader__']
372 374 except (TypeError, AttributeError, KeyError):
373 375 pass
374 376
375 377 try:
376 378 module = reload(module)
377 379 except:
378 380 # restore module dictionary on failed reload
379 381 module.__dict__.update(old_dict)
380 382 raise
381 383
382 384 # iterate over all objects and update functions & classes
383 385 for name, new_obj in list(module.__dict__.items()):
384 386 key = (module.__name__, name)
385 387 if key not in old_objects: continue
386 388
387 389 new_refs = []
388 390 for old_ref in old_objects[key]:
389 391 old_obj = old_ref()
390 392 if old_obj is None: continue
391 393 new_refs.append(old_ref)
392 394 update_generic(old_obj, new_obj)
393 395
394 396 if new_refs:
395 397 old_objects[key] = new_refs
396 398 else:
397 399 del old_objects[key]
398 400
399 401 return module
400 402
401 403 #------------------------------------------------------------------------------
402 404 # IPython connectivity
403 405 #------------------------------------------------------------------------------
404 406
405 407 from IPython.core.magic import Magics, magics_class, line_magic
406 408
407 409 @magics_class
408 410 class AutoreloadMagics(Magics):
409 411 def __init__(self, *a, **kw):
410 412 super(AutoreloadMagics, self).__init__(*a, **kw)
411 413 self._reloader = ModuleReloader()
412 414 self._reloader.check_all = False
413 415 self.loaded_modules = set(sys.modules)
414 416
415 417 @line_magic
416 418 def autoreload(self, parameter_s=''):
417 419 r"""%autoreload => Reload modules automatically
418 420
419 421 %autoreload
420 422 Reload all modules (except those excluded by %aimport) automatically
421 423 now.
422 424
423 425 %autoreload 0
424 426 Disable automatic reloading.
425 427
426 428 %autoreload 1
427 429 Reload all modules imported with %aimport every time before executing
428 430 the Python code typed.
429 431
430 432 %autoreload 2
431 433 Reload all modules (except those excluded by %aimport) every time
432 434 before executing the Python code typed.
433 435
434 436 Reloading Python modules in a reliable way is in general
435 437 difficult, and unexpected things may occur. %autoreload tries to
436 438 work around common pitfalls by replacing function code objects and
437 439 parts of classes previously in the module with new versions. This
438 440 makes the following things to work:
439 441
440 442 - Functions and classes imported via 'from xxx import foo' are upgraded
441 443 to new versions when 'xxx' is reloaded.
442 444
443 445 - Methods and properties of classes are upgraded on reload, so that
444 446 calling 'c.foo()' on an object 'c' created before the reload causes
445 447 the new code for 'foo' to be executed.
446 448
447 449 Some of the known remaining caveats are:
448 450
449 451 - Replacing code objects does not always succeed: changing a @property
450 452 in a class to an ordinary method or a method to a member variable
451 453 can cause problems (but in old objects only).
452 454
453 455 - Functions that are removed (eg. via monkey-patching) from a module
454 456 before it is reloaded are not upgraded.
455 457
456 458 - C extension modules cannot be reloaded, and so cannot be
457 459 autoreloaded.
458 460
459 461 """
460 462 if parameter_s == '':
461 463 self._reloader.check(True)
462 464 elif parameter_s == '0':
463 465 self._reloader.enabled = False
464 466 elif parameter_s == '1':
465 467 self._reloader.check_all = False
466 468 self._reloader.enabled = True
467 469 elif parameter_s == '2':
468 470 self._reloader.check_all = True
469 471 self._reloader.enabled = True
470 472
471 473 @line_magic
472 474 def aimport(self, parameter_s='', stream=None):
473 475 """%aimport => Import modules for automatic reloading.
474 476
475 477 %aimport
476 478 List modules to automatically import and not to import.
477 479
478 480 %aimport foo
479 481 Import module 'foo' and mark it to be autoreloaded for %autoreload 1
480 482
481 483 %aimport foo, bar
482 484 Import modules 'foo', 'bar' and mark them to be autoreloaded for %autoreload 1
483 485
484 486 %aimport -foo
485 487 Mark module 'foo' to not be autoreloaded for %autoreload 1
486 488 """
487 489 modname = parameter_s
488 490 if not modname:
489 491 to_reload = sorted(self._reloader.modules.keys())
490 492 to_skip = sorted(self._reloader.skip_modules.keys())
491 493 if stream is None:
492 494 stream = sys.stdout
493 495 if self._reloader.check_all:
494 496 stream.write("Modules to reload:\nall-except-skipped\n")
495 497 else:
496 498 stream.write("Modules to reload:\n%s\n" % ' '.join(to_reload))
497 499 stream.write("\nModules to skip:\n%s\n" % ' '.join(to_skip))
498 500 elif modname.startswith('-'):
499 501 modname = modname[1:]
500 502 self._reloader.mark_module_skipped(modname)
501 503 else:
502 504 for _module in ([_.strip() for _ in modname.split(',')]):
503 505 top_module, top_name = self._reloader.aimport_module(_module)
504 506
505 507 # Inject module to user namespace
506 508 self.shell.push({top_name: top_module})
507 509
508 510 def pre_run_cell(self):
509 511 if self._reloader.enabled:
510 512 try:
511 513 self._reloader.check()
512 514 except:
513 515 pass
514 516
515 517 def post_execute_hook(self):
516 518 """Cache the modification times of any modules imported in this execution
517 519 """
518 520 newly_loaded_modules = set(sys.modules) - self.loaded_modules
519 521 for modname in newly_loaded_modules:
520 522 _, pymtime = self._reloader.filename_and_mtime(sys.modules[modname])
521 523 if pymtime is not None:
522 524 self._reloader.modules_mtimes[modname] = pymtime
523 525
524 526 self.loaded_modules.update(newly_loaded_modules)
525 527
526 528
527 529 def load_ipython_extension(ip):
528 530 """Load the extension in IPython."""
529 531 auto_reload = AutoreloadMagics(ip)
530 532 ip.register_magics(auto_reload)
531 533 ip.events.register('pre_run_cell', auto_reload.pre_run_cell)
532 534 ip.events.register('post_execute', auto_reload.post_execute_hook)
General Comments 0
You need to be logged in to leave comments. Login now