Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

celery.py 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. # -*- coding: utf-8 -*-
  2. """
  3. The :program:`celery` umbrella command.
  4. .. program:: celery
  5. """
  6. from __future__ import absolute_import, unicode_literals
  7. import anyjson
  8. import numbers
  9. import os
  10. import sys
  11. from functools import partial
  12. from importlib import import_module
  13. from celery.five import string_t, values
  14. from celery.platforms import EX_OK, EX_FAILURE, EX_UNAVAILABLE, EX_USAGE
  15. from celery.utils import term
  16. from celery.utils import text
  17. from celery.utils.timeutils import maybe_iso8601
  18. # Cannot use relative imports here due to a Windows issue (#1111).
  19. from celery.bin.base import Command, Option, Extensions
  20. # Import commands from other modules
  21. from celery.bin.amqp import amqp
  22. from celery.bin.beat import beat
  23. from celery.bin.events import events
  24. from celery.bin.graph import graph
  25. from celery.bin.worker import worker
  26. __all__ = ['CeleryCommand', 'main']
  27. HELP = """
  28. ---- -- - - ---- Commands- -------------- --- ------------
  29. {commands}
  30. ---- -- - - --------- -- - -------------- --- ------------
  31. Type '{prog_name} <command> --help' for help using a specific command.
  32. """
  33. MIGRATE_PROGRESS_FMT = """\
  34. Migrating task {state.count}/{state.strtotal}: \
  35. {body[task]}[{body[id]}]\
  36. """
  37. DEBUG = os.environ.get('C_DEBUG', False)
  38. command_classes = [
  39. ('Main', ['worker', 'events', 'beat', 'shell', 'multi', 'amqp'], 'green'),
  40. ('Remote Control', ['status', 'inspect', 'control'], 'blue'),
  41. ('Utils', ['purge', 'list', 'migrate', 'call', 'result', 'report'], None),
  42. ]
  43. if DEBUG: # pragma: no cover
  44. command_classes.append(
  45. ('Debug', ['graph'], 'red'),
  46. )
  47. def determine_exit_status(ret):
  48. if isinstance(ret, numbers.Integral):
  49. return ret
  50. return EX_OK if ret else EX_FAILURE
  51. def main(argv=None):
  52. # Fix for setuptools generated scripts, so that it will
  53. # work with multiprocessing fork emulation.
  54. # (see multiprocessing.forking.get_preparation_data())
  55. try:
  56. if __name__ != '__main__': # pragma: no cover
  57. sys.modules['__main__'] = sys.modules[__name__]
  58. cmd = CeleryCommand()
  59. cmd.maybe_patch_concurrency()
  60. from billiard import freeze_support
  61. freeze_support()
  62. cmd.execute_from_commandline(argv)
  63. except KeyboardInterrupt:
  64. pass
  65. class multi(Command):
  66. """Start multiple worker instances."""
  67. respects_app_option = False
  68. def get_options(self):
  69. return ()
  70. def run_from_argv(self, prog_name, argv, command=None):
  71. from celery.bin.multi import MultiTool
  72. multi = MultiTool(quiet=self.quiet, no_color=self.no_color)
  73. return multi.execute_from_commandline(
  74. [command] + argv, prog_name,
  75. )
  76. class list_(Command):
  77. """Get info from broker.
  78. Examples::
  79. celery list bindings
  80. NOTE: For RabbitMQ the management plugin is required.
  81. """
  82. args = '[bindings]'
  83. def list_bindings(self, management):
  84. try:
  85. bindings = management.get_bindings()
  86. except NotImplementedError:
  87. raise self.Error('Your transport cannot list bindings.')
  88. def fmt(q, e, r):
  89. return self.out('{0:<28} {1:<28} {2}'.format(q, e, r))
  90. fmt('Queue', 'Exchange', 'Routing Key')
  91. fmt('-' * 16, '-' * 16, '-' * 16)
  92. for b in bindings:
  93. fmt(b['destination'], b['source'], b['routing_key'])
  94. def run(self, what=None, *_, **kw):
  95. topics = {'bindings': self.list_bindings}
  96. available = ', '.join(topics)
  97. if not what:
  98. raise self.UsageError(
  99. 'You must specify one of {0}'.format(available))
  100. if what not in topics:
  101. raise self.UsageError(
  102. 'unknown topic {0!r} (choose one of: {1})'.format(
  103. what, available))
  104. with self.app.connection() as conn:
  105. self.app.amqp.TaskConsumer(conn).declare()
  106. topics[what](conn.manager)
  107. class call(Command):
  108. """Call a task by name.
  109. Examples::
  110. celery call tasks.add --args='[2, 2]'
  111. celery call tasks.add --args='[2, 2]' --countdown=10
  112. """
  113. args = '<task_name>'
  114. option_list = Command.option_list + (
  115. Option('--args', '-a', help='positional arguments (json).'),
  116. Option('--kwargs', '-k', help='keyword arguments (json).'),
  117. Option('--eta', help='scheduled time (ISO-8601).'),
  118. Option('--countdown', type='float',
  119. help='eta in seconds from now (float/int).'),
  120. Option('--expires', help='expiry time (ISO-8601/float/int).'),
  121. Option('--serializer', default='json', help='defaults to json.'),
  122. Option('--queue', help='custom queue name.'),
  123. Option('--exchange', help='custom exchange name.'),
  124. Option('--routing-key', help='custom routing key.'),
  125. )
  126. def run(self, name, *_, **kw):
  127. # Positional args.
  128. args = kw.get('args') or ()
  129. if isinstance(args, string_t):
  130. args = anyjson.loads(args)
  131. # Keyword args.
  132. kwargs = kw.get('kwargs') or {}
  133. if isinstance(kwargs, string_t):
  134. kwargs = anyjson.loads(kwargs)
  135. # Expires can be int/float.
  136. expires = kw.get('expires') or None
  137. try:
  138. expires = float(expires)
  139. except (TypeError, ValueError):
  140. # or a string describing an ISO 8601 datetime.
  141. try:
  142. expires = maybe_iso8601(expires)
  143. except (TypeError, ValueError):
  144. raise
  145. res = self.app.send_task(name, args=args, kwargs=kwargs,
  146. countdown=kw.get('countdown'),
  147. serializer=kw.get('serializer'),
  148. queue=kw.get('queue'),
  149. exchange=kw.get('exchange'),
  150. routing_key=kw.get('routing_key'),
  151. eta=maybe_iso8601(kw.get('eta')),
  152. expires=expires)
  153. self.out(res.id)
  154. class purge(Command):
  155. """Erase all messages from all known task queues.
  156. WARNING: There is no undo operation for this command.
  157. """
  158. warn_prelude = (
  159. '{warning}: This will remove all tasks from {queues}: {names}.\n'
  160. ' There is no undo for this operation!\n\n'
  161. '(to skip this prompt use the -f option)\n'
  162. )
  163. warn_prompt = 'Are you sure you want to delete all tasks'
  164. fmt_purged = 'Purged {mnum} {messages} from {qnum} known task {queues}.'
  165. fmt_empty = 'No messages purged from {qnum} {queues}'
  166. option_list = Command.option_list + (
  167. Option('--force', '-f', action='store_true',
  168. help='Do not prompt for verification'),
  169. )
  170. def run(self, force=False, **kwargs):
  171. names = list(sorted(self.app.amqp.queues.keys()))
  172. qnum = len(names)
  173. if not force:
  174. self.out(self.warn_prelude.format(
  175. warning=self.colored.red('WARNING'),
  176. queues=text.pluralize(qnum, 'queue'), names=', '.join(names),
  177. ))
  178. if self.ask(self.warn_prompt, ('yes', 'no'), 'no') != 'yes':
  179. return
  180. messages = self.app.control.purge()
  181. fmt = self.fmt_purged if messages else self.fmt_empty
  182. self.out(fmt.format(
  183. mnum=messages, qnum=qnum,
  184. messages=text.pluralize(messages, 'message'),
  185. queues=text.pluralize(qnum, 'queue')))
  186. class result(Command):
  187. """Gives the return value for a given task id.
  188. Examples::
  189. celery result 8f511516-e2f5-4da4-9d2f-0fb83a86e500
  190. celery result 8f511516-e2f5-4da4-9d2f-0fb83a86e500 -t tasks.add
  191. celery result 8f511516-e2f5-4da4-9d2f-0fb83a86e500 --traceback
  192. """
  193. args = '<task_id>'
  194. option_list = Command.option_list + (
  195. Option('--task', '-t', help='name of task (if custom backend)'),
  196. Option('--traceback', action='store_true',
  197. help='show traceback instead'),
  198. )
  199. def run(self, task_id, *args, **kwargs):
  200. result_cls = self.app.AsyncResult
  201. task = kwargs.get('task')
  202. traceback = kwargs.get('traceback', False)
  203. if task:
  204. result_cls = self.app.tasks[task].AsyncResult
  205. result = result_cls(task_id)
  206. if traceback:
  207. value = result.traceback
  208. else:
  209. value = result.get()
  210. self.out(self.pretty(value)[1])
  211. class _RemoteControl(Command):
  212. name = None
  213. choices = None
  214. leaf = False
  215. option_list = Command.option_list + (
  216. Option('--timeout', '-t', type='float',
  217. help='Timeout in seconds (float) waiting for reply'),
  218. Option('--destination', '-d',
  219. help='Comma separated list of destination node names.'))
  220. def __init__(self, *args, **kwargs):
  221. self.show_body = kwargs.pop('show_body', True)
  222. self.show_reply = kwargs.pop('show_reply', True)
  223. super(_RemoteControl, self).__init__(*args, **kwargs)
  224. @classmethod
  225. def get_command_info(self, command,
  226. indent=0, prefix='', color=None, help=False):
  227. if help:
  228. help = '|' + text.indent(self.choices[command][1], indent + 4)
  229. else:
  230. help = None
  231. try:
  232. # see if it uses args.
  233. meth = getattr(self, command)
  234. return text.join([
  235. '|' + text.indent('{0}{1} {2}'.format(
  236. prefix, color(command), meth.__doc__), indent),
  237. help,
  238. ])
  239. except AttributeError:
  240. return text.join([
  241. '|' + text.indent(prefix + str(color(command)), indent), help,
  242. ])
  243. @classmethod
  244. def list_commands(self, indent=0, prefix='', color=None, help=False):
  245. color = color if color else lambda x: x
  246. prefix = prefix + ' ' if prefix else ''
  247. return '\n'.join(self.get_command_info(c, indent, prefix, color, help)
  248. for c in sorted(self.choices))
  249. @property
  250. def epilog(self):
  251. return '\n'.join([
  252. '[Commands]',
  253. self.list_commands(indent=4, help=True)
  254. ])
  255. def usage(self, command):
  256. return '%prog {0} [options] {1} <command> [arg1 .. argN]'.format(
  257. command, self.args)
  258. def call(self, *args, **kwargs):
  259. raise NotImplementedError('call')
  260. def run(self, *args, **kwargs):
  261. if not args:
  262. raise self.UsageError(
  263. 'Missing {0.name} method. See --help'.format(self))
  264. return self.do_call_method(args, **kwargs)
  265. def do_call_method(self, args, **kwargs):
  266. method = args[0]
  267. if method == 'help':
  268. raise self.Error("Did you mean '{0.name} --help'?".format(self))
  269. if method not in self.choices:
  270. raise self.UsageError(
  271. 'Unknown {0.name} method {1}'.format(self, method))
  272. if self.app.connection().transport.driver_type == 'sql':
  273. raise self.Error('Broadcast not supported by SQL broker transport')
  274. destination = kwargs.get('destination')
  275. timeout = kwargs.get('timeout') or self.choices[method][0]
  276. if destination and isinstance(destination, string_t):
  277. destination = [dest.strip() for dest in destination.split(',')]
  278. handler = getattr(self, method, self.call)
  279. replies = handler(method, *args[1:], timeout=timeout,
  280. destination=destination,
  281. callback=self.say_remote_command_reply)
  282. if not replies:
  283. raise self.Error('No nodes replied within time constraint.',
  284. status=EX_UNAVAILABLE)
  285. return replies
  286. class inspect(_RemoteControl):
  287. """Inspect the worker at runtime.
  288. Availability: RabbitMQ (amqp), Redis, and MongoDB transports.
  289. Examples::
  290. celery inspect active --timeout=5
  291. celery inspect scheduled -d worker1@example.com
  292. celery inspect revoked -d w1@e.com,w2@e.com
  293. """
  294. name = 'inspect'
  295. choices = {
  296. 'active': (1.0, 'dump active tasks (being processed)'),
  297. 'active_queues': (1.0, 'dump queues being consumed from'),
  298. 'scheduled': (1.0, 'dump scheduled tasks (eta/countdown/retry)'),
  299. 'reserved': (1.0, 'dump reserved tasks (waiting to be processed)'),
  300. 'stats': (1.0, 'dump worker statistics'),
  301. 'revoked': (1.0, 'dump of revoked task ids'),
  302. 'registered': (1.0, 'dump of registered tasks'),
  303. 'ping': (0.2, 'ping worker(s)'),
  304. 'clock': (1.0, 'get value of logical clock'),
  305. 'conf': (1.0, 'dump worker configuration'),
  306. 'report': (1.0, 'get bugreport info'),
  307. 'memsample': (1.0, 'sample memory (requires psutil)'),
  308. 'memdump': (1.0, 'dump memory samples (requires psutil)'),
  309. 'objgraph': (60.0, 'create object graph (requires objgraph)'),
  310. }
  311. def call(self, method, *args, **options):
  312. i = self.app.control.inspect(**options)
  313. return getattr(i, method)(*args)
  314. def objgraph(self, type_='Request', *args, **kwargs):
  315. return self.call('objgraph', type_, **kwargs)
  316. def conf(self, with_defaults=False, *args, **kwargs):
  317. return self.call('conf', with_defaults, **kwargs)
  318. class control(_RemoteControl):
  319. """Workers remote control.
  320. Availability: RabbitMQ (amqp), Redis, and MongoDB transports.
  321. Examples::
  322. celery control enable_events --timeout=5
  323. celery control -d worker1@example.com enable_events
  324. celery control -d w1.e.com,w2.e.com enable_events
  325. celery control -d w1.e.com add_consumer queue_name
  326. celery control -d w1.e.com cancel_consumer queue_name
  327. celery control -d w1.e.com add_consumer queue exchange direct rkey
  328. """
  329. name = 'control'
  330. choices = {
  331. 'enable_events': (1.0, 'tell worker(s) to enable events'),
  332. 'disable_events': (1.0, 'tell worker(s) to disable events'),
  333. 'add_consumer': (1.0, 'tell worker(s) to start consuming a queue'),
  334. 'cancel_consumer': (1.0, 'tell worker(s) to stop consuming a queue'),
  335. 'rate_limit': (
  336. 1.0, 'tell worker(s) to modify the rate limit for a task type'),
  337. 'time_limit': (
  338. 1.0, 'tell worker(s) to modify the time limit for a task type.'),
  339. 'autoscale': (1.0, 'change autoscale settings'),
  340. 'pool_grow': (1.0, 'start more pool processes'),
  341. 'pool_shrink': (1.0, 'use less pool processes'),
  342. }
  343. def call(self, method, *args, **options):
  344. return getattr(self.app.control, method)(*args, reply=True, **options)
  345. def pool_grow(self, method, n=1, **kwargs):
  346. """[N=1]"""
  347. return self.call(method, int(n), **kwargs)
  348. def pool_shrink(self, method, n=1, **kwargs):
  349. """[N=1]"""
  350. return self.call(method, int(n), **kwargs)
  351. def autoscale(self, method, max=None, min=None, **kwargs):
  352. """[max] [min]"""
  353. return self.call(method, int(max), int(min), **kwargs)
  354. def rate_limit(self, method, task_name, rate_limit, **kwargs):
  355. """<task_name> <rate_limit> (e.g. 5/s | 5/m | 5/h)>"""
  356. return self.call(method, task_name, rate_limit, **kwargs)
  357. def time_limit(self, method, task_name, soft, hard=None, **kwargs):
  358. """<task_name> <soft_secs> [hard_secs]"""
  359. return self.call(method, task_name,
  360. float(soft), float(hard), **kwargs)
  361. def add_consumer(self, method, queue, exchange=None,
  362. exchange_type='direct', routing_key=None, **kwargs):
  363. """<queue> [exchange [type [routing_key]]]"""
  364. return self.call(method, queue, exchange,
  365. exchange_type, routing_key, **kwargs)
  366. def cancel_consumer(self, method, queue, **kwargs):
  367. """<queue>"""
  368. return self.call(method, queue, **kwargs)
  369. class status(Command):
  370. """Show list of workers that are online."""
  371. option_list = inspect.option_list
  372. def run(self, *args, **kwargs):
  373. I = inspect(
  374. app=self.app,
  375. no_color=kwargs.get('no_color', False),
  376. stdout=self.stdout, stderr=self.stderr,
  377. show_reply=False, show_body=False, quiet=True,
  378. )
  379. replies = I.run('ping', **kwargs)
  380. if not replies:
  381. raise self.Error('No nodes replied within time constraint',
  382. status=EX_UNAVAILABLE)
  383. nodecount = len(replies)
  384. if not kwargs.get('quiet', False):
  385. self.out('\n{0} {1} online.'.format(
  386. nodecount, text.pluralize(nodecount, 'node')))
  387. class migrate(Command):
  388. """Migrate tasks from one broker to another.
  389. Examples::
  390. celery migrate redis://localhost amqp://guest@localhost//
  391. celery migrate django:// redis://localhost
  392. NOTE: This command is experimental, make sure you have
  393. a backup of the tasks before you continue.
  394. """
  395. args = '<source_url> <dest_url>'
  396. option_list = Command.option_list + (
  397. Option('--limit', '-n', type='int',
  398. help='Number of tasks to consume (int)'),
  399. Option('--timeout', '-t', type='float', default=1.0,
  400. help='Timeout in seconds (float) waiting for tasks'),
  401. Option('--ack-messages', '-a', action='store_true',
  402. help='Ack messages from source broker.'),
  403. Option('--tasks', '-T',
  404. help='List of task names to filter on.'),
  405. Option('--queues', '-Q',
  406. help='List of queues to migrate.'),
  407. Option('--forever', '-F', action='store_true',
  408. help='Continually migrate tasks until killed.'),
  409. )
  410. progress_fmt = MIGRATE_PROGRESS_FMT
  411. def on_migrate_task(self, state, body, message):
  412. self.out(self.progress_fmt.format(state=state, body=body))
  413. def run(self, source, destination, **kwargs):
  414. from kombu import Connection
  415. from celery.contrib.migrate import migrate_tasks
  416. migrate_tasks(Connection(source),
  417. Connection(destination),
  418. callback=self.on_migrate_task,
  419. **kwargs)
  420. class shell(Command): # pragma: no cover
  421. """Start shell session with convenient access to celery symbols.
  422. The following symbols will be added to the main globals:
  423. - celery: the current application.
  424. - chord, group, chain, chunks,
  425. xmap, xstarmap subtask, Task
  426. - all registered tasks.
  427. """
  428. option_list = Command.option_list + (
  429. Option('--ipython', '-I',
  430. action='store_true', dest='force_ipython',
  431. help='force iPython.'),
  432. Option('--bpython', '-B',
  433. action='store_true', dest='force_bpython',
  434. help='force bpython.'),
  435. Option('--python', '-P',
  436. action='store_true', dest='force_python',
  437. help='force default Python shell.'),
  438. Option('--without-tasks', '-T', action='store_true',
  439. help="don't add tasks to locals."),
  440. Option('--eventlet', action='store_true',
  441. help='use eventlet.'),
  442. Option('--gevent', action='store_true', help='use gevent.'),
  443. )
  444. def run(self, force_ipython=False, force_bpython=False,
  445. force_python=False, without_tasks=False, eventlet=False,
  446. gevent=False, **kwargs):
  447. sys.path.insert(0, os.getcwd())
  448. if eventlet:
  449. import_module('celery.concurrency.eventlet')
  450. if gevent:
  451. import_module('celery.concurrency.gevent')
  452. import celery
  453. import celery.task.base
  454. self.app.loader.import_default_modules()
  455. self.locals = {'app': self.app,
  456. 'celery': self.app,
  457. 'Task': celery.Task,
  458. 'chord': celery.chord,
  459. 'group': celery.group,
  460. 'chain': celery.chain,
  461. 'chunks': celery.chunks,
  462. 'xmap': celery.xmap,
  463. 'xstarmap': celery.xstarmap,
  464. 'subtask': celery.subtask,
  465. 'signature': celery.signature}
  466. if not without_tasks:
  467. self.locals.update(dict(
  468. (task.__name__, task) for task in values(self.app.tasks)
  469. if not task.name.startswith('celery.')),
  470. )
  471. if force_python:
  472. return self.invoke_fallback_shell()
  473. elif force_bpython:
  474. return self.invoke_bpython_shell()
  475. elif force_ipython:
  476. return self.invoke_ipython_shell()
  477. return self.invoke_default_shell()
  478. def invoke_default_shell(self):
  479. try:
  480. import IPython # noqa
  481. except ImportError:
  482. try:
  483. import bpython # noqa
  484. except ImportError:
  485. return self.invoke_fallback_shell()
  486. else:
  487. return self.invoke_bpython_shell()
  488. else:
  489. return self.invoke_ipython_shell()
  490. def invoke_fallback_shell(self):
  491. import code
  492. try:
  493. import readline
  494. except ImportError:
  495. pass
  496. else:
  497. import rlcompleter
  498. readline.set_completer(
  499. rlcompleter.Completer(self.locals).complete)
  500. readline.parse_and_bind('tab:complete')
  501. code.interact(local=self.locals)
  502. def invoke_ipython_shell(self):
  503. for ip in (self._ipython, self._ipython_pre_10,
  504. self._ipython_terminal, self._ipython_010,
  505. self._no_ipython):
  506. try:
  507. return ip()
  508. except ImportError:
  509. pass
  510. def _ipython(self):
  511. from IPython import start_ipython
  512. start_ipython(argv=[], user_ns=self.locals)
  513. def _ipython_pre_10(self): # pragma: no cover
  514. from IPython.frontend.terminal.ipapp import TerminalIPythonApp
  515. app = TerminalIPythonApp.instance()
  516. app.initialize(argv=[])
  517. app.shell.user_ns.update(self.locals)
  518. app.start()
  519. def _ipython_terminal(self): # pragma: no cover
  520. from IPython.terminal import embed
  521. embed.TerminalInteractiveShell(user_ns=self.locals).mainloop()
  522. def _ipython_010(self): # pragma: no cover
  523. from IPython.Shell import IPShell
  524. IPShell(argv=[], user_ns=self.locals).mainloop()
  525. def _no_ipython(self): # pragma: no cover
  526. raise ImportError("no suitable ipython found")
  527. def invoke_bpython_shell(self):
  528. import bpython
  529. bpython.embed(self.locals)
  530. class help(Command):
  531. """Show help screen and exit."""
  532. def usage(self, command):
  533. return '%prog <command> [options] {0.args}'.format(self)
  534. def run(self, *args, **kwargs):
  535. self.parser.print_help()
  536. self.out(HELP.format(
  537. prog_name=self.prog_name,
  538. commands=CeleryCommand.list_commands(colored=self.colored),
  539. ))
  540. return EX_USAGE
  541. class report(Command):
  542. """Shows information useful to include in bugreports."""
  543. def run(self, *args, **kwargs):
  544. self.out(self.app.bugreport())
  545. return EX_OK
  546. class CeleryCommand(Command):
  547. namespace = 'celery'
  548. ext_fmt = '{self.namespace}.commands'
  549. commands = {
  550. 'amqp': amqp,
  551. 'beat': beat,
  552. 'call': call,
  553. 'control': control,
  554. 'events': events,
  555. 'graph': graph,
  556. 'help': help,
  557. 'inspect': inspect,
  558. 'list': list_,
  559. 'migrate': migrate,
  560. 'multi': multi,
  561. 'purge': purge,
  562. 'report': report,
  563. 'result': result,
  564. 'shell': shell,
  565. 'status': status,
  566. 'worker': worker,
  567. }
  568. enable_config_from_cmdline = True
  569. prog_name = 'celery'
  570. @classmethod
  571. def register_command(cls, fun, name=None):
  572. cls.commands[name or fun.__name__] = fun
  573. return fun
  574. def execute(self, command, argv=None):
  575. try:
  576. cls = self.commands[command]
  577. except KeyError:
  578. cls, argv = self.commands['help'], ['help']
  579. cls = self.commands.get(command) or self.commands['help']
  580. try:
  581. return cls(
  582. app=self.app, on_error=self.on_error,
  583. no_color=self.no_color, quiet=self.quiet,
  584. on_usage_error=partial(self.on_usage_error, command=command),
  585. ).run_from_argv(self.prog_name, argv[1:], command=argv[0])
  586. except self.UsageError as exc:
  587. self.on_usage_error(exc)
  588. return exc.status
  589. except self.Error as exc:
  590. self.on_error(exc)
  591. return exc.status
  592. def on_usage_error(self, exc, command=None):
  593. if command:
  594. helps = '{self.prog_name} {command} --help'
  595. else:
  596. helps = '{self.prog_name} --help'
  597. self.error(self.colored.magenta('Error: {0}'.format(exc)))
  598. self.error("""Please try '{0}'""".format(helps.format(
  599. self=self, command=command,
  600. )))
  601. def _relocate_args_from_start(self, argv, index=0):
  602. if argv:
  603. rest = []
  604. while index < len(argv):
  605. value = argv[index]
  606. if value.startswith('--'):
  607. rest.append(value)
  608. elif value.startswith('-'):
  609. # we eat the next argument even though we don't know
  610. # if this option takes an argument or not.
  611. # instead we will assume what is the command name in the
  612. # return statements below.
  613. try:
  614. nxt = argv[index + 1]
  615. if nxt.startswith('-'):
  616. # is another option
  617. rest.append(value)
  618. else:
  619. # is (maybe) a value for this option
  620. rest.extend([value, nxt])
  621. index += 1
  622. except IndexError:
  623. rest.append(value)
  624. break
  625. else:
  626. break
  627. index += 1
  628. if argv[index:]:
  629. # if there are more arguments left then divide and swap
  630. # we assume the first argument in argv[i:] is the command
  631. # name.
  632. return argv[index:] + rest
  633. # if there are no more arguments then the last arg in rest'
  634. # must be the command.
  635. [rest.pop()] + rest
  636. return []
  637. def prepare_prog_name(self, name):
  638. if name == '__main__.py':
  639. return sys.modules['__main__'].__file__
  640. return name
  641. def handle_argv(self, prog_name, argv):
  642. self.prog_name = self.prepare_prog_name(prog_name)
  643. argv = self._relocate_args_from_start(argv)
  644. _, argv = self.prepare_args(None, argv)
  645. try:
  646. command = argv[0]
  647. except IndexError:
  648. command, argv = 'help', ['help']
  649. return self.execute(command, argv)
  650. def execute_from_commandline(self, argv=None):
  651. argv = sys.argv if argv is None else argv
  652. if 'multi' in argv[1:3]: # Issue 1008
  653. self.respects_app_option = False
  654. try:
  655. sys.exit(determine_exit_status(
  656. super(CeleryCommand, self).execute_from_commandline(argv)))
  657. except KeyboardInterrupt:
  658. sys.exit(EX_FAILURE)
  659. @classmethod
  660. def get_command_info(self, command, indent=0, color=None, colored=None):
  661. colored = term.colored() if colored is None else colored
  662. colored = colored.names[color] if color else lambda x: x
  663. obj = self.commands[command]
  664. cmd = 'celery {0}'.format(colored(command))
  665. if obj.leaf:
  666. return '|' + text.indent(cmd, indent)
  667. return text.join([
  668. ' ',
  669. '|' + text.indent('{0} --help'.format(cmd), indent),
  670. obj.list_commands(indent, 'celery {0}'.format(command), colored),
  671. ])
  672. @classmethod
  673. def list_commands(self, indent=0, colored=None):
  674. colored = term.colored() if colored is None else colored
  675. white = colored.white
  676. ret = []
  677. for cls, commands, color in command_classes:
  678. ret.extend([
  679. text.indent('+ {0}: '.format(white(cls)), indent),
  680. '\n'.join(
  681. self.get_command_info(command, indent + 4, color, colored)
  682. for command in commands),
  683. ''
  684. ])
  685. return '\n'.join(ret).strip()
  686. def with_pool_option(self, argv):
  687. if len(argv) > 1 and 'worker' in argv[0:3]:
  688. # this command supports custom pools
  689. # that may have to be loaded as early as possible.
  690. return (['-P'], ['--pool'])
  691. def on_concurrency_setup(self):
  692. self.load_extension_commands()
  693. def load_extension_commands(self):
  694. names = Extensions(self.ext_fmt.format(self=self),
  695. self.register_command).load()
  696. if names:
  697. command_classes.append(('Extensions', names, 'magenta'))
  698. def command(*args, **kwargs):
  699. """Deprecated: Use classmethod :meth:`CeleryCommand.register_command`
  700. instead."""
  701. _register = CeleryCommand.register_command
  702. return _register(args[0]) if args else _register
  703. if __name__ == '__main__': # pragma: no cover
  704. main()