| 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 |