base.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import
  3. import os
  4. import sys
  5. import warnings
  6. from optparse import OptionParser, make_option as Option
  7. from .. import __version__, Celery
  8. from ..exceptions import CDeprecationWarning, CPendingDeprecationWarning
  9. # always enable DeprecationWarnings, so our users can see them.
  10. for warning in (CDeprecationWarning, CPendingDeprecationWarning):
  11. warnings.simplefilter("once", warning, 0)
  12. class Command(object):
  13. """Base class for command line applications.
  14. :keyword app: The current app.
  15. :keyword get_app: Callable returning the current app if no app provided.
  16. """
  17. _default_broker_url = r'amqp://guest:guest@localhost:5672//'
  18. #: Arg list used in help.
  19. args = ''
  20. #: Application version.
  21. version = __version__
  22. #: If false the parser will raise an exception if positional
  23. #: args are provided.
  24. supports_args = True
  25. #: List of options (without preload options).
  26. option_list = ()
  27. #: List of options to parse before parsing other options.
  28. preload_options = (
  29. Option("--app",
  30. default=None, action="store", dest="app",
  31. help="Name of the app instance to use. "),
  32. Option("-b", "--broker",
  33. default=None, action="store", dest="broker",
  34. help="Broker URL. Default is %s" % (
  35. _default_broker_url, )),
  36. Option("--loader",
  37. default=None, action="store", dest="loader",
  38. help="Name of the loader class to use. "
  39. "Taken from the environment variable CELERY_LOADER, "
  40. "or 'default' if that is not set."),
  41. Option("--config",
  42. default="celeryconfig", action="store",
  43. dest="config_module",
  44. help="Name of the module to read configuration from."),
  45. )
  46. #: Enable if the application should support config from the cmdline.
  47. enable_config_from_cmdline = False
  48. #: Default configuration namespace.
  49. namespace = "celery"
  50. Parser = OptionParser
  51. def __init__(self, app=None, get_app=None):
  52. self.app = app
  53. self.get_app = get_app or self._get_default_app
  54. def run(self, *args, **options):
  55. """This is the body of the command called by :meth:`handle_argv`."""
  56. raise NotImplementedError("subclass responsibility")
  57. def execute_from_commandline(self, argv=None):
  58. """Execute application from command line.
  59. :keyword argv: The list of command line arguments.
  60. Defaults to ``sys.argv``.
  61. """
  62. if argv is None:
  63. argv = list(sys.argv)
  64. argv = self.setup_app_from_commandline(argv)
  65. prog_name = os.path.basename(argv[0])
  66. return self.handle_argv(prog_name, argv[1:])
  67. def usage(self):
  68. """Returns the command-line usage string for this app."""
  69. return "%%prog [options] %s" % (self.args, )
  70. def get_options(self):
  71. """Get supported command line options."""
  72. return self.option_list
  73. def handle_argv(self, prog_name, argv):
  74. """Parses command line arguments from ``argv`` and dispatches
  75. to :meth:`run`.
  76. :param prog_name: The program name (``argv[0]``).
  77. :param argv: Command arguments.
  78. Exits with an error message if :attr:`supports_args` is disabled
  79. and ``argv`` contains positional arguments.
  80. """
  81. options, args = self.parse_options(prog_name, argv)
  82. for o in vars(options):
  83. v = getattr(options, o)
  84. if isinstance(v, basestring):
  85. setattr(options, o, os.path.expanduser(v))
  86. argv = map(lambda a: isinstance(a, basestring)
  87. and os.path.expanduser(a) or a, argv)
  88. if not self.supports_args and args:
  89. sys.stderr.write(
  90. "\nUnrecognized command line arguments: %s\n" % (
  91. ", ".join(args), ))
  92. sys.stderr.write("\nTry --help?\n")
  93. sys.exit(1)
  94. return self.run(*args, **vars(options))
  95. def parse_options(self, prog_name, arguments):
  96. """Parse the available options."""
  97. # Don't want to load configuration to just print the version,
  98. # so we handle --version manually here.
  99. if "--version" in arguments:
  100. print(self.version)
  101. sys.exit(0)
  102. parser = self.create_parser(prog_name)
  103. options, args = parser.parse_args(arguments)
  104. return options, args
  105. def create_parser(self, prog_name):
  106. return self.Parser(prog=prog_name,
  107. usage=self.usage(),
  108. version=self.version,
  109. option_list=(self.preload_options +
  110. self.get_options()))
  111. def prepare_preload_options(self, options):
  112. """Optional handler to do additional processing of preload options.
  113. Configuration must not have been initialized
  114. until after this is called.
  115. """
  116. pass
  117. def setup_app_from_commandline(self, argv):
  118. preload_options = self.parse_preload_options(argv)
  119. self.prepare_preload_options(preload_options)
  120. app = (preload_options.get("app") or
  121. os.environ.get("CELERY_APP") or
  122. self.app)
  123. loader = (preload_options.get("loader") or
  124. os.environ.get("CELERY_LOADER") or
  125. "default")
  126. broker = preload_options.get("broker", None)
  127. if broker:
  128. os.environ["CELERY_BROKER_URL"] = broker
  129. config_module = preload_options.get("config_module")
  130. if config_module:
  131. os.environ["CELERY_CONFIG_MODULE"] = config_module
  132. if app:
  133. self.app = self.get_cls_by_name(app)
  134. else:
  135. self.app = self.get_app(loader=loader)
  136. if self.enable_config_from_cmdline:
  137. argv = self.process_cmdline_config(argv)
  138. return argv
  139. def get_cls_by_name(self, name):
  140. from ..utils import get_cls_by_name, import_from_cwd
  141. return get_cls_by_name(name, imp=import_from_cwd)
  142. def process_cmdline_config(self, argv):
  143. try:
  144. cargs_start = argv.index('--')
  145. except ValueError:
  146. return argv
  147. argv, cargs = argv[:cargs_start], argv[cargs_start + 1:]
  148. self.app.config_from_cmdline(cargs, namespace=self.namespace)
  149. return argv
  150. def parse_preload_options(self, args):
  151. acc = {}
  152. opts = {}
  153. for opt in self.preload_options:
  154. for t in (opt._long_opts, opt._short_opts):
  155. opts.update(dict(zip(t, [opt.dest] * len(t))))
  156. index = 0
  157. length = len(args)
  158. while index < length:
  159. arg = args[index]
  160. if arg.startswith('--') and '=' in arg:
  161. key, value = arg.split('=', 1)
  162. dest = opts.get(key)
  163. if dest:
  164. acc[dest] = value
  165. elif arg.startswith('-'):
  166. dest = opts.get(arg)
  167. if dest:
  168. acc[dest] = args[index + 1]
  169. index += 1
  170. index += 1
  171. return acc
  172. def _get_default_app(self, *args, **kwargs):
  173. return Celery(*args, **kwargs)
  174. def daemon_options(default_pidfile=None, default_logfile=None):
  175. return (
  176. Option('-f', '--logfile', default=default_logfile,
  177. action="store", dest="logfile",
  178. help="Path to the logfile"),
  179. Option('--pidfile', default=default_pidfile,
  180. action="store", dest="pidfile",
  181. help="Path to the pidfile."),
  182. Option('--uid', default=None,
  183. action="store", dest="uid",
  184. help="Effective user id to run as when detached."),
  185. Option('--gid', default=None,
  186. action="store", dest="gid",
  187. help="Effective group id to run as when detached."),
  188. Option('--umask', default=0,
  189. action="store", type="int", dest="umask",
  190. help="Umask of the process when detached."),
  191. Option('--workdir', default=None,
  192. action="store", dest="working_directory",
  193. help="Directory to change to when detached."),
  194. )