rrq 0.5.0__py3-none-any.whl → 0.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rrq/cli.py +39 -64
- rrq/cli_commands/__init__.py +1 -0
- rrq/cli_commands/base.py +102 -0
- rrq/cli_commands/commands/__init__.py +1 -0
- rrq/cli_commands/commands/debug.py +551 -0
- rrq/cli_commands/commands/dlq.py +853 -0
- rrq/cli_commands/commands/jobs.py +516 -0
- rrq/cli_commands/commands/monitor.py +776 -0
- rrq/cli_commands/commands/queues.py +539 -0
- rrq/cli_commands/utils.py +161 -0
- rrq/client.py +39 -35
- rrq/constants.py +10 -0
- rrq/cron.py +67 -8
- rrq/hooks.py +217 -0
- rrq/job.py +5 -5
- rrq/registry.py +0 -3
- rrq/settings.py +13 -1
- rrq/store.py +211 -53
- rrq/worker.py +6 -6
- {rrq-0.5.0.dist-info → rrq-0.7.1.dist-info}/METADATA +209 -25
- rrq-0.7.1.dist-info/RECORD +26 -0
- {rrq-0.5.0.dist-info → rrq-0.7.1.dist-info}/WHEEL +1 -1
- rrq-0.5.0.dist-info/RECORD +0 -16
- {rrq-0.5.0.dist-info → rrq-0.7.1.dist-info}/entry_points.txt +0 -0
- {rrq-0.5.0.dist-info → rrq-0.7.1.dist-info}/licenses/LICENSE +0 -0
rrq/cli.py
CHANGED
|
@@ -363,6 +363,38 @@ def rrq():
|
|
|
363
363
|
pass
|
|
364
364
|
|
|
365
365
|
|
|
366
|
+
# Register modular commands
|
|
367
|
+
try:
|
|
368
|
+
# Import new command classes
|
|
369
|
+
from .cli_commands.commands.queues import QueueCommands
|
|
370
|
+
from .cli_commands.commands.jobs import JobCommands
|
|
371
|
+
from .cli_commands.commands.monitor import MonitorCommands
|
|
372
|
+
from .cli_commands.commands.debug import DebugCommands
|
|
373
|
+
from .cli_commands.commands.dlq import DLQCommands
|
|
374
|
+
|
|
375
|
+
# Register new commands with existing CLI
|
|
376
|
+
command_classes = [
|
|
377
|
+
QueueCommands(),
|
|
378
|
+
JobCommands(),
|
|
379
|
+
MonitorCommands(),
|
|
380
|
+
DebugCommands(),
|
|
381
|
+
DLQCommands(),
|
|
382
|
+
]
|
|
383
|
+
|
|
384
|
+
for command_instance in command_classes:
|
|
385
|
+
try:
|
|
386
|
+
command_instance.register(rrq)
|
|
387
|
+
except Exception as e:
|
|
388
|
+
click.echo(
|
|
389
|
+
f"Warning: Failed to register command {command_instance.__class__.__name__}: {e}",
|
|
390
|
+
err=True,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
except ImportError as e:
|
|
394
|
+
# Fall back to original CLI if new modules aren't available
|
|
395
|
+
click.echo(f"Warning: Enhanced CLI features not available: {e}", err=True)
|
|
396
|
+
|
|
397
|
+
|
|
366
398
|
@rrq.group("worker")
|
|
367
399
|
def worker_cli():
|
|
368
400
|
"""Manage RRQ workers (run, watch)."""
|
|
@@ -562,8 +594,13 @@ def _run_multiple_workers(
|
|
|
562
594
|
# Pass the RRQ_SETTINGS env var as explicit parameter to subprocess
|
|
563
595
|
cmd.extend(["--settings", os.getenv("RRQ_SETTINGS")])
|
|
564
596
|
else:
|
|
565
|
-
#
|
|
566
|
-
|
|
597
|
+
# Error: No settings provided for multi-worker mode
|
|
598
|
+
click.echo(
|
|
599
|
+
"Error: Multi-worker mode requires explicit settings. "
|
|
600
|
+
"Please provide either --settings option or set RRQ_SETTINGS environment variable.",
|
|
601
|
+
err=True,
|
|
602
|
+
)
|
|
603
|
+
sys.exit(1)
|
|
567
604
|
if queues:
|
|
568
605
|
for q_name in queues:
|
|
569
606
|
cmd.extend(["--queue", q_name])
|
|
@@ -742,65 +779,3 @@ def check_command(settings_object_path: str):
|
|
|
742
779
|
else:
|
|
743
780
|
click.echo(click.style("Health check FAILED.", fg="red"))
|
|
744
781
|
sys.exit(1)
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
@rrq.group("dlq")
|
|
748
|
-
def dlq_cli():
|
|
749
|
-
"""Manage the Dead Letter Queue (DLQ)."""
|
|
750
|
-
pass
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
@dlq_cli.command("requeue")
|
|
754
|
-
@click.option(
|
|
755
|
-
"--settings",
|
|
756
|
-
"settings_object_path",
|
|
757
|
-
type=str,
|
|
758
|
-
required=False,
|
|
759
|
-
default=None,
|
|
760
|
-
help=(
|
|
761
|
-
"Python settings path for application worker settings "
|
|
762
|
-
"(e.g., myapp.worker_config.rrq_settings). "
|
|
763
|
-
"Must include `job_registry: JobRegistry` if requeueing requires handler resolution."
|
|
764
|
-
),
|
|
765
|
-
)
|
|
766
|
-
@click.option(
|
|
767
|
-
"--dlq-name",
|
|
768
|
-
"dlq_name",
|
|
769
|
-
type=str,
|
|
770
|
-
required=False,
|
|
771
|
-
default=None,
|
|
772
|
-
help="Name of the DLQ (without prefix). Defaults to settings.default_dlq_name.",
|
|
773
|
-
)
|
|
774
|
-
@click.option(
|
|
775
|
-
"--queue",
|
|
776
|
-
"target_queue",
|
|
777
|
-
type=str,
|
|
778
|
-
required=False,
|
|
779
|
-
default=None,
|
|
780
|
-
help="Name of the target queue (without prefix). Defaults to settings.default_queue_name.",
|
|
781
|
-
)
|
|
782
|
-
@click.option(
|
|
783
|
-
"--limit",
|
|
784
|
-
type=int,
|
|
785
|
-
required=False,
|
|
786
|
-
default=None,
|
|
787
|
-
help="Maximum number of DLQ jobs to requeue; all if not set.",
|
|
788
|
-
)
|
|
789
|
-
def dlq_requeue_command(
|
|
790
|
-
settings_object_path: str,
|
|
791
|
-
dlq_name: str,
|
|
792
|
-
target_queue: str,
|
|
793
|
-
limit: int,
|
|
794
|
-
):
|
|
795
|
-
"""Requeue jobs from the dead letter queue back into a live queue."""
|
|
796
|
-
rrq_settings = _load_app_settings(settings_object_path)
|
|
797
|
-
dlq_to_use = dlq_name or rrq_settings.default_dlq_name
|
|
798
|
-
queue_to_use = target_queue or rrq_settings.default_queue_name
|
|
799
|
-
job_store = JobStore(settings=rrq_settings)
|
|
800
|
-
click.echo(
|
|
801
|
-
f"Requeuing jobs from DLQ '{dlq_to_use}' to queue '{queue_to_use}' (limit: {limit or 'all'})..."
|
|
802
|
-
)
|
|
803
|
-
count = asyncio.run(job_store.requeue_dlq(dlq_to_use, queue_to_use, limit))
|
|
804
|
-
click.echo(
|
|
805
|
-
f"Requeued {count} job(s) from DLQ '{dlq_to_use}' to queue '{queue_to_use}'."
|
|
806
|
-
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""RRQ CLI module"""
|
rrq/cli_commands/base.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Base classes and utilities for RRQ CLI commands"""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import importlib
|
|
5
|
+
import os
|
|
6
|
+
import pkgutil
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from typing import Callable
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from ..settings import RRQSettings
|
|
13
|
+
from ..store import JobStore
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BaseCommand(ABC):
|
|
17
|
+
"""Base class for all RRQ CLI commands"""
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def register(self, cli_group: click.Group) -> None:
|
|
21
|
+
"""Register the command with the CLI group"""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AsyncCommand(BaseCommand):
|
|
26
|
+
"""Base class for async CLI commands"""
|
|
27
|
+
|
|
28
|
+
def make_async(self, func: Callable) -> Callable:
|
|
29
|
+
"""Wrapper to run async functions in click commands"""
|
|
30
|
+
|
|
31
|
+
def wrapper(*args, **kwargs):
|
|
32
|
+
return asyncio.run(func(*args, **kwargs))
|
|
33
|
+
|
|
34
|
+
return wrapper
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def load_app_settings(settings_object_path: str | None = None) -> RRQSettings:
|
|
38
|
+
"""Load the settings object from the given path.
|
|
39
|
+
|
|
40
|
+
If not provided, the RRQ_SETTINGS environment variable will be used.
|
|
41
|
+
If the environment variable is not set, will create a default settings object.
|
|
42
|
+
"""
|
|
43
|
+
# Import the original function from cli.py
|
|
44
|
+
from ..cli import _load_app_settings
|
|
45
|
+
|
|
46
|
+
return _load_app_settings(settings_object_path)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def resolve_settings_source(
|
|
50
|
+
settings_object_path: str | None = None,
|
|
51
|
+
) -> tuple[str | None, str]:
|
|
52
|
+
"""Resolve the settings path and its source."""
|
|
53
|
+
# Import the original function from cli.py
|
|
54
|
+
from ..cli import _resolve_settings_source
|
|
55
|
+
|
|
56
|
+
return _resolve_settings_source(settings_object_path)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def auto_discover_commands(package_path: str) -> list[type[BaseCommand]]:
|
|
60
|
+
"""Auto-discover command classes in the given package"""
|
|
61
|
+
commands = []
|
|
62
|
+
|
|
63
|
+
# Get the package module
|
|
64
|
+
try:
|
|
65
|
+
package = importlib.import_module(package_path)
|
|
66
|
+
package_dir = os.path.dirname(package.__file__)
|
|
67
|
+
except ImportError:
|
|
68
|
+
# Return empty list for non-existent packages
|
|
69
|
+
return commands
|
|
70
|
+
|
|
71
|
+
# Iterate through all modules in the package
|
|
72
|
+
for _, module_name, is_pkg in pkgutil.iter_modules([package_dir]):
|
|
73
|
+
if is_pkg:
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
# Import the module
|
|
77
|
+
module_path = f"{package_path}.{module_name}"
|
|
78
|
+
try:
|
|
79
|
+
module = importlib.import_module(module_path)
|
|
80
|
+
|
|
81
|
+
# Look for BaseCommand subclasses
|
|
82
|
+
for attr_name in dir(module):
|
|
83
|
+
attr = getattr(module, attr_name)
|
|
84
|
+
if (
|
|
85
|
+
isinstance(attr, type)
|
|
86
|
+
and issubclass(attr, BaseCommand)
|
|
87
|
+
and attr not in (BaseCommand, AsyncCommand)
|
|
88
|
+
):
|
|
89
|
+
commands.append(attr)
|
|
90
|
+
except ImportError:
|
|
91
|
+
# Skip modules that can't be imported
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
return commands
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
async def get_job_store(settings: RRQSettings) -> JobStore:
|
|
98
|
+
"""Create and return a JobStore instance"""
|
|
99
|
+
job_store = JobStore(settings=settings)
|
|
100
|
+
# Test connection
|
|
101
|
+
await job_store.redis.ping()
|
|
102
|
+
return job_store
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""RRQ CLI commands module"""
|