PageRenderTime 32ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/master/buildbot/worker_transition.py

https://gitlab.com/murder187ss/buildbot
Python | 324 lines | 309 code | 0 blank | 15 comment | 0 complexity | e08b9105b74bb1e7a759cfd4fdfe1270 MD5 | raw file
  1. # This file is part of Buildbot. Buildbot is free software: you can
  2. # redistribute it and/or modify it under the terms of the GNU General Public
  3. # License as published by the Free Software Foundation, version 2.
  4. #
  5. # This program is distributed in the hope that it will be useful, but WITHOUT
  6. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  7. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  8. # details.
  9. #
  10. # You should have received a copy of the GNU General Public License along with
  11. # this program; if not, write to the Free Software Foundation, Inc., 51
  12. # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  13. #
  14. # Copyright Buildbot Team Members
  15. """
  16. Utility functions to support transition from "slave"-named API to
  17. "worker"-named.
  18. Use of old API generates Python warning which may be logged, ignored or treated
  19. as an error using Python builtin warnings API.
  20. """
  21. import functools
  22. import sys
  23. import warnings
  24. from twisted.python.deprecate import deprecatedModuleAttribute as _deprecatedModuleAttribute
  25. from twisted.python.deprecate import getWarningMethod
  26. from twisted.python.deprecate import setWarningMethod
  27. from twisted.python.versions import Version
  28. __all__ = (
  29. "DeprecatedWorkerNameWarning",
  30. "deprecatedWorkerClassMethod",
  31. "WorkerAPICompatMixin",
  32. "setupWorkerTransition",
  33. "deprecatedWorkerModuleAttribute",
  34. "reportDeprecatedWorkerNameUsage",
  35. "reportDeprecatedWorkerModuleUsage",
  36. )
  37. _WORKER_WARNING_MARK = "[WORKER]"
  38. def _compat_name(new_name, compat_name=None):
  39. """Returns old API ("slave") name for new name ("worker").
  40. >>> assert _compat_name("Worker") == "Slave"
  41. >>> assert _compat_name("SomeWorkerStuff") == "SomeSlaveStuff"
  42. >>> assert _compat_name("SomeWorker", compat_name="SomeBuildSlave") == \
  43. "SomeBuildSlave"
  44. If `compat_name` is not specified old name is construct by replacing in
  45. `new_name`:
  46. "worker" -> "slave",
  47. "Worker" -> "Slave".
  48. For the sake of simplicity of usage if `compat_name` argument is specified
  49. it will returned as the result.
  50. """
  51. if compat_name is not None:
  52. assert "slave" in compat_name.lower()
  53. assert new_name == "" or "worker" in new_name.lower(), new_name
  54. return compat_name
  55. compat_replacements = {
  56. "worker": "slave",
  57. "Worker": "Slave",
  58. }
  59. compat_name = new_name
  60. assert "slave" not in compat_name.lower()
  61. assert "worker" in compat_name.lower()
  62. for new_word, old_word in compat_replacements.iteritems():
  63. compat_name = compat_name.replace(new_word, old_word)
  64. assert compat_name != new_name
  65. assert "slave" in compat_name.lower()
  66. assert "worker" not in compat_name.lower()
  67. return compat_name
  68. # DeprecationWarning or PendingDeprecationWarning may be used as
  69. # the base class, but by default deprecation warnings are disabled in Python,
  70. # so by default old-API usage warnings will be ignored - this is not what
  71. # we want.
  72. class DeprecatedWorkerAPIWarning(Warning):
  73. """Base class for deprecated API warnings."""
  74. class DeprecatedWorkerNameWarning(DeprecatedWorkerAPIWarning):
  75. """Warning class for use of deprecated classes, functions, methods
  76. and attributes.
  77. """
  78. # Separate warnings about deprecated modules from other deprecated
  79. # identifiers. Deprecated modules are loaded only once and it's hard to
  80. # predict in tests exact places where warning should be issued (in contrast
  81. # warnings about other identifiers will be issued every usage).
  82. class DeprecatedWorkerModuleWarning(DeprecatedWorkerAPIWarning):
  83. """Warning class for use of deprecated modules."""
  84. def reportDeprecatedWorkerNameUsage(message, stacklevel=None, filename=None,
  85. lineno=None):
  86. """Hook that is ran when old API name is used.
  87. :param stacklevel: stack level relative to the caller's frame.
  88. Defaults to caller of the caller of this function.
  89. """
  90. if filename is None:
  91. if stacklevel is None:
  92. # Warning will refer to the caller of the caller of this function.
  93. stacklevel = 3
  94. else:
  95. stacklevel += 2
  96. warnings.warn(DeprecatedWorkerNameWarning(message), None, stacklevel)
  97. else:
  98. assert stacklevel is None
  99. if lineno is None:
  100. lineno = 0
  101. warnings.warn_explicit(
  102. DeprecatedWorkerNameWarning(message),
  103. DeprecatedWorkerNameWarning,
  104. filename, lineno)
  105. def reportDeprecatedWorkerModuleUsage(message, stacklevel=None):
  106. """Hook that is ran when old API module is used.
  107. :param stacklevel: stack level relative to the caller's frame.
  108. Defaults to caller of the caller of this function.
  109. """
  110. if stacklevel is None:
  111. # Warning will refer to the caller of the caller of this function.
  112. stacklevel = 3
  113. else:
  114. stacklevel += 2
  115. warnings.warn(DeprecatedWorkerModuleWarning(message), None, stacklevel)
  116. def setupWorkerTransition():
  117. """Hook Twisted deprecation machinery to use custom warning class
  118. for Worker API deprecation warnings."""
  119. default_warn_method = getWarningMethod()
  120. def custom_warn_method(message, category, stacklevel):
  121. if stacklevel is not None:
  122. stacklevel += 1
  123. if _WORKER_WARNING_MARK in message:
  124. # Message contains our mark - it's Worker API Renaming warning,
  125. # issue it appropriately.
  126. message = message.replace(_WORKER_WARNING_MARK, "")
  127. warnings.warn(
  128. DeprecatedWorkerNameWarning(message), message, stacklevel)
  129. else:
  130. # Other's warning message
  131. default_warn_method(message, category, stacklevel)
  132. setWarningMethod(custom_warn_method)
  133. def deprecatedWorkerModuleAttribute(scope, attribute, compat_name=None,
  134. new_name=None):
  135. """This is similar to Twisted's deprecatedModuleAttribute, but for
  136. Worker API Rename warnings.
  137. Can be used to create compatibility attributes for module-level classes,
  138. functions and global variables.
  139. :param scope: module scope (locals() in the context of a module)
  140. :param attribute: module object (class, function, global variable)
  141. :param compat_name: optional compatibility name (will be generated if not
  142. specified)
  143. :param new_name: optional new name (will be used name of attribute object
  144. in the module is not specified). If empty string is specified, then no
  145. new name is assumed for this attribute.
  146. """
  147. module_name = scope["__name__"]
  148. assert module_name in sys.modules, "scope must be module, i.e. locals()"
  149. assert sys.modules[module_name].__dict__ is scope, \
  150. "scope must be module, i.e. locals()"
  151. if new_name is None:
  152. attribute_name = scope.keys()[scope.values().index(attribute)]
  153. else:
  154. attribute_name = new_name
  155. compat_name = _compat_name(attribute_name, compat_name=compat_name)
  156. scope[compat_name] = attribute
  157. if attribute_name:
  158. msg = "Use {0} instead.".format(attribute_name)
  159. else:
  160. msg = "Don't use it."
  161. _deprecatedModuleAttribute(
  162. Version("Buildbot", 0, 9, 0),
  163. _WORKER_WARNING_MARK + msg,
  164. module_name, compat_name)
  165. def deprecatedWorkerClassProperty(scope, prop, compat_name=None,
  166. new_name=None):
  167. """Define compatibility class property.
  168. Can be used to create compatibility attribute for class property.
  169. :param scope: class scope (locals() in the context of a scope)
  170. :param prop: property object for which compatibility name should be
  171. created.
  172. :param compat_name: optional compatibility name (will be generated if not
  173. specified)
  174. :param new_name: optional new name (will be used name of attribute object
  175. in the module is not specified). If empty string is specified, then no
  176. new name is assumed for this attribute.
  177. """
  178. if new_name is None:
  179. attribute_name = scope.keys()[scope.values().index(prop)]
  180. else:
  181. attribute_name = new_name
  182. compat_name = _compat_name(attribute_name, compat_name=compat_name)
  183. if attribute_name:
  184. advice_msg = "use '{0}' instead".format(attribute_name)
  185. else:
  186. advice_msg = "don't use it"
  187. def get(self):
  188. reportDeprecatedWorkerNameUsage(
  189. "'{old}' property is deprecated, "
  190. "{advice}.".format(
  191. old=compat_name, advice=advice_msg))
  192. return getattr(self, attribute_name)
  193. assert compat_name not in scope
  194. scope[compat_name] = property(get)
  195. def deprecatedWorkerClassMethod(scope, method, compat_name=None):
  196. """Define old-named method inside class."""
  197. method_name = method.__name__
  198. compat_name = _compat_name(method_name, compat_name=compat_name)
  199. assert compat_name not in scope
  200. def old_method(self, *args, **kwargs):
  201. reportDeprecatedWorkerNameUsage(
  202. "'{old}' method is deprecated, use '{new}' instead.".format(
  203. new=method_name, old=compat_name))
  204. return getattr(self, method_name)(*args, **kwargs)
  205. functools.update_wrapper(old_method, method)
  206. scope[compat_name] = old_method
  207. class WorkerAPICompatMixin(object):
  208. """Mixin class for classes that have old-named worker attributes."""
  209. def __getattr__(self, name):
  210. if name not in self.__compat_attrs:
  211. raise AttributeError(
  212. "'{class_name}' object has no attribute '{attr_name}'".format(
  213. class_name=self.__class__.__name__,
  214. attr_name=name))
  215. new_name = self.__compat_attrs[name]
  216. # TODO: Log class name, operation type etc.
  217. reportDeprecatedWorkerNameUsage(
  218. "'{old}' attribute is deprecated, use '{new}' instead.".format(
  219. new=new_name, old=name))
  220. return getattr(self, new_name)
  221. def __setattr__(self, name, value):
  222. if name in self.__compat_attrs:
  223. new_name = self.__compat_attrs[name]
  224. # TODO: Log class name, operation type etc.
  225. reportDeprecatedWorkerNameUsage(
  226. "'{old}' attribute is deprecated, use '{new}' instead.".format(
  227. new=new_name, old=name))
  228. return setattr(self, new_name, value)
  229. else:
  230. object.__setattr__(self, name, value)
  231. @property
  232. def __compat_attrs(self):
  233. # It's unreliable to initialize attributes in __init__() since
  234. # old-style classes are used and parent initializers are mostly
  235. # not called.
  236. if "_compat_attrs_mapping" not in self.__dict__:
  237. self.__dict__["_compat_attrs_mapping"] = {}
  238. return self._compat_attrs_mapping
  239. def _registerOldWorkerAttr(self, attr_name, name=None):
  240. """Define old-named attribute inside class instance."""
  241. compat_name = _compat_name(attr_name, compat_name=name)
  242. assert compat_name not in self.__dict__
  243. assert compat_name not in self.__compat_attrs
  244. self.__compat_attrs[compat_name] = attr_name
  245. # Enable worker transition hooks
  246. setupWorkerTransition()