blob: 03ca2fdc74badd90f15a3f02ef1575e72bdd1001 [file] [log] [blame]
Tor Norbye3a2425a2013-11-04 10:16:08 -08001"""
2Copied from the python xreload (available for change)
3
4Alternative to reload().
5
6This works by executing the module in a scratch namespace, and then
7patching classes, methods and functions in place. This avoids the
8need to patch instances. New objects are copied into the target
9namespace.
10
11Some of the many limitations include:
12
13- Global mutable objects other than classes are simply replaced, not patched
14
15- Code using metaclasses is not handled correctly
16
17- Code creating global singletons is not handled correctly
18
19- Functions and methods using decorators (other than classmethod and
20 staticmethod) is not handled correctly
21
22- Renamings are not handled correctly
23
24- Dependent modules are not reloaded
25
26- When a dependent module contains 'from foo import bar', and
27 reloading foo deletes foo.bar, the dependent module continues to use
28 the old foo.bar object rather than failing
29
30- Frozen modules and modules loaded from zip files aren't handled
31 correctly
32
33- Classes involving __slots__ are not handled correctly
34"""
35
36import imp
37import sys
38import types
39
40
41def xreload(mod):
42 """Reload a module in place, updating classes, methods and functions.
43
44 Args:
45 mod: a module object
46
47 Returns:
48 The (updated) input object itself.
49 """
50 # Get the module name, e.g. 'foo.bar.whatever'
51 modname = mod.__name__
52 # Get the module namespace (dict) early; this is part of the type check
53 modns = mod.__dict__
54 # Parse it into package name and module name, e.g. 'foo.bar' and 'whatever'
55 i = modname.rfind(".")
56 if i >= 0:
57 pkgname, modname = modname[:i], modname[i+1:]
58 else:
59 pkgname = None
60 # Compute the search path
61 if pkgname:
62 # We're not reloading the package, only the module in it
63 pkg = sys.modules[pkgname]
64 path = pkg.__path__ # Search inside the package
65 else:
66 # Search the top-level module path
67 pkg = None
68 path = None # Make find_module() uses the default search path
69 # Find the module; may raise ImportError
70 (stream, filename, (suffix, mode, kind)) = imp.find_module(modname, path)
71 # Turn it into a code object
72 try:
73 # Is it Python source code or byte code read from a file?
74 if kind not in (imp.PY_COMPILED, imp.PY_SOURCE):
75 # Fall back to built-in reload()
76 return reload(mod)
77 if kind == imp.PY_SOURCE:
78 source = stream.read()
79 code = compile(source, filename, "exec")
80 else:
81 import marshal
82 code = marshal.load(stream)
83 finally:
84 if stream:
85 stream.close()
86 # Execute the code. We copy the module dict to a temporary; then
87 # clear the module dict; then execute the new code in the module
88 # dict; then swap things back and around. This trick (due to
89 # Glyph Lefkowitz) ensures that the (readonly) __globals__
90 # attribute of methods and functions is set to the correct dict
91 # object.
92 tmpns = modns.copy()
93 modns.clear()
94 modns["__name__"] = tmpns["__name__"]
95 exec(code, modns)
96 # Now we get to the hard part
97 oldnames = set(tmpns)
98 newnames = set(modns)
99 # Update attributes in place
100 for name in oldnames & newnames:
101 modns[name] = _update(tmpns[name], modns[name])
102 # Done!
103 return mod
104
105
106def _update(oldobj, newobj):
107 """Update oldobj, if possible in place, with newobj.
108
109 If oldobj is immutable, this simply returns newobj.
110
111 Args:
112 oldobj: the object to be updated
113 newobj: the object used as the source for the update
114
115 Returns:
116 either oldobj, updated in place, or newobj.
117 """
118 if oldobj is newobj:
119 # Probably something imported
120 return newobj
121 if type(oldobj) is not type(newobj):
122 # Cop-out: if the type changed, give up
123 return newobj
124 if hasattr(newobj, "__reload_update__"):
125 # Provide a hook for updating
126 return newobj.__reload_update__(oldobj)
127
128 if hasattr(types, 'ClassType'):
129 classtype = types.ClassType
130 else:
131 classtype = type
132
133 if isinstance(newobj, classtype):
134 return _update_class(oldobj, newobj)
135 if isinstance(newobj, types.FunctionType):
136 return _update_function(oldobj, newobj)
137 if isinstance(newobj, types.MethodType):
138 return _update_method(oldobj, newobj)
139 if isinstance(newobj, classmethod):
140 return _update_classmethod(oldobj, newobj)
141 if isinstance(newobj, staticmethod):
142 return _update_staticmethod(oldobj, newobj)
143 # Not something we recognize, just give up
144 return newobj
145
146
147# All of the following functions have the same signature as _update()
148
149
150def _update_function(oldfunc, newfunc):
151 """Update a function object."""
152 oldfunc.__doc__ = newfunc.__doc__
153 oldfunc.__dict__.update(newfunc.__dict__)
154
155 try:
156 oldfunc.__code__ = newfunc.__code__
157 except AttributeError:
158 oldfunc.func_code = newfunc.func_code
159 try:
160 oldfunc.__defaults__ = newfunc.__defaults__
161 except AttributeError:
162 oldfunc.func_defaults = newfunc.func_defaults
163
164 return oldfunc
165
166
167def _update_method(oldmeth, newmeth):
168 """Update a method object."""
169 # XXX What if im_func is not a function?
170 _update(oldmeth.im_func, newmeth.im_func)
171 return oldmeth
172
173
174def _update_class(oldclass, newclass):
175 """Update a class object."""
176 olddict = oldclass.__dict__
177 newdict = newclass.__dict__
178 oldnames = set(olddict)
179 newnames = set(newdict)
180 for name in newnames - oldnames:
181 setattr(oldclass, name, newdict[name])
182 for name in oldnames - newnames:
183 delattr(oldclass, name)
184 for name in oldnames & newnames - set(['__dict__', '__doc__']):
185 setattr(oldclass, name, _update(olddict[name], newdict[name]))
186 return oldclass
187
188
189def _update_classmethod(oldcm, newcm):
190 """Update a classmethod update."""
191 # While we can't modify the classmethod object itself (it has no
192 # mutable attributes), we *can* extract the underlying function
193 # (by calling __get__(), which returns a method object) and update
194 # it in-place. We don't have the class available to pass to
195 # __get__() but any object except None will do.
196 _update(oldcm.__get__(0), newcm.__get__(0))
197 return newcm
198
199
200def _update_staticmethod(oldsm, newsm):
201 """Update a staticmethod update."""
202 # While we can't modify the staticmethod object itself (it has no
203 # mutable attributes), we *can* extract the underlying function
204 # (by calling __get__(), which returns it) and update it in-place.
205 # We don't have the class available to pass to __get__() but any
206 # object except None will do.
207 _update(oldsm.__get__(0), newsm.__get__(0))
208 return newsm