123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536 |
- """IPython extension to reload modules before executing user code.
- ``autoreload`` reloads modules automatically before entering the execution of
- code typed at the IPython prompt.
- This makes for example the following workflow possible:
- .. sourcecode:: ipython
- In [1]: %load_ext autoreload
- In [2]: %autoreload 2
- In [3]: from foo import some_function
- In [4]: some_function()
- Out[4]: 42
- In [5]: # open foo.py in an editor and change some_function to return 43
- In [6]: some_function()
- Out[6]: 43
- The module was reloaded without reloading it explicitly, and the object
- imported with ``from foo import ...`` was also updated.
- Usage
- =====
- The following magic commands are provided:
- ``%autoreload``
- Reload all modules (except those excluded by ``%aimport``)
- automatically now.
- ``%autoreload 0``
- Disable automatic reloading.
- ``%autoreload 1``
- Reload all modules imported with ``%aimport`` every time before
- executing the Python code typed.
- ``%autoreload 2``
- Reload all modules (except those excluded by ``%aimport``) every
- time before executing the Python code typed.
- ``%aimport``
- List modules which are to be automatically imported or not to be imported.
- ``%aimport foo``
- Import module 'foo' and mark it to be autoreloaded for ``%autoreload 1``
- ``%aimport -foo``
- Mark module 'foo' to not be autoreloaded.
- Caveats
- =======
- Reloading Python modules in a reliable way is in general difficult,
- and unexpected things may occur. ``%autoreload`` tries to work around
- common pitfalls by replacing function code objects and parts of
- classes previously in the module with new versions. This makes the
- following things to work:
- - Functions and classes imported via 'from xxx import foo' are upgraded
- to new versions when 'xxx' is reloaded.
- - Methods and properties of classes are upgraded on reload, so that
- calling 'c.foo()' on an object 'c' created before the reload causes
- the new code for 'foo' to be executed.
- Some of the known remaining caveats are:
- - Replacing code objects does not always succeed: changing a @property
- in a class to an ordinary method or a method to a member variable
- can cause problems (but in old objects only).
- - Functions that are removed (eg. via monkey-patching) from a module
- before it is reloaded are not upgraded.
- - C extension modules cannot be reloaded, and so cannot be autoreloaded.
- """
- from __future__ import print_function
- skip_doctest = True
- #-----------------------------------------------------------------------------
- # Copyright (C) 2000 Thomas Heller
- # Copyright (C) 2008 Pauli Virtanen <pav@iki.fi>
- # Copyright (C) 2012 The IPython Development Team
- #
- # Distributed under the terms of the BSD License. The full license is in
- # the file COPYING, distributed as part of this software.
- #-----------------------------------------------------------------------------
- #
- # This IPython module is written by Pauli Virtanen, based on the autoreload
- # code by Thomas Heller.
- #-----------------------------------------------------------------------------
- # Imports
- #-----------------------------------------------------------------------------
- import os
- import sys
- import traceback
- import types
- import weakref
- try:
- # Reload is not defined by default in Python3.
- reload
- except NameError:
- from imp import reload
- from IPython.utils import openpy
- from IPython.utils.py3compat import PY3
- #------------------------------------------------------------------------------
- # Autoreload functionality
- #------------------------------------------------------------------------------
- class ModuleReloader(object):
- enabled = False
- """Whether this reloader is enabled"""
- check_all = True
- """Autoreload all modules, not just those listed in 'modules'"""
- def __init__(self):
- # Modules that failed to reload: {module: mtime-on-failed-reload, ...}
- self.failed = {}
- # Modules specially marked as autoreloadable.
- self.modules = {}
- # Modules specially marked as not autoreloadable.
- self.skip_modules = {}
- # (module-name, name) -> weakref, for replacing old code objects
- self.old_objects = {}
- # Module modification timestamps
- self.modules_mtimes = {}
- # Cache module modification times
- self.check(check_all=True, do_reload=False)
- def mark_module_skipped(self, module_name):
- """Skip reloading the named module in the future"""
- try:
- del self.modules[module_name]
- except KeyError:
- pass
- self.skip_modules[module_name] = True
- def mark_module_reloadable(self, module_name):
- """Reload the named module in the future (if it is imported)"""
- try:
- del self.skip_modules[module_name]
- except KeyError:
- pass
- self.modules[module_name] = True
- def aimport_module(self, module_name):
- """Import a module, and mark it reloadable
- Returns
- -------
- top_module : module
- The imported module if it is top-level, or the top-level
- top_name : module
- Name of top_module
- """
- self.mark_module_reloadable(module_name)
- __import__(module_name)
- top_name = module_name.split('.')[0]
- top_module = sys.modules[top_name]
- return top_module, top_name
- def filename_and_mtime(self, module):
- if not hasattr(module, '__file__') or module.__file__ is None:
- return None, None
- if getattr(module, '__name__', None) in [None, '__mp_main__', '__main__']:
- # we cannot reload(__main__) or reload(__mp_main__)
- return None, None
- filename = module.__file__
- path, ext = os.path.splitext(filename)
- if ext.lower() == '.py':
- py_filename = filename
- else:
- try:
- py_filename = openpy.source_from_cache(filename)
- except ValueError:
- return None, None
- try:
- pymtime = os.stat(py_filename).st_mtime
- except OSError:
- return None, None
- return py_filename, pymtime
- def check(self, check_all=False, do_reload=True):
- """Check whether some modules need to be reloaded."""
- if not self.enabled and not check_all:
- return
- if check_all or self.check_all:
- modules = list(sys.modules.keys())
- else:
- modules = list(self.modules.keys())
- for modname in modules:
- m = sys.modules.get(modname, None)
- if modname in self.skip_modules:
- continue
- py_filename, pymtime = self.filename_and_mtime(m)
- if py_filename is None:
- continue
- try:
- if pymtime <= self.modules_mtimes[modname]:
- continue
- except KeyError:
- self.modules_mtimes[modname] = pymtime
- continue
- else:
- if self.failed.get(py_filename, None) == pymtime:
- continue
- self.modules_mtimes[modname] = pymtime
- # If we've reached this point, we should try to reload the module
- if do_reload:
- try:
- superreload(m, reload, self.old_objects)
- if py_filename in self.failed:
- del self.failed[py_filename]
- except:
- print("[autoreload of %s failed: %s]" % (
- modname, traceback.format_exc(1)), file=sys.stderr)
- self.failed[py_filename] = pymtime
- #------------------------------------------------------------------------------
- # superreload
- #------------------------------------------------------------------------------
- if PY3:
- func_attrs = ['__code__', '__defaults__', '__doc__',
- '__closure__', '__globals__', '__dict__']
- else:
- func_attrs = ['func_code', 'func_defaults', 'func_doc',
- 'func_closure', 'func_globals', 'func_dict']
- def update_function(old, new):
- """Upgrade the code object of a function"""
- for name in func_attrs:
- try:
- setattr(old, name, getattr(new, name))
- except (AttributeError, TypeError):
- pass
- def update_class(old, new):
- """Replace stuff in the __dict__ of a class, and upgrade
- method code objects"""
- for key in list(old.__dict__.keys()):
- old_obj = getattr(old, key)
- try:
- new_obj = getattr(new, key)
- except AttributeError:
- # obsolete attribute: remove it
- try:
- delattr(old, key)
- except (AttributeError, TypeError):
- pass
- continue
- if update_generic(old_obj, new_obj): continue
- try:
- setattr(old, key, getattr(new, key))
- except (AttributeError, TypeError):
- pass # skip non-writable attributes
- def update_property(old, new):
- """Replace get/set/del functions of a property"""
- update_generic(old.fdel, new.fdel)
- update_generic(old.fget, new.fget)
- update_generic(old.fset, new.fset)
- def isinstance2(a, b, typ):
- return isinstance(a, typ) and isinstance(b, typ)
- UPDATE_RULES = [
- (lambda a, b: isinstance2(a, b, type),
- update_class),
- (lambda a, b: isinstance2(a, b, types.FunctionType),
- update_function),
- (lambda a, b: isinstance2(a, b, property),
- update_property),
- ]
- if PY3:
- UPDATE_RULES.extend([(lambda a, b: isinstance2(a, b, types.MethodType),
- lambda a, b: update_function(a.__func__, b.__func__)),
- ])
- else:
- UPDATE_RULES.extend([(lambda a, b: isinstance2(a, b, types.ClassType),
- update_class),
- (lambda a, b: isinstance2(a, b, types.MethodType),
- lambda a, b: update_function(a.__func__, b.__func__)),
- ])
- def update_generic(a, b):
- for type_check, update in UPDATE_RULES:
- if type_check(a, b):
- update(a, b)
- return True
- return False
- class StrongRef(object):
- def __init__(self, obj):
- self.obj = obj
- def __call__(self):
- return self.obj
- def superreload(module, reload=reload, old_objects={}):
- """Enhanced version of the builtin reload function.
- superreload remembers objects previously in the module, and
- - upgrades the class dictionary of every old class in the module
- - upgrades the code object of every old function and method
- - clears the module's namespace before reloading
- """
- # collect old objects in the module
- for name, obj in list(module.__dict__.items()):
- if not hasattr(obj, '__module__') or obj.__module__ != module.__name__:
- continue
- key = (module.__name__, name)
- try:
- old_objects.setdefault(key, []).append(weakref.ref(obj))
- except TypeError:
- # weakref doesn't work for all types;
- # create strong references for 'important' cases
- if not PY3 and isinstance(obj, types.ClassType):
- old_objects.setdefault(key, []).append(StrongRef(obj))
- # reload module
- try:
- # clear namespace first from old cruft
- old_dict = module.__dict__.copy()
- old_name = module.__name__
- module.__dict__.clear()
- module.__dict__['__name__'] = old_name
- module.__dict__['__loader__'] = old_dict['__loader__']
- except (TypeError, AttributeError, KeyError):
- pass
- try:
- module = reload(module)
- except:
- # restore module dictionary on failed reload
- module.__dict__.update(old_dict)
- raise
- # iterate over all objects and update functions & classes
- for name, new_obj in list(module.__dict__.items()):
- key = (module.__name__, name)
- if key not in old_objects: continue
- new_refs = []
- for old_ref in old_objects[key]:
- old_obj = old_ref()
- if old_obj is None: continue
- new_refs.append(old_ref)
- update_generic(old_obj, new_obj)
- if new_refs:
- old_objects[key] = new_refs
- else:
- del old_objects[key]
- return module
- #------------------------------------------------------------------------------
- # IPython connectivity
- #------------------------------------------------------------------------------
- from IPython.core.magic import Magics, magics_class, line_magic
- @magics_class
- class AutoreloadMagics(Magics):
- def __init__(self, *a, **kw):
- super(AutoreloadMagics, self).__init__(*a, **kw)
- self._reloader = ModuleReloader()
- self._reloader.check_all = False
- self.loaded_modules = set(sys.modules)
- @line_magic
- def autoreload(self, parameter_s=''):
- r"""%autoreload => Reload modules automatically
- %autoreload
- Reload all modules (except those excluded by %aimport) automatically
- now.
- %autoreload 0
- Disable automatic reloading.
- %autoreload 1
- Reload all modules imported with %aimport every time before executing
- the Python code typed.
- %autoreload 2
- Reload all modules (except those 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. %autoreload tries to
- work around common pitfalls by replacing function code objects and
- parts of classes previously in the module with new versions. This
- makes the following things to work:
- - Functions and classes imported via 'from xxx import foo' are upgraded
- to new versions when 'xxx' is reloaded.
- - Methods and properties of classes are upgraded on reload, so that
- calling 'c.foo()' on an object 'c' created before the reload causes
- the new code for 'foo' to be executed.
- Some of the known remaining caveats are:
- - Replacing code objects does not always succeed: changing a @property
- in a class to an ordinary method or a method to a member variable
- can cause problems (but in old objects only).
- - Functions that are removed (eg. via monkey-patching) from a module
- before it is reloaded are not upgraded.
- - C extension modules cannot be reloaded, and so cannot be
- autoreloaded.
- """
- if parameter_s == '':
- self._reloader.check(True)
- elif parameter_s == '0':
- self._reloader.enabled = False
- elif parameter_s == '1':
- self._reloader.check_all = False
- self._reloader.enabled = True
- elif parameter_s == '2':
- self._reloader.check_all = True
- self._reloader.enabled = True
- @line_magic
- def aimport(self, parameter_s='', stream=None):
- """%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 = sorted(self._reloader.modules.keys())
- to_skip = sorted(self._reloader.skip_modules.keys())
- if stream is None:
- stream = sys.stdout
- if self._reloader.check_all:
- stream.write("Modules to reload:\nall-except-skipped\n")
- else:
- stream.write("Modules to reload:\n%s\n" % ' '.join(to_reload))
- stream.write("\nModules to skip:\n%s\n" % ' '.join(to_skip))
- elif modname.startswith('-'):
- modname = modname[1:]
- self._reloader.mark_module_skipped(modname)
- else:
- top_module, top_name = self._reloader.aimport_module(modname)
- # Inject module to user namespace
- self.shell.push({top_name: top_module})
- def pre_run_cell(self):
- if self._reloader.enabled:
- try:
- self._reloader.check()
- except:
- pass
- def post_execute_hook(self):
- """Cache the modification times of any modules imported in this execution
- """
- newly_loaded_modules = set(sys.modules) - self.loaded_modules
- for modname in newly_loaded_modules:
- _, pymtime = self._reloader.filename_and_mtime(sys.modules[modname])
- if pymtime is not None:
- self._reloader.modules_mtimes[modname] = pymtime
- self.loaded_modules.update(newly_loaded_modules)
- def load_ipython_extension(ip):
- """Load the extension in IPython."""
- auto_reload = AutoreloadMagics(ip)
- ip.register_magics(auto_reload)
- ip.events.register('pre_run_cell', auto_reload.pre_run_cell)
- ip.events.register('post_execute', auto_reload.post_execute_hook)
|