Imported Upstream version 12.1.0
[contrib/python-twisted.git] / twisted / python / rebuild.py
1 # -*- test-case-name: twisted.test.test_rebuild -*-
2 # Copyright (c) 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(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             # It's a builtin module; nothing to replace here.
228             continue
229
230         if hasattr(mod, '__bundle__'):
231             # PyObjC has a few buggy objects which segfault if you hash() them.
232             # It doesn't make sense to try rebuilding extension modules like
233             # this anyway, so don't try.
234             continue
235
236         changed = 0
237
238         for k, v in mod.__dict__.items():
239             try:
240                 hash(v)
241             except Exception:
242                 continue
243             if fromOldModule(v):
244                 if type(v) == types.ClassType:
245                     if doLog:
246                         log.logfile.write("c")
247                         log.logfile.flush()
248                     nv = latestClass(v)
249                 else:
250                     if doLog:
251                         log.logfile.write("f")
252                         log.logfile.flush()
253                     nv = latestFunction(v)
254                 changed = 1
255                 setattr(mod, k, nv)
256             else:
257                 # Replace bases of non-module classes just to be sure.
258                 if type(v) == types.ClassType:
259                     for base in v.__bases__:
260                         if fromOldModule(base):
261                             latestClass(v)
262         if doLog and not changed and ((modcount % 10) ==0) :
263             log.logfile.write(".")
264             log.logfile.flush()
265
266     components.ALLOW_DUPLICATES = False
267     if doLog:
268         log.msg('')
269         log.msg('   Rebuilt %s.' % str(module.__name__))
270     return module
271