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.

@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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