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