ipy_autoreload.py
243 lines
| 7.4 KiB
| text/x-python
|
PythonLexer
vivainio2
|
r1035 | """ | |
IPython extension: autoreload modules before executing the next line | |||
vivainio2
|
r1037 | Try:: | |
%autoreload? | |||
for documentation. | |||
vivainio2
|
r1035 | """ | |
# Pauli Virtanen <pav@iki.fi>, 2008. | |||
# Thomas Heller, 2000. | |||
# | |||
# This IPython module is written by Pauli Virtanen, based on the autoreload | |||
# code by Thomas Heller. | |||
#------------------------------------------------------------------------------ | |||
# Autoreload functionality | |||
#------------------------------------------------------------------------------ | |||
import time, os, threading, sys, types, imp, inspect, traceback, atexit | |||
def _get_compiled_ext(): | |||
"""Official way to get the extension of compiled files (.pyc or .pyo)""" | |||
for ext, mode, typ in imp.get_suffixes(): | |||
if typ == imp.PY_COMPILED: | |||
return ext | |||
PY_COMPILED_EXT = _get_compiled_ext() | |||
class ModuleReloader(object): | |||
skipped = {} | |||
"""Modules that failed to reload: {module: mtime-on-failed-reload, ...}""" | |||
modules = {} | |||
"""Modules specially marked as autoreloadable.""" | |||
skip_modules = {} | |||
"""Modules specially marked as not autoreloadable.""" | |||
check_all = True | |||
"""Autoreload all modules, not just those listed in 'modules'""" | |||
def check(self, check_all=False): | |||
"""Check whether some modules need to be reloaded.""" | |||
if check_all or self.check_all: | |||
modules = sys.modules.keys() | |||
else: | |||
modules = self.modules.keys() | |||
for modname in modules: | |||
m = sys.modules.get(modname, None) | |||
if modname in self.skip_modules: | |||
continue | |||
if not hasattr(m, '__file__'): | |||
continue | |||
if m.__name__ == '__main__': | |||
# we cannot reload(__main__) | |||
continue | |||
filename = m.__file__ | |||
dirname = os.path.dirname(filename) | |||
path, ext = os.path.splitext(filename) | |||
if ext.lower() == '.py': | |||
ext = PY_COMPILED_EXT | |||
filename = os.path.join(dirname, path + PY_COMPILED_EXT) | |||
if ext != PY_COMPILED_EXT: | |||
continue | |||
try: | |||
pymtime = os.stat(filename[:-1]).st_mtime | |||
if pymtime <= os.stat(filename).st_mtime: | |||
continue | |||
if self.skipped.get(filename[:-1], None) == pymtime: | |||
continue | |||
except OSError: | |||
continue | |||
try: | |||
superreload(m) | |||
if filename[:-1] in self.skipped: | |||
del self.skipped[filename[:-1]] | |||
except: | |||
self.skipped[filename[:-1]] = pymtime | |||
def update_function(old, new, attrnames): | |||
for name in attrnames: | |||
setattr(old, name, getattr(new, name)) | |||
def superreload(module, reload=reload): | |||
"""Enhanced version of the builtin reload function. | |||
superreload replaces the class dictionary of every top-level | |||
class in the module with the new one automatically, | |||
as well as every function's code object. | |||
""" | |||
module = reload(module) | |||
# iterate over all objects and update them | |||
count = 0 | |||
for name, new_obj in module.__dict__.items(): | |||
key = (module.__name__, name) | |||
if _old_objects.has_key(key): | |||
for old_obj in _old_objects[key]: | |||
if type(new_obj) == types.ClassType: | |||
old_obj.__dict__.update(new_obj.__dict__) | |||
count += 1 | |||
elif type(new_obj) == types.FunctionType: | |||
update_function(old_obj, | |||
new_obj, | |||
"func_code func_defaults func_doc".split()) | |||
count += 1 | |||
elif type(new_obj) == types.MethodType: | |||
update_function(old_obj.im_func, | |||
new_obj.im_func, | |||
"func_code func_defaults func_doc".split()) | |||
count += 1 | |||
return module | |||
reloader = ModuleReloader() | |||
#------------------------------------------------------------------------------ | |||
# IPython monkey-patching | |||
#------------------------------------------------------------------------------ | |||
import IPython.iplib | |||
vivainio2
|
r1036 | autoreload_enabled = False | |
def runcode_hook(self): | |||
if not autoreload_enabled: | |||
raise IPython.ipapi.TryNext | |||
try: | |||
reloader.check() | |||
except: | |||
pass | |||
vivainio2
|
r1035 | ||
def enable_autoreload(): | |||
vivainio2
|
r1036 | global autoreload_enabled | |
autoreload_enabled = True | |||
vivainio2
|
r1035 | ||
def disable_autoreload(): | |||
vivainio2
|
r1036 | global autoreload_enabled | |
autoreload_enabled = False | |||
vivainio2
|
r1035 | #------------------------------------------------------------------------------ | |
# IPython connectivity | |||
#------------------------------------------------------------------------------ | |||
import IPython.ipapi | |||
ip = IPython.ipapi.get() | |||
def autoreload_f(self, parameter_s=''): | |||
r""" %autoreload => Reload modules automatically | |||
%autoreload | |||
Reload all modules (except thoses excluded by %aimport) automatically now. | |||
%autoreload 1 | |||
Reload all modules imported with %aimport every time before executing | |||
the Python code typed. | |||
%autoreload 2 | |||
Reload all modules (except thoses excluded by %aimport) every time | |||
before executing the Python code typed. | |||
Reloading Python modules in a reliable way is in general | |||
difficult, and unexpected things may occur. Some of the common | |||
caveats relevant for 'autoreload' are: | |||
- Modules are not reloaded in any specific order, and no dependency | |||
analysis is done. For example, modules with 'from xxx import foo' | |||
retain old versions of 'foo' when 'xxx' is autoreloaded. | |||
- Functions or objects imported from the autoreloaded module to | |||
the interactive namespace are not updated. | |||
- C extension modules cannot be reloaded, and so cannot be | |||
autoreloaded. | |||
""" | |||
if parameter_s == '': | |||
reloader.check(True) | |||
elif parameter_s == '0': | |||
disable_autoreload() | |||
elif parameter_s == '1': | |||
reloader.check_all = False | |||
enable_autoreload() | |||
elif parameter_s == '2': | |||
reloader.check_all = True | |||
enable_autoreload() | |||
def aimport_f(self, parameter_s=''): | |||
"""%aimport => Import modules for automatic reloading. | |||
%aimport | |||
List modules to automatically import and not to import. | |||
%aimport foo | |||
Import module 'foo' and mark it to be autoreloaded for %autoreload 1 | |||
%aimport -foo | |||
Mark module 'foo' to not be autoreloaded for %autoreload 1 | |||
""" | |||
modname = parameter_s | |||
if not modname: | |||
to_reload = reloader.modules.keys() | |||
to_reload.sort() | |||
to_skip = reloader.skip_modules.keys() | |||
to_skip.sort() | |||
if reloader.check_all: | |||
print "Modules to reload:\nall-expect-skipped" | |||
else: | |||
print "Modules to reload:\n%s" % ' '.join(to_reload) | |||
print "\nModules to skip:\n%s" % ' '.join(to_skip) | |||
elif modname.startswith('-'): | |||
modname = modname[1:] | |||
try: del reloader.modules[modname] | |||
except KeyError: pass | |||
reloader.skip_modules[modname] = True | |||
else: | |||
try: del reloader.skip_modules[modname] | |||
except KeyError: pass | |||
reloader.modules[modname] = True | |||
mod = __import__(modname) | |||
ip.to_user_ns({modname: mod}) | |||
vivainio2
|
r1036 | def init(): | |
ip.expose_magic('autoreload', autoreload_f) | |||
ip.expose_magic('aimport', aimport_f) | |||
ip.set_hook('pre_runcode_hook', runcode_hook) | |||
init() |