OLD | NEW |
| (Empty) |
1 # -*- test-case-name: twisted.test.test_rebuild -*- | |
2 # Copyright (c) 2001-2007 Twisted Matrix Laboratories. | |
3 # See LICENSE for details. | |
4 | |
5 | |
6 """ | |
7 *Real* reloading support for Python. | |
8 """ | |
9 | |
10 # System Imports | |
11 import sys | |
12 import types | |
13 import time | |
14 import linecache | |
15 | |
16 # Sibling Imports | |
17 from twisted.python import log, reflect | |
18 | |
19 lastRebuild = time.time() | |
20 | |
21 | |
22 class Sensitive: | |
23 """ | |
24 A utility mixin that's sensitive to rebuilds. | |
25 | |
26 This is a mixin for classes (usually those which represent collections of | |
27 callbacks) to make sure that their code is up-to-date before running. | |
28 """ | |
29 | |
30 lastRebuild = lastRebuild | |
31 | |
32 def needRebuildUpdate(self): | |
33 yn = (self.lastRebuild < lastRebuild) | |
34 return yn | |
35 | |
36 def rebuildUpToDate(self): | |
37 self.lastRebuild = time.time() | |
38 | |
39 def latestVersionOf(self, anObject): | |
40 """ | |
41 Get the latest version of an object. | |
42 | |
43 This can handle just about anything callable; instances, functions, | |
44 methods, and classes. | |
45 """ | |
46 t = type(anObject) | |
47 if t == types.FunctionType: | |
48 return latestFunction(anObject) | |
49 elif t == types.MethodType: | |
50 if anObject.im_self is None: | |
51 return getattr(anObject.im_class, anObject.__name__) | |
52 else: | |
53 return getattr(anObject.im_self, anObject.__name__) | |
54 elif t == types.InstanceType: | |
55 # Kick it, if it's out of date. | |
56 getattr(anObject, 'nothing', None) | |
57 return anObject | |
58 elif t == types.ClassType: | |
59 return latestClass(anObject) | |
60 else: | |
61 log.msg('warning returning anObject!') | |
62 return anObject | |
63 | |
64 _modDictIDMap = {} | |
65 | |
66 def latestFunction(oldFunc): | |
67 """ | |
68 Get the latest version of a function. | |
69 """ | |
70 # This may be CPython specific, since I believe jython instantiates a new | |
71 # module upon reload. | |
72 dictID = id(oldFunc.func_globals) | |
73 module = _modDictIDMap.get(dictID) | |
74 if module is None: | |
75 return oldFunc | |
76 return getattr(module, oldFunc.__name__) | |
77 | |
78 | |
79 def latestClass(oldClass): | |
80 """ | |
81 Get the latest version of a class. | |
82 """ | |
83 module = reflect.namedModule(oldClass.__module__) | |
84 newClass = getattr(module, oldClass.__name__) | |
85 newBases = [latestClass(base) for base in newClass.__bases__] | |
86 | |
87 try: | |
88 # This makes old-style stuff work | |
89 newClass.__bases__ = tuple(newBases) | |
90 return newClass | |
91 except TypeError: | |
92 if newClass.__module__ == "__builtin__": | |
93 # __builtin__ members can't be reloaded sanely | |
94 return newClass | |
95 ctor = getattr(newClass, '__metaclass__', type) | |
96 return ctor(newClass.__name__, tuple(newBases), dict(newClass.__dict__)) | |
97 | |
98 | |
99 class RebuildError(Exception): | |
100 """ | |
101 Exception raised when trying to rebuild a class whereas it's not possible. | |
102 """ | |
103 | |
104 | |
105 def updateInstance(self): | |
106 """ | |
107 Updates an instance to be current. | |
108 """ | |
109 try: | |
110 self.__class__ = latestClass(self.__class__) | |
111 except TypeError: | |
112 if hasattr(self.__class__, '__slots__'): | |
113 raise RebuildError("Can't rebuild class with __slots__ on Python < 2
.6") | |
114 else: | |
115 raise | |
116 | |
117 | |
118 def __getattr__(self, name): | |
119 """ | |
120 A getattr method to cause a class to be refreshed. | |
121 """ | |
122 if name == '__del__': | |
123 raise AttributeError("Without this, Python segfaults.") | |
124 updateInstance(self) | |
125 log.msg("(rebuilding stale %s instance (%s))" % (reflect.qual(self.__class__
), name)) | |
126 result = getattr(self, name) | |
127 return result | |
128 | |
129 | |
130 def rebuild(module, doLog=1): | |
131 """ | |
132 Reload a module and do as much as possible to replace its references. | |
133 """ | |
134 global lastRebuild | |
135 lastRebuild = time.time() | |
136 if hasattr(module, 'ALLOW_TWISTED_REBUILD'): | |
137 # Is this module allowed to be rebuilt? | |
138 if not module.ALLOW_TWISTED_REBUILD: | |
139 raise RuntimeError("I am not allowed to be rebuilt.") | |
140 if doLog: | |
141 log.msg('Rebuilding %s...' % str(module.__name__)) | |
142 | |
143 ## Safely handle adapter re-registration | |
144 from twisted.python import components | |
145 components.ALLOW_DUPLICATES = True | |
146 | |
147 d = module.__dict__ | |
148 _modDictIDMap[id(d)] = module | |
149 newclasses = {} | |
150 classes = {} | |
151 functions = {} | |
152 values = {} | |
153 if doLog: | |
154 log.msg(' (scanning %s): ' % str(module.__name__)) | |
155 for k, v in d.items(): | |
156 if type(v) == types.ClassType: | |
157 # Failure condition -- instances of classes with buggy | |
158 # __hash__/__cmp__ methods referenced at the module level... | |
159 if v.__module__ == module.__name__: | |
160 classes[v] = 1 | |
161 if doLog: | |
162 log.logfile.write("c") | |
163 log.logfile.flush() | |
164 elif type(v) == types.FunctionType: | |
165 if v.func_globals is module.__dict__: | |
166 functions[v] = 1 | |
167 if doLog: | |
168 log.logfile.write("f") | |
169 log.logfile.flush() | |
170 elif isinstance(v, type): | |
171 if v.__module__ == module.__name__: | |
172 newclasses[v] = 1 | |
173 if doLog: | |
174 log.logfile.write("o") | |
175 log.logfile.flush() | |
176 | |
177 values.update(classes) | |
178 values.update(functions) | |
179 fromOldModule = values.has_key | |
180 newclasses = newclasses.keys() | |
181 classes = classes.keys() | |
182 functions = functions.keys() | |
183 | |
184 if doLog: | |
185 log.msg('') | |
186 log.msg(' (reload %s)' % str(module.__name__)) | |
187 | |
188 # Boom. | |
189 reload(module) | |
190 # Make sure that my traceback printing will at least be recent... | |
191 linecache.clearcache() | |
192 | |
193 if doLog: | |
194 log.msg(' (cleaning %s): ' % str(module.__name__)) | |
195 | |
196 for clazz in classes: | |
197 if getattr(module, clazz.__name__) is clazz: | |
198 log.msg("WARNING: class %s not replaced by reload!" % reflect.qual(c
lazz)) | |
199 else: | |
200 if doLog: | |
201 log.logfile.write("x") | |
202 log.logfile.flush() | |
203 clazz.__bases__ = () | |
204 clazz.__dict__.clear() | |
205 clazz.__getattr__ = __getattr__ | |
206 clazz.__module__ = module.__name__ | |
207 if newclasses: | |
208 import gc | |
209 for nclass in newclasses: | |
210 ga = getattr(module, nclass.__name__) | |
211 if ga is nclass: | |
212 log.msg("WARNING: new-class %s not replaced by reload!" % reflect.qu
al(nclass)) | |
213 else: | |
214 for r in gc.get_referrers(nclass): | |
215 if getattr(r, '__class__', None) is nclass: | |
216 r.__class__ = ga | |
217 if doLog: | |
218 log.msg('') | |
219 log.msg(' (fixing %s): ' % str(module.__name__)) | |
220 modcount = 0 | |
221 for mk, mod in sys.modules.items(): | |
222 modcount = modcount + 1 | |
223 if mod == module or mod is None: | |
224 continue | |
225 | |
226 if not hasattr(mod, '__file__'): | |
227 # It's a builtin module; nothing to replace here. | |
228 continue | |
229 changed = 0 | |
230 | |
231 for k, v in mod.__dict__.items(): | |
232 try: | |
233 hash(v) | |
234 except TypeError: | |
235 continue | |
236 if fromOldModule(v): | |
237 if type(v) == types.ClassType: | |
238 if doLog: | |
239 log.logfile.write("c") | |
240 log.logfile.flush() | |
241 nv = latestClass(v) | |
242 else: | |
243 if doLog: | |
244 log.logfile.write("f") | |
245 log.logfile.flush() | |
246 nv = latestFunction(v) | |
247 changed = 1 | |
248 setattr(mod, k, nv) | |
249 else: | |
250 # Replace bases of non-module classes just to be sure. | |
251 if type(v) == types.ClassType: | |
252 for base in v.__bases__: | |
253 if fromOldModule(base): | |
254 latestClass(v) | |
255 if doLog and not changed and ((modcount % 10) ==0) : | |
256 log.logfile.write(".") | |
257 log.logfile.flush() | |
258 | |
259 components.ALLOW_DUPLICATES = False | |
260 if doLog: | |
261 log.msg('') | |
262 log.msg(' Rebuilt %s.' % str(module.__name__)) | |
263 return module | |
264 | |
OLD | NEW |