Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

284 wiersze
11 KiB

  1. # -*- coding: utf-8 -*-
  2. """
  3. RQ command line tool
  4. """
  5. from __future__ import (absolute_import, division, print_function,
  6. unicode_literals)
  7. from functools import update_wrapper
  8. import os
  9. import sys
  10. import click
  11. from redis.exceptions import ConnectionError
  12. from rq import Connection, __version__ as version
  13. from rq.cli.helpers import (read_config_file, refresh,
  14. setup_loghandlers_from_args,
  15. show_both, show_queues, show_workers, CliConfig)
  16. from rq.contrib.legacy import cleanup_ghosts
  17. from rq.defaults import (DEFAULT_CONNECTION_CLASS, DEFAULT_JOB_CLASS,
  18. DEFAULT_QUEUE_CLASS, DEFAULT_WORKER_CLASS,
  19. DEFAULT_RESULT_TTL, DEFAULT_WORKER_TTL,
  20. DEFAULT_JOB_MONITORING_INTERVAL,
  21. DEFAULT_LOGGING_FORMAT, DEFAULT_LOGGING_DATE_FORMAT)
  22. from rq.exceptions import InvalidJobOperationError
  23. from rq.registry import FailedJobRegistry, clean_registries
  24. from rq.utils import import_attribute
  25. from rq.suspension import (suspend as connection_suspend,
  26. resume as connection_resume, is_suspended)
  27. from rq.worker_registration import clean_worker_registry
  28. # Disable the warning that Click displays (as of Click version 5.0) when users
  29. # use unicode_literals in Python 2.
  30. # See http://click.pocoo.org/dev/python3/#unicode-literals for more details.
  31. click.disable_unicode_literals_warning = True
  32. shared_options = [
  33. click.option('--url', '-u',
  34. envvar='RQ_REDIS_URL',
  35. help='URL describing Redis connection details.'),
  36. click.option('--config', '-c',
  37. envvar='RQ_CONFIG',
  38. help='Module containing RQ settings.'),
  39. click.option('--worker-class', '-w',
  40. envvar='RQ_WORKER_CLASS',
  41. default=DEFAULT_WORKER_CLASS,
  42. help='RQ Worker class to use'),
  43. click.option('--job-class', '-j',
  44. envvar='RQ_JOB_CLASS',
  45. default=DEFAULT_JOB_CLASS,
  46. help='RQ Job class to use'),
  47. click.option('--queue-class',
  48. envvar='RQ_QUEUE_CLASS',
  49. default=DEFAULT_QUEUE_CLASS,
  50. help='RQ Queue class to use'),
  51. click.option('--connection-class',
  52. envvar='RQ_CONNECTION_CLASS',
  53. default=DEFAULT_CONNECTION_CLASS,
  54. help='Redis client class to use'),
  55. click.option('--path', '-P',
  56. default='.',
  57. help='Specify the import path.',
  58. multiple=True)
  59. ]
  60. def pass_cli_config(func):
  61. # add all the shared options to the command
  62. for option in shared_options:
  63. func = option(func)
  64. # pass the cli config object into the command
  65. def wrapper(*args, **kwargs):
  66. ctx = click.get_current_context()
  67. cli_config = CliConfig(**kwargs)
  68. return ctx.invoke(func, cli_config, *args[1:], **kwargs)
  69. return update_wrapper(wrapper, func)
  70. @click.group()
  71. @click.version_option(version)
  72. def main():
  73. """RQ command line tool."""
  74. pass
  75. @main.command()
  76. @click.option('--all', '-a', is_flag=True, help='Empty all queues')
  77. @click.argument('queues', nargs=-1)
  78. @pass_cli_config
  79. def empty(cli_config, all, queues, **options):
  80. """Empty given queues."""
  81. if all:
  82. queues = cli_config.queue_class.all(connection=cli_config.connection,
  83. job_class=cli_config.job_class)
  84. else:
  85. queues = [cli_config.queue_class(queue,
  86. connection=cli_config.connection,
  87. job_class=cli_config.job_class)
  88. for queue in queues]
  89. if not queues:
  90. click.echo('Nothing to do')
  91. sys.exit(0)
  92. for queue in queues:
  93. num_jobs = queue.empty()
  94. click.echo('{0} jobs removed from {1} queue'.format(num_jobs, queue.name))
  95. @main.command()
  96. @click.option('--all', '-a', is_flag=True, help='Requeue all failed jobs')
  97. @click.option('--queue', required=True, type=str)
  98. @click.argument('job_ids', nargs=-1)
  99. @pass_cli_config
  100. def requeue(cli_config, queue, all, job_class, job_ids, **options):
  101. """Requeue failed jobs."""
  102. failed_job_registry = FailedJobRegistry(queue,
  103. connection=cli_config.connection)
  104. if all:
  105. job_ids = failed_job_registry.get_job_ids()
  106. if not job_ids:
  107. click.echo('Nothing to do')
  108. sys.exit(0)
  109. click.echo('Requeueing {0} jobs from failed queue'.format(len(job_ids)))
  110. fail_count = 0
  111. with click.progressbar(job_ids) as job_ids:
  112. for job_id in job_ids:
  113. try:
  114. failed_job_registry.requeue(job_id)
  115. except InvalidJobOperationError:
  116. fail_count += 1
  117. if fail_count > 0:
  118. click.secho('Unable to requeue {0} jobs from failed job registry'.format(fail_count), fg='red')
  119. @main.command()
  120. @click.option('--interval', '-i', type=float, help='Updates stats every N seconds (default: don\'t poll)')
  121. @click.option('--raw', '-r', is_flag=True, help='Print only the raw numbers, no bar charts')
  122. @click.option('--only-queues', '-Q', is_flag=True, help='Show only queue info')
  123. @click.option('--only-workers', '-W', is_flag=True, help='Show only worker info')
  124. @click.option('--by-queue', '-R', is_flag=True, help='Shows workers by queue')
  125. @click.argument('queues', nargs=-1)
  126. @pass_cli_config
  127. def info(cli_config, interval, raw, only_queues, only_workers, by_queue, queues,
  128. **options):
  129. """RQ command-line monitor."""
  130. if only_queues:
  131. func = show_queues
  132. elif only_workers:
  133. func = show_workers
  134. else:
  135. func = show_both
  136. try:
  137. with Connection(cli_config.connection):
  138. if queues:
  139. qs = list(map(cli_config.queue_class, queues))
  140. else:
  141. qs = cli_config.queue_class.all()
  142. for queue in qs:
  143. clean_registries(queue)
  144. clean_worker_registry(queue)
  145. refresh(interval, func, qs, raw, by_queue,
  146. cli_config.queue_class, cli_config.worker_class)
  147. except ConnectionError as e:
  148. click.echo(e)
  149. sys.exit(1)
  150. except KeyboardInterrupt:
  151. click.echo()
  152. sys.exit(0)
  153. @main.command()
  154. @click.option('--burst', '-b', is_flag=True, help='Run in burst mode (quit after all work is done)')
  155. @click.option('--logging_level', type=str, default="INFO", help='Set logging level')
  156. @click.option('--log-format', type=str, default=DEFAULT_LOGGING_FORMAT, help='Set the format of the logs')
  157. @click.option('--date-format', type=str, default=DEFAULT_LOGGING_DATE_FORMAT, help='Set the date format of the logs')
  158. @click.option('--name', '-n', help='Specify a different name')
  159. @click.option('--results-ttl', type=int, default=DEFAULT_RESULT_TTL, help='Default results timeout to be used')
  160. @click.option('--worker-ttl', type=int, default=DEFAULT_WORKER_TTL, help='Default worker timeout to be used')
  161. @click.option('--job-monitoring-interval', type=int, default=DEFAULT_JOB_MONITORING_INTERVAL, help='Default job monitoring interval to be used')
  162. @click.option('--disable-job-desc-logging', is_flag=True, help='Turn off description logging.')
  163. @click.option('--verbose', '-v', is_flag=True, help='Show more output')
  164. @click.option('--quiet', '-q', is_flag=True, help='Show less output')
  165. @click.option('--sentry-dsn', envvar='RQ_SENTRY_DSN', help='Report exceptions to this Sentry DSN')
  166. @click.option('--exception-handler', help='Exception handler(s) to use', multiple=True)
  167. @click.option('--pid', help='Write the process ID number to a file at the specified path')
  168. @click.option('--disable-default-exception-handler', '-d', is_flag=True, help='Disable RQ\'s default exception handler')
  169. @click.option('--max-jobs', type=int, default=None, help='Maximum number of jobs to execute')
  170. @click.argument('queues', nargs=-1)
  171. @pass_cli_config
  172. def worker(cli_config, burst, logging_level, name, results_ttl,
  173. worker_ttl, job_monitoring_interval, disable_job_desc_logging, verbose, quiet, sentry_dsn,
  174. exception_handler, pid, disable_default_exception_handler, max_jobs, queues,
  175. log_format, date_format, **options):
  176. """Starts an RQ worker."""
  177. settings = read_config_file(cli_config.config) if cli_config.config else {}
  178. # Worker specific default arguments
  179. queues = queues or settings.get('QUEUES', ['default'])
  180. sentry_dsn = sentry_dsn or settings.get('SENTRY_DSN')
  181. name = name or settings.get('NAME')
  182. if pid:
  183. with open(os.path.expanduser(pid), "w") as fp:
  184. fp.write(str(os.getpid()))
  185. setup_loghandlers_from_args(verbose, quiet, date_format, log_format)
  186. try:
  187. cleanup_ghosts(cli_config.connection)
  188. exception_handlers = []
  189. for h in exception_handler:
  190. exception_handlers.append(import_attribute(h))
  191. if is_suspended(cli_config.connection):
  192. click.secho('RQ is currently suspended, to resume job execution run "rq resume"', fg='red')
  193. sys.exit(1)
  194. queues = [cli_config.queue_class(queue,
  195. connection=cli_config.connection,
  196. job_class=cli_config.job_class)
  197. for queue in queues]
  198. worker = cli_config.worker_class(
  199. queues, name=name, connection=cli_config.connection,
  200. default_worker_ttl=worker_ttl, default_result_ttl=results_ttl,
  201. job_monitoring_interval=job_monitoring_interval,
  202. job_class=cli_config.job_class, queue_class=cli_config.queue_class,
  203. exception_handlers=exception_handlers or None,
  204. disable_default_exception_handler=disable_default_exception_handler,
  205. log_job_description=not disable_job_desc_logging
  206. )
  207. # Should we configure Sentry?
  208. if sentry_dsn:
  209. from rq.contrib.sentry import register_sentry
  210. register_sentry(sentry_dsn)
  211. worker.work(burst=burst, logging_level=logging_level, date_format=date_format, log_format=log_format, max_jobs=max_jobs)
  212. except ConnectionError as e:
  213. print(e)
  214. sys.exit(1)
  215. @main.command()
  216. @click.option('--duration', help='Seconds you want the workers to be suspended. Default is forever.', type=int)
  217. @pass_cli_config
  218. def suspend(cli_config, duration, **options):
  219. """Suspends all workers, to resume run `rq resume`"""
  220. if duration is not None and duration < 1:
  221. click.echo("Duration must be an integer greater than 1")
  222. sys.exit(1)
  223. connection_suspend(cli_config.connection, duration)
  224. if duration:
  225. msg = """Suspending workers for {0} seconds. No new jobs will be started during that time, but then will
  226. automatically resume""".format(duration)
  227. click.echo(msg)
  228. else:
  229. click.echo("Suspending workers. No new jobs will be started. But current jobs will be completed")
  230. @main.command()
  231. @pass_cli_config
  232. def resume(cli_config, **options):
  233. """Resumes processing of queues, that where suspended with `rq suspend`"""
  234. connection_resume(cli_config.connection)
  235. click.echo("Resuming workers.")