hook.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. """Cement core hooks module."""
  2. import operator
  3. import types
  4. from ..core import exc, backend
  5. from ..utils.misc import minimal_logger
  6. LOG = minimal_logger(__name__)
  7. class HookManager(object):
  8. """
  9. Manages the hook system to define, get, run, etc hooks within the
  10. the Cement Framework and applications Built on Cement (tm).
  11. :param use_backend_globals: Whether to use backend globals (backward
  12. compatibility and deprecated).
  13. """
  14. def __init__(self, use_backend_globals=False):
  15. if use_backend_globals is True:
  16. self.__hooks__ = backend.__hooks__
  17. else:
  18. self.__hooks__ = {}
  19. def define(self, name):
  20. """
  21. Define a hook namespace that the application and plugins can register
  22. hooks in.
  23. :param name: The name of the hook, stored as hooks['name']
  24. :raises: cement.core.exc.FrameworkError
  25. Usage:
  26. .. code-block:: python
  27. from cement.core.foundation import CementApp
  28. with CementApp('myapp') as app:
  29. app.hook.define('my_hook_name')
  30. """
  31. LOG.debug("defining hook '%s'" % name)
  32. if name in self.__hooks__:
  33. raise exc.FrameworkError("Hook name '%s' already defined!" % name)
  34. self.__hooks__[name] = []
  35. def defined(self, hook_name):
  36. """
  37. Test whether a hook name is defined.
  38. :param hook_name: The name of the hook.
  39. I.e. ``my_hook_does_awesome_things``.
  40. :returns: True if the hook is defined, False otherwise.
  41. :rtype: ``boolean``
  42. Usage:
  43. .. code-block:: python
  44. from cement.core.foundation import CementApp
  45. with CementApp('myapp') as app:
  46. app.hook.defined('some_hook_name'):
  47. # do something about it
  48. pass
  49. """
  50. if hook_name in self.__hooks__:
  51. return True
  52. else:
  53. return False
  54. def register(self, name, func, weight=0):
  55. """
  56. Register a function to a hook. The function will be called, in order
  57. of weight, when the hook is run.
  58. :param name: The name of the hook to register too.
  59. I.e. ``pre_setup``, ``post_run``, etc.
  60. :param func: The function to register to the hook. This is an
  61. *un-instantiated*, non-instance method, simple function.
  62. :param weight: The weight in which to order the hook function.
  63. :type weight: ``int``
  64. Usage:
  65. .. code-block:: python
  66. from cement.core.foundation import CementApp
  67. def my_hook_func(app):
  68. # do something with app?
  69. return True
  70. with CementApp('myapp') as app:
  71. app.hook.define('my_hook_name')
  72. app.hook.register('my_hook_name', my_hook_func)
  73. """
  74. if name not in self.__hooks__:
  75. LOG.debug("hook name '%s' is not defined! ignoring..." % name)
  76. return False
  77. LOG.debug("registering hook '%s' from %s into hooks['%s']" %
  78. (func.__name__, func.__module__, name))
  79. # Hooks are as follows: (weight, name, func)
  80. self.__hooks__[name].append((int(weight), func.__name__, func))
  81. def run(self, name, *args, **kwargs):
  82. """
  83. Run all defined hooks in the namespace. Yields the result of each
  84. hook function run.
  85. :param name: The name of the hook function.
  86. :param args: Additional arguments to be passed to the hook functions.
  87. :param kwargs: Additional keyword arguments to be passed to the hook
  88. functions.
  89. :raises: FrameworkError
  90. Usage:
  91. .. code-block:: python
  92. from cement.core.foundation import CementApp
  93. def my_hook_func(app):
  94. # do something with app?
  95. return True
  96. with CementApp('myapp') as app:
  97. app.hook.define('my_hook_name')
  98. app.hook.register('my_hook_name', my_hook_func)
  99. for res in app.hook.run('my_hook_name', self):
  100. # do something with the result?
  101. pass
  102. """
  103. if name not in self.__hooks__:
  104. raise exc.FrameworkError("Hook name '%s' is not defined!" % name)
  105. # Will order based on weight (the first item in the tuple)
  106. self.__hooks__[name].sort(key=operator.itemgetter(0))
  107. for hook in self.__hooks__[name]:
  108. LOG.debug("running hook '%s' (%s) from %s" %
  109. (name, hook[2], hook[2].__module__))
  110. res = hook[2](*args, **kwargs)
  111. # Check if result is a nested generator - needed to support e.g.
  112. # asyncio
  113. if isinstance(res, types.GeneratorType):
  114. for _res in res:
  115. yield _res
  116. else:
  117. yield res
  118. # the following is only used for backward compat with < 2.7.x!
  119. def define(name):
  120. """
  121. DEPRECATION WARNING: This function is deprecated as of Cement 2.7.x and
  122. will be removed in future versions of Cement.
  123. Use ``CementApp.hook.define()`` instead.
  124. ---
  125. Define a hook namespace that plugins can register hooks in.
  126. :param name: The name of the hook, stored as hooks['name']
  127. :raises: cement.core.exc.FrameworkError
  128. Usage:
  129. .. code-block:: python
  130. from cement.core import hook
  131. hook.define('myhookname_hook')
  132. """
  133. # only log debug for now as this won't be removed until Cement 3.x and
  134. # we don't have access to CementApp.Meta.ignore_deprecation_warnings here
  135. LOG.debug(
  136. 'Cement Deprecation Warning: `hook.define()` has been deprecated, '
  137. 'and will be removed in future versions of Cement. You should now '
  138. 'use `CementApp.hook.define()` instead.'
  139. )
  140. LOG.debug("defining hook '%s'" % name)
  141. if name in backend.__hooks__:
  142. raise exc.FrameworkError("Hook name '%s' already defined!" % name)
  143. backend.__hooks__[name] = []
  144. def defined(hook_name):
  145. """
  146. DEPRECATION WARNING: This function is deprecated as of Cement 2.7.x and
  147. will be removed in future versions of Cement.
  148. Use ``CementApp.hook.defined()`` instead.
  149. ---
  150. Test whether a hook name is defined.
  151. :param hook_name: The name of the hook.
  152. I.e. ``my_hook_does_awesome_things``.
  153. :returns: True if the hook is defined, False otherwise.
  154. :rtype: ``boolean``
  155. """
  156. # only log debug for now as this won't be removed until Cement 3.x and
  157. # we don't have access to CementApp.Meta.ignore_deprecation_warnings here
  158. LOG.debug(
  159. 'Cement Deprecation Warning: `hook.defined()` has been deprecated, '
  160. 'and will be removed in future versions of Cement. You should now '
  161. 'use `CementApp.hook.defined()` instead.'
  162. )
  163. if hook_name in backend.__hooks__:
  164. return True
  165. else:
  166. return False
  167. def register(name, func, weight=0):
  168. """
  169. DEPRECATION WARNING: This function is deprecated as of Cement 2.7.x and
  170. will be removed in future versions of Cement.
  171. Use ``CementApp.hook.register()`` instead.
  172. ---
  173. Register a function to a hook. The function will be called, in order of
  174. weight, when the hook is run.
  175. :param name: The name of the hook to register too. I.e. ``pre_setup``,
  176. ``post_run``, etc.
  177. :param func: The function to register to the hook. This is an
  178. *un-instantiated*, non-instance method, simple function.
  179. :param weight: The weight in which to order the hook function.
  180. :type weight: ``int``
  181. Usage:
  182. .. code-block:: python
  183. from cement.core import hook
  184. def my_hook(*args, **kwargs):
  185. # do something here
  186. res = 'Something to return'
  187. return res
  188. hook.register('post_setup', my_hook)
  189. """
  190. # only log debug for now as this won't be removed until Cement 3.x and
  191. # we don't have access to CementApp.Meta.ignore_deprecation_warnings here
  192. LOG.debug(
  193. 'Cement Deprecation Warning: `hook.register()` has been deprecated, '
  194. 'and will be removed in future versions of Cement. You should now '
  195. 'use `CementApp.hook.register()` instead.'
  196. )
  197. if name not in backend.__hooks__:
  198. LOG.debug("hook name '%s' is not defined! ignoring..." % name)
  199. return False
  200. LOG.debug("registering hook '%s' from %s into hooks['%s']" %
  201. (func.__name__, func.__module__, name))
  202. # Hooks are as follows: (weight, name, func)
  203. backend.__hooks__[name].append((int(weight), func.__name__, func))
  204. def run(name, *args, **kwargs):
  205. """
  206. DEPRECATION WARNING: This function is deprecated as of Cement 2.7.x and
  207. will be removed in future versions of Cement.
  208. Use ``CementApp.hook.run()`` instead.
  209. ---
  210. Run all defined hooks in the namespace. Yields the result of each hook
  211. function run.
  212. :param name: The name of the hook function.
  213. :param args: Additional arguments to be passed to the hook functions.
  214. :param kwargs: Additional keyword arguments to be passed to the hook
  215. functions.
  216. :raises: FrameworkError
  217. Usage:
  218. .. code-block:: python
  219. from cement.core import hook
  220. for result in hook.run('hook_name'):
  221. # do something with result from each hook function
  222. ...
  223. """
  224. # only log debug for now as this won't be removed until Cement 3.x and
  225. # we don't have access to CementApp.Meta.ignore_deprecation_warnings here
  226. LOG.debug(
  227. 'Cement Deprecation Warning: `hook.run()` has been deprecated, '
  228. 'and will be removed in future versions of Cement. You should now '
  229. 'use `CementApp.hook.run()` instead.'
  230. )
  231. if name not in backend.__hooks__:
  232. raise exc.FrameworkError("Hook name '%s' is not defined!" % name)
  233. # Will order based on weight (the first item in the tuple)
  234. backend.__hooks__[name].sort(key=operator.itemgetter(0))
  235. for hook in backend.__hooks__[name]:
  236. LOG.debug("running hook '%s' (%s) from %s" %
  237. (name, hook[2], hook[2].__module__))
  238. res = hook[2](*args, **kwargs)
  239. # Check if result is a nested generator - needed to support e.g.
  240. # asyncio
  241. if isinstance(res, types.GeneratorType):
  242. for _res in res:
  243. yield _res
  244. else:
  245. yield res