348 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			348 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | # -*- coding: utf-8 -*- | ||
|  | """
 | ||
|  | Provides a reload() function that acts recursively. | ||
|  | 
 | ||
|  | Python's normal :func:`python:reload` function only reloads the module that it's | ||
|  | passed. The :func:`reload` function in this module also reloads everything | ||
|  | imported from that module, which is useful when you're changing files deep | ||
|  | inside a package. | ||
|  | 
 | ||
|  | To use this as your default reload function, type this for Python 2:: | ||
|  | 
 | ||
|  |     import __builtin__ | ||
|  |     from yap_ipython.lib import deepreload | ||
|  |     __builtin__.reload = deepreload.reload | ||
|  | 
 | ||
|  | Or this for Python 3:: | ||
|  | 
 | ||
|  |     import builtins | ||
|  |     from yap_ipython.lib import deepreload | ||
|  |     builtins.reload = deepreload.reload | ||
|  | 
 | ||
|  | A reference to the original :func:`python:reload` is stored in this module as | ||
|  | :data:`original_reload`, so you can restore it later. | ||
|  | 
 | ||
|  | This code is almost entirely based on knee.py, which is a Python | ||
|  | re-implementation of hierarchical module import. | ||
|  | """
 | ||
|  | #***************************************************************************** | ||
|  | #       Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu> | ||
|  | # | ||
|  | #  Distributed under the terms of the BSD License.  The full license is in | ||
|  | #  the file COPYING, distributed as part of this software. | ||
|  | #***************************************************************************** | ||
|  | 
 | ||
|  | import builtins as builtin_mod | ||
|  | from contextlib import contextmanager | ||
|  | import imp | ||
|  | import sys | ||
|  | 
 | ||
|  | from types import ModuleType | ||
|  | from warnings import warn | ||
|  | import types | ||
|  | 
 | ||
|  | original_import = builtin_mod.__import__ | ||
|  | 
 | ||
|  | @contextmanager | ||
|  | def replace_import_hook(new_import): | ||
|  |     saved_import = builtin_mod.__import__ | ||
|  |     builtin_mod.__import__ = new_import | ||
|  |     try: | ||
|  |         yield | ||
|  |     finally: | ||
|  |         builtin_mod.__import__ = saved_import | ||
|  | 
 | ||
|  | def get_parent(globals, level): | ||
|  |     """
 | ||
|  |     parent, name = get_parent(globals, level) | ||
|  | 
 | ||
|  |     Return the package that an import is being performed in.  If globals comes | ||
|  |     from the module foo.bar.bat (not itself a package), this returns the | ||
|  |     sys.modules entry for foo.bar.  If globals is from a package's __init__.py, | ||
|  |     the package's entry in sys.modules is returned. | ||
|  | 
 | ||
|  |     If globals doesn't come from a package or a module in a package, or a | ||
|  |     corresponding entry is not found in sys.modules, None is returned. | ||
|  |     """
 | ||
|  |     orig_level = level | ||
|  | 
 | ||
|  |     if not level or not isinstance(globals, dict): | ||
|  |         return None, '' | ||
|  | 
 | ||
|  |     pkgname = globals.get('__package__', None) | ||
|  | 
 | ||
|  |     if pkgname is not None: | ||
|  |         # __package__ is set, so use it | ||
|  |         if not hasattr(pkgname, 'rindex'): | ||
|  |             raise ValueError('__package__ set to non-string') | ||
|  |         if len(pkgname) == 0: | ||
|  |             if level > 0: | ||
|  |                 raise ValueError('Attempted relative import in non-package') | ||
|  |             return None, '' | ||
|  |         name = pkgname | ||
|  |     else: | ||
|  |         # __package__ not set, so figure it out and set it | ||
|  |         if '__name__' not in globals: | ||
|  |             return None, '' | ||
|  |         modname = globals['__name__'] | ||
|  | 
 | ||
|  |         if '__path__' in globals: | ||
|  |             # __path__ is set, so modname is already the package name | ||
|  |             globals['__package__'] = name = modname | ||
|  |         else: | ||
|  |             # Normal module, so work out the package name if any | ||
|  |             lastdot = modname.rfind('.') | ||
|  |             if lastdot < 0 < level: | ||
|  |                 raise ValueError("Attempted relative import in non-package") | ||
|  |             if lastdot < 0: | ||
|  |                 globals['__package__'] = None | ||
|  |                 return None, '' | ||
|  |             globals['__package__'] = name = modname[:lastdot] | ||
|  | 
 | ||
|  |     dot = len(name) | ||
|  |     for x in range(level, 1, -1): | ||
|  |         try: | ||
|  |             dot = name.rindex('.', 0, dot) | ||
|  |         except ValueError: | ||
|  |             raise ValueError("attempted relative import beyond top-level " | ||
|  |                              "package") | ||
|  |     name = name[:dot] | ||
|  | 
 | ||
|  |     try: | ||
|  |         parent = sys.modules[name] | ||
|  |     except: | ||
|  |         if orig_level < 1: | ||
|  |             warn("Parent module '%.200s' not found while handling absolute " | ||
|  |                  "import" % name) | ||
|  |             parent = None | ||
|  |         else: | ||
|  |             raise SystemError("Parent module '%.200s' not loaded, cannot " | ||
|  |                               "perform relative import" % name) | ||
|  | 
 | ||
|  |     # We expect, but can't guarantee, if parent != None, that: | ||
|  |     # - parent.__name__ == name | ||
|  |     # - parent.__dict__ is globals | ||
|  |     # If this is violated...  Who cares? | ||
|  |     return parent, name | ||
|  | 
 | ||
|  | def load_next(mod, altmod, name, buf): | ||
|  |     """
 | ||
|  |     mod, name, buf = load_next(mod, altmod, name, buf) | ||
|  | 
 | ||
|  |     altmod is either None or same as mod | ||
|  |     """
 | ||
|  | 
 | ||
|  |     if len(name) == 0: | ||
|  |         # completely empty module name should only happen in | ||
|  |         # 'from . import' (or '__import__("")') | ||
|  |         return mod, None, buf | ||
|  | 
 | ||
|  |     dot = name.find('.') | ||
|  |     if dot == 0: | ||
|  |         raise ValueError('Empty module name') | ||
|  | 
 | ||
|  |     if dot < 0: | ||
|  |         subname = name | ||
|  |         next = None | ||
|  |     else: | ||
|  |         subname = name[:dot] | ||
|  |         next = name[dot+1:] | ||
|  | 
 | ||
|  |     if buf != '': | ||
|  |         buf += '.' | ||
|  |     buf += subname | ||
|  | 
 | ||
|  |     result = import_submodule(mod, subname, buf) | ||
|  |     if result is None and mod != altmod: | ||
|  |         result = import_submodule(altmod, subname, subname) | ||
|  |         if result is not None: | ||
|  |             buf = subname | ||
|  | 
 | ||
|  |     if result is None: | ||
|  |         raise ImportError("No module named %.200s" % name) | ||
|  | 
 | ||
|  |     return result, next, buf | ||
|  | 
 | ||
|  | 
 | ||
|  | # Need to keep track of what we've already reloaded to prevent cyclic evil | ||
|  | found_now = {} | ||
|  | 
 | ||
|  | def import_submodule(mod, subname, fullname): | ||
|  |     """m = import_submodule(mod, subname, fullname)""" | ||
|  |     # Require: | ||
|  |     # if mod == None: subname == fullname | ||
|  |     # else: mod.__name__ + "." + subname == fullname | ||
|  | 
 | ||
|  |     global found_now | ||
|  |     if fullname in found_now and fullname in sys.modules: | ||
|  |         m = sys.modules[fullname] | ||
|  |     else: | ||
|  |         print('Reloading', fullname) | ||
|  |         found_now[fullname] = 1 | ||
|  |         oldm = sys.modules.get(fullname, None) | ||
|  | 
 | ||
|  |         if mod is None: | ||
|  |             path = None | ||
|  |         elif hasattr(mod, '__path__'): | ||
|  |             path = mod.__path__ | ||
|  |         else: | ||
|  |             return None | ||
|  | 
 | ||
|  |         try: | ||
|  |             # This appears to be necessary on Python 3, because imp.find_module() | ||
|  |             # tries to import standard libraries (like io) itself, and we don't | ||
|  |             # want them to be processed by our deep_import_hook. | ||
|  |             with replace_import_hook(original_import): | ||
|  |                 fp, filename, stuff = imp.find_module(subname, path) | ||
|  |         except ImportError: | ||
|  |             return None | ||
|  | 
 | ||
|  |         try: | ||
|  |             m = imp.load_module(fullname, fp, filename, stuff) | ||
|  |         except: | ||
|  |             # load_module probably removed name from modules because of | ||
|  |             # the error.  Put back the original module object. | ||
|  |             if oldm: | ||
|  |                 sys.modules[fullname] = oldm | ||
|  |             raise | ||
|  |         finally: | ||
|  |             if fp: fp.close() | ||
|  | 
 | ||
|  |         add_submodule(mod, m, fullname, subname) | ||
|  | 
 | ||
|  |     return m | ||
|  | 
 | ||
|  | def add_submodule(mod, submod, fullname, subname): | ||
|  |     """mod.{subname} = submod""" | ||
|  |     if mod is None: | ||
|  |         return #Nothing to do here. | ||
|  | 
 | ||
|  |     if submod is None: | ||
|  |         submod = sys.modules[fullname] | ||
|  | 
 | ||
|  |     setattr(mod, subname, submod) | ||
|  | 
 | ||
|  |     return | ||
|  | 
 | ||
|  | def ensure_fromlist(mod, fromlist, buf, recursive): | ||
|  |     """Handle 'from module import a, b, c' imports.""" | ||
|  |     if not hasattr(mod, '__path__'): | ||
|  |         return | ||
|  |     for item in fromlist: | ||
|  |         if not hasattr(item, 'rindex'): | ||
|  |             raise TypeError("Item in ``from list'' not a string") | ||
|  |         if item == '*': | ||
|  |             if recursive: | ||
|  |                 continue # avoid endless recursion | ||
|  |             try: | ||
|  |                 all = mod.__all__ | ||
|  |             except AttributeError: | ||
|  |                 pass | ||
|  |             else: | ||
|  |                 ret = ensure_fromlist(mod, all, buf, 1) | ||
|  |                 if not ret: | ||
|  |                     return 0 | ||
|  |         elif not hasattr(mod, item): | ||
|  |             import_submodule(mod, item, buf + '.' + item) | ||
|  | 
 | ||
|  | def deep_import_hook(name, globals=None, locals=None, fromlist=None, level=-1): | ||
|  |     """Replacement for __import__()""" | ||
|  |     parent, buf = get_parent(globals, level) | ||
|  | 
 | ||
|  |     head, name, buf = load_next(parent, None if level < 0 else parent, name, buf) | ||
|  | 
 | ||
|  |     tail = head | ||
|  |     while name: | ||
|  |         tail, name, buf = load_next(tail, tail, name, buf) | ||
|  | 
 | ||
|  |     # If tail is None, both get_parent and load_next found | ||
|  |     # an empty module name: someone called __import__("") or | ||
|  |     # doctored faulty bytecode | ||
|  |     if tail is None: | ||
|  |         raise ValueError('Empty module name') | ||
|  | 
 | ||
|  |     if not fromlist: | ||
|  |         return head | ||
|  | 
 | ||
|  |     ensure_fromlist(tail, fromlist, buf, 0) | ||
|  |     return tail | ||
|  | 
 | ||
|  | modules_reloading = {} | ||
|  | 
 | ||
|  | def deep_reload_hook(m): | ||
|  |     """Replacement for reload().""" | ||
|  |     # Hardcode this one  as it would raise a NotImplemeentedError from the | ||
|  |     # bowels of Python and screw up the import machinery after. | ||
|  |     # unlike other imports the `exclude` list already in place is not enough. | ||
|  | 
 | ||
|  |     if m is types: | ||
|  |         return m | ||
|  |     if not isinstance(m, ModuleType): | ||
|  |         raise TypeError("reload() argument must be module") | ||
|  | 
 | ||
|  |     name = m.__name__ | ||
|  | 
 | ||
|  |     if name not in sys.modules: | ||
|  |         raise ImportError("reload(): module %.200s not in sys.modules" % name) | ||
|  | 
 | ||
|  |     global modules_reloading | ||
|  |     try: | ||
|  |         return modules_reloading[name] | ||
|  |     except: | ||
|  |         modules_reloading[name] = m | ||
|  | 
 | ||
|  |     dot = name.rfind('.') | ||
|  |     if dot < 0: | ||
|  |         subname = name | ||
|  |         path = None | ||
|  |     else: | ||
|  |         try: | ||
|  |             parent = sys.modules[name[:dot]] | ||
|  |         except KeyError: | ||
|  |             modules_reloading.clear() | ||
|  |             raise ImportError("reload(): parent %.200s not in sys.modules" % name[:dot]) | ||
|  |         subname = name[dot+1:] | ||
|  |         path = getattr(parent, "__path__", None) | ||
|  | 
 | ||
|  |     try: | ||
|  |         # This appears to be necessary on Python 3, because imp.find_module() | ||
|  |         # tries to import standard libraries (like io) itself, and we don't | ||
|  |         # want them to be processed by our deep_import_hook. | ||
|  |         with replace_import_hook(original_import): | ||
|  |             fp, filename, stuff  = imp.find_module(subname, path) | ||
|  |     finally: | ||
|  |         modules_reloading.clear() | ||
|  | 
 | ||
|  |     try: | ||
|  |         newm = imp.load_module(name, fp, filename, stuff) | ||
|  |     except: | ||
|  |          # load_module probably removed name from modules because of | ||
|  |          # the error.  Put back the original module object. | ||
|  |         sys.modules[name] = m | ||
|  |         raise | ||
|  |     finally: | ||
|  |         if fp: fp.close() | ||
|  | 
 | ||
|  |     modules_reloading.clear() | ||
|  |     return newm | ||
|  | 
 | ||
|  | # Save the original hooks | ||
|  | original_reload = imp.reload | ||
|  | 
 | ||
|  | # Replacement for reload() | ||
|  | def reload(module, exclude=('sys', 'os.path', 'builtins', '__main__', | ||
|  |                             'numpy', 'numpy._globals')): | ||
|  |     """Recursively reload all modules used in the given module.  Optionally
 | ||
|  |     takes a list of modules to exclude from reloading.  The default exclude | ||
|  |     list contains sys, __main__, and __builtin__, to prevent, e.g., resetting | ||
|  |     display, exception, and io hooks. | ||
|  |     """
 | ||
|  |     global found_now | ||
|  |     for i in exclude: | ||
|  |         found_now[i] = 1 | ||
|  |     try: | ||
|  |         with replace_import_hook(deep_import_hook): | ||
|  |             return deep_reload_hook(module) | ||
|  |     finally: | ||
|  |         found_now = {} |