##// END OF EJS Templates
Store timestamps for modules to autoreload...
Thomas Kluyver -
Show More
@@ -130,20 +130,23 b' class ModuleReloader(object):'
130 enabled = False
130 enabled = False
131 """Whether this reloader is enabled"""
131 """Whether this reloader is enabled"""
132
132
133 failed = {}
134 """Modules that failed to reload: {module: mtime-on-failed-reload, ...}"""
135
136 modules = {}
137 """Modules specially marked as autoreloadable."""
138
139 skip_modules = {}
140 """Modules specially marked as not autoreloadable."""
141
142 check_all = True
133 check_all = True
143 """Autoreload all modules, not just those listed in 'modules'"""
134 """Autoreload all modules, not just those listed in 'modules'"""
144
135
145 old_objects = {}
136 def __init__(self):
146 """(module-name, name) -> weakref, for replacing old code objects"""
137 # Modules that failed to reload: {module: mtime-on-failed-reload, ...}
138 self.failed = {}
139 # Modules specially marked as autoreloadable.
140 self.modules = {}
141 # Modules specially marked as not autoreloadable.
142 self.skip_modules = {}
143 # (module-name, name) -> weakref, for replacing old code objects
144 self.old_objects = {}
145 # Module modification timestamps
146 self.modules_mtimes = {}
147
148 # Cache module modification times
149 self.check(check_all=True, do_reload=False)
147
150
148 def mark_module_skipped(self, module_name):
151 def mark_module_skipped(self, module_name):
149 """Skip reloading the named module in the future"""
152 """Skip reloading the named module in the future"""
@@ -179,7 +182,33 b' class ModuleReloader(object):'
179 top_module = sys.modules[top_name]
182 top_module = sys.modules[top_name]
180 return top_module, top_name
183 return top_module, top_name
181
184
182 def check(self, check_all=False):
185 def filename_and_mtime(self, module):
186 if not hasattr(module, '__file__'):
187 return None, None
188
189 if module.__name__ == '__main__':
190 # we cannot reload(__main__)
191 return None, None
192
193 filename = module.__file__
194 path, ext = os.path.splitext(filename)
195
196 if ext.lower() == '.py':
197 py_filename = filename
198 else:
199 try:
200 py_filename = openpy.source_from_cache(filename)
201 except ValueError:
202 return None, None
203
204 try:
205 pymtime = os.stat(py_filename).st_mtime
206 except OSError:
207 return None, None
208
209 return py_filename, pymtime
210
211 def check(self, check_all=False, do_reload=True):
183 """Check whether some modules need to be reloaded."""
212 """Check whether some modules need to be reloaded."""
184
213
185 if not self.enabled and not check_all:
214 if not self.enabled and not check_all:
@@ -196,43 +225,32 b' class ModuleReloader(object):'
196 if modname in self.skip_modules:
225 if modname in self.skip_modules:
197 continue
226 continue
198
227
199 if not hasattr(m, '__file__'):
228 py_filename, pymtime = self.filename_and_mtime(m)
229 if py_filename is None:
200 continue
230 continue
201
231
202 if m.__name__ == '__main__':
203 # we cannot reload(__main__)
204 continue
205
206 filename = m.__file__
207 path, ext = os.path.splitext(filename)
208
209 if ext.lower() == '.py':
210 pyc_filename = openpy.cache_from_source(filename)
211 py_filename = filename
212 else:
213 pyc_filename = filename
214 try:
215 py_filename = openpy.source_from_cache(filename)
216 except ValueError:
217 continue
218
219 try:
232 try:
220 pymtime = os.stat(py_filename).st_mtime
233 if pymtime <= self.modules_mtimes[modname]:
221 if pymtime <= os.stat(pyc_filename).st_mtime:
222 continue
234 continue
235 except KeyError:
236 self.modules_mtimes[modname] = pymtime
237 continue
238 else:
223 if self.failed.get(py_filename, None) == pymtime:
239 if self.failed.get(py_filename, None) == pymtime:
224 continue
240 continue
225 except OSError:
226 continue
227
241
228 try:
242 self.modules_mtimes[modname] = pymtime
229 superreload(m, reload, self.old_objects)
243
230 if py_filename in self.failed:
244 # If we've reached this point, we should try to reload the module
231 del self.failed[py_filename]
245 if do_reload:
232 except:
246 try:
233 print("[autoreload of %s failed: %s]" % (
247 superreload(m, reload, self.old_objects)
234 modname, traceback.format_exc(1)), file=sys.stderr)
248 if py_filename in self.failed:
235 self.failed[py_filename] = pymtime
249 del self.failed[py_filename]
250 except:
251 print("[autoreload of %s failed: %s]" % (
252 modname, traceback.format_exc(1)), file=sys.stderr)
253 self.failed[py_filename] = pymtime
236
254
237 #------------------------------------------------------------------------------
255 #------------------------------------------------------------------------------
238 # superreload
256 # superreload
@@ -400,6 +418,7 b' class AutoreloadMagics(Magics):'
400 super(AutoreloadMagics, self).__init__(*a, **kw)
418 super(AutoreloadMagics, self).__init__(*a, **kw)
401 self._reloader = ModuleReloader()
419 self._reloader = ModuleReloader()
402 self._reloader.check_all = False
420 self._reloader.check_all = False
421 self.loaded_modules = set(sys.modules)
403
422
404 @line_magic
423 @line_magic
405 def autoreload(self, parameter_s=''):
424 def autoreload(self, parameter_s=''):
@@ -497,9 +516,21 b' class AutoreloadMagics(Magics):'
497 except:
516 except:
498 pass
517 pass
499
518
519 def post_execute_hook(self):
520 """Cache the modification times of any modules imported in this execution
521 """
522 newly_loaded_modules = set(sys.modules) - self.loaded_modules
523 for modname in newly_loaded_modules:
524 _, pymtime = self._reloader.filename_and_mtime(sys.modules[modname])
525 if pymtime is not None:
526 self._reloader.modules_mtimes[modname] = pymtime
527
528 self.loaded_modules.update(newly_loaded_modules)
529
500
530
501 def load_ipython_extension(ip):
531 def load_ipython_extension(ip):
502 """Load the extension in IPython."""
532 """Load the extension in IPython."""
503 auto_reload = AutoreloadMagics(ip)
533 auto_reload = AutoreloadMagics(ip)
504 ip.register_magics(auto_reload)
534 ip.register_magics(auto_reload)
505 ip.events.register('pre_run_cell', auto_reload.pre_run_cell)
535 ip.events.register('pre_run_cell', auto_reload.pre_run_cell)
536 ip.register_post_execute(auto_reload.post_execute_hook)
@@ -50,6 +50,7 b' class FakeShell(object):'
50 def run_code(self, code):
50 def run_code(self, code):
51 self.events.trigger('pre_run_cell')
51 self.events.trigger('pre_run_cell')
52 exec(code, self.ns)
52 exec(code, self.ns)
53 self.auto_magics.post_execute_hook()
53
54
54 def push(self, items):
55 def push(self, items):
55 self.ns.update(items)
56 self.ns.update(items)
@@ -59,6 +60,7 b' class FakeShell(object):'
59
60
60 def magic_aimport(self, parameter, stream=None):
61 def magic_aimport(self, parameter, stream=None):
61 self.auto_magics.aimport(parameter, stream=stream)
62 self.auto_magics.aimport(parameter, stream=stream)
63 self.auto_magics.post_execute_hook()
62
64
63
65
64 class Fixture(object):
66 class Fixture(object):
@@ -168,12 +170,10 b" class Bar: # old-style class: weakref doesn't work for it on Python < 2.7"
168 self.shell.magic_aimport(mod_name)
170 self.shell.magic_aimport(mod_name)
169 stream = StringIO()
171 stream = StringIO()
170 self.shell.magic_aimport("", stream=stream)
172 self.shell.magic_aimport("", stream=stream)
171 nt.assert_true(("Modules to reload:\n%s" % mod_name) in
173 nt.assert_in(("Modules to reload:\n%s" % mod_name), stream.getvalue())
172 stream.getvalue())
173
174
174 nt.assert_raises(
175 with nt.assert_raises(ImportError):
175 ImportError,
176 self.shell.magic_aimport("tmpmod_as318989e89ds")
176 self.shell.magic_aimport, "tmpmod_as318989e89ds")
177 else:
177 else:
178 self.shell.magic_autoreload("2")
178 self.shell.magic_autoreload("2")
179 self.shell.run_code("import %s" % mod_name)
179 self.shell.run_code("import %s" % mod_name)
General Comments 0
You need to be logged in to leave comments. Login now