##// END OF EJS Templates
Fix #14230 (missing param for autoreload)...
Matthias Bussonnier -
Show More
@@ -1,154 +1,158
1 1 """Infrastructure for registering and firing callbacks on application events.
2 2
3 3 Unlike :mod:`IPython.core.hooks`, which lets end users set single functions to
4 4 be called at specific times, or a collection of alternative methods to try,
5 5 callbacks are designed to be used by extension authors. A number of callbacks
6 6 can be registered for the same event without needing to be aware of one another.
7 7
8 8 The functions defined in this module are no-ops indicating the names of available
9 9 events and the arguments which will be passed to them.
10 10
11 11 .. note::
12 12
13 13 This API is experimental in IPython 2.0, and may be revised in future versions.
14 14 """
15 15
16 16
17 17 class EventManager(object):
18 18 """Manage a collection of events and a sequence of callbacks for each.
19 19
20 20 This is attached to :class:`~IPython.core.interactiveshell.InteractiveShell`
21 21 instances as an ``events`` attribute.
22 22
23 23 .. note::
24 24
25 25 This API is experimental in IPython 2.0, and may be revised in future versions.
26 26 """
27 27
28 28 def __init__(self, shell, available_events, print_on_error=True):
29 29 """Initialise the :class:`CallbackManager`.
30 30
31 31 Parameters
32 32 ----------
33 33 shell
34 34 The :class:`~IPython.core.interactiveshell.InteractiveShell` instance
35 35 available_events
36 36 An iterable of names for callback events.
37 37 print_on_error:
38 38 A boolean flag to set whether the EventManager will print a warning which a event errors.
39 39 """
40 40 self.shell = shell
41 41 self.callbacks = {n:[] for n in available_events}
42 42 self.print_on_error = print_on_error
43 43
44 44 def register(self, event, function):
45 45 """Register a new event callback.
46 46
47 47 Parameters
48 48 ----------
49 49 event : str
50 50 The event for which to register this callback.
51 51 function : callable
52 52 A function to be called on the given event. It should take the same
53 53 parameters as the appropriate callback prototype.
54 54
55 55 Raises
56 56 ------
57 57 TypeError
58 58 If ``function`` is not callable.
59 59 KeyError
60 60 If ``event`` is not one of the known events.
61 61 """
62 62 if not callable(function):
63 63 raise TypeError('Need a callable, got %r' % function)
64 64 if function not in self.callbacks[event]:
65 65 self.callbacks[event].append(function)
66 66
67 67 def unregister(self, event, function):
68 68 """Remove a callback from the given event."""
69 69 if function in self.callbacks[event]:
70 70 return self.callbacks[event].remove(function)
71 71
72 72 raise ValueError('Function {!r} is not registered as a {} callback'.format(function, event))
73 73
74 74 def trigger(self, event, *args, **kwargs):
75 75 """Call callbacks for ``event``.
76 76
77 77 Any additional arguments are passed to all callbacks registered for this
78 78 event. Exceptions raised by callbacks are caught, and a message printed.
79 79 """
80 80 for func in self.callbacks[event][:]:
81 81 try:
82 82 func(*args, **kwargs)
83 83 except (Exception, KeyboardInterrupt):
84 84 if self.print_on_error:
85 print("Error in callback {} (for {}):".format(func, event))
85 print(
86 "Error in callback {} (for {}), with arguments args {},kwargs {}:".format(
87 func, event, args, kwargs
88 )
89 )
86 90 self.shell.showtraceback()
87 91
88 92 # event_name -> prototype mapping
89 93 available_events = {}
90 94
91 95 def _define_event(callback_function):
92 96 available_events[callback_function.__name__] = callback_function
93 97 return callback_function
94 98
95 99 # ------------------------------------------------------------------------------
96 100 # Callback prototypes
97 101 #
98 102 # No-op functions which describe the names of available events and the
99 103 # signatures of callbacks for those events.
100 104 # ------------------------------------------------------------------------------
101 105
102 106 @_define_event
103 107 def pre_execute():
104 108 """Fires before code is executed in response to user/frontend action.
105 109
106 110 This includes comm and widget messages and silent execution, as well as user
107 111 code cells.
108 112 """
109 113 pass
110 114
111 115 @_define_event
112 116 def pre_run_cell(info):
113 117 """Fires before user-entered code runs.
114 118
115 119 Parameters
116 120 ----------
117 121 info : :class:`~IPython.core.interactiveshell.ExecutionInfo`
118 122 An object containing information used for the code execution.
119 123 """
120 124 pass
121 125
122 126 @_define_event
123 127 def post_execute():
124 128 """Fires after code is executed in response to user/frontend action.
125 129
126 130 This includes comm and widget messages and silent execution, as well as user
127 131 code cells.
128 132 """
129 133 pass
130 134
131 135 @_define_event
132 136 def post_run_cell(result):
133 137 """Fires after user-entered code runs.
134 138
135 139 Parameters
136 140 ----------
137 141 result : :class:`~IPython.core.interactiveshell.ExecutionResult`
138 142 The object which will be returned as the execution result.
139 143 """
140 144 pass
141 145
142 146 @_define_event
143 147 def shell_initialized(ip):
144 148 """Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`.
145 149
146 150 This is before extensions and startup scripts are loaded, so it can only be
147 151 set by subclassing.
148 152
149 153 Parameters
150 154 ----------
151 155 ip : :class:`~IPython.core.interactiveshell.InteractiveShell`
152 156 The newly initialised shell.
153 157 """
154 158 pass
@@ -1,727 +1,727
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``, ``%autoreload now``
33 33
34 34 Reload all modules (except those excluded by ``%aimport``)
35 35 automatically now.
36 36
37 37 ``%autoreload 0``, ``%autoreload off``
38 38
39 39 Disable automatic reloading.
40 40
41 41 ``%autoreload 1``, ``%autoreload explicit``
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``, ``%autoreload all``
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 ``%autoreload 3``, ``%autoreload complete``
52 52
53 53 Same as 2/all, but also adds any new objects in the module. See
54 54 unit test at IPython/extensions/tests/test_autoreload.py::test_autoload_newly_added_objects
55 55
56 56 Adding ``--print`` or ``-p`` to the ``%autoreload`` line will print autoreload activity to
57 57 standard out. ``--log`` or ``-l`` will do it to the log at INFO level; both can be used
58 58 simultaneously.
59 59
60 60 ``%aimport``
61 61
62 62 List modules which are to be automatically imported or not to be imported.
63 63
64 64 ``%aimport foo``
65 65
66 66 Import module 'foo' and mark it to be autoreloaded for ``%autoreload 1``
67 67
68 68 ``%aimport foo, bar``
69 69
70 70 Import modules 'foo', 'bar' and mark them to be autoreloaded for ``%autoreload 1``
71 71
72 72 ``%aimport -foo``
73 73
74 74 Mark module 'foo' to not be autoreloaded.
75 75
76 76 Caveats
77 77 =======
78 78
79 79 Reloading Python modules in a reliable way is in general difficult,
80 80 and unexpected things may occur. ``%autoreload`` tries to work around
81 81 common pitfalls by replacing function code objects and parts of
82 82 classes previously in the module with new versions. This makes the
83 83 following things to work:
84 84
85 85 - Functions and classes imported via 'from xxx import foo' are upgraded
86 86 to new versions when 'xxx' is reloaded.
87 87
88 88 - Methods and properties of classes are upgraded on reload, so that
89 89 calling 'c.foo()' on an object 'c' created before the reload causes
90 90 the new code for 'foo' to be executed.
91 91
92 92 Some of the known remaining caveats are:
93 93
94 94 - Replacing code objects does not always succeed: changing a @property
95 95 in a class to an ordinary method or a method to a member variable
96 96 can cause problems (but in old objects only).
97 97
98 98 - Functions that are removed (eg. via monkey-patching) from a module
99 99 before it is reloaded are not upgraded.
100 100
101 101 - C extension modules cannot be reloaded, and so cannot be autoreloaded.
102 102
103 103 - While comparing Enum and Flag, the 'is' Identity Operator is used (even in the case '==' has been used (Similar to the 'None' keyword)).
104 104
105 105 - Reloading a module, or importing the same module by a different name, creates new Enums. These may look the same, but are not.
106 106 """
107 107
108 108 from IPython.core import magic_arguments
109 109 from IPython.core.magic import Magics, magics_class, line_magic
110 110
111 111 __skip_doctest__ = True
112 112
113 113 # -----------------------------------------------------------------------------
114 114 # Copyright (C) 2000 Thomas Heller
115 115 # Copyright (C) 2008 Pauli Virtanen <pav@iki.fi>
116 116 # Copyright (C) 2012 The IPython Development Team
117 117 #
118 118 # Distributed under the terms of the BSD License. The full license is in
119 119 # the file COPYING, distributed as part of this software.
120 120 # -----------------------------------------------------------------------------
121 121 #
122 122 # This IPython module is written by Pauli Virtanen, based on the autoreload
123 123 # code by Thomas Heller.
124 124
125 125 # -----------------------------------------------------------------------------
126 126 # Imports
127 127 # -----------------------------------------------------------------------------
128 128
129 129 import os
130 130 import sys
131 131 import traceback
132 132 import types
133 133 import weakref
134 134 import gc
135 135 import logging
136 136 from importlib import import_module, reload
137 137 from importlib.util import source_from_cache
138 138
139 139 # ------------------------------------------------------------------------------
140 140 # Autoreload functionality
141 141 # ------------------------------------------------------------------------------
142 142
143 143
144 144 class ModuleReloader:
145 145 enabled = False
146 146 """Whether this reloader is enabled"""
147 147
148 148 check_all = True
149 149 """Autoreload all modules, not just those listed in 'modules'"""
150 150
151 151 autoload_obj = False
152 152 """Autoreload all modules AND autoload all new objects"""
153 153
154 154 def __init__(self, shell=None):
155 155 # Modules that failed to reload: {module: mtime-on-failed-reload, ...}
156 156 self.failed = {}
157 157 # Modules specially marked as autoreloadable.
158 158 self.modules = {}
159 159 # Modules specially marked as not autoreloadable.
160 160 self.skip_modules = {}
161 161 # (module-name, name) -> weakref, for replacing old code objects
162 162 self.old_objects = {}
163 163 # Module modification timestamps
164 164 self.modules_mtimes = {}
165 165 self.shell = shell
166 166
167 167 # Reporting callable for verbosity
168 168 self._report = lambda msg: None # by default, be quiet.
169 169
170 170 # Cache module modification times
171 171 self.check(check_all=True, do_reload=False)
172 172
173 173 # To hide autoreload errors
174 174 self.hide_errors = False
175 175
176 176 def mark_module_skipped(self, module_name):
177 177 """Skip reloading the named module in the future"""
178 178 try:
179 179 del self.modules[module_name]
180 180 except KeyError:
181 181 pass
182 182 self.skip_modules[module_name] = True
183 183
184 184 def mark_module_reloadable(self, module_name):
185 185 """Reload the named module in the future (if it is imported)"""
186 186 try:
187 187 del self.skip_modules[module_name]
188 188 except KeyError:
189 189 pass
190 190 self.modules[module_name] = True
191 191
192 192 def aimport_module(self, module_name):
193 193 """Import a module, and mark it reloadable
194 194
195 195 Returns
196 196 -------
197 197 top_module : module
198 198 The imported module if it is top-level, or the top-level
199 199 top_name : module
200 200 Name of top_module
201 201
202 202 """
203 203 self.mark_module_reloadable(module_name)
204 204
205 205 import_module(module_name)
206 206 top_name = module_name.split(".")[0]
207 207 top_module = sys.modules[top_name]
208 208 return top_module, top_name
209 209
210 210 def filename_and_mtime(self, module):
211 211 if not hasattr(module, "__file__") or module.__file__ is None:
212 212 return None, None
213 213
214 214 if getattr(module, "__name__", None) in [None, "__mp_main__", "__main__"]:
215 215 # we cannot reload(__main__) or reload(__mp_main__)
216 216 return None, None
217 217
218 218 filename = module.__file__
219 219 path, ext = os.path.splitext(filename)
220 220
221 221 if ext.lower() == ".py":
222 222 py_filename = filename
223 223 else:
224 224 try:
225 225 py_filename = source_from_cache(filename)
226 226 except ValueError:
227 227 return None, None
228 228
229 229 try:
230 230 pymtime = os.stat(py_filename).st_mtime
231 231 except OSError:
232 232 return None, None
233 233
234 234 return py_filename, pymtime
235 235
236 236 def check(self, check_all=False, do_reload=True):
237 237 """Check whether some modules need to be reloaded."""
238 238
239 239 if not self.enabled and not check_all:
240 240 return
241 241
242 242 if check_all or self.check_all:
243 243 modules = list(sys.modules.keys())
244 244 else:
245 245 modules = list(self.modules.keys())
246 246
247 247 for modname in modules:
248 248 m = sys.modules.get(modname, None)
249 249
250 250 if modname in self.skip_modules:
251 251 continue
252 252
253 253 py_filename, pymtime = self.filename_and_mtime(m)
254 254 if py_filename is None:
255 255 continue
256 256
257 257 try:
258 258 if pymtime <= self.modules_mtimes[modname]:
259 259 continue
260 260 except KeyError:
261 261 self.modules_mtimes[modname] = pymtime
262 262 continue
263 263 else:
264 264 if self.failed.get(py_filename, None) == pymtime:
265 265 continue
266 266
267 267 self.modules_mtimes[modname] = pymtime
268 268
269 269 # If we've reached this point, we should try to reload the module
270 270 if do_reload:
271 271 self._report(f"Reloading '{modname}'.")
272 272 try:
273 273 if self.autoload_obj:
274 274 superreload(m, reload, self.old_objects, self.shell)
275 275 else:
276 276 superreload(m, reload, self.old_objects)
277 277 if py_filename in self.failed:
278 278 del self.failed[py_filename]
279 279 except:
280 280 if not self.hide_errors:
281 281 print(
282 282 "[autoreload of {} failed: {}]".format(
283 283 modname, traceback.format_exc(10)
284 284 ),
285 285 file=sys.stderr,
286 286 )
287 287 self.failed[py_filename] = pymtime
288 288
289 289
290 290 # ------------------------------------------------------------------------------
291 291 # superreload
292 292 # ------------------------------------------------------------------------------
293 293
294 294
295 295 func_attrs = [
296 296 "__code__",
297 297 "__defaults__",
298 298 "__doc__",
299 299 "__closure__",
300 300 "__globals__",
301 301 "__dict__",
302 302 ]
303 303
304 304
305 305 def update_function(old, new):
306 306 """Upgrade the code object of a function"""
307 307 for name in func_attrs:
308 308 try:
309 309 setattr(old, name, getattr(new, name))
310 310 except (AttributeError, TypeError):
311 311 pass
312 312
313 313
314 314 def update_instances(old, new):
315 315 """Use garbage collector to find all instances that refer to the old
316 316 class definition and update their __class__ to point to the new class
317 317 definition"""
318 318
319 319 refs = gc.get_referrers(old)
320 320
321 321 for ref in refs:
322 322 if type(ref) is old:
323 323 object.__setattr__(ref, "__class__", new)
324 324
325 325
326 326 def update_class(old, new):
327 327 """Replace stuff in the __dict__ of a class, and upgrade
328 328 method code objects, and add new methods, if any"""
329 329 for key in list(old.__dict__.keys()):
330 330 old_obj = getattr(old, key)
331 331 try:
332 332 new_obj = getattr(new, key)
333 333 # explicitly checking that comparison returns True to handle
334 334 # cases where `==` doesn't return a boolean.
335 335 if (old_obj == new_obj) is True:
336 336 continue
337 337 except AttributeError:
338 338 # obsolete attribute: remove it
339 339 try:
340 340 delattr(old, key)
341 341 except (AttributeError, TypeError):
342 342 pass
343 343 continue
344 344 except ValueError:
345 345 # can't compare nested structures containing
346 346 # numpy arrays using `==`
347 347 pass
348 348
349 349 if update_generic(old_obj, new_obj):
350 350 continue
351 351
352 352 try:
353 353 setattr(old, key, getattr(new, key))
354 354 except (AttributeError, TypeError):
355 355 pass # skip non-writable attributes
356 356
357 357 for key in list(new.__dict__.keys()):
358 358 if key not in list(old.__dict__.keys()):
359 359 try:
360 360 setattr(old, key, getattr(new, key))
361 361 except (AttributeError, TypeError):
362 362 pass # skip non-writable attributes
363 363
364 364 # update all instances of class
365 365 update_instances(old, new)
366 366
367 367
368 368 def update_property(old, new):
369 369 """Replace get/set/del functions of a property"""
370 370 update_generic(old.fdel, new.fdel)
371 371 update_generic(old.fget, new.fget)
372 372 update_generic(old.fset, new.fset)
373 373
374 374
375 375 def isinstance2(a, b, typ):
376 376 return isinstance(a, typ) and isinstance(b, typ)
377 377
378 378
379 379 UPDATE_RULES = [
380 380 (lambda a, b: isinstance2(a, b, type), update_class),
381 381 (lambda a, b: isinstance2(a, b, types.FunctionType), update_function),
382 382 (lambda a, b: isinstance2(a, b, property), update_property),
383 383 ]
384 384 UPDATE_RULES.extend(
385 385 [
386 386 (
387 387 lambda a, b: isinstance2(a, b, types.MethodType),
388 388 lambda a, b: update_function(a.__func__, b.__func__),
389 389 ),
390 390 ]
391 391 )
392 392
393 393
394 394 def update_generic(a, b):
395 395 for type_check, update in UPDATE_RULES:
396 396 if type_check(a, b):
397 397 update(a, b)
398 398 return True
399 399 return False
400 400
401 401
402 402 class StrongRef:
403 403 def __init__(self, obj):
404 404 self.obj = obj
405 405
406 406 def __call__(self):
407 407 return self.obj
408 408
409 409
410 410 mod_attrs = [
411 411 "__name__",
412 412 "__doc__",
413 413 "__package__",
414 414 "__loader__",
415 415 "__spec__",
416 416 "__file__",
417 417 "__cached__",
418 418 "__builtins__",
419 419 ]
420 420
421 421
422 422 def append_obj(module, d, name, obj, autoload=False):
423 423 in_module = hasattr(obj, "__module__") and obj.__module__ == module.__name__
424 424 if autoload:
425 425 # check needed for module global built-ins
426 426 if not in_module and name in mod_attrs:
427 427 return False
428 428 else:
429 429 if not in_module:
430 430 return False
431 431
432 432 key = (module.__name__, name)
433 433 try:
434 434 d.setdefault(key, []).append(weakref.ref(obj))
435 435 except TypeError:
436 436 pass
437 437 return True
438 438
439 439
440 440 def superreload(module, reload=reload, old_objects=None, shell=None):
441 441 """Enhanced version of the builtin reload function.
442 442
443 443 superreload remembers objects previously in the module, and
444 444
445 445 - upgrades the class dictionary of every old class in the module
446 446 - upgrades the code object of every old function and method
447 447 - clears the module's namespace before reloading
448 448
449 449 """
450 450 if old_objects is None:
451 451 old_objects = {}
452 452
453 453 # collect old objects in the module
454 454 for name, obj in list(module.__dict__.items()):
455 455 if not append_obj(module, old_objects, name, obj):
456 456 continue
457 457 key = (module.__name__, name)
458 458 try:
459 459 old_objects.setdefault(key, []).append(weakref.ref(obj))
460 460 except TypeError:
461 461 pass
462 462
463 463 # reload module
464 464 try:
465 465 # clear namespace first from old cruft
466 466 old_dict = module.__dict__.copy()
467 467 old_name = module.__name__
468 468 module.__dict__.clear()
469 469 module.__dict__["__name__"] = old_name
470 470 module.__dict__["__loader__"] = old_dict["__loader__"]
471 471 except (TypeError, AttributeError, KeyError):
472 472 pass
473 473
474 474 try:
475 475 module = reload(module)
476 476 except:
477 477 # restore module dictionary on failed reload
478 478 module.__dict__.update(old_dict)
479 479 raise
480 480
481 481 # iterate over all objects and update functions & classes
482 482 for name, new_obj in list(module.__dict__.items()):
483 483 key = (module.__name__, name)
484 484 if key not in old_objects:
485 485 # here 'shell' acts both as a flag and as an output var
486 486 if (
487 487 shell is None
488 488 or name == "Enum"
489 489 or not append_obj(module, old_objects, name, new_obj, True)
490 490 ):
491 491 continue
492 492 shell.user_ns[name] = new_obj
493 493
494 494 new_refs = []
495 495 for old_ref in old_objects[key]:
496 496 old_obj = old_ref()
497 497 if old_obj is None:
498 498 continue
499 499 new_refs.append(old_ref)
500 500 update_generic(old_obj, new_obj)
501 501
502 502 if new_refs:
503 503 old_objects[key] = new_refs
504 504 else:
505 505 del old_objects[key]
506 506
507 507 return module
508 508
509 509
510 510 # ------------------------------------------------------------------------------
511 511 # IPython connectivity
512 512 # ------------------------------------------------------------------------------
513 513
514 514
515 515 @magics_class
516 516 class AutoreloadMagics(Magics):
517 517 def __init__(self, *a, **kw):
518 518 super().__init__(*a, **kw)
519 519 self._reloader = ModuleReloader(self.shell)
520 520 self._reloader.check_all = False
521 521 self._reloader.autoload_obj = False
522 522 self.loaded_modules = set(sys.modules)
523 523
524 524 @line_magic
525 525 @magic_arguments.magic_arguments()
526 526 @magic_arguments.argument(
527 527 "mode",
528 528 type=str,
529 529 default="now",
530 530 nargs="?",
531 531 help="""blank or 'now' - Reload all modules (except those excluded by %%aimport)
532 532 automatically now.
533 533
534 534 '0' or 'off' - Disable automatic reloading.
535 535
536 536 '1' or 'explicit' - Reload only modules imported with %%aimport every
537 537 time before executing the Python code typed.
538 538
539 539 '2' or 'all' - Reload all modules (except those excluded by %%aimport)
540 540 every time before executing the Python code typed.
541 541
542 542 '3' or 'complete' - Same as 2/all, but also but also adds any new
543 543 objects in the module.
544 544 """,
545 545 )
546 546 @magic_arguments.argument(
547 547 "-p",
548 548 "--print",
549 549 action="store_true",
550 550 default=False,
551 551 help="Show autoreload activity using `print` statements",
552 552 )
553 553 @magic_arguments.argument(
554 554 "-l",
555 555 "--log",
556 556 action="store_true",
557 557 default=False,
558 558 help="Show autoreload activity using the logger",
559 559 )
560 560 @magic_arguments.argument(
561 561 "--hide-errors",
562 562 action="store_true",
563 563 default=False,
564 564 help="Hide autoreload errors",
565 565 )
566 566 def autoreload(self, line=""):
567 567 r"""%autoreload => Reload modules automatically
568 568
569 569 %autoreload or %autoreload now
570 570 Reload all modules (except those excluded by %aimport) automatically
571 571 now.
572 572
573 573 %autoreload 0 or %autoreload off
574 574 Disable automatic reloading.
575 575
576 576 %autoreload 1 or %autoreload explicit
577 577 Reload only modules imported with %aimport every time before executing
578 578 the Python code typed.
579 579
580 580 %autoreload 2 or %autoreload all
581 581 Reload all modules (except those excluded by %aimport) every time
582 582 before executing the Python code typed.
583 583
584 584 %autoreload 3 or %autoreload complete
585 585 Same as 2/all, but also but also adds any new objects in the module. See
586 586 unit test at IPython/extensions/tests/test_autoreload.py::test_autoload_newly_added_objects
587 587
588 588 The optional arguments --print and --log control display of autoreload activity. The default
589 589 is to act silently; --print (or -p) will print out the names of modules that are being
590 590 reloaded, and --log (or -l) outputs them to the log at INFO level.
591 591
592 592 The optional argument --hide-errors hides any errors that can happen when trying to
593 593 reload code.
594 594
595 595 Reloading Python modules in a reliable way is in general
596 596 difficult, and unexpected things may occur. %autoreload tries to
597 597 work around common pitfalls by replacing function code objects and
598 598 parts of classes previously in the module with new versions. This
599 599 makes the following things to work:
600 600
601 601 - Functions and classes imported via 'from xxx import foo' are upgraded
602 602 to new versions when 'xxx' is reloaded.
603 603
604 604 - Methods and properties of classes are upgraded on reload, so that
605 605 calling 'c.foo()' on an object 'c' created before the reload causes
606 606 the new code for 'foo' to be executed.
607 607
608 608 Some of the known remaining caveats are:
609 609
610 610 - Replacing code objects does not always succeed: changing a @property
611 611 in a class to an ordinary method or a method to a member variable
612 612 can cause problems (but in old objects only).
613 613
614 614 - Functions that are removed (eg. via monkey-patching) from a module
615 615 before it is reloaded are not upgraded.
616 616
617 617 - C extension modules cannot be reloaded, and so cannot be
618 618 autoreloaded.
619 619
620 620 """
621 621 args = magic_arguments.parse_argstring(self.autoreload, line)
622 622 mode = args.mode.lower()
623 623
624 624 p = print
625 625
626 626 logger = logging.getLogger("autoreload")
627 627
628 628 l = logger.info
629 629
630 630 def pl(msg):
631 631 p(msg)
632 632 l(msg)
633 633
634 634 if args.print is False and args.log is False:
635 635 self._reloader._report = lambda msg: None
636 636 elif args.print is True:
637 637 if args.log is True:
638 638 self._reloader._report = pl
639 639 else:
640 640 self._reloader._report = p
641 641 elif args.log is True:
642 642 self._reloader._report = l
643 643
644 644 self._reloader.hide_errors = args.hide_errors
645 645
646 646 if mode == "" or mode == "now":
647 647 self._reloader.check(True)
648 648 elif mode == "0" or mode == "off":
649 649 self._reloader.enabled = False
650 650 elif mode == "1" or mode == "explicit":
651 651 self._reloader.enabled = True
652 652 self._reloader.check_all = False
653 653 self._reloader.autoload_obj = False
654 654 elif mode == "2" or mode == "all":
655 655 self._reloader.enabled = True
656 656 self._reloader.check_all = True
657 657 self._reloader.autoload_obj = False
658 658 elif mode == "3" or mode == "complete":
659 659 self._reloader.enabled = True
660 660 self._reloader.check_all = True
661 661 self._reloader.autoload_obj = True
662 662 else:
663 663 raise ValueError(f'Unrecognized autoreload mode "{mode}".')
664 664
665 665 @line_magic
666 666 def aimport(self, parameter_s="", stream=None):
667 667 """%aimport => Import modules for automatic reloading.
668 668
669 669 %aimport
670 670 List modules to automatically import and not to import.
671 671
672 672 %aimport foo
673 673 Import module 'foo' and mark it to be autoreloaded for %autoreload explicit
674 674
675 675 %aimport foo, bar
676 676 Import modules 'foo', 'bar' and mark them to be autoreloaded for %autoreload explicit
677 677
678 678 %aimport -foo, bar
679 679 Mark module 'foo' to not be autoreloaded for %autoreload explicit, all, or complete, and 'bar'
680 680 to be autoreloaded for mode explicit.
681 681 """
682 682 modname = parameter_s
683 683 if not modname:
684 684 to_reload = sorted(self._reloader.modules.keys())
685 685 to_skip = sorted(self._reloader.skip_modules.keys())
686 686 if stream is None:
687 687 stream = sys.stdout
688 688 if self._reloader.check_all:
689 689 stream.write("Modules to reload:\nall-except-skipped\n")
690 690 else:
691 691 stream.write("Modules to reload:\n%s\n" % " ".join(to_reload))
692 692 stream.write("\nModules to skip:\n%s\n" % " ".join(to_skip))
693 693 else:
694 694 for _module in [_.strip() for _ in modname.split(",")]:
695 695 if _module.startswith("-"):
696 696 _module = _module[1:].strip()
697 697 self._reloader.mark_module_skipped(_module)
698 698 else:
699 699 top_module, top_name = self._reloader.aimport_module(_module)
700 700
701 701 # Inject module to user namespace
702 702 self.shell.push({top_name: top_module})
703 703
704 def pre_run_cell(self):
704 def pre_run_cell(self, info):
705 705 if self._reloader.enabled:
706 706 try:
707 707 self._reloader.check()
708 708 except:
709 709 pass
710 710
711 711 def post_execute_hook(self):
712 712 """Cache the modification times of any modules imported in this execution"""
713 713 newly_loaded_modules = set(sys.modules) - self.loaded_modules
714 714 for modname in newly_loaded_modules:
715 715 _, pymtime = self._reloader.filename_and_mtime(sys.modules[modname])
716 716 if pymtime is not None:
717 717 self._reloader.modules_mtimes[modname] = pymtime
718 718
719 719 self.loaded_modules.update(newly_loaded_modules)
720 720
721 721
722 722 def load_ipython_extension(ip):
723 723 """Load the extension in IPython."""
724 724 auto_reload = AutoreloadMagics(ip)
725 725 ip.register_magics(auto_reload)
726 726 ip.events.register("pre_run_cell", auto_reload.pre_run_cell)
727 727 ip.events.register("post_execute", auto_reload.post_execute_hook)
@@ -1,689 +1,711
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 platform
17 17 import pytest
18 18 import sys
19 19 import tempfile
20 20 import textwrap
21 21 import shutil
22 22 import random
23 23 import time
24 import traceback
24 25 from io import StringIO
25 26 from dataclasses import dataclass
26 27
27 28 import IPython.testing.tools as tt
28 29
29 30 from unittest import TestCase
30 31
31 32 from IPython.extensions.autoreload import AutoreloadMagics
32 33 from IPython.core.events import EventManager, pre_run_cell
33 34 from IPython.testing.decorators import skipif_not_numpy
35 from IPython.core.interactiveshell import ExecutionInfo
34 36
35 37 if platform.python_implementation() == "PyPy":
36 38 pytest.skip(
37 39 "Current autoreload implementation is extremely slow on PyPy",
38 40 allow_module_level=True,
39 41 )
40 42
41 43 # -----------------------------------------------------------------------------
42 44 # Test fixture
43 45 # -----------------------------------------------------------------------------
44 46
45 47 noop = lambda *a, **kw: None
46 48
47 49
48 50 class FakeShell:
49 51 def __init__(self):
50 52 self.ns = {}
51 53 self.user_ns = self.ns
52 54 self.user_ns_hidden = {}
53 55 self.events = EventManager(self, {"pre_run_cell", pre_run_cell})
54 56 self.auto_magics = AutoreloadMagics(shell=self)
55 57 self.events.register("pre_run_cell", self.auto_magics.pre_run_cell)
56 58
57 59 register_magics = set_hook = noop
58 60
61 def showtraceback(
62 self,
63 exc_tuple=None,
64 filename=None,
65 tb_offset=None,
66 exception_only=False,
67 running_compiled_code=False,
68 ):
69 traceback.print_exc()
70
59 71 def run_code(self, code):
60 self.events.trigger("pre_run_cell")
72 self.events.trigger(
73 "pre_run_cell",
74 ExecutionInfo(
75 raw_cell="",
76 store_history=False,
77 silent=False,
78 shell_futures=False,
79 cell_id=None,
80 ),
81 )
61 82 exec(code, self.user_ns)
62 83 self.auto_magics.post_execute_hook()
63 84
64 85 def push(self, items):
65 86 self.ns.update(items)
66 87
67 88 def magic_autoreload(self, parameter):
68 89 self.auto_magics.autoreload(parameter)
69 90
70 91 def magic_aimport(self, parameter, stream=None):
71 92 self.auto_magics.aimport(parameter, stream=stream)
72 93 self.auto_magics.post_execute_hook()
73 94
74 95
75 96 class Fixture(TestCase):
76 97 """Fixture for creating test module files"""
77 98
78 99 test_dir = None
79 100 old_sys_path = None
80 101 filename_chars = "abcdefghijklmopqrstuvwxyz0123456789"
81 102
82 103 def setUp(self):
83 104 self.test_dir = tempfile.mkdtemp()
84 105 self.old_sys_path = list(sys.path)
85 106 sys.path.insert(0, self.test_dir)
86 107 self.shell = FakeShell()
87 108
88 109 def tearDown(self):
89 110 shutil.rmtree(self.test_dir)
90 111 sys.path = self.old_sys_path
91 112
92 113 self.test_dir = None
93 114 self.old_sys_path = None
94 115 self.shell = None
95 116
96 117 def get_module(self):
97 118 module_name = "tmpmod_" + "".join(random.sample(self.filename_chars, 20))
98 119 if module_name in sys.modules:
99 120 del sys.modules[module_name]
100 121 file_name = os.path.join(self.test_dir, module_name + ".py")
101 122 return module_name, file_name
102 123
103 124 def write_file(self, filename, content):
104 125 """
105 126 Write a file, and force a timestamp difference of at least one second
106 127
107 128 Notes
108 129 -----
109 130 Python's .pyc files record the timestamp of their compilation
110 131 with a time resolution of one second.
111 132
112 133 Therefore, we need to force a timestamp difference between .py
113 134 and .pyc, without having the .py file be timestamped in the
114 135 future, and without changing the timestamp of the .pyc file
115 136 (because that is stored in the file). The only reliable way
116 137 to achieve this seems to be to sleep.
117 138 """
118 139 content = textwrap.dedent(content)
119 140 # Sleep one second + eps
120 141 time.sleep(1.05)
121 142
122 143 # Write
123 144 with open(filename, "w", encoding="utf-8") as f:
124 145 f.write(content)
125 146
126 147 def new_module(self, code):
127 148 code = textwrap.dedent(code)
128 149 mod_name, mod_fn = self.get_module()
129 150 with open(mod_fn, "w", encoding="utf-8") as f:
130 151 f.write(code)
131 152 return mod_name, mod_fn
132 153
133 154
134 155 # -----------------------------------------------------------------------------
135 156 # Test automatic reloading
136 157 # -----------------------------------------------------------------------------
137 158
138 159
139 160 def pickle_get_current_class(obj):
140 161 """
141 162 Original issue comes from pickle; hence the name.
142 163 """
143 164 name = obj.__class__.__name__
144 165 module_name = getattr(obj, "__module__", None)
145 166 obj2 = sys.modules[module_name]
146 167 for subpath in name.split("."):
147 168 obj2 = getattr(obj2, subpath)
148 169 return obj2
149 170
150 171
151 172 class TestAutoreload(Fixture):
152 173 def test_reload_enums(self):
153 174 mod_name, mod_fn = self.new_module(
154 175 textwrap.dedent(
155 176 """
156 177 from enum import Enum
157 178 class MyEnum(Enum):
158 179 A = 'A'
159 180 B = 'B'
160 181 """
161 182 )
162 183 )
163 184 self.shell.magic_autoreload("2")
164 185 self.shell.magic_aimport(mod_name)
165 186 self.write_file(
166 187 mod_fn,
167 188 textwrap.dedent(
168 189 """
169 190 from enum import Enum
170 191 class MyEnum(Enum):
171 192 A = 'A'
172 193 B = 'B'
173 194 C = 'C'
174 195 """
175 196 ),
176 197 )
177 198 with tt.AssertNotPrints(
178 199 ("[autoreload of %s failed:" % mod_name), channel="stderr"
179 200 ):
180 201 self.shell.run_code("pass") # trigger another reload
181 202
182 203 def test_reload_class_type(self):
183 204 self.shell.magic_autoreload("2")
184 205 mod_name, mod_fn = self.new_module(
185 206 """
186 207 class Test():
187 208 def meth(self):
188 209 return "old"
189 210 """
190 211 )
191 212 assert "test" not in self.shell.ns
192 213 assert "result" not in self.shell.ns
193 214
194 215 self.shell.run_code("from %s import Test" % mod_name)
195 216 self.shell.run_code("test = Test()")
196 217
197 218 self.write_file(
198 219 mod_fn,
199 220 """
200 221 class Test():
201 222 def meth(self):
202 223 return "new"
203 224 """,
204 225 )
205 226
206 227 test_object = self.shell.ns["test"]
207 228
208 229 # important to trigger autoreload logic !
209 230 self.shell.run_code("pass")
210 231
211 232 test_class = pickle_get_current_class(test_object)
212 233 assert isinstance(test_object, test_class)
213 234
214 235 # extra check.
215 236 self.shell.run_code("import pickle")
216 237 self.shell.run_code("p = pickle.dumps(test)")
217 238
218 239 def test_reload_class_attributes(self):
219 240 self.shell.magic_autoreload("2")
220 241 mod_name, mod_fn = self.new_module(
221 242 textwrap.dedent(
222 243 """
223 244 class MyClass:
224 245
225 246 def __init__(self, a=10):
226 247 self.a = a
227 248 self.b = 22
228 249 # self.toto = 33
229 250
230 251 def square(self):
231 252 print('compute square')
232 253 return self.a*self.a
233 254 """
234 255 )
235 256 )
236 257 self.shell.run_code("from %s import MyClass" % mod_name)
237 258 self.shell.run_code("first = MyClass(5)")
238 259 self.shell.run_code("first.square()")
239 260 with self.assertRaises(AttributeError):
240 261 self.shell.run_code("first.cube()")
241 262 with self.assertRaises(AttributeError):
242 263 self.shell.run_code("first.power(5)")
243 264 self.shell.run_code("first.b")
244 265 with self.assertRaises(AttributeError):
245 266 self.shell.run_code("first.toto")
246 267
247 268 # remove square, add power
248 269
249 270 self.write_file(
250 271 mod_fn,
251 272 textwrap.dedent(
252 273 """
253 274 class MyClass:
254 275
255 276 def __init__(self, a=10):
256 277 self.a = a
257 278 self.b = 11
258 279
259 280 def power(self, p):
260 281 print('compute power '+str(p))
261 282 return self.a**p
262 283 """
263 284 ),
264 285 )
265 286
266 287 self.shell.run_code("second = MyClass(5)")
267 288
268 289 for object_name in {"first", "second"}:
269 290 self.shell.run_code(f"{object_name}.power(5)")
270 291 with self.assertRaises(AttributeError):
271 292 self.shell.run_code(f"{object_name}.cube()")
272 293 with self.assertRaises(AttributeError):
273 294 self.shell.run_code(f"{object_name}.square()")
274 295 self.shell.run_code(f"{object_name}.b")
275 296 self.shell.run_code(f"{object_name}.a")
276 297 with self.assertRaises(AttributeError):
277 298 self.shell.run_code(f"{object_name}.toto")
278 299
279 300 @skipif_not_numpy
280 301 def test_comparing_numpy_structures(self):
281 302 self.shell.magic_autoreload("2")
303 self.shell.run_code("1+1")
282 304 mod_name, mod_fn = self.new_module(
283 305 textwrap.dedent(
284 306 """
285 307 import numpy as np
286 308 class MyClass:
287 309 a = (np.array((.1, .2)),
288 310 np.array((.2, .3)))
289 311 """
290 312 )
291 313 )
292 314 self.shell.run_code("from %s import MyClass" % mod_name)
293 315 self.shell.run_code("first = MyClass()")
294 316
295 317 # change property `a`
296 318 self.write_file(
297 319 mod_fn,
298 320 textwrap.dedent(
299 321 """
300 322 import numpy as np
301 323 class MyClass:
302 324 a = (np.array((.3, .4)),
303 325 np.array((.5, .6)))
304 326 """
305 327 ),
306 328 )
307 329
308 330 with tt.AssertNotPrints(
309 331 ("[autoreload of %s failed:" % mod_name), channel="stderr"
310 332 ):
311 333 self.shell.run_code("pass") # trigger another reload
312 334
313 335 def test_autoload_newly_added_objects(self):
314 336 # All of these fail with %autoreload 2
315 337 self.shell.magic_autoreload("3")
316 338 mod_code = """
317 339 def func1(): pass
318 340 """
319 341 mod_name, mod_fn = self.new_module(textwrap.dedent(mod_code))
320 342 self.shell.run_code(f"from {mod_name} import *")
321 343 self.shell.run_code("func1()")
322 344 with self.assertRaises(NameError):
323 345 self.shell.run_code("func2()")
324 346 with self.assertRaises(NameError):
325 347 self.shell.run_code("t = Test()")
326 348 with self.assertRaises(NameError):
327 349 self.shell.run_code("number")
328 350
329 351 # ----------- TEST NEW OBJ LOADED --------------------------
330 352
331 353 new_code = """
332 354 def func1(): pass
333 355 def func2(): pass
334 356 class Test: pass
335 357 number = 0
336 358 from enum import Enum
337 359 class TestEnum(Enum):
338 360 A = 'a'
339 361 """
340 362 self.write_file(mod_fn, textwrap.dedent(new_code))
341 363
342 364 # test function now exists in shell's namespace namespace
343 365 self.shell.run_code("func2()")
344 366 # test function now exists in module's dict
345 367 self.shell.run_code(f"import sys; sys.modules['{mod_name}'].func2()")
346 368 # test class now exists
347 369 self.shell.run_code("t = Test()")
348 370 # test global built-in var now exists
349 371 self.shell.run_code("number")
350 372 # test the enumerations gets loaded successfully
351 373 self.shell.run_code("TestEnum.A")
352 374
353 375 # ----------- TEST NEW OBJ CAN BE CHANGED --------------------
354 376
355 377 new_code = """
356 378 def func1(): return 'changed'
357 379 def func2(): return 'changed'
358 380 class Test:
359 381 def new_func(self):
360 382 return 'changed'
361 383 number = 1
362 384 from enum import Enum
363 385 class TestEnum(Enum):
364 386 A = 'a'
365 387 B = 'added'
366 388 """
367 389 self.write_file(mod_fn, textwrap.dedent(new_code))
368 390 self.shell.run_code("assert func1() == 'changed'")
369 391 self.shell.run_code("assert func2() == 'changed'")
370 392 self.shell.run_code("t = Test(); assert t.new_func() == 'changed'")
371 393 self.shell.run_code("assert number == 1")
372 394 if sys.version_info < (3, 12):
373 395 self.shell.run_code("assert TestEnum.B.value == 'added'")
374 396
375 397 # ----------- TEST IMPORT FROM MODULE --------------------------
376 398
377 399 new_mod_code = """
378 400 from enum import Enum
379 401 class Ext(Enum):
380 402 A = 'ext'
381 403 def ext_func():
382 404 return 'ext'
383 405 class ExtTest:
384 406 def meth(self):
385 407 return 'ext'
386 408 ext_int = 2
387 409 """
388 410 new_mod_name, new_mod_fn = self.new_module(textwrap.dedent(new_mod_code))
389 411 current_mod_code = f"""
390 412 from {new_mod_name} import *
391 413 """
392 414 self.write_file(mod_fn, textwrap.dedent(current_mod_code))
393 415 self.shell.run_code("assert Ext.A.value == 'ext'")
394 416 self.shell.run_code("assert ext_func() == 'ext'")
395 417 self.shell.run_code("t = ExtTest(); assert t.meth() == 'ext'")
396 418 self.shell.run_code("assert ext_int == 2")
397 419
398 420 def test_verbose_names(self):
399 421 # Asserts correspondense between original mode names and their verbose equivalents.
400 422 @dataclass
401 423 class AutoreloadSettings:
402 424 check_all: bool
403 425 enabled: bool
404 426 autoload_obj: bool
405 427
406 428 def gather_settings(mode):
407 429 self.shell.magic_autoreload(mode)
408 430 module_reloader = self.shell.auto_magics._reloader
409 431 return AutoreloadSettings(
410 432 module_reloader.check_all,
411 433 module_reloader.enabled,
412 434 module_reloader.autoload_obj,
413 435 )
414 436
415 437 assert gather_settings("0") == gather_settings("off")
416 438 assert gather_settings("0") == gather_settings("OFF") # Case insensitive
417 439 assert gather_settings("1") == gather_settings("explicit")
418 440 assert gather_settings("2") == gather_settings("all")
419 441 assert gather_settings("3") == gather_settings("complete")
420 442
421 443 # And an invalid mode name raises an exception.
422 444 with self.assertRaises(ValueError):
423 445 self.shell.magic_autoreload("4")
424 446
425 447 def test_aimport_parsing(self):
426 448 # Modules can be included or excluded all in one line.
427 449 module_reloader = self.shell.auto_magics._reloader
428 450 self.shell.magic_aimport("os") # import and mark `os` for auto-reload.
429 451 assert module_reloader.modules["os"] is True
430 452 assert "os" not in module_reloader.skip_modules.keys()
431 453
432 454 self.shell.magic_aimport("-math") # forbid autoreloading of `math`
433 455 assert module_reloader.skip_modules["math"] is True
434 456 assert "math" not in module_reloader.modules.keys()
435 457
436 458 self.shell.magic_aimport(
437 459 "-os, math"
438 460 ) # Can do this all in one line; wasn't possible before.
439 461 assert module_reloader.modules["math"] is True
440 462 assert "math" not in module_reloader.skip_modules.keys()
441 463 assert module_reloader.skip_modules["os"] is True
442 464 assert "os" not in module_reloader.modules.keys()
443 465
444 466 def test_autoreload_output(self):
445 467 self.shell.magic_autoreload("complete")
446 468 mod_code = """
447 469 def func1(): pass
448 470 """
449 471 mod_name, mod_fn = self.new_module(mod_code)
450 472 self.shell.run_code(f"import {mod_name}")
451 473 with tt.AssertPrints("", channel="stdout"): # no output; this is default
452 474 self.shell.run_code("pass")
453 475
454 476 self.shell.magic_autoreload("complete --print")
455 477 self.write_file(mod_fn, mod_code) # "modify" the module
456 478 with tt.AssertPrints(
457 479 f"Reloading '{mod_name}'.", channel="stdout"
458 480 ): # see something printed out
459 481 self.shell.run_code("pass")
460 482
461 483 self.shell.magic_autoreload("complete -p")
462 484 self.write_file(mod_fn, mod_code) # "modify" the module
463 485 with tt.AssertPrints(
464 486 f"Reloading '{mod_name}'.", channel="stdout"
465 487 ): # see something printed out
466 488 self.shell.run_code("pass")
467 489
468 490 self.shell.magic_autoreload("complete --print --log")
469 491 self.write_file(mod_fn, mod_code) # "modify" the module
470 492 with tt.AssertPrints(
471 493 f"Reloading '{mod_name}'.", channel="stdout"
472 494 ): # see something printed out
473 495 self.shell.run_code("pass")
474 496
475 497 self.shell.magic_autoreload("complete --print --log")
476 498 self.write_file(mod_fn, mod_code) # "modify" the module
477 499 with self.assertLogs(logger="autoreload") as lo: # see something printed out
478 500 self.shell.run_code("pass")
479 501 assert lo.output == [f"INFO:autoreload:Reloading '{mod_name}'."]
480 502
481 503 self.shell.magic_autoreload("complete -l")
482 504 self.write_file(mod_fn, mod_code) # "modify" the module
483 505 with self.assertLogs(logger="autoreload") as lo: # see something printed out
484 506 self.shell.run_code("pass")
485 507 assert lo.output == [f"INFO:autoreload:Reloading '{mod_name}'."]
486 508
487 509 def _check_smoketest(self, use_aimport=True):
488 510 """
489 511 Functional test for the automatic reloader using either
490 512 '%autoreload 1' or '%autoreload 2'
491 513 """
492 514
493 515 mod_name, mod_fn = self.new_module(
494 516 """
495 517 x = 9
496 518
497 519 z = 123 # this item will be deleted
498 520
499 521 def foo(y):
500 522 return y + 3
501 523
502 524 class Baz(object):
503 525 def __init__(self, x):
504 526 self.x = x
505 527 def bar(self, y):
506 528 return self.x + y
507 529 @property
508 530 def quux(self):
509 531 return 42
510 532 def zzz(self):
511 533 '''This method will be deleted below'''
512 534 return 99
513 535
514 536 class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
515 537 def foo(self):
516 538 return 1
517 539 """
518 540 )
519 541
520 542 #
521 543 # Import module, and mark for reloading
522 544 #
523 545 if use_aimport:
524 546 self.shell.magic_autoreload("1")
525 547 self.shell.magic_aimport(mod_name)
526 548 stream = StringIO()
527 549 self.shell.magic_aimport("", stream=stream)
528 550 self.assertIn(("Modules to reload:\n%s" % mod_name), stream.getvalue())
529 551
530 552 with self.assertRaises(ImportError):
531 553 self.shell.magic_aimport("tmpmod_as318989e89ds")
532 554 else:
533 555 self.shell.magic_autoreload("2")
534 556 self.shell.run_code("import %s" % mod_name)
535 557 stream = StringIO()
536 558 self.shell.magic_aimport("", stream=stream)
537 559 self.assertTrue(
538 560 "Modules to reload:\nall-except-skipped" in stream.getvalue()
539 561 )
540 562 self.assertIn(mod_name, self.shell.ns)
541 563
542 564 mod = sys.modules[mod_name]
543 565
544 566 #
545 567 # Test module contents
546 568 #
547 569 old_foo = mod.foo
548 570 old_obj = mod.Baz(9)
549 571 old_obj2 = mod.Bar()
550 572
551 573 def check_module_contents():
552 574 self.assertEqual(mod.x, 9)
553 575 self.assertEqual(mod.z, 123)
554 576
555 577 self.assertEqual(old_foo(0), 3)
556 578 self.assertEqual(mod.foo(0), 3)
557 579
558 580 obj = mod.Baz(9)
559 581 self.assertEqual(old_obj.bar(1), 10)
560 582 self.assertEqual(obj.bar(1), 10)
561 583 self.assertEqual(obj.quux, 42)
562 584 self.assertEqual(obj.zzz(), 99)
563 585
564 586 obj2 = mod.Bar()
565 587 self.assertEqual(old_obj2.foo(), 1)
566 588 self.assertEqual(obj2.foo(), 1)
567 589
568 590 check_module_contents()
569 591
570 592 #
571 593 # Simulate a failed reload: no reload should occur and exactly
572 594 # one error message should be printed
573 595 #
574 596 self.write_file(
575 597 mod_fn,
576 598 """
577 599 a syntax error
578 600 """,
579 601 )
580 602
581 603 with tt.AssertPrints(
582 604 ("[autoreload of %s failed:" % mod_name), channel="stderr"
583 605 ):
584 606 self.shell.run_code("pass") # trigger reload
585 607 with tt.AssertNotPrints(
586 608 ("[autoreload of %s failed:" % mod_name), channel="stderr"
587 609 ):
588 610 self.shell.run_code("pass") # trigger another reload
589 611 check_module_contents()
590 612
591 613 #
592 614 # Rewrite module (this time reload should succeed)
593 615 #
594 616 self.write_file(
595 617 mod_fn,
596 618 """
597 619 x = 10
598 620
599 621 def foo(y):
600 622 return y + 4
601 623
602 624 class Baz(object):
603 625 def __init__(self, x):
604 626 self.x = x
605 627 def bar(self, y):
606 628 return self.x + y + 1
607 629 @property
608 630 def quux(self):
609 631 return 43
610 632
611 633 class Bar: # old-style class
612 634 def foo(self):
613 635 return 2
614 636 """,
615 637 )
616 638
617 639 def check_module_contents():
618 640 self.assertEqual(mod.x, 10)
619 641 self.assertFalse(hasattr(mod, "z"))
620 642
621 643 self.assertEqual(old_foo(0), 4) # superreload magic!
622 644 self.assertEqual(mod.foo(0), 4)
623 645
624 646 obj = mod.Baz(9)
625 647 self.assertEqual(old_obj.bar(1), 11) # superreload magic!
626 648 self.assertEqual(obj.bar(1), 11)
627 649
628 650 self.assertEqual(old_obj.quux, 43)
629 651 self.assertEqual(obj.quux, 43)
630 652
631 653 self.assertFalse(hasattr(old_obj, "zzz"))
632 654 self.assertFalse(hasattr(obj, "zzz"))
633 655
634 656 obj2 = mod.Bar()
635 657 self.assertEqual(old_obj2.foo(), 2)
636 658 self.assertEqual(obj2.foo(), 2)
637 659
638 660 self.shell.run_code("pass") # trigger reload
639 661 check_module_contents()
640 662
641 663 #
642 664 # Another failure case: deleted file (shouldn't reload)
643 665 #
644 666 os.unlink(mod_fn)
645 667
646 668 self.shell.run_code("pass") # trigger reload
647 669 check_module_contents()
648 670
649 671 #
650 672 # Disable autoreload and rewrite module: no reload should occur
651 673 #
652 674 if use_aimport:
653 675 self.shell.magic_aimport("-" + mod_name)
654 676 stream = StringIO()
655 677 self.shell.magic_aimport("", stream=stream)
656 678 self.assertTrue(("Modules to skip:\n%s" % mod_name) in stream.getvalue())
657 679
658 680 # This should succeed, although no such module exists
659 681 self.shell.magic_aimport("-tmpmod_as318989e89ds")
660 682 else:
661 683 self.shell.magic_autoreload("0")
662 684
663 685 self.write_file(
664 686 mod_fn,
665 687 """
666 688 x = -99
667 689 """,
668 690 )
669 691
670 692 self.shell.run_code("pass") # trigger reload
671 693 self.shell.run_code("pass")
672 694 check_module_contents()
673 695
674 696 #
675 697 # Re-enable autoreload: reload should now occur
676 698 #
677 699 if use_aimport:
678 700 self.shell.magic_aimport(mod_name)
679 701 else:
680 702 self.shell.magic_autoreload("")
681 703
682 704 self.shell.run_code("pass") # trigger reload
683 705 self.assertEqual(mod.x, -99)
684 706
685 707 def test_smoketest_aimport(self):
686 708 self._check_smoketest(use_aimport=True)
687 709
688 710 def test_smoketest_autoreload(self):
689 711 self._check_smoketest(use_aimport=False)
General Comments 0
You need to be logged in to leave comments. Login now