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