This commit is contained in:
@@ -4,10 +4,9 @@ import numbers
|
||||
from collections import OrderedDict
|
||||
from functools import update_wrapper
|
||||
from pprint import pformat
|
||||
from typing import Any
|
||||
|
||||
import click
|
||||
from click import Context, ParamType
|
||||
from click import ParamType
|
||||
from kombu.utils.objects import cached_property
|
||||
|
||||
from celery._state import get_current_app
|
||||
@@ -171,37 +170,19 @@ class CeleryCommand(click.Command):
|
||||
formatter.write_dl(opts_group)
|
||||
|
||||
|
||||
class DaemonOption(CeleryOption):
|
||||
"""Common daemonization option"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(args,
|
||||
help_group=kwargs.pop("help_group", "Daemonization Options"),
|
||||
callback=kwargs.pop("callback", self.daemon_setting),
|
||||
**kwargs)
|
||||
|
||||
def daemon_setting(self, ctx: Context, opt: CeleryOption, value: Any) -> Any:
|
||||
"""
|
||||
Try to fetch daemonization option from applications settings.
|
||||
Use the daemon command name as prefix (eg. `worker` -> `worker_pidfile`)
|
||||
"""
|
||||
return value or getattr(ctx.obj.app.conf, f"{ctx.command.name}_{self.name}", None)
|
||||
|
||||
|
||||
class CeleryDaemonCommand(CeleryCommand):
|
||||
"""Daemon commands."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize a Celery command with common daemon options."""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.params.extend((
|
||||
DaemonOption("--logfile", "-f", help="Log destination; defaults to stderr"),
|
||||
DaemonOption("--pidfile", help="PID file path; defaults to no PID file"),
|
||||
DaemonOption("--uid", help="Drops privileges to this user ID"),
|
||||
DaemonOption("--gid", help="Drops privileges to this group ID"),
|
||||
DaemonOption("--umask", help="Create files and directories with this umask"),
|
||||
DaemonOption("--executable", help="Override path to the Python executable"),
|
||||
))
|
||||
self.params.append(CeleryOption(('-f', '--logfile'), help_group="Daemonization Options",
|
||||
help="Log destination; defaults to stderr"))
|
||||
self.params.append(CeleryOption(('--pidfile',), help_group="Daemonization Options"))
|
||||
self.params.append(CeleryOption(('--uid',), help_group="Daemonization Options"))
|
||||
self.params.append(CeleryOption(('--gid',), help_group="Daemonization Options"))
|
||||
self.params.append(CeleryOption(('--umask',), help_group="Daemonization Options"))
|
||||
self.params.append(CeleryOption(('--executable',), help_group="Daemonization Options"))
|
||||
|
||||
|
||||
class CommaSeparatedList(ParamType):
|
||||
|
||||
@@ -11,6 +11,7 @@ except ImportError:
|
||||
|
||||
import click
|
||||
import click.exceptions
|
||||
from click.types import ParamType
|
||||
from click_didyoumean import DYMGroup
|
||||
from click_plugins import with_plugins
|
||||
|
||||
@@ -47,6 +48,34 @@ Unable to load celery application.
|
||||
{0}""")
|
||||
|
||||
|
||||
class App(ParamType):
|
||||
"""Application option."""
|
||||
|
||||
name = "application"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
return find_app(value)
|
||||
except ModuleNotFoundError as e:
|
||||
if e.name != value:
|
||||
exc = traceback.format_exc()
|
||||
self.fail(
|
||||
UNABLE_TO_LOAD_APP_ERROR_OCCURRED.format(value, exc)
|
||||
)
|
||||
self.fail(UNABLE_TO_LOAD_APP_MODULE_NOT_FOUND.format(e.name))
|
||||
except AttributeError as e:
|
||||
attribute_name = e.args[0].capitalize()
|
||||
self.fail(UNABLE_TO_LOAD_APP_APP_MISSING.format(attribute_name))
|
||||
except Exception:
|
||||
exc = traceback.format_exc()
|
||||
self.fail(
|
||||
UNABLE_TO_LOAD_APP_ERROR_OCCURRED.format(value, exc)
|
||||
)
|
||||
|
||||
|
||||
APP = App()
|
||||
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
_PLUGINS = entry_points(group='celery.commands')
|
||||
else:
|
||||
@@ -62,11 +91,7 @@ else:
|
||||
'--app',
|
||||
envvar='APP',
|
||||
cls=CeleryOption,
|
||||
# May take either: a str when invoked from command line (Click),
|
||||
# or a Celery object when invoked from inside Celery; hence the
|
||||
# need to prevent Click from "processing" the Celery object and
|
||||
# converting it into its str representation.
|
||||
type=click.UNPROCESSED,
|
||||
type=APP,
|
||||
help_group="Global Options")
|
||||
@click.option('-b',
|
||||
'--broker',
|
||||
@@ -135,26 +160,6 @@ def celery(ctx, app, broker, result_backend, loader, config, workdir,
|
||||
os.environ['CELERY_CONFIG_MODULE'] = config
|
||||
if skip_checks:
|
||||
os.environ['CELERY_SKIP_CHECKS'] = 'true'
|
||||
|
||||
if isinstance(app, str):
|
||||
try:
|
||||
app = find_app(app)
|
||||
except ModuleNotFoundError as e:
|
||||
if e.name != app:
|
||||
exc = traceback.format_exc()
|
||||
ctx.fail(
|
||||
UNABLE_TO_LOAD_APP_ERROR_OCCURRED.format(app, exc)
|
||||
)
|
||||
ctx.fail(UNABLE_TO_LOAD_APP_MODULE_NOT_FOUND.format(e.name))
|
||||
except AttributeError as e:
|
||||
attribute_name = e.args[0].capitalize()
|
||||
ctx.fail(UNABLE_TO_LOAD_APP_APP_MISSING.format(attribute_name))
|
||||
except Exception:
|
||||
exc = traceback.format_exc()
|
||||
ctx.fail(
|
||||
UNABLE_TO_LOAD_APP_ERROR_OCCURRED.format(app, exc)
|
||||
)
|
||||
|
||||
ctx.obj = CLIContext(app=app, no_color=no_color, workdir=workdir,
|
||||
quiet=quiet)
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""The ``celery control``, ``. inspect`` and ``. status`` programs."""
|
||||
from functools import partial
|
||||
from typing import Literal
|
||||
|
||||
import click
|
||||
from kombu.utils.json import dumps
|
||||
@@ -40,69 +39,18 @@ def _consume_arguments(meta, method, args):
|
||||
args[:] = args[i:]
|
||||
|
||||
|
||||
def _compile_arguments(command, args):
|
||||
meta = Panel.meta[command]
|
||||
def _compile_arguments(action, args):
|
||||
meta = Panel.meta[action]
|
||||
arguments = {}
|
||||
if meta.args:
|
||||
arguments.update({
|
||||
k: v for k, v in _consume_arguments(meta, command, args)
|
||||
k: v for k, v in _consume_arguments(meta, action, args)
|
||||
})
|
||||
if meta.variadic:
|
||||
arguments.update({meta.variadic: args})
|
||||
return arguments
|
||||
|
||||
|
||||
_RemoteControlType = Literal['inspect', 'control']
|
||||
|
||||
|
||||
def _verify_command_name(type_: _RemoteControlType, command: str) -> None:
|
||||
choices = _get_commands_of_type(type_)
|
||||
|
||||
if command not in choices:
|
||||
command_listing = ", ".join(choices)
|
||||
raise click.UsageError(
|
||||
message=f'Command {command} not recognized. Available {type_} commands: {command_listing}',
|
||||
)
|
||||
|
||||
|
||||
def _list_option(type_: _RemoteControlType):
|
||||
def callback(ctx: click.Context, param, value) -> None:
|
||||
if not value:
|
||||
return
|
||||
choices = _get_commands_of_type(type_)
|
||||
|
||||
formatter = click.HelpFormatter()
|
||||
|
||||
with formatter.section(f'{type_.capitalize()} Commands'):
|
||||
command_list = []
|
||||
for command_name, info in choices.items():
|
||||
if info.signature:
|
||||
command_preview = f'{command_name} {info.signature}'
|
||||
else:
|
||||
command_preview = command_name
|
||||
command_list.append((command_preview, info.help))
|
||||
formatter.write_dl(command_list)
|
||||
ctx.obj.echo(formatter.getvalue(), nl=False)
|
||||
ctx.exit()
|
||||
|
||||
return click.option(
|
||||
'--list',
|
||||
is_flag=True,
|
||||
help=f'List available {type_} commands and exit.',
|
||||
expose_value=False,
|
||||
is_eager=True,
|
||||
callback=callback,
|
||||
)
|
||||
|
||||
|
||||
def _get_commands_of_type(type_: _RemoteControlType) -> dict:
|
||||
command_name_info_pairs = [
|
||||
(name, info) for name, info in Panel.meta.items()
|
||||
if info.type == type_ and info.visible
|
||||
]
|
||||
return dict(sorted(command_name_info_pairs))
|
||||
|
||||
|
||||
@click.command(cls=CeleryCommand)
|
||||
@click.option('-t',
|
||||
'--timeout',
|
||||
@@ -148,8 +96,10 @@ def status(ctx, timeout, destination, json, **kwargs):
|
||||
|
||||
@click.command(cls=CeleryCommand,
|
||||
context_settings={'allow_extra_args': True})
|
||||
@click.argument('command')
|
||||
@_list_option('inspect')
|
||||
@click.argument("action", type=click.Choice([
|
||||
name for name, info in Panel.meta.items()
|
||||
if info.type == 'inspect' and info.visible
|
||||
]))
|
||||
@click.option('-t',
|
||||
'--timeout',
|
||||
cls=CeleryOption,
|
||||
@@ -171,19 +121,19 @@ def status(ctx, timeout, destination, json, **kwargs):
|
||||
help='Use json as output format.')
|
||||
@click.pass_context
|
||||
@handle_preload_options
|
||||
def inspect(ctx, command, timeout, destination, json, **kwargs):
|
||||
"""Inspect the workers by sending them the COMMAND inspect command.
|
||||
def inspect(ctx, action, timeout, destination, json, **kwargs):
|
||||
"""Inspect the worker at runtime.
|
||||
|
||||
Availability: RabbitMQ (AMQP) and Redis transports.
|
||||
"""
|
||||
_verify_command_name('inspect', command)
|
||||
callback = None if json else partial(_say_remote_command_reply, ctx,
|
||||
show_reply=True)
|
||||
arguments = _compile_arguments(command, ctx.args)
|
||||
arguments = _compile_arguments(action, ctx.args)
|
||||
inspect = ctx.obj.app.control.inspect(timeout=timeout,
|
||||
destination=destination,
|
||||
callback=callback)
|
||||
replies = inspect._request(command, **arguments)
|
||||
replies = inspect._request(action,
|
||||
**arguments)
|
||||
|
||||
if not replies:
|
||||
raise CeleryCommandException(
|
||||
@@ -203,8 +153,10 @@ def inspect(ctx, command, timeout, destination, json, **kwargs):
|
||||
|
||||
@click.command(cls=CeleryCommand,
|
||||
context_settings={'allow_extra_args': True})
|
||||
@click.argument('command')
|
||||
@_list_option('control')
|
||||
@click.argument("action", type=click.Choice([
|
||||
name for name, info in Panel.meta.items()
|
||||
if info.type == 'control' and info.visible
|
||||
]))
|
||||
@click.option('-t',
|
||||
'--timeout',
|
||||
cls=CeleryOption,
|
||||
@@ -226,17 +178,16 @@ def inspect(ctx, command, timeout, destination, json, **kwargs):
|
||||
help='Use json as output format.')
|
||||
@click.pass_context
|
||||
@handle_preload_options
|
||||
def control(ctx, command, timeout, destination, json):
|
||||
"""Send the COMMAND control command to the workers.
|
||||
def control(ctx, action, timeout, destination, json):
|
||||
"""Workers remote control.
|
||||
|
||||
Availability: RabbitMQ (AMQP), Redis, and MongoDB transports.
|
||||
"""
|
||||
_verify_command_name('control', command)
|
||||
callback = None if json else partial(_say_remote_command_reply, ctx,
|
||||
show_reply=True)
|
||||
args = ctx.args
|
||||
arguments = _compile_arguments(command, args)
|
||||
replies = ctx.obj.app.control.broadcast(command, timeout=timeout,
|
||||
arguments = _compile_arguments(action, args)
|
||||
replies = ctx.obj.app.control.broadcast(action, timeout=timeout,
|
||||
destination=destination,
|
||||
callback=callback,
|
||||
reply=True,
|
||||
|
||||
Reference in New Issue
Block a user