base.py 8.4 KB

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