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