django-cfg 1.1.61__py3-none-any.whl → 1.1.63__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.
- django_cfg/__init__.py +1 -1
- django_cfg/management/commands/rundramatiq.py +174 -202
- django_cfg/modules/django_tasks.py +54 -428
- django_cfg/modules/dramatiq_setup.py +16 -0
- {django_cfg-1.1.61.dist-info → django_cfg-1.1.63.dist-info}/METADATA +145 -4
- {django_cfg-1.1.61.dist-info → django_cfg-1.1.63.dist-info}/RECORD +9 -27
- django_cfg/apps/accounts/tests/__init__.py +0 -1
- django_cfg/apps/accounts/tests/test_models.py +0 -412
- django_cfg/apps/accounts/tests/test_otp_views.py +0 -143
- django_cfg/apps/accounts/tests/test_serializers.py +0 -331
- django_cfg/apps/accounts/tests/test_services.py +0 -401
- django_cfg/apps/accounts/tests/test_signals.py +0 -110
- django_cfg/apps/accounts/tests/test_views.py +0 -255
- django_cfg/apps/newsletter/tests/__init__.py +0 -1
- django_cfg/apps/newsletter/tests/run_tests.py +0 -47
- django_cfg/apps/newsletter/tests/test_email_integration.py +0 -256
- django_cfg/apps/newsletter/tests/test_email_tracking.py +0 -332
- django_cfg/apps/newsletter/tests/test_newsletter_manager.py +0 -83
- django_cfg/apps/newsletter/tests/test_newsletter_models.py +0 -157
- django_cfg/apps/support/tests/__init__.py +0 -0
- django_cfg/apps/support/tests/test_models.py +0 -106
- django_cfg/apps/tasks/@docs/CONFIGURATION.md +0 -663
- django_cfg/apps/tasks/@docs/README.md +0 -195
- django_cfg/apps/tasks/@docs/TASKS_QUEUES.md +0 -423
- django_cfg/apps/tasks/@docs/TROUBLESHOOTING.md +0 -506
- {django_cfg-1.1.61.dist-info → django_cfg-1.1.63.dist-info}/WHEEL +0 -0
- {django_cfg-1.1.61.dist-info → django_cfg-1.1.63.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.1.61.dist-info → django_cfg-1.1.63.dist-info}/licenses/LICENSE +0 -0
@@ -1,18 +1,16 @@
|
|
1
1
|
"""
|
2
2
|
Django-CFG Task Service Module.
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
Simplified and focused task service for Dramatiq integration.
|
5
|
+
Provides essential functionality without unnecessary complexity.
|
6
6
|
"""
|
7
7
|
|
8
|
-
from typing import Optional, Dict, Any, List
|
8
|
+
from typing import Optional, Dict, Any, List
|
9
9
|
import logging
|
10
|
-
import subprocess
|
11
|
-
import time
|
12
10
|
from urllib.parse import urlparse
|
13
11
|
|
14
12
|
from django_cfg.modules.base import BaseModule
|
15
|
-
from django_cfg.models.tasks import TaskConfig,
|
13
|
+
from django_cfg.models.tasks import TaskConfig, validate_task_config
|
16
14
|
from django_cfg.models.constance import ConstanceField
|
17
15
|
|
18
16
|
# Django imports (will be available when Django is configured)
|
@@ -26,15 +24,8 @@ except ImportError:
|
|
26
24
|
# Optional imports
|
27
25
|
try:
|
28
26
|
import dramatiq
|
29
|
-
from dramatiq.brokers.redis import RedisBroker
|
30
27
|
except ImportError:
|
31
28
|
dramatiq = None
|
32
|
-
RedisBroker = None
|
33
|
-
|
34
|
-
try:
|
35
|
-
import django_dramatiq
|
36
|
-
except ImportError:
|
37
|
-
django_dramatiq = None
|
38
29
|
|
39
30
|
try:
|
40
31
|
import redis
|
@@ -44,294 +35,121 @@ except ImportError:
|
|
44
35
|
logger = logging.getLogger(__name__)
|
45
36
|
|
46
37
|
|
47
|
-
class TaskManager:
|
48
|
-
"""
|
49
|
-
Task management and worker control.
|
50
|
-
|
51
|
-
Provides high-level interface for managing Dramatiq workers,
|
52
|
-
monitoring task queues, and controlling task execution.
|
53
|
-
"""
|
54
|
-
|
55
|
-
def __init__(self, config: DramatiqConfig):
|
56
|
-
self.config = config
|
57
|
-
self._broker = None
|
58
|
-
self._workers = []
|
59
|
-
|
60
|
-
@property
|
61
|
-
def broker(self):
|
62
|
-
"""Get Dramatiq broker instance (lazy-loaded)."""
|
63
|
-
if self._broker is None:
|
64
|
-
if dramatiq is None:
|
65
|
-
logger.error("Dramatiq not available")
|
66
|
-
return None
|
67
|
-
|
68
|
-
try:
|
69
|
-
# This will be configured by Django settings
|
70
|
-
self._broker = dramatiq.get_broker()
|
71
|
-
except Exception as e:
|
72
|
-
logger.error(f"Failed to get Dramatiq broker: {e}")
|
73
|
-
return None
|
74
|
-
|
75
|
-
return self._broker
|
76
|
-
|
77
|
-
def get_queue_stats(self) -> List[Dict[str, Any]]:
|
78
|
-
"""Get statistics for all configured queues."""
|
79
|
-
if not self.broker:
|
80
|
-
return []
|
81
|
-
|
82
|
-
stats = []
|
83
|
-
for queue_name in self.config.dramatiq.queues:
|
84
|
-
try:
|
85
|
-
# Get queue statistics from broker
|
86
|
-
queue_stats = {
|
87
|
-
"name": queue_name,
|
88
|
-
"pending": 0, # Will be populated by actual broker stats
|
89
|
-
"running": 0,
|
90
|
-
"completed": 0,
|
91
|
-
"failed": 0,
|
92
|
-
}
|
93
|
-
|
94
|
-
# TODO: Implement actual queue statistics retrieval
|
95
|
-
# This depends on the specific broker implementation
|
96
|
-
|
97
|
-
stats.append(queue_stats)
|
98
|
-
except Exception as e:
|
99
|
-
logger.error(f"Failed to get stats for queue {queue_name}: {e}")
|
100
|
-
|
101
|
-
return stats
|
102
|
-
|
103
|
-
def get_worker_stats(self) -> List[Dict[str, Any]]:
|
104
|
-
"""Get statistics for all active workers."""
|
105
|
-
# TODO: Implement worker statistics retrieval
|
106
|
-
# This would typically involve checking process status,
|
107
|
-
# memory usage, and current task information
|
108
|
-
|
109
|
-
return []
|
110
|
-
|
111
|
-
def clear_queue(self, queue_name: str) -> bool:
|
112
|
-
"""Clear all messages from a specific queue."""
|
113
|
-
if not self.broker:
|
114
|
-
return False
|
115
|
-
|
116
|
-
try:
|
117
|
-
# TODO: Implement queue clearing
|
118
|
-
logger.info(f"Cleared queue: {queue_name}")
|
119
|
-
return True
|
120
|
-
except Exception as e:
|
121
|
-
logger.error(f"Failed to clear queue {queue_name}: {e}")
|
122
|
-
return False
|
123
|
-
|
124
|
-
def retry_failed_tasks(self, queue_name: Optional[str] = None) -> int:
|
125
|
-
"""Retry failed tasks in specified queue or all queues."""
|
126
|
-
if not self.broker:
|
127
|
-
return 0
|
128
|
-
|
129
|
-
try:
|
130
|
-
# TODO: Implement failed task retry logic
|
131
|
-
retried_count = 0
|
132
|
-
logger.info(f"Retried {retried_count} failed tasks")
|
133
|
-
return retried_count
|
134
|
-
except Exception as e:
|
135
|
-
logger.error(f"Failed to retry tasks: {e}")
|
136
|
-
return 0
|
137
|
-
|
138
|
-
|
139
38
|
class DjangoTasks(BaseModule):
|
140
39
|
"""
|
141
|
-
|
40
|
+
Simplified Django-CFG task service.
|
142
41
|
|
143
|
-
|
144
|
-
|
42
|
+
Focuses on essential functionality:
|
43
|
+
- Configuration management
|
44
|
+
- Task discovery
|
45
|
+
- Health checks
|
46
|
+
- Constance integration
|
145
47
|
"""
|
146
48
|
|
147
49
|
def __init__(self):
|
148
50
|
super().__init__()
|
149
51
|
self._config: Optional[TaskConfig] = None
|
150
|
-
self._manager: Optional[TaskManager] = None
|
151
52
|
self._redis_url: Optional[str] = None
|
152
53
|
|
153
54
|
@property
|
154
55
|
def config(self) -> Optional[TaskConfig]:
|
155
56
|
"""Get task configuration (lazy-loaded)."""
|
156
|
-
|
157
|
-
try:
|
158
|
-
# First try the base class method
|
159
|
-
django_config = self.get_config() # This returns full DjangoConfig
|
160
|
-
if django_config and hasattr(django_config, 'tasks'):
|
161
|
-
task_config = django_config.tasks
|
162
|
-
if task_config and isinstance(task_config, TaskConfig):
|
163
|
-
# Update cache with fresh config
|
164
|
-
self._config = task_config
|
165
|
-
logger.debug(f"Loaded TaskConfig: enabled={task_config.enabled}")
|
166
|
-
return self._config
|
167
|
-
elif task_config is None:
|
168
|
-
logger.debug("Tasks configuration is None in Django config")
|
169
|
-
else:
|
170
|
-
logger.error(f"Expected TaskConfig, got {type(task_config)}")
|
171
|
-
else:
|
172
|
-
logger.debug("No tasks attribute found in Django config")
|
173
|
-
|
174
|
-
# Fallback: try to import config directly
|
57
|
+
if self._config is None:
|
175
58
|
try:
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
return self._config
|
191
|
-
|
192
|
-
@property
|
193
|
-
def manager(self) -> Optional[TaskManager]:
|
194
|
-
"""Get task manager (lazy-loaded)."""
|
195
|
-
if self._manager is None and self.config:
|
196
|
-
try:
|
197
|
-
self._manager = TaskManager(self.config.dramatiq)
|
59
|
+
# Get config from django-cfg
|
60
|
+
django_config = self.get_config()
|
61
|
+
if django_config and hasattr(django_config, 'tasks'):
|
62
|
+
self._config = django_config.tasks
|
63
|
+
logger.debug(f"Loaded TaskConfig: enabled={self._config.enabled if self._config else False}")
|
64
|
+
else:
|
65
|
+
# Fallback: try direct import
|
66
|
+
try:
|
67
|
+
from api.config import config as api_config
|
68
|
+
if hasattr(api_config, 'tasks') and api_config.tasks:
|
69
|
+
self._config = api_config.tasks
|
70
|
+
logger.debug(f"Loaded TaskConfig from api.config: enabled={self._config.enabled}")
|
71
|
+
except ImportError:
|
72
|
+
logger.debug("Could not import api.config")
|
198
73
|
except Exception as e:
|
199
|
-
logger.
|
200
|
-
|
201
|
-
return self.
|
74
|
+
logger.warning(f"Failed to get task config: {e}")
|
75
|
+
|
76
|
+
return self._config
|
202
77
|
|
203
78
|
def is_enabled(self) -> bool:
|
204
79
|
"""Check if task system is enabled and properly configured."""
|
205
|
-
if not self.config:
|
206
|
-
return False
|
207
|
-
|
208
|
-
if not self.config.enabled:
|
80
|
+
if not self.config or not self.config.enabled:
|
209
81
|
return False
|
210
82
|
|
211
83
|
# Check if required dependencies are available
|
212
|
-
if dramatiq is None
|
213
|
-
logger.warning("Dramatiq
|
84
|
+
if dramatiq is None:
|
85
|
+
logger.warning("Dramatiq not available")
|
214
86
|
return False
|
215
87
|
|
216
88
|
return True
|
217
89
|
|
218
90
|
def get_redis_url(self) -> Optional[str]:
|
219
91
|
"""Get Redis URL from Django-CFG cache configuration."""
|
220
|
-
# Always try to get the URL if not cached
|
221
92
|
if self._redis_url is None:
|
222
93
|
try:
|
223
|
-
# Use get_current_config from django_cfg.core.config
|
224
94
|
from django_cfg.core.config import get_current_config
|
225
95
|
django_config = get_current_config()
|
226
96
|
|
227
|
-
# If that fails, try to import directly from api.config
|
228
97
|
if not django_config:
|
229
98
|
try:
|
230
99
|
from api.config import config
|
231
100
|
django_config = config
|
232
|
-
logger.debug("Got Django config from direct import")
|
233
101
|
except ImportError:
|
234
102
|
logger.warning("Could not import config from api.config")
|
235
103
|
|
236
|
-
logger.debug(f"Django config type: {type(django_config)}")
|
237
|
-
logger.debug(f"Has cache_default: {hasattr(django_config, 'cache_default') if django_config else False}")
|
238
|
-
|
239
104
|
if django_config and hasattr(django_config, 'cache_default') and django_config.cache_default:
|
240
105
|
cache_config = django_config.cache_default
|
241
|
-
logger.debug(f"Cache config type: {type(cache_config)}")
|
242
|
-
logger.debug(f"Cache config redis_url: {getattr(cache_config, 'redis_url', 'NOT_FOUND')}")
|
243
|
-
|
244
106
|
if hasattr(cache_config, 'redis_url') and cache_config.redis_url:
|
245
107
|
self._redis_url = cache_config.redis_url
|
246
108
|
logger.debug(f"Got Redis URL: {self._redis_url}")
|
247
109
|
elif hasattr(cache_config, 'location') and cache_config.location:
|
248
110
|
self._redis_url = cache_config.location
|
249
111
|
logger.debug(f"Got Redis URL from location: {self._redis_url}")
|
250
|
-
else:
|
251
|
-
logger.warning("Cache config exists but no redis_url or location found")
|
252
|
-
else:
|
253
|
-
logger.warning("No cache_default configuration found")
|
254
112
|
except Exception as e:
|
255
113
|
logger.warning(f"Failed to get Redis URL: {e}")
|
256
|
-
import traceback
|
257
|
-
logger.warning(f"Traceback: {traceback.format_exc()}")
|
258
114
|
|
259
115
|
return self._redis_url
|
260
116
|
|
261
|
-
def check_redis_connection(self) -> bool:
|
262
|
-
"""Check if Redis connection is available."""
|
263
|
-
redis_url = self.get_redis_url()
|
264
|
-
if not redis_url:
|
265
|
-
return False
|
266
|
-
|
267
|
-
if redis is None:
|
268
|
-
logger.error("Redis library not available")
|
269
|
-
return False
|
270
|
-
|
271
|
-
try:
|
272
|
-
parsed = urlparse(redis_url)
|
273
|
-
r = redis.Redis(
|
274
|
-
host=parsed.hostname or 'localhost',
|
275
|
-
port=parsed.port or 6379,
|
276
|
-
db=self.config.dramatiq.redis_db if self.config else 1,
|
277
|
-
password=parsed.password,
|
278
|
-
socket_timeout=5
|
279
|
-
)
|
280
|
-
|
281
|
-
# Test connection
|
282
|
-
r.ping()
|
283
|
-
return True
|
284
|
-
except Exception as e:
|
285
|
-
logger.error(f"Redis connection failed: {e}")
|
286
|
-
return False
|
287
|
-
|
288
117
|
def get_redis_client(self):
|
289
118
|
"""Get Redis client instance."""
|
290
119
|
redis_url = self.get_redis_url()
|
291
|
-
if not redis_url:
|
292
|
-
logger.warning("No Redis URL available for client")
|
293
|
-
return None
|
294
|
-
|
295
|
-
if redis is None:
|
296
|
-
logger.error("Redis library not available")
|
120
|
+
if not redis_url or redis is None:
|
297
121
|
return None
|
298
122
|
|
299
123
|
try:
|
300
124
|
parsed = urlparse(redis_url)
|
301
|
-
|
302
|
-
redis_db = 1 # default
|
303
|
-
try:
|
304
|
-
task_config = self.config # This should return TaskConfig
|
305
|
-
if task_config:
|
306
|
-
logger.debug(f"TaskConfig type: {type(task_config)}")
|
307
|
-
if hasattr(task_config, 'dramatiq') and task_config.dramatiq:
|
308
|
-
redis_db = task_config.dramatiq.redis_db
|
309
|
-
logger.debug(f"Using redis_db: {redis_db}")
|
310
|
-
else:
|
311
|
-
logger.warning("No dramatiq config found in TaskConfig")
|
312
|
-
else:
|
313
|
-
logger.warning("No TaskConfig available")
|
314
|
-
except Exception as e:
|
315
|
-
logger.error(f"Error getting redis_db: {e}")
|
316
|
-
|
317
|
-
client = redis.Redis(
|
125
|
+
return redis.Redis(
|
318
126
|
host=parsed.hostname or 'localhost',
|
319
127
|
port=parsed.port or 6379,
|
320
|
-
db=redis_db,
|
128
|
+
db=self.config.dramatiq.redis_db if self.config else 1,
|
321
129
|
password=parsed.password,
|
322
130
|
socket_timeout=5
|
323
131
|
)
|
324
|
-
|
325
|
-
logger.debug(f"Created Redis client: host={parsed.hostname}, port={parsed.port}, db={redis_db}")
|
326
|
-
return client
|
327
|
-
|
328
132
|
except Exception as e:
|
329
133
|
logger.error(f"Failed to create Redis client: {e}")
|
330
134
|
return None
|
331
135
|
|
332
|
-
def _get_current_timestamp(self) ->
|
333
|
-
"""Get current timestamp."""
|
334
|
-
|
136
|
+
def _get_current_timestamp(self) -> str:
|
137
|
+
"""Get current timestamp in ISO format."""
|
138
|
+
from datetime import datetime
|
139
|
+
return datetime.now().isoformat()
|
140
|
+
|
141
|
+
def check_redis_connection(self) -> bool:
|
142
|
+
"""Check if Redis connection is available."""
|
143
|
+
redis_client = self.get_redis_client()
|
144
|
+
if not redis_client:
|
145
|
+
return False
|
146
|
+
|
147
|
+
try:
|
148
|
+
redis_client.ping()
|
149
|
+
return True
|
150
|
+
except Exception as e:
|
151
|
+
logger.error(f"Redis connection failed: {e}")
|
152
|
+
return False
|
335
153
|
|
336
154
|
def validate_configuration(self) -> bool:
|
337
155
|
"""Validate complete task system configuration."""
|
@@ -346,38 +164,6 @@ class DjangoTasks(BaseModule):
|
|
346
164
|
|
347
165
|
return validate_task_config(self.config, redis_url)
|
348
166
|
|
349
|
-
def get_dramatiq_settings(self) -> Dict[str, Any]:
|
350
|
-
"""Generate Django settings for Dramatiq integration."""
|
351
|
-
if not self.config or not self.is_enabled():
|
352
|
-
return {}
|
353
|
-
|
354
|
-
redis_url = self.get_redis_url()
|
355
|
-
if not redis_url:
|
356
|
-
logger.error("Cannot generate Dramatiq settings: Redis URL not available")
|
357
|
-
return {}
|
358
|
-
|
359
|
-
try:
|
360
|
-
return self.config.get_dramatiq_settings(redis_url)
|
361
|
-
except Exception as e:
|
362
|
-
logger.error(f"Failed to generate Dramatiq settings: {e}")
|
363
|
-
return {}
|
364
|
-
|
365
|
-
|
366
|
-
def get_installed_apps(self) -> List[str]:
|
367
|
-
"""Get Django apps required for task system."""
|
368
|
-
if not self.is_enabled():
|
369
|
-
return []
|
370
|
-
|
371
|
-
apps = ["django_dramatiq"]
|
372
|
-
|
373
|
-
# Add optional apps based on configuration
|
374
|
-
if self.config and self.config.dramatiq.admin_enabled:
|
375
|
-
# Admin integration is included in django_dramatiq
|
376
|
-
# Add our custom tasks app for enhanced admin interface
|
377
|
-
apps.append("django_cfg.apps.tasks")
|
378
|
-
|
379
|
-
return apps
|
380
|
-
|
381
167
|
def discover_tasks(self) -> List[str]:
|
382
168
|
"""Discover task modules in Django apps."""
|
383
169
|
if not self.config or not self.config.auto_discover_tasks:
|
@@ -402,19 +188,13 @@ class DjangoTasks(BaseModule):
|
|
402
188
|
pass
|
403
189
|
except Exception as e:
|
404
190
|
logger.warning(f"Error importing task module {module_path}: {e}")
|
405
|
-
|
406
191
|
except Exception as e:
|
407
192
|
logger.error(f"Task discovery failed: {e}")
|
408
193
|
|
409
194
|
return discovered
|
410
195
|
|
411
196
|
def get_constance_fields(self) -> List[ConstanceField]:
|
412
|
-
"""
|
413
|
-
Get Constance fields for Dramatiq configuration.
|
414
|
-
|
415
|
-
Returns:
|
416
|
-
List of ConstanceField objects for dynamic task configuration
|
417
|
-
"""
|
197
|
+
"""Get Constance fields for Dramatiq configuration."""
|
418
198
|
if not self.is_enabled():
|
419
199
|
return []
|
420
200
|
|
@@ -460,84 +240,18 @@ class DjangoTasks(BaseModule):
|
|
460
240
|
logger.debug(f"Generated {len(fields)} Constance fields for Dramatiq")
|
461
241
|
return fields
|
462
242
|
|
463
|
-
def start_workers(self, processes: Optional[int] = None, queues: Optional[List[str]] = None) -> bool:
|
464
|
-
"""Start Dramatiq workers programmatically."""
|
465
|
-
logger.warning("Auto-start workers functionality has been removed. Please start workers manually using: python manage.py rundramatiq")
|
466
|
-
return False
|
467
|
-
|
468
|
-
def stop_workers(self, graceful: bool = True) -> bool:
|
469
|
-
"""Stop all Dramatiq workers."""
|
470
|
-
try:
|
471
|
-
timeout = self.config.dramatiq.worker.shutdown_timeout if self.config else 30
|
472
|
-
logger.info(f"Stopping workers (graceful={graceful}, timeout={timeout}s)")
|
473
|
-
|
474
|
-
# Find and kill Dramatiq worker processes
|
475
|
-
try:
|
476
|
-
# Find worker processes
|
477
|
-
result = subprocess.run(
|
478
|
-
["pgrep", "-f", "rundramatiq"],
|
479
|
-
capture_output=True,
|
480
|
-
text=True
|
481
|
-
)
|
482
|
-
|
483
|
-
if result.returncode == 0:
|
484
|
-
pids = result.stdout.strip().split('\n')
|
485
|
-
pids = [pid.strip() for pid in pids if pid.strip()]
|
486
|
-
|
487
|
-
if pids:
|
488
|
-
logger.info(f"Found {len(pids)} worker processes: {pids}")
|
489
|
-
|
490
|
-
# Send appropriate signal
|
491
|
-
signal = "TERM" if graceful else "KILL"
|
492
|
-
|
493
|
-
for pid in pids:
|
494
|
-
try:
|
495
|
-
subprocess.run(["kill", f"-{signal}", pid], check=True)
|
496
|
-
logger.info(f"Sent {signal} signal to worker process {pid}")
|
497
|
-
except subprocess.CalledProcessError:
|
498
|
-
logger.warning(f"Failed to send {signal} signal to process {pid}")
|
499
|
-
|
500
|
-
# Wait for graceful shutdown if requested
|
501
|
-
if graceful:
|
502
|
-
logger.info(f"Waiting up to {timeout}s for graceful shutdown...")
|
503
|
-
# TODO: Could implement actual waiting logic here
|
504
|
-
|
505
|
-
logger.info("✅ Dramatiq workers stopped successfully")
|
506
|
-
return True
|
507
|
-
else:
|
508
|
-
logger.info("No worker processes found")
|
509
|
-
return True
|
510
|
-
else:
|
511
|
-
logger.info("No worker processes found")
|
512
|
-
return True
|
513
|
-
|
514
|
-
except Exception as e:
|
515
|
-
logger.error(f"Failed to find/stop worker processes: {e}")
|
516
|
-
return False
|
517
|
-
|
518
|
-
except Exception as e:
|
519
|
-
logger.error(f"Failed to stop workers: {e}")
|
520
|
-
return False
|
521
|
-
|
522
243
|
def get_health_status(self) -> Dict[str, Any]:
|
523
244
|
"""Get comprehensive health status of task system."""
|
524
245
|
status = {
|
525
246
|
"enabled": self.is_enabled(),
|
526
247
|
"redis_connection": False,
|
527
248
|
"configuration_valid": False,
|
528
|
-
"workers": [],
|
529
|
-
"queues": [],
|
530
249
|
"discovered_modules": [],
|
531
250
|
}
|
532
251
|
|
533
252
|
if self.is_enabled():
|
534
253
|
status["redis_connection"] = self.check_redis_connection()
|
535
254
|
status["configuration_valid"] = self.validate_configuration()
|
536
|
-
|
537
|
-
if self.manager:
|
538
|
-
status["workers"] = self.manager.get_worker_stats()
|
539
|
-
status["queues"] = self.manager.get_queue_stats()
|
540
|
-
|
541
255
|
status["discovered_modules"] = self.discover_tasks()
|
542
256
|
|
543
257
|
return status
|
@@ -549,12 +263,7 @@ _task_service_instance: Optional[DjangoTasks] = None
|
|
549
263
|
|
550
264
|
|
551
265
|
def get_task_service() -> DjangoTasks:
|
552
|
-
"""
|
553
|
-
Get the global task service instance.
|
554
|
-
|
555
|
-
Returns:
|
556
|
-
DjangoTasks: The singleton task service instance
|
557
|
-
"""
|
266
|
+
"""Get the global task service instance."""
|
558
267
|
global _task_service_instance
|
559
268
|
|
560
269
|
if _task_service_instance is None:
|
@@ -594,79 +303,6 @@ def get_task_health() -> Dict[str, Any]:
|
|
594
303
|
}
|
595
304
|
|
596
305
|
|
597
|
-
def enqueue_task(actor_name: str, *args, queue_name: str = "default", **kwargs) -> bool:
|
598
|
-
"""
|
599
|
-
Enqueue a task for processing.
|
600
|
-
|
601
|
-
Args:
|
602
|
-
actor_name: Name of the Dramatiq actor
|
603
|
-
*args: Task arguments
|
604
|
-
queue_name: Queue to send task to
|
605
|
-
**kwargs: Task keyword arguments
|
606
|
-
|
607
|
-
Returns:
|
608
|
-
bool: True if task was successfully enqueued
|
609
|
-
"""
|
610
|
-
try:
|
611
|
-
service = get_task_service()
|
612
|
-
if not service.is_enabled():
|
613
|
-
logger.error("Task system not enabled")
|
614
|
-
return False
|
615
|
-
|
616
|
-
# TODO: Implement actual task enqueueing
|
617
|
-
# This would involve getting the actor and calling send()
|
618
|
-
|
619
|
-
logger.info(f"Enqueued task {actor_name} to queue {queue_name}")
|
620
|
-
return True
|
621
|
-
except Exception as e:
|
622
|
-
logger.error(f"Failed to enqueue task {actor_name}: {e}")
|
623
|
-
return False
|
624
|
-
|
625
|
-
|
626
|
-
def clear_dramatiq_queues() -> bool:
|
627
|
-
"""
|
628
|
-
Clear all Dramatiq queues on startup.
|
629
|
-
|
630
|
-
Returns:
|
631
|
-
bool: True if queues were cleared successfully
|
632
|
-
"""
|
633
|
-
try:
|
634
|
-
service = get_task_service()
|
635
|
-
if not service.is_enabled():
|
636
|
-
logger.debug("Task system not enabled, skipping queue clearing")
|
637
|
-
return True
|
638
|
-
|
639
|
-
# Get broker and clear all queues
|
640
|
-
if hasattr(service, 'manager') and service.manager and service.manager.broker:
|
641
|
-
broker = service.manager.broker
|
642
|
-
queue_names = service.config.dramatiq.queues
|
643
|
-
|
644
|
-
for queue_name in queue_names:
|
645
|
-
try:
|
646
|
-
# Clear the queue
|
647
|
-
if hasattr(broker, 'flush'):
|
648
|
-
broker.flush(queue_name)
|
649
|
-
logger.info(f"Cleared Dramatiq queue: {queue_name}")
|
650
|
-
elif hasattr(broker, 'client'):
|
651
|
-
# For Redis broker, clear using Redis client
|
652
|
-
redis_client = broker.client
|
653
|
-
queue_key = f"dramatiq:queue:{queue_name}"
|
654
|
-
redis_client.delete(queue_key)
|
655
|
-
logger.info(f"Cleared Redis queue: {queue_name}")
|
656
|
-
except Exception as e:
|
657
|
-
logger.warning(f"Failed to clear queue {queue_name}: {e}")
|
658
|
-
|
659
|
-
logger.info("✅ Dramatiq queues cleared on startup")
|
660
|
-
return True
|
661
|
-
else:
|
662
|
-
logger.debug("Broker not available, skipping queue clearing")
|
663
|
-
return True
|
664
|
-
|
665
|
-
except Exception as e:
|
666
|
-
logger.error(f"Failed to clear Dramatiq queues: {e}")
|
667
|
-
return False
|
668
|
-
|
669
|
-
|
670
306
|
def initialize_task_system():
|
671
307
|
"""
|
672
308
|
Initialize the task system during Django app startup.
|
@@ -708,9 +344,6 @@ def initialize_task_system():
|
|
708
344
|
def extend_constance_config_with_tasks():
|
709
345
|
"""
|
710
346
|
Extend Constance configuration with Dramatiq task fields if tasks are enabled.
|
711
|
-
|
712
|
-
This function should be called during Django configuration setup to automatically
|
713
|
-
add task-related Constance fields when the task system is enabled.
|
714
347
|
"""
|
715
348
|
try:
|
716
349
|
service = get_task_service()
|
@@ -727,21 +360,14 @@ def extend_constance_config_with_tasks():
|
|
727
360
|
return []
|
728
361
|
|
729
362
|
|
730
|
-
# === Broker Creation (Simplified) ===
|
731
|
-
# Note: We now use django-dramatiq.setup() instead of custom broker creation
|
732
|
-
|
733
|
-
|
734
363
|
# === Exports ===
|
735
364
|
|
736
365
|
__all__ = [
|
737
366
|
"DjangoTasks",
|
738
|
-
"TaskManager",
|
739
367
|
"get_task_service",
|
740
368
|
"reset_task_service",
|
741
369
|
"is_task_system_available",
|
742
370
|
"get_task_health",
|
743
|
-
"enqueue_task",
|
744
371
|
"extend_constance_config_with_tasks",
|
745
372
|
"initialize_task_system",
|
746
|
-
|
747
|
-
]
|
373
|
+
]
|
@@ -0,0 +1,16 @@
|
|
1
|
+
"""
|
2
|
+
Dramatiq broker module for django-cfg CLI integration.
|
3
|
+
|
4
|
+
This module provides the broker instance required by Dramatiq CLI.
|
5
|
+
It's a thin wrapper around django_dramatiq.setup with broker export.
|
6
|
+
|
7
|
+
Usage:
|
8
|
+
dramatiq django_cfg.modules.dramatiq_setup [task_modules...]
|
9
|
+
"""
|
10
|
+
|
11
|
+
# Import django_dramatiq setup (handles Django initialization)
|
12
|
+
import django_dramatiq.setup
|
13
|
+
|
14
|
+
# Re-export the broker for Dramatiq CLI
|
15
|
+
import dramatiq
|
16
|
+
broker = dramatiq.get_broker()
|