| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
""" |
|---|
| 7 |
*Real* reloading support for Python. |
|---|
| 8 |
""" |
|---|
| 9 |
|
|---|
| 10 |
|
|---|
| 11 |
import sys |
|---|
| 12 |
import types |
|---|
| 13 |
import time |
|---|
| 14 |
import linecache |
|---|
| 15 |
|
|---|
| 16 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 71 |
|
|---|
| 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 |
|
|---|
| 89 |
newClass.__bases__ = tuple(newBases) |
|---|
| 90 |
return newClass |
|---|
| 91 |
except TypeError: |
|---|
| 92 |
if newClass.__module__ == "__builtin__": |
|---|
| 93 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 158 |
|
|---|
| 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 |
|
|---|
| 189 |
reload(module) |
|---|
| 190 |
|
|---|
| 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(clazz)) |
|---|
| 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.qual(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 |
|
|---|
| 228 |
continue |
|---|
| 229 |
changed = 0 |
|---|
| 230 |
|
|---|
| 231 |
for k, v in mod.__dict__.items(): |
|---|
| 232 |
try: |
|---|
| 233 |
hash(v) |
|---|
| 234 |
except Exception: |
|---|
| 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 |
|
|---|
| 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 |
|
|---|