deepreload.py
347 lines
| 10.6 KiB
| text/x-python
|
PythonLexer
Ville M. Vainio
|
r1032 | # -*- coding: utf-8 -*- | ||
""" | ||||
Thomas Kluyver
|
r15811 | Provides a reload() function that acts recursively. | ||
Fernando Perez
|
r1376 | |||
Thomas Kluyver
|
r15811 | 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:: | ||||
Brian Granger
|
r2499 | |||
Thomas Kluyver
|
r15811 | import __builtin__ | ||
from IPython.lib import deepreload | ||||
__builtin__.reload = deepreload.reload | ||||
Brian Granger
|
r2499 | |||
Thomas Kluyver
|
r15811 | Or this for Python 3:: | ||
Bernardo B. Marques
|
r4872 | |||
Thomas Kluyver
|
r15811 | import builtins | ||
from IPython.lib import deepreload | ||||
builtins.reload = deepreload.reload | ||||
Brian Granger
|
r2499 | |||
Thomas Kluyver
|
r15811 | A reference to the original :func:`python:reload` is stored in this module as | ||
:data:`original_reload`, so you can restore it later. | ||||
Bernardo B. Marques
|
r4872 | |||
Bradley M. Froehle
|
r6531 | This code is almost entirely based on knee.py, which is a Python | ||
re-implementation of hierarchical module import. | ||||
Fernando Perez
|
r1853 | """ | ||
Ville M. Vainio
|
r1032 | #***************************************************************************** | ||
# 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. | ||||
#***************************************************************************** | ||||
Thomas Kluyver
|
r23129 | import builtins as builtin_mod | ||
Thomas Kluyver
|
r6548 | from contextlib import contextmanager | ||
Ville M. Vainio
|
r1032 | import imp | ||
import sys | ||||
Bradley M. Froehle
|
r6531 | from types import ModuleType | ||
from warnings import warn | ||||
Matthias Bussonnier
|
r23391 | import types | ||
Bradley M. Froehle
|
r6531 | |||
Thomas Kluyver
|
r13351 | original_import = builtin_mod.__import__ | ||
Thomas Kluyver
|
r6548 | |||
@contextmanager | ||||
def replace_import_hook(new_import): | ||||
Thomas Kluyver
|
r13351 | saved_import = builtin_mod.__import__ | ||
builtin_mod.__import__ = new_import | ||||
Thomas Kluyver
|
r6548 | try: | ||
yield | ||||
finally: | ||||
Thomas Kluyver
|
r13351 | builtin_mod.__import__ = saved_import | ||
Thomas Kluyver
|
r6548 | |||
Bradley M. Froehle
|
r6531 | 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 | ||||
Ville M. Vainio
|
r1032 | |||
Bradley M. Froehle
|
r6531 | 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 | ||||
Ville M. Vainio
|
r1032 | else: | ||
Bradley M. Froehle
|
r6531 | # __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('.') | ||||
Rémy Léone
|
r21780 | if lastdot < 0 < level: | ||
Bradley M. Froehle
|
r6531 | raise ValueError("Attempted relative import in non-package") | ||
if lastdot < 0: | ||||
globals['__package__'] = None | ||||
return None, '' | ||||
globals['__package__'] = name = modname[:lastdot] | ||||
dot = len(name) | ||||
Thomas Kluyver
|
r13356 | for x in range(level, 1, -1): | ||
Bradley M. Froehle
|
r6531 | 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 | ||||
Ville M. Vainio
|
r1032 | else: | ||
Bradley M. Froehle
|
r6531 | subname = name[:dot] | ||
next = name[dot+1:] | ||||
Ville M. Vainio
|
r1032 | |||
Bradley M. Froehle
|
r6531 | 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 | ||||
Ville M. Vainio
|
r1032 | |||
Matthias Bussonnier
|
r23391 | |||
Ville M. Vainio
|
r1032 | # Need to keep track of what we've already reloaded to prevent cyclic evil | ||
found_now = {} | ||||
Bradley M. Froehle
|
r6531 | def import_submodule(mod, subname, fullname): | ||
"""m = import_submodule(mod, subname, fullname)""" | ||||
# Require: | ||||
# if mod == None: subname == fullname | ||||
# else: mod.__name__ + "." + subname == fullname | ||||
Ville M. Vainio
|
r1032 | global found_now | ||
Bradley M. Froehle
|
r6531 | if fullname in found_now and fullname in sys.modules: | ||
m = sys.modules[fullname] | ||||
else: | ||||
Thomas Kluyver
|
r13348 | print('Reloading', fullname) | ||
Bradley M. Froehle
|
r6531 | 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 | ||||
Ville M. Vainio
|
r1032 | try: | ||
Thomas Kluyver
|
r6548 | # 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) | ||||
Bradley M. Froehle
|
r6531 | 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] | ||||
Bernardo B. Marques
|
r4872 | |||
Bradley M. Froehle
|
r6531 | 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) | ||||
Bradley M. Froehle
|
r6532 | def deep_import_hook(name, globals=None, locals=None, fromlist=None, level=-1): | ||
Bradley M. Froehle
|
r6531 | """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 | ||||
Bernardo B. Marques
|
r4872 | |||
Bradley M. Froehle
|
r6531 | ensure_fromlist(tail, fromlist, buf, 0) | ||
return tail | ||||
modules_reloading = {} | ||||
Bradley M. Froehle
|
r6532 | def deep_reload_hook(m): | ||
Bradley M. Froehle
|
r6531 | """Replacement for reload().""" | ||
Min ho Kim
|
r25167 | # Hardcode this one as it would raise a NotImplementedError from the | ||
Matthias Bussonnier
|
r23391 | # bowels of Python and screw up the import machinery after. | ||
luzpaz
|
r24084 | # unlike other imports the `exclude` list already in place is not enough. | ||
Matthias Bussonnier
|
r23391 | |||
if m is types: | ||||
return m | ||||
Bradley M. Froehle
|
r6531 | 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 | ||||
Ville M. Vainio
|
r1032 | try: | ||
Bradley M. Froehle
|
r6531 | 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) | ||||
Bernardo B. Marques
|
r4872 | |||
Ville M. Vainio
|
r1032 | try: | ||
Thomas Kluyver
|
r6548 | # 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) | ||||
Ville M. Vainio
|
r1032 | finally: | ||
Bradley M. Froehle
|
r6531 | modules_reloading.clear() | ||
Bernardo B. Marques
|
r4872 | |||
Bradley M. Froehle
|
r6531 | 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() | ||||
Ville M. Vainio
|
r1032 | |||
Bradley M. Froehle
|
r6531 | modules_reloading.clear() | ||
return newm | ||||
Ville M. Vainio
|
r1032 | |||
# Save the original hooks | ||||
Matthias Bussonnier
|
r23391 | original_reload = imp.reload | ||
Ville M. Vainio
|
r1032 | |||
# Replacement for reload() | ||||
Thomas Kluyver
|
r23129 | def reload(module, exclude=('sys', 'os.path', 'builtins', '__main__', | ||
Thomas Kluyver
|
r22971 | 'numpy', 'numpy._globals')): | ||
Ville M. Vainio
|
r1032 | """Recursively reload all modules used in the given module. Optionally | ||
takes a list of modules to exclude from reloading. The default exclude | ||||
Bernardo B. Marques
|
r4872 | list contains sys, __main__, and __builtin__, to prevent, e.g., resetting | ||
Ville M. Vainio
|
r1032 | display, exception, and io hooks. | ||
""" | ||||
global found_now | ||||
for i in exclude: | ||||
found_now[i] = 1 | ||||
try: | ||||
Thomas Kluyver
|
r6548 | with replace_import_hook(deep_import_hook): | ||
Abhinav Upadhyay
|
r12882 | return deep_reload_hook(module) | ||
Ville M. Vainio
|
r1032 | finally: | ||
found_now = {} | ||||