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