abstract.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. # -*- coding: utf-8 -*-
  2. """
  3. celery.abstract
  4. ~~~~~~~~~~~~~~~
  5. Implements components and boot-steps.
  6. :copyright: (c) 2009 - 2012 by Ask Solem.
  7. :license: BSD, see LICENSE for more details.
  8. """
  9. from __future__ import absolute_import
  10. from collections import defaultdict
  11. from importlib import import_module
  12. from .datastructures import DependencyGraph
  13. from .utils import instantiate
  14. class Namespace(object):
  15. """A namespace containing components.
  16. Every component must belong to a namespace.
  17. When component classes are created they are added to the
  18. mapping of unclaimed components. The components will be
  19. claimed when the namespace they belong to is created.
  20. :keyword name: Set the name of this namespace.
  21. :keyword app: Set the Celery app for this namespace.
  22. """
  23. name = None
  24. _unclaimed = defaultdict(dict)
  25. _started_count = 0
  26. def __init__(self, name=None, app=None, logger=None):
  27. self.app = app
  28. self.name = name or self.name
  29. self.logger = logger or self.app.log.get_default_logger()
  30. self.services = []
  31. def modules(self):
  32. """Subclasses can override this to return a
  33. list of modules to import before components are claimed."""
  34. return []
  35. def load_modules(self):
  36. """Will load the component modules this namespace depends on."""
  37. for m in self.modules():
  38. self.import_module(m)
  39. def apply(self, parent, **kwargs):
  40. """Apply the components in this namespace to an object.
  41. This will apply the ``__init__`` and ``include`` methods
  42. of each components with the object as argument.
  43. For ``StartStopComponents`` the services created
  44. will also be added the the objects ``components`` attribute.
  45. """
  46. self._debug("Loading modules.")
  47. self.load_modules()
  48. self._debug("Claiming components.")
  49. self.components = self._claim()
  50. self._debug("Building boot step graph.")
  51. self.boot_steps = [self.bind_component(name, parent, **kwargs)
  52. for name in self._finalize_boot_steps()]
  53. self._debug("New boot order: %r" % (
  54. [c.name for c in self.boot_steps], ))
  55. for component in self.boot_steps:
  56. component.include(parent)
  57. return self
  58. def bind_component(self, name, parent, **kwargs):
  59. """Bind component to parent object and this namespace."""
  60. comp = self[name](parent, **kwargs)
  61. comp.namespace = self
  62. return comp
  63. def import_module(self, module):
  64. return import_module(module)
  65. def __getitem__(self, name):
  66. return self.components[name]
  67. def _find_last(self):
  68. for C in self.components.itervalues():
  69. if C.last:
  70. return C
  71. def _finalize_boot_steps(self):
  72. G = self.graph = DependencyGraph((C.name, C.requires)
  73. for C in self.components.itervalues())
  74. last = self._find_last()
  75. if last:
  76. for obj in G:
  77. if obj != last.name:
  78. G.add_edge(last.name, obj)
  79. return G.topsort()
  80. def _claim(self):
  81. return self._unclaimed[self.name]
  82. def _debug(self, msg, *args):
  83. return self.logger.debug("[%s] " + msg,
  84. *(self.name.capitalize(), ) + args)
  85. class ComponentType(type):
  86. """Metaclass for components."""
  87. def __new__(cls, name, bases, attrs):
  88. abstract = attrs.pop("abstract", False)
  89. if not abstract:
  90. try:
  91. cname = attrs["name"]
  92. except KeyError:
  93. raise NotImplementedError("Components must be named")
  94. namespace = attrs.get("namespace", None)
  95. if not namespace:
  96. attrs["namespace"], _, attrs["name"] = cname.partition('.')
  97. cls = super(ComponentType, cls).__new__(cls, name, bases, attrs)
  98. if not abstract:
  99. Namespace._unclaimed[cls.namespace][cls.name] = cls
  100. return cls
  101. class Component(object):
  102. """A component.
  103. The :meth:`__init__` method is called when the component
  104. is bound to a parent object, and can as such be used
  105. to initialize attributes in the parent object at
  106. parent instantiation-time.
  107. """
  108. __metaclass__ = ComponentType
  109. #: The name of the component, or the namespace
  110. #: and the name of the component separated by dot.
  111. name = None
  112. #: List of component names this component depends on.
  113. #: Note that the dependencies must be in the same namespace.
  114. requires = ()
  115. #: can be used to specify the namespace,
  116. #: if the name does not include it.
  117. namespace = None
  118. #: if set the component will not be registered,
  119. #: but can be used as a component base class.
  120. abstract = True
  121. #: Optional obj created by the :meth:`create` method.
  122. #: This is used by StartStopComponents to keep the
  123. #: original service object.
  124. obj = None
  125. #: This flag is reserved for the workers Consumer,
  126. #: since it is required to always be started last.
  127. #: There can only be one object marked with lsat
  128. #: in every namespace.
  129. last = False
  130. #: This provides the default for :meth:`include_if`.
  131. enabled = True
  132. def __init__(self, parent, **kwargs):
  133. pass
  134. def create(self, parent):
  135. """Create the component."""
  136. pass
  137. def include_if(self, parent):
  138. """An optional predicate that decided whether this
  139. component should be created."""
  140. return self.enabled
  141. def instantiate(self, qualname, *args, **kwargs):
  142. return instantiate(qualname, *args, **kwargs)
  143. def include(self, parent):
  144. if self.include_if(parent):
  145. self.obj = self.create(parent)
  146. return True
  147. class StartStopComponent(Component):
  148. abstract = True
  149. terminable = False
  150. def start(self):
  151. return self.obj.start()
  152. def stop(self):
  153. return self.obj.stop()
  154. def terminate(self):
  155. if self.terminable:
  156. return self.obj.terminate()
  157. return self.obj.stop()
  158. def include(self, parent):
  159. if super(StartStopComponent, self).include(parent):
  160. parent.components.append(self.obj)