cronradar-celery 0.0.2__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.
Potentially problematic release.
This version of cronradar-celery might be problematic. Click here for more details.
- cronradar_celery/__init__.py +66 -0
- cronradar_celery/discovery.py +77 -0
- cronradar_celery/monitors.py +105 -0
- cronradar_celery/utils.py +196 -0
- cronradar_celery-0.0.2.dist-info/METADATA +127 -0
- cronradar_celery-0.0.2.dist-info/RECORD +9 -0
- cronradar_celery-0.0.2.dist-info/WHEEL +5 -0
- cronradar_celery-0.0.2.dist-info/licenses/LICENSE +41 -0
- cronradar_celery-0.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CronRadar Celery Integration
|
|
3
|
+
|
|
4
|
+
Automatic monitoring for Celery periodic tasks.
|
|
5
|
+
Monitors all tasks by default - use @skip_monitor to opt-out.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from celery import Celery
|
|
11
|
+
from celery.signals import task_success, task_failure, task_retry
|
|
12
|
+
|
|
13
|
+
from .monitors import setup_task_monitoring, skip_monitor_decorator
|
|
14
|
+
from .discovery import discover_and_sync_tasks
|
|
15
|
+
|
|
16
|
+
__version__ = "0.0.2"
|
|
17
|
+
|
|
18
|
+
# Track initialization to prevent duplicate setup
|
|
19
|
+
_initialized = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def setup_cronradar(app: Celery, api_key: Optional[str] = None) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Configure CronRadar monitoring for Celery tasks.
|
|
25
|
+
Monitors all periodic tasks by default - use @skip_monitor decorator to opt-out.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
app: Celery application instance
|
|
29
|
+
api_key: Optional API key (defaults to CRONRADAR_API_KEY env var)
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
from celery import Celery
|
|
33
|
+
from cronradar_celery import setup_cronradar
|
|
34
|
+
|
|
35
|
+
app = Celery('myapp')
|
|
36
|
+
setup_cronradar(app)
|
|
37
|
+
"""
|
|
38
|
+
global _initialized
|
|
39
|
+
|
|
40
|
+
if _initialized:
|
|
41
|
+
print("[CronRadar.Celery] Already initialized. Skipping duplicate setup.", file=sys.stderr)
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
_initialized = True
|
|
45
|
+
|
|
46
|
+
# Store API key in environment if provided
|
|
47
|
+
if api_key:
|
|
48
|
+
import os
|
|
49
|
+
os.environ['CRONRADAR_API_KEY'] = api_key
|
|
50
|
+
|
|
51
|
+
print("[CronRadar.Celery] Initializing MonitorAll mode - use @skip_monitor to opt-out", file=sys.stderr)
|
|
52
|
+
|
|
53
|
+
# Setup task monitoring via signals
|
|
54
|
+
setup_task_monitoring(app)
|
|
55
|
+
|
|
56
|
+
# Auto-discover and sync beat schedule tasks
|
|
57
|
+
discover_and_sync_tasks(app)
|
|
58
|
+
|
|
59
|
+
print("[CronRadar.Celery] ✓ Monitoring enabled successfully!", file=sys.stderr)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# Decorator for opting out
|
|
63
|
+
skip_monitor = skip_monitor_decorator
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
__all__ = ['setup_cronradar', 'skip_monitor', '__version__']
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auto-discovery of Celery Beat periodic tasks.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
from celery import Celery
|
|
8
|
+
|
|
9
|
+
import cronradar
|
|
10
|
+
from .utils import normalize_task_name, generate_readable_name, schedule_to_cron
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def discover_and_sync_tasks(app: Celery) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Discover Celery Beat periodic tasks and sync them to CronRadar.
|
|
16
|
+
Syncs all periodic tasks found in beat schedule.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
app: Celery application instance
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
# Get beat schedule configuration
|
|
23
|
+
beat_schedule = app.conf.beat_schedule
|
|
24
|
+
|
|
25
|
+
if not beat_schedule:
|
|
26
|
+
print("[CronRadar.Celery] No beat schedule configured. Skipping auto-discovery.", file=sys.stderr)
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
print(f"[CronRadar.Celery] Discovering {len(beat_schedule)} periodic tasks...", file=sys.stderr)
|
|
30
|
+
|
|
31
|
+
synced_count = 0
|
|
32
|
+
|
|
33
|
+
for schedule_name, schedule_config in beat_schedule.items():
|
|
34
|
+
try:
|
|
35
|
+
# Extract task name and schedule
|
|
36
|
+
task_name = schedule_config.get('task')
|
|
37
|
+
schedule = schedule_config.get('schedule')
|
|
38
|
+
|
|
39
|
+
if not task_name or not schedule:
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
# Normalize task name to monitor key
|
|
43
|
+
monitor_key = normalize_task_name(task_name)
|
|
44
|
+
|
|
45
|
+
if not monitor_key:
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
# Convert schedule to cron expression
|
|
49
|
+
cron_expression = schedule_to_cron(schedule)
|
|
50
|
+
|
|
51
|
+
if not cron_expression:
|
|
52
|
+
print(f"[CronRadar.Celery] Skipping '{task_name}': Unable to convert schedule to cron", file=sys.stderr)
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
# Generate readable name
|
|
56
|
+
readable_name = generate_readable_name(task_name)
|
|
57
|
+
|
|
58
|
+
# Sync to CronRadar
|
|
59
|
+
cronradar.sync_monitor(
|
|
60
|
+
monitor_key,
|
|
61
|
+
cron_expression,
|
|
62
|
+
source='celery',
|
|
63
|
+
name=readable_name
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
synced_count += 1
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
print(f"[CronRadar.Celery] Failed to sync '{schedule_name}': {e}", file=sys.stderr)
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
if synced_count > 0:
|
|
73
|
+
print(f"[CronRadar.Celery] ✓ Synced {synced_count} periodic tasks", file=sys.stderr)
|
|
74
|
+
|
|
75
|
+
except Exception as e:
|
|
76
|
+
print(f"[CronRadar.Celery] Discovery error: {e}", file=sys.stderr)
|
|
77
|
+
# Don't fail app startup on discovery errors
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task monitoring via Celery signals.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from functools import wraps
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
from celery import Celery
|
|
9
|
+
from celery.signals import task_success
|
|
10
|
+
|
|
11
|
+
import cronradar
|
|
12
|
+
from .utils import normalize_task_name
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def skip_monitor_decorator(func: Callable) -> Callable:
|
|
16
|
+
"""
|
|
17
|
+
Mark a Celery task to skip monitoring in MonitorAll mode.
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
from cronradar_celery import skip_monitor
|
|
21
|
+
|
|
22
|
+
@app.task
|
|
23
|
+
@skip_monitor
|
|
24
|
+
def internal_cleanup():
|
|
25
|
+
pass
|
|
26
|
+
"""
|
|
27
|
+
func._cronradar_skip = True
|
|
28
|
+
return func
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def setup_task_monitoring(app: Celery) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Setup Celery signal handlers for task monitoring.
|
|
34
|
+
Monitors all tasks unless they have @skip_monitor decorator.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
app: Celery application instance
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
@task_success.connect
|
|
41
|
+
def handle_task_success(sender=None, **kwargs):
|
|
42
|
+
"""
|
|
43
|
+
Handle successful task execution.
|
|
44
|
+
Monitors all tasks unless marked with @skip_monitor.
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
# Get task name from sender
|
|
48
|
+
if not sender or not hasattr(sender, 'name'):
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
task_name = sender.name
|
|
52
|
+
|
|
53
|
+
# Check if this task should be monitored
|
|
54
|
+
if not should_monitor_task(sender):
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
# Normalize task name to monitor key
|
|
58
|
+
monitor_key = normalize_task_name(task_name)
|
|
59
|
+
|
|
60
|
+
if not monitor_key:
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
# Use base SDK to monitor
|
|
64
|
+
# Don't pass schedule here - it's already synced via discovery
|
|
65
|
+
cronradar.monitor(monitor_key)
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
# Never break user's task execution
|
|
69
|
+
print(f"[CronRadar.Celery] Monitor error: {e}", file=sys.stderr)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def should_monitor_task(task) -> bool:
|
|
73
|
+
"""
|
|
74
|
+
Determine if a task should be monitored.
|
|
75
|
+
Monitors all tasks unless marked with @skip_monitor decorator.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
task: Celery task instance
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
True if task should be monitored
|
|
82
|
+
"""
|
|
83
|
+
try:
|
|
84
|
+
# Get the actual task function
|
|
85
|
+
task_func = None
|
|
86
|
+
|
|
87
|
+
# Try to get the original function
|
|
88
|
+
if hasattr(task, 'run'):
|
|
89
|
+
task_func = task.run
|
|
90
|
+
elif hasattr(task, '__wrapped__'):
|
|
91
|
+
task_func = task.__wrapped__
|
|
92
|
+
elif callable(task):
|
|
93
|
+
task_func = task
|
|
94
|
+
|
|
95
|
+
if not task_func:
|
|
96
|
+
# If we can't determine, monitor by default
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
# Monitor all tasks unless @skip_monitor decorator present
|
|
100
|
+
return not getattr(task_func, '_cronradar_skip', False)
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
print(f"[CronRadar.Celery] Error checking task decorators: {e}", file=sys.stderr)
|
|
104
|
+
# If we can't determine, monitor by default
|
|
105
|
+
return True
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for Celery integration.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import re
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from datetime import timedelta
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def normalize_task_name(task_name: str) -> str:
|
|
12
|
+
"""
|
|
13
|
+
Normalize Celery task name to CronRadar monitor key format.
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
'myapp.tasks.send_email' -> 'myapp-tasks-send-email'
|
|
17
|
+
'tasks.process_queue' -> 'tasks-process-queue'
|
|
18
|
+
"""
|
|
19
|
+
if not task_name:
|
|
20
|
+
return task_name
|
|
21
|
+
|
|
22
|
+
# Replace dots and underscores with hyphens
|
|
23
|
+
key = task_name.replace('.', '-').replace('_', '-')
|
|
24
|
+
|
|
25
|
+
# Lowercase
|
|
26
|
+
key = key.lower()
|
|
27
|
+
|
|
28
|
+
# Remove any non-alphanumeric except hyphens
|
|
29
|
+
key = re.sub(r'[^a-z0-9-]+', '-', key)
|
|
30
|
+
|
|
31
|
+
# Remove leading/trailing hyphens
|
|
32
|
+
key = key.strip('-')
|
|
33
|
+
|
|
34
|
+
# Limit length
|
|
35
|
+
if len(key) > 64:
|
|
36
|
+
key = key[:64]
|
|
37
|
+
|
|
38
|
+
return key
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def generate_readable_name(task_name: str) -> str:
|
|
42
|
+
"""
|
|
43
|
+
Generate human-readable name from Celery task name.
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
'myapp.tasks.send_email' -> 'Send Email'
|
|
47
|
+
'tasks.process_queue' -> 'Process Queue'
|
|
48
|
+
"""
|
|
49
|
+
if not task_name:
|
|
50
|
+
return task_name
|
|
51
|
+
|
|
52
|
+
# Take last part after final dot (the actual task name)
|
|
53
|
+
parts = task_name.split('.')
|
|
54
|
+
name = parts[-1] if parts else task_name
|
|
55
|
+
|
|
56
|
+
# Convert snake_case to Title Case
|
|
57
|
+
if '_' in name:
|
|
58
|
+
return ' '.join(word.capitalize() for word in name.split('_'))
|
|
59
|
+
|
|
60
|
+
# Convert camelCase to Title Case
|
|
61
|
+
if name and name[0].islower() and any(c.isupper() for c in name):
|
|
62
|
+
result = re.sub(r'([A-Z])', r' \1', name)
|
|
63
|
+
return result.strip().title()
|
|
64
|
+
|
|
65
|
+
# Default: capitalize first letter
|
|
66
|
+
return name.capitalize()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def crontab_to_cron(crontab_obj) -> Optional[str]:
|
|
70
|
+
"""
|
|
71
|
+
Convert Celery crontab schedule to cron expression string.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
crontab_obj: celery.schedules.crontab instance
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Cron expression string or None if conversion fails
|
|
78
|
+
|
|
79
|
+
Examples:
|
|
80
|
+
crontab(hour=0, minute=0) -> '0 0 * * *'
|
|
81
|
+
crontab(hour='*/6', minute=0) -> '0 */6 * * *'
|
|
82
|
+
"""
|
|
83
|
+
try:
|
|
84
|
+
from celery.schedules import crontab
|
|
85
|
+
|
|
86
|
+
if not isinstance(crontab_obj, crontab):
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
# Extract fields (minute, hour, day_of_month, month, day_of_week)
|
|
90
|
+
minute = _format_cron_field(crontab_obj.minute)
|
|
91
|
+
hour = _format_cron_field(crontab_obj.hour)
|
|
92
|
+
day_of_month = _format_cron_field(crontab_obj.day_of_month)
|
|
93
|
+
month = _format_cron_field(crontab_obj.month_of_year)
|
|
94
|
+
day_of_week = _format_cron_field(crontab_obj.day_of_week)
|
|
95
|
+
|
|
96
|
+
return f"{minute} {hour} {day_of_month} {month} {day_of_week}"
|
|
97
|
+
except Exception as e:
|
|
98
|
+
print(f"[CronRadar.Celery] Failed to convert crontab: {e}", file=sys.stderr)
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def timedelta_to_cron(interval: timedelta) -> str:
|
|
103
|
+
"""
|
|
104
|
+
Convert timedelta interval to approximate cron expression.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
interval: timedelta representing the repeat interval
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Approximate cron expression
|
|
111
|
+
|
|
112
|
+
Examples:
|
|
113
|
+
timedelta(hours=1) -> '0 * * * *'
|
|
114
|
+
timedelta(days=1) -> '0 0 * * *'
|
|
115
|
+
"""
|
|
116
|
+
total_seconds = interval.total_seconds()
|
|
117
|
+
|
|
118
|
+
# Convert to common intervals
|
|
119
|
+
if total_seconds < 60:
|
|
120
|
+
return "* * * * *" # Every minute (can't represent sub-minute)
|
|
121
|
+
elif total_seconds == 60:
|
|
122
|
+
return "* * * * *" # Every minute
|
|
123
|
+
elif total_seconds == 300:
|
|
124
|
+
return "*/5 * * * *" # Every 5 minutes
|
|
125
|
+
elif total_seconds == 900:
|
|
126
|
+
return "*/15 * * * *" # Every 15 minutes
|
|
127
|
+
elif total_seconds == 1800:
|
|
128
|
+
return "*/30 * * * *" # Every 30 minutes
|
|
129
|
+
elif total_seconds == 3600:
|
|
130
|
+
return "0 * * * *" # Every hour
|
|
131
|
+
elif total_seconds % 3600 == 0 and total_seconds < 86400:
|
|
132
|
+
hours = int(total_seconds / 3600)
|
|
133
|
+
return f"0 */{hours} * * *" # Every N hours
|
|
134
|
+
elif total_seconds == 86400:
|
|
135
|
+
return "0 0 * * *" # Daily
|
|
136
|
+
elif total_seconds == 604800:
|
|
137
|
+
return "0 0 * * 0" # Weekly (Sunday)
|
|
138
|
+
else:
|
|
139
|
+
# Default to hourly for other intervals
|
|
140
|
+
return "0 * * * *"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _format_cron_field(field) -> str:
|
|
144
|
+
"""
|
|
145
|
+
Format a Celery crontab field for cron expression.
|
|
146
|
+
|
|
147
|
+
Celery uses sets for multiple values and '*' for all.
|
|
148
|
+
"""
|
|
149
|
+
if field is None or field == '*':
|
|
150
|
+
return '*'
|
|
151
|
+
|
|
152
|
+
# If it's a set, join with commas
|
|
153
|
+
if isinstance(field, set):
|
|
154
|
+
return ','.join(str(x) for x in sorted(field))
|
|
155
|
+
|
|
156
|
+
# If it's a string, return as-is (handles '*/N' patterns)
|
|
157
|
+
if isinstance(field, str):
|
|
158
|
+
return field
|
|
159
|
+
|
|
160
|
+
# Single value
|
|
161
|
+
return str(field)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def schedule_to_cron(schedule) -> Optional[str]:
|
|
165
|
+
"""
|
|
166
|
+
Convert any Celery schedule object to cron expression.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
schedule: Celery schedule object (crontab, timedelta, solar, etc.)
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Cron expression string or None if conversion fails
|
|
173
|
+
"""
|
|
174
|
+
try:
|
|
175
|
+
from celery.schedules import crontab, schedule as base_schedule
|
|
176
|
+
|
|
177
|
+
# Handle crontab directly
|
|
178
|
+
if isinstance(schedule, crontab):
|
|
179
|
+
return crontab_to_cron(schedule)
|
|
180
|
+
|
|
181
|
+
# Handle schedule objects with run_every (timedelta-based)
|
|
182
|
+
if isinstance(schedule, base_schedule) and hasattr(schedule, 'run_every'):
|
|
183
|
+
if isinstance(schedule.run_every, timedelta):
|
|
184
|
+
return timedelta_to_cron(schedule.run_every)
|
|
185
|
+
|
|
186
|
+
# Handle raw timedelta
|
|
187
|
+
if isinstance(schedule, timedelta):
|
|
188
|
+
return timedelta_to_cron(schedule)
|
|
189
|
+
|
|
190
|
+
# Unknown schedule type
|
|
191
|
+
print(f"[CronRadar.Celery] Unknown schedule type: {type(schedule)}", file=sys.stderr)
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
except Exception as e:
|
|
195
|
+
print(f"[CronRadar.Celery] Failed to convert schedule: {e}", file=sys.stderr)
|
|
196
|
+
return None
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cronradar-celery
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Automatic monitoring for Celery periodic tasks. Monitor selective tasks or all tasks with auto-discovery.
|
|
5
|
+
Home-page: https://github.com/cronradar/cronradar-celery
|
|
6
|
+
Author: Administrator
|
|
7
|
+
Author-email: contact@cronradar.com
|
|
8
|
+
License: Proprietary
|
|
9
|
+
Project-URL: Documentation, https://cronradar.com/docs
|
|
10
|
+
Project-URL: Source, https://github.com/cronradar/cronradar-celery
|
|
11
|
+
Project-URL: Tracker, https://github.com/cronradar/cronradar-celery/issues
|
|
12
|
+
Keywords: celery monitoring cron task beat periodic auto-discovery cronradar
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: Other/Proprietary License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: System :: Monitoring
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: cronradar>=0.0.1
|
|
29
|
+
Requires-Dist: celery>=5.0.0
|
|
30
|
+
Dynamic: author
|
|
31
|
+
Dynamic: author-email
|
|
32
|
+
Dynamic: classifier
|
|
33
|
+
Dynamic: description
|
|
34
|
+
Dynamic: description-content-type
|
|
35
|
+
Dynamic: home-page
|
|
36
|
+
Dynamic: keywords
|
|
37
|
+
Dynamic: license
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
Dynamic: project-url
|
|
40
|
+
Dynamic: requires-dist
|
|
41
|
+
Dynamic: requires-python
|
|
42
|
+
Dynamic: summary
|
|
43
|
+
|
|
44
|
+
# CronRadar Celery Integration
|
|
45
|
+
|
|
46
|
+
Monitor Celery periodic tasks. Auto-discover Beat schedules and track execution. Get alerts when tasks fail or don't run on schedule.
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install cronradar-celery
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
Set environment variable:
|
|
57
|
+
```bash
|
|
58
|
+
export CRONRADAR_API_KEY=ck_app_xxxxx_yyyyy
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Get your API key from [cronradar.com/dashboard](https://cronradar.com/dashboard)
|
|
62
|
+
|
|
63
|
+
Or pass it directly:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
setup_cronradar(app, api_key='ck_app_xxxxx_yyyyy')
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Quick Start
|
|
70
|
+
|
|
71
|
+
Monitor all periodic tasks by default - use `@skip_monitor` to opt-out:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from celery import Celery
|
|
75
|
+
from cronradar_celery import setup_cronradar, skip_monitor
|
|
76
|
+
|
|
77
|
+
app = Celery('myapp')
|
|
78
|
+
setup_cronradar(app)
|
|
79
|
+
|
|
80
|
+
@app.task
|
|
81
|
+
def send_daily_report():
|
|
82
|
+
# Monitored automatically
|
|
83
|
+
generate_report()
|
|
84
|
+
|
|
85
|
+
@app.task
|
|
86
|
+
@skip_monitor
|
|
87
|
+
def internal_cleanup():
|
|
88
|
+
# Opted out
|
|
89
|
+
pass
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Decorator Order (Important!)
|
|
93
|
+
|
|
94
|
+
The `@skip_monitor` decorator **must** be placed **after** `@app.task`:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
# ✅ Correct
|
|
98
|
+
@app.task
|
|
99
|
+
@skip_monitor
|
|
100
|
+
def my_task():
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
# ❌ Wrong - won't work
|
|
104
|
+
@skip_monitor
|
|
105
|
+
@app.task
|
|
106
|
+
def my_task():
|
|
107
|
+
pass
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Requirements
|
|
111
|
+
|
|
112
|
+
- Python 3.8+
|
|
113
|
+
- Celery 5.0+
|
|
114
|
+
- cronradar 1.0+
|
|
115
|
+
|
|
116
|
+
## Links
|
|
117
|
+
|
|
118
|
+
- 📚 [Documentation](https://cronradar.com/docs/sdks/celery)
|
|
119
|
+
- 📦 [PyPI](https://pypi.org/project/cronradar-celery)
|
|
120
|
+
- 🐛 [GitHub Issues](https://github.com/cronradar/cronradar-celery/issues)
|
|
121
|
+
- ✉️ support@cronradar.com
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
Proprietary - © 2025 CronRadar. All rights reserved.
|
|
126
|
+
|
|
127
|
+
See [LICENSE](./LICENSE) for details. This integration may only be used with the CronRadar monitoring service.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
cronradar_celery/__init__.py,sha256=n-XaIACZ_XD1V9L80rnMJsRhPEsR_QTPbcleT4lY-lM,1873
|
|
2
|
+
cronradar_celery/discovery.py,sha256=wfUAKJYMpx9N11cF1oFuop6VMwiGITIqGwLDtgRSxv0,2582
|
|
3
|
+
cronradar_celery/monitors.py,sha256=DOBINczz5WIjaN3kSq2-XsPAO1ys1uL8uxYRBBQeyCE,2923
|
|
4
|
+
cronradar_celery/utils.py,sha256=ncXWHHGAt_RgOWZ3HWOgXVi4xGH_tRgPL9OVT4PPp3g,5920
|
|
5
|
+
cronradar_celery-0.0.2.dist-info/licenses/LICENSE,sha256=jsvzCDX0wx-tjGeCiBXLFeEQ_lIhQmf4KPxGa0619SI,1484
|
|
6
|
+
cronradar_celery-0.0.2.dist-info/METADATA,sha256=s_oe3kwU98Vdv3uZwSJ74d7QWujT__-4a1VyW4MsdTg,3375
|
|
7
|
+
cronradar_celery-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
cronradar_celery-0.0.2.dist-info/top_level.txt,sha256=XuNSMb2-Fmd47TD9MFPV64w18xaMyEajwvDD37YnD4Q,17
|
|
9
|
+
cronradar_celery-0.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
CronRadar SDK License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 CronRadar. All rights reserved.
|
|
4
|
+
|
|
5
|
+
## Permitted Use
|
|
6
|
+
|
|
7
|
+
You may use this software solely in connection with the CronRadar monitoring
|
|
8
|
+
service available at cronradar.com. You may view, study, and modify the source
|
|
9
|
+
code for the purpose of fixing bugs or improving functionality.
|
|
10
|
+
|
|
11
|
+
## Contributions
|
|
12
|
+
|
|
13
|
+
If you modify this software to fix bugs or add improvements, you may submit
|
|
14
|
+
those modifications back to CronRadar via pull request. By submitting, you
|
|
15
|
+
grant CronRadar a perpetual, worldwide, non-exclusive, royalty-free license
|
|
16
|
+
to use, modify, and distribute your contributions under any terms.
|
|
17
|
+
|
|
18
|
+
## Restrictions
|
|
19
|
+
|
|
20
|
+
You may NOT:
|
|
21
|
+
- Use this software with any service other than CronRadar
|
|
22
|
+
- Fork or use this software for your own projects or products
|
|
23
|
+
- Redistribute this software (use official package managers only)
|
|
24
|
+
- Create derivative works for purposes other than contributing back to CronRadar
|
|
25
|
+
- Remove or modify any license, copyright, or attribution notices
|
|
26
|
+
- Use this software to create a competing monitoring service
|
|
27
|
+
|
|
28
|
+
## Termination
|
|
29
|
+
|
|
30
|
+
Your license terminates immediately if you violate any restriction. Upon
|
|
31
|
+
termination, you must stop using the software.
|
|
32
|
+
|
|
33
|
+
## Disclaimer
|
|
34
|
+
|
|
35
|
+
THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
36
|
+
IMPLIED. IN NO EVENT SHALL CRONRADAR BE LIABLE FOR ANY DAMAGES ARISING FROM
|
|
37
|
+
THE USE OF THIS SOFTWARE.
|
|
38
|
+
|
|
39
|
+
## Questions
|
|
40
|
+
|
|
41
|
+
For licensing questions: support@cronradar.com
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cronradar_celery
|