Skip to content

HTTPS clone URL

Subversion checkout URL

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