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