Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rebuild.py 7.0KB

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