##// END OF EJS Templates
Update docstring for deepreload module
Thomas Kluyver -
Show More
@@ -1,339 +1,347 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 A module to change reload() so that it acts recursively.
3 Provides a reload() function that acts recursively.
4 To enable it type::
5
4
6 import __builtin__, deepreload
5 Python's normal :func:`python:reload` function only reloads the module that it's
7 __builtin__.reload = deepreload.reload
6 passed. The :func:`reload` function in this module also reloads everything
7 imported from that module, which is useful when you're changing files deep
8 inside a package.
9
10 To use this as your default reload function, type this for Python 2::
8
11
9 You can then disable it with::
12 import __builtin__
13 from IPython.lib import deepreload
14 __builtin__.reload = deepreload.reload
10
15
11 __builtin__.reload = deepreload.original_reload
16 Or this for Python 3::
12
17
13 Alternatively, you can add a dreload builtin alongside normal reload with::
18 import builtins
19 from IPython.lib import deepreload
20 builtins.reload = deepreload.reload
14
21
15 __builtin__.dreload = deepreload.reload
22 A reference to the original :func:`python:reload` is stored in this module as
23 :data:`original_reload`, so you can restore it later.
16
24
17 This code is almost entirely based on knee.py, which is a Python
25 This code is almost entirely based on knee.py, which is a Python
18 re-implementation of hierarchical module import.
26 re-implementation of hierarchical module import.
19 """
27 """
20 from __future__ import print_function
28 from __future__ import print_function
21 #*****************************************************************************
29 #*****************************************************************************
22 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
30 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
23 #
31 #
24 # Distributed under the terms of the BSD License. The full license is in
32 # Distributed under the terms of the BSD License. The full license is in
25 # the file COPYING, distributed as part of this software.
33 # the file COPYING, distributed as part of this software.
26 #*****************************************************************************
34 #*****************************************************************************
27
35
28 from contextlib import contextmanager
36 from contextlib import contextmanager
29 import imp
37 import imp
30 import sys
38 import sys
31
39
32 from types import ModuleType
40 from types import ModuleType
33 from warnings import warn
41 from warnings import warn
34
42
35 from IPython.utils.py3compat import builtin_mod, builtin_mod_name
43 from IPython.utils.py3compat import builtin_mod, builtin_mod_name
36
44
37 original_import = builtin_mod.__import__
45 original_import = builtin_mod.__import__
38
46
39 @contextmanager
47 @contextmanager
40 def replace_import_hook(new_import):
48 def replace_import_hook(new_import):
41 saved_import = builtin_mod.__import__
49 saved_import = builtin_mod.__import__
42 builtin_mod.__import__ = new_import
50 builtin_mod.__import__ = new_import
43 try:
51 try:
44 yield
52 yield
45 finally:
53 finally:
46 builtin_mod.__import__ = saved_import
54 builtin_mod.__import__ = saved_import
47
55
48 def get_parent(globals, level):
56 def get_parent(globals, level):
49 """
57 """
50 parent, name = get_parent(globals, level)
58 parent, name = get_parent(globals, level)
51
59
52 Return the package that an import is being performed in. If globals comes
60 Return the package that an import is being performed in. If globals comes
53 from the module foo.bar.bat (not itself a package), this returns the
61 from the module foo.bar.bat (not itself a package), this returns the
54 sys.modules entry for foo.bar. If globals is from a package's __init__.py,
62 sys.modules entry for foo.bar. If globals is from a package's __init__.py,
55 the package's entry in sys.modules is returned.
63 the package's entry in sys.modules is returned.
56
64
57 If globals doesn't come from a package or a module in a package, or a
65 If globals doesn't come from a package or a module in a package, or a
58 corresponding entry is not found in sys.modules, None is returned.
66 corresponding entry is not found in sys.modules, None is returned.
59 """
67 """
60 orig_level = level
68 orig_level = level
61
69
62 if not level or not isinstance(globals, dict):
70 if not level or not isinstance(globals, dict):
63 return None, ''
71 return None, ''
64
72
65 pkgname = globals.get('__package__', None)
73 pkgname = globals.get('__package__', None)
66
74
67 if pkgname is not None:
75 if pkgname is not None:
68 # __package__ is set, so use it
76 # __package__ is set, so use it
69 if not hasattr(pkgname, 'rindex'):
77 if not hasattr(pkgname, 'rindex'):
70 raise ValueError('__package__ set to non-string')
78 raise ValueError('__package__ set to non-string')
71 if len(pkgname) == 0:
79 if len(pkgname) == 0:
72 if level > 0:
80 if level > 0:
73 raise ValueError('Attempted relative import in non-package')
81 raise ValueError('Attempted relative import in non-package')
74 return None, ''
82 return None, ''
75 name = pkgname
83 name = pkgname
76 else:
84 else:
77 # __package__ not set, so figure it out and set it
85 # __package__ not set, so figure it out and set it
78 if '__name__' not in globals:
86 if '__name__' not in globals:
79 return None, ''
87 return None, ''
80 modname = globals['__name__']
88 modname = globals['__name__']
81
89
82 if '__path__' in globals:
90 if '__path__' in globals:
83 # __path__ is set, so modname is already the package name
91 # __path__ is set, so modname is already the package name
84 globals['__package__'] = name = modname
92 globals['__package__'] = name = modname
85 else:
93 else:
86 # Normal module, so work out the package name if any
94 # Normal module, so work out the package name if any
87 lastdot = modname.rfind('.')
95 lastdot = modname.rfind('.')
88 if lastdot < 0 and level > 0:
96 if lastdot < 0 and level > 0:
89 raise ValueError("Attempted relative import in non-package")
97 raise ValueError("Attempted relative import in non-package")
90 if lastdot < 0:
98 if lastdot < 0:
91 globals['__package__'] = None
99 globals['__package__'] = None
92 return None, ''
100 return None, ''
93 globals['__package__'] = name = modname[:lastdot]
101 globals['__package__'] = name = modname[:lastdot]
94
102
95 dot = len(name)
103 dot = len(name)
96 for x in range(level, 1, -1):
104 for x in range(level, 1, -1):
97 try:
105 try:
98 dot = name.rindex('.', 0, dot)
106 dot = name.rindex('.', 0, dot)
99 except ValueError:
107 except ValueError:
100 raise ValueError("attempted relative import beyond top-level "
108 raise ValueError("attempted relative import beyond top-level "
101 "package")
109 "package")
102 name = name[:dot]
110 name = name[:dot]
103
111
104 try:
112 try:
105 parent = sys.modules[name]
113 parent = sys.modules[name]
106 except:
114 except:
107 if orig_level < 1:
115 if orig_level < 1:
108 warn("Parent module '%.200s' not found while handling absolute "
116 warn("Parent module '%.200s' not found while handling absolute "
109 "import" % name)
117 "import" % name)
110 parent = None
118 parent = None
111 else:
119 else:
112 raise SystemError("Parent module '%.200s' not loaded, cannot "
120 raise SystemError("Parent module '%.200s' not loaded, cannot "
113 "perform relative import" % name)
121 "perform relative import" % name)
114
122
115 # We expect, but can't guarantee, if parent != None, that:
123 # We expect, but can't guarantee, if parent != None, that:
116 # - parent.__name__ == name
124 # - parent.__name__ == name
117 # - parent.__dict__ is globals
125 # - parent.__dict__ is globals
118 # If this is violated... Who cares?
126 # If this is violated... Who cares?
119 return parent, name
127 return parent, name
120
128
121 def load_next(mod, altmod, name, buf):
129 def load_next(mod, altmod, name, buf):
122 """
130 """
123 mod, name, buf = load_next(mod, altmod, name, buf)
131 mod, name, buf = load_next(mod, altmod, name, buf)
124
132
125 altmod is either None or same as mod
133 altmod is either None or same as mod
126 """
134 """
127
135
128 if len(name) == 0:
136 if len(name) == 0:
129 # completely empty module name should only happen in
137 # completely empty module name should only happen in
130 # 'from . import' (or '__import__("")')
138 # 'from . import' (or '__import__("")')
131 return mod, None, buf
139 return mod, None, buf
132
140
133 dot = name.find('.')
141 dot = name.find('.')
134 if dot == 0:
142 if dot == 0:
135 raise ValueError('Empty module name')
143 raise ValueError('Empty module name')
136
144
137 if dot < 0:
145 if dot < 0:
138 subname = name
146 subname = name
139 next = None
147 next = None
140 else:
148 else:
141 subname = name[:dot]
149 subname = name[:dot]
142 next = name[dot+1:]
150 next = name[dot+1:]
143
151
144 if buf != '':
152 if buf != '':
145 buf += '.'
153 buf += '.'
146 buf += subname
154 buf += subname
147
155
148 result = import_submodule(mod, subname, buf)
156 result = import_submodule(mod, subname, buf)
149 if result is None and mod != altmod:
157 if result is None and mod != altmod:
150 result = import_submodule(altmod, subname, subname)
158 result = import_submodule(altmod, subname, subname)
151 if result is not None:
159 if result is not None:
152 buf = subname
160 buf = subname
153
161
154 if result is None:
162 if result is None:
155 raise ImportError("No module named %.200s" % name)
163 raise ImportError("No module named %.200s" % name)
156
164
157 return result, next, buf
165 return result, next, buf
158
166
159 # Need to keep track of what we've already reloaded to prevent cyclic evil
167 # Need to keep track of what we've already reloaded to prevent cyclic evil
160 found_now = {}
168 found_now = {}
161
169
162 def import_submodule(mod, subname, fullname):
170 def import_submodule(mod, subname, fullname):
163 """m = import_submodule(mod, subname, fullname)"""
171 """m = import_submodule(mod, subname, fullname)"""
164 # Require:
172 # Require:
165 # if mod == None: subname == fullname
173 # if mod == None: subname == fullname
166 # else: mod.__name__ + "." + subname == fullname
174 # else: mod.__name__ + "." + subname == fullname
167
175
168 global found_now
176 global found_now
169 if fullname in found_now and fullname in sys.modules:
177 if fullname in found_now and fullname in sys.modules:
170 m = sys.modules[fullname]
178 m = sys.modules[fullname]
171 else:
179 else:
172 print('Reloading', fullname)
180 print('Reloading', fullname)
173 found_now[fullname] = 1
181 found_now[fullname] = 1
174 oldm = sys.modules.get(fullname, None)
182 oldm = sys.modules.get(fullname, None)
175
183
176 if mod is None:
184 if mod is None:
177 path = None
185 path = None
178 elif hasattr(mod, '__path__'):
186 elif hasattr(mod, '__path__'):
179 path = mod.__path__
187 path = mod.__path__
180 else:
188 else:
181 return None
189 return None
182
190
183 try:
191 try:
184 # This appears to be necessary on Python 3, because imp.find_module()
192 # This appears to be necessary on Python 3, because imp.find_module()
185 # tries to import standard libraries (like io) itself, and we don't
193 # tries to import standard libraries (like io) itself, and we don't
186 # want them to be processed by our deep_import_hook.
194 # want them to be processed by our deep_import_hook.
187 with replace_import_hook(original_import):
195 with replace_import_hook(original_import):
188 fp, filename, stuff = imp.find_module(subname, path)
196 fp, filename, stuff = imp.find_module(subname, path)
189 except ImportError:
197 except ImportError:
190 return None
198 return None
191
199
192 try:
200 try:
193 m = imp.load_module(fullname, fp, filename, stuff)
201 m = imp.load_module(fullname, fp, filename, stuff)
194 except:
202 except:
195 # load_module probably removed name from modules because of
203 # load_module probably removed name from modules because of
196 # the error. Put back the original module object.
204 # the error. Put back the original module object.
197 if oldm:
205 if oldm:
198 sys.modules[fullname] = oldm
206 sys.modules[fullname] = oldm
199 raise
207 raise
200 finally:
208 finally:
201 if fp: fp.close()
209 if fp: fp.close()
202
210
203 add_submodule(mod, m, fullname, subname)
211 add_submodule(mod, m, fullname, subname)
204
212
205 return m
213 return m
206
214
207 def add_submodule(mod, submod, fullname, subname):
215 def add_submodule(mod, submod, fullname, subname):
208 """mod.{subname} = submod"""
216 """mod.{subname} = submod"""
209 if mod is None:
217 if mod is None:
210 return #Nothing to do here.
218 return #Nothing to do here.
211
219
212 if submod is None:
220 if submod is None:
213 submod = sys.modules[fullname]
221 submod = sys.modules[fullname]
214
222
215 setattr(mod, subname, submod)
223 setattr(mod, subname, submod)
216
224
217 return
225 return
218
226
219 def ensure_fromlist(mod, fromlist, buf, recursive):
227 def ensure_fromlist(mod, fromlist, buf, recursive):
220 """Handle 'from module import a, b, c' imports."""
228 """Handle 'from module import a, b, c' imports."""
221 if not hasattr(mod, '__path__'):
229 if not hasattr(mod, '__path__'):
222 return
230 return
223 for item in fromlist:
231 for item in fromlist:
224 if not hasattr(item, 'rindex'):
232 if not hasattr(item, 'rindex'):
225 raise TypeError("Item in ``from list'' not a string")
233 raise TypeError("Item in ``from list'' not a string")
226 if item == '*':
234 if item == '*':
227 if recursive:
235 if recursive:
228 continue # avoid endless recursion
236 continue # avoid endless recursion
229 try:
237 try:
230 all = mod.__all__
238 all = mod.__all__
231 except AttributeError:
239 except AttributeError:
232 pass
240 pass
233 else:
241 else:
234 ret = ensure_fromlist(mod, all, buf, 1)
242 ret = ensure_fromlist(mod, all, buf, 1)
235 if not ret:
243 if not ret:
236 return 0
244 return 0
237 elif not hasattr(mod, item):
245 elif not hasattr(mod, item):
238 import_submodule(mod, item, buf + '.' + item)
246 import_submodule(mod, item, buf + '.' + item)
239
247
240 def deep_import_hook(name, globals=None, locals=None, fromlist=None, level=-1):
248 def deep_import_hook(name, globals=None, locals=None, fromlist=None, level=-1):
241 """Replacement for __import__()"""
249 """Replacement for __import__()"""
242 parent, buf = get_parent(globals, level)
250 parent, buf = get_parent(globals, level)
243
251
244 head, name, buf = load_next(parent, None if level < 0 else parent, name, buf)
252 head, name, buf = load_next(parent, None if level < 0 else parent, name, buf)
245
253
246 tail = head
254 tail = head
247 while name:
255 while name:
248 tail, name, buf = load_next(tail, tail, name, buf)
256 tail, name, buf = load_next(tail, tail, name, buf)
249
257
250 # If tail is None, both get_parent and load_next found
258 # If tail is None, both get_parent and load_next found
251 # an empty module name: someone called __import__("") or
259 # an empty module name: someone called __import__("") or
252 # doctored faulty bytecode
260 # doctored faulty bytecode
253 if tail is None:
261 if tail is None:
254 raise ValueError('Empty module name')
262 raise ValueError('Empty module name')
255
263
256 if not fromlist:
264 if not fromlist:
257 return head
265 return head
258
266
259 ensure_fromlist(tail, fromlist, buf, 0)
267 ensure_fromlist(tail, fromlist, buf, 0)
260 return tail
268 return tail
261
269
262 modules_reloading = {}
270 modules_reloading = {}
263
271
264 def deep_reload_hook(m):
272 def deep_reload_hook(m):
265 """Replacement for reload()."""
273 """Replacement for reload()."""
266 if not isinstance(m, ModuleType):
274 if not isinstance(m, ModuleType):
267 raise TypeError("reload() argument must be module")
275 raise TypeError("reload() argument must be module")
268
276
269 name = m.__name__
277 name = m.__name__
270
278
271 if name not in sys.modules:
279 if name not in sys.modules:
272 raise ImportError("reload(): module %.200s not in sys.modules" % name)
280 raise ImportError("reload(): module %.200s not in sys.modules" % name)
273
281
274 global modules_reloading
282 global modules_reloading
275 try:
283 try:
276 return modules_reloading[name]
284 return modules_reloading[name]
277 except:
285 except:
278 modules_reloading[name] = m
286 modules_reloading[name] = m
279
287
280 dot = name.rfind('.')
288 dot = name.rfind('.')
281 if dot < 0:
289 if dot < 0:
282 subname = name
290 subname = name
283 path = None
291 path = None
284 else:
292 else:
285 try:
293 try:
286 parent = sys.modules[name[:dot]]
294 parent = sys.modules[name[:dot]]
287 except KeyError:
295 except KeyError:
288 modules_reloading.clear()
296 modules_reloading.clear()
289 raise ImportError("reload(): parent %.200s not in sys.modules" % name[:dot])
297 raise ImportError("reload(): parent %.200s not in sys.modules" % name[:dot])
290 subname = name[dot+1:]
298 subname = name[dot+1:]
291 path = getattr(parent, "__path__", None)
299 path = getattr(parent, "__path__", None)
292
300
293 try:
301 try:
294 # This appears to be necessary on Python 3, because imp.find_module()
302 # This appears to be necessary on Python 3, because imp.find_module()
295 # tries to import standard libraries (like io) itself, and we don't
303 # tries to import standard libraries (like io) itself, and we don't
296 # want them to be processed by our deep_import_hook.
304 # want them to be processed by our deep_import_hook.
297 with replace_import_hook(original_import):
305 with replace_import_hook(original_import):
298 fp, filename, stuff = imp.find_module(subname, path)
306 fp, filename, stuff = imp.find_module(subname, path)
299 finally:
307 finally:
300 modules_reloading.clear()
308 modules_reloading.clear()
301
309
302 try:
310 try:
303 newm = imp.load_module(name, fp, filename, stuff)
311 newm = imp.load_module(name, fp, filename, stuff)
304 except:
312 except:
305 # load_module probably removed name from modules because of
313 # load_module probably removed name from modules because of
306 # the error. Put back the original module object.
314 # the error. Put back the original module object.
307 sys.modules[name] = m
315 sys.modules[name] = m
308 raise
316 raise
309 finally:
317 finally:
310 if fp: fp.close()
318 if fp: fp.close()
311
319
312 modules_reloading.clear()
320 modules_reloading.clear()
313 return newm
321 return newm
314
322
315 # Save the original hooks
323 # Save the original hooks
316 try:
324 try:
317 original_reload = builtin_mod.reload
325 original_reload = builtin_mod.reload
318 except AttributeError:
326 except AttributeError:
319 original_reload = imp.reload # Python 3
327 original_reload = imp.reload # Python 3
320
328
321 # Replacement for reload()
329 # Replacement for reload()
322 def reload(module, exclude=['sys', 'os.path', builtin_mod_name, '__main__']):
330 def reload(module, exclude=['sys', 'os.path', builtin_mod_name, '__main__']):
323 """Recursively reload all modules used in the given module. Optionally
331 """Recursively reload all modules used in the given module. Optionally
324 takes a list of modules to exclude from reloading. The default exclude
332 takes a list of modules to exclude from reloading. The default exclude
325 list contains sys, __main__, and __builtin__, to prevent, e.g., resetting
333 list contains sys, __main__, and __builtin__, to prevent, e.g., resetting
326 display, exception, and io hooks.
334 display, exception, and io hooks.
327 """
335 """
328 global found_now
336 global found_now
329 for i in exclude:
337 for i in exclude:
330 found_now[i] = 1
338 found_now[i] = 1
331 try:
339 try:
332 with replace_import_hook(deep_import_hook):
340 with replace_import_hook(deep_import_hook):
333 return deep_reload_hook(module)
341 return deep_reload_hook(module)
334 finally:
342 finally:
335 found_now = {}
343 found_now = {}
336
344
337 # Uncomment the following to automatically activate deep reloading whenever
345 # Uncomment the following to automatically activate deep reloading whenever
338 # this module is imported
346 # this module is imported
339 #builtin_mod.reload = reload
347 #builtin_mod.reload = reload
General Comments 0
You need to be logged in to leave comments. Login now