django-cfg 1.4.107__py3-none-any.whl → 1.4.108__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 django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/views/profile.py +19 -9
- django_cfg/apps/centrifugo/views/admin_api.py +4 -7
- django_cfg/apps/centrifugo/views/monitoring.py +3 -6
- django_cfg/apps/centrifugo/views/testing_api.py +3 -6
- django_cfg/apps/dashboard/services/system_health_service.py +16 -11
- django_cfg/apps/dashboard/views/activity_views.py +3 -5
- django_cfg/apps/dashboard/views/apizones_views.py +4 -5
- django_cfg/apps/dashboard/views/charts_views.py +4 -5
- django_cfg/apps/dashboard/views/overview_views.py +4 -5
- django_cfg/apps/dashboard/views/statistics_views.py +4 -5
- django_cfg/apps/dashboard/views/system_views.py +4 -5
- django_cfg/apps/knowbase/__init__.py +2 -2
- django_cfg/apps/knowbase/apps.py +2 -8
- django_cfg/apps/knowbase/views/base.py +9 -4
- django_cfg/apps/support/views/api.py +16 -7
- django_cfg/apps/tasks/__init__.py +61 -2
- django_cfg/apps/tasks/admin/__init__.py +3 -10
- django_cfg/apps/tasks/admin/config.py +98 -0
- django_cfg/apps/tasks/admin/task_log.py +265 -0
- django_cfg/apps/tasks/apps.py +7 -9
- django_cfg/apps/tasks/filters/__init__.py +10 -0
- django_cfg/apps/tasks/filters/task_log.py +121 -0
- django_cfg/apps/tasks/migrations/0001_initial.py +196 -0
- django_cfg/apps/tasks/models/__init__.py +4 -0
- django_cfg/apps/tasks/models/task_log.py +246 -0
- django_cfg/apps/tasks/serializers/__init__.py +28 -0
- django_cfg/apps/tasks/serializers/task_log.py +249 -0
- django_cfg/apps/tasks/services/__init__.py +10 -0
- django_cfg/apps/tasks/services/client/__init__.py +7 -0
- django_cfg/apps/tasks/services/client/client.py +234 -0
- django_cfg/apps/tasks/services/config_helper.py +63 -0
- django_cfg/apps/tasks/services/sync.py +204 -0
- django_cfg/apps/tasks/urls.py +7 -13
- django_cfg/apps/tasks/views/__init__.py +4 -10
- django_cfg/apps/tasks/views/task_log.py +41 -0
- django_cfg/apps/tasks/views/task_log_base.py +41 -0
- django_cfg/apps/tasks/views/task_log_overview.py +100 -0
- django_cfg/apps/tasks/views/task_log_related.py +41 -0
- django_cfg/apps/tasks/views/task_log_stats.py +91 -0
- django_cfg/apps/tasks/views/task_log_timeline.py +81 -0
- django_cfg/apps/urls.py +0 -1
- django_cfg/cli/commands/info.py +1 -1
- django_cfg/cli/utils.py +1 -1
- django_cfg/core/base/config_model.py +1 -1
- django_cfg/core/builders/apps_builder.py +1 -1
- django_cfg/core/generation/integration_generators/__init__.py +1 -1
- django_cfg/core/generation/integration_generators/tasks.py +14 -18
- django_cfg/core/generation/security_generators/crypto_fields.py +2 -1
- django_cfg/core/integration/display/startup.py +1 -1
- django_cfg/mixins/__init__.py +12 -0
- django_cfg/mixins/admin_api.py +37 -0
- django_cfg/mixins/client_api.py +39 -0
- django_cfg/models/django/constance.py +2 -8
- django_cfg/models/django/crypto_fields.py +13 -48
- django_cfg/models/tasks/__init__.py +8 -10
- django_cfg/models/tasks/backends.py +76 -207
- django_cfg/models/tasks/config.py +20 -127
- django_cfg/models/tasks/utils.py +17 -29
- django_cfg/modules/django_client/management/commands/generate_client.py +13 -1
- django_cfg/modules/django_unfold/navigation.py +121 -22
- django_cfg/pyproject.toml +2 -2
- django_cfg/registry/core.py +1 -1
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.4.107.dist-info → django_cfg-1.4.108.dist-info}/METADATA +3 -3
- {django_cfg-1.4.107.dist-info → django_cfg-1.4.108.dist-info}/RECORD +70 -107
- django_cfg/apps/tasks/admin/actions.py +0 -29
- django_cfg/apps/tasks/admin/tasks_admin.py +0 -154
- django_cfg/apps/tasks/api/serializers.py +0 -82
- django_cfg/apps/tasks/api/views.py +0 -571
- django_cfg/apps/tasks/serializers.py +0 -82
- django_cfg/apps/tasks/static/tasks/css/dashboard-alpine.css +0 -299
- django_cfg/apps/tasks/static/tasks/css/dashboard.css +0 -120
- django_cfg/apps/tasks/static/tasks/js/alpine/README.md +0 -47
- django_cfg/apps/tasks/static/tasks/js/alpine/actions/index.js +0 -8
- django_cfg/apps/tasks/static/tasks/js/alpine/actions/management.js +0 -123
- django_cfg/apps/tasks/static/tasks/js/alpine/actions/pagination.js +0 -21
- django_cfg/apps/tasks/static/tasks/js/alpine/actions/tasks.js +0 -101
- django_cfg/apps/tasks/static/tasks/js/alpine/actions/workers.js +0 -59
- django_cfg/apps/tasks/static/tasks/js/alpine/computed.js +0 -35
- django_cfg/apps/tasks/static/tasks/js/alpine/index.js +0 -148
- django_cfg/apps/tasks/static/tasks/js/alpine/loaders/index.js +0 -36
- django_cfg/apps/tasks/static/tasks/js/alpine/loaders/overview.js +0 -37
- django_cfg/apps/tasks/static/tasks/js/alpine/loaders/queues.js +0 -27
- django_cfg/apps/tasks/static/tasks/js/alpine/loaders/tasks.js +0 -32
- django_cfg/apps/tasks/static/tasks/js/alpine/loaders/workers.js +0 -21
- django_cfg/apps/tasks/static/tasks/js/alpine/state.js +0 -36
- django_cfg/apps/tasks/static/tasks/js/alpine/utils/formatters.js +0 -42
- django_cfg/apps/tasks/static/tasks/js/alpine/utils/helpers.js +0 -68
- django_cfg/apps/tasks/static/tasks/js/dashboard-alpine.js +0 -725
- django_cfg/apps/tasks/tasks/__init__.py +0 -10
- django_cfg/apps/tasks/tasks/demo_tasks.py +0 -127
- django_cfg/apps/tasks/templates/tasks/components/management_actions.html +0 -71
- django_cfg/apps/tasks/templates/tasks/components/overview_content.html +0 -94
- django_cfg/apps/tasks/templates/tasks/components/queues_content.html +0 -44
- django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +0 -45
- django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -151
- django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +0 -61
- django_cfg/apps/tasks/templates/tasks/components/tasks_mjs_integration.html +0 -269
- django_cfg/apps/tasks/templates/tasks/components/workers_content.html +0 -60
- django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -20
- django_cfg/apps/tasks/templates/tasks/pages/dashboard-improved.html +0 -168
- django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +0 -77
- django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +0 -40
- django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +0 -40
- django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +0 -86
- django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +0 -90
- django_cfg/apps/tasks/urls_admin.py +0 -15
- django_cfg/apps/tasks/utils/__init__.py +0 -1
- django_cfg/apps/tasks/utils/simulator.py +0 -353
- django_cfg/apps/tasks/views/api.py +0 -571
- django_cfg/apps/tasks/views/dashboard.py +0 -89
- django_cfg/management/commands/rundramatiq.py +0 -24
- django_cfg/management/commands/rundramatiq_simulator.py +0 -22
- django_cfg/management/commands/task_clear.py +0 -25
- django_cfg/management/commands/task_status.py +0 -24
- django_cfg/modules/django_tasks/__init__.py +0 -29
- django_cfg/modules/django_tasks/dramatiq_setup.py +0 -20
- django_cfg/modules/django_tasks/factory.py +0 -127
- django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq.py +0 -253
- django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +0 -436
- django_cfg/modules/django_tasks/management/commands/task_clear.py +0 -226
- django_cfg/modules/django_tasks/management/commands/task_status.py +0 -257
- django_cfg/modules/django_tasks/service.py +0 -281
- django_cfg/modules/django_tasks/settings.py +0 -107
- /django_cfg/{modules/django_tasks/management → apps/tasks/migrations}/__init__.py +0 -0
- {django_cfg-1.4.107.dist-info → django_cfg-1.4.108.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.107.dist-info → django_cfg-1.4.108.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.107.dist-info → django_cfg-1.4.108.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Django management command for checking task system status.
|
|
3
|
-
|
|
4
|
-
This command provides comprehensive status information about the
|
|
5
|
-
Dramatiq task system, including queue statistics, worker status,
|
|
6
|
-
and configuration details.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import json
|
|
10
|
-
from typing import Any, Dict
|
|
11
|
-
|
|
12
|
-
from django.core.management.base import BaseCommand, CommandError
|
|
13
|
-
|
|
14
|
-
from django_cfg.modules.django_logging import get_logger
|
|
15
|
-
|
|
16
|
-
logger = get_logger('task_status')
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class Command(BaseCommand):
|
|
20
|
-
"""
|
|
21
|
-
Display comprehensive task system status.
|
|
22
|
-
|
|
23
|
-
Shows information about:
|
|
24
|
-
- Task system configuration
|
|
25
|
-
- Redis connection status
|
|
26
|
-
- Queue statistics
|
|
27
|
-
- Worker status
|
|
28
|
-
- Discovered task modules
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
# Web execution metadata
|
|
32
|
-
web_executable = True
|
|
33
|
-
requires_input = False
|
|
34
|
-
is_destructive = False
|
|
35
|
-
|
|
36
|
-
help = "Display task system status and statistics"
|
|
37
|
-
|
|
38
|
-
def add_arguments(self, parser):
|
|
39
|
-
"""Add command line arguments."""
|
|
40
|
-
parser.add_argument(
|
|
41
|
-
"--format",
|
|
42
|
-
choices=["text", "json"],
|
|
43
|
-
default="text",
|
|
44
|
-
help="Output format (default: text)",
|
|
45
|
-
)
|
|
46
|
-
parser.add_argument(
|
|
47
|
-
"--verbose",
|
|
48
|
-
action="store_true",
|
|
49
|
-
help="Show detailed information",
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
def handle(self, *args, **options):
|
|
53
|
-
"""Handle the command execution."""
|
|
54
|
-
logger.info("Starting task_status command")
|
|
55
|
-
try:
|
|
56
|
-
# Import here to avoid issues if dramatiq is not installed
|
|
57
|
-
from django_cfg.modules.django_tasks import get_task_service
|
|
58
|
-
|
|
59
|
-
# Get task service
|
|
60
|
-
task_service = get_task_service()
|
|
61
|
-
|
|
62
|
-
# Get comprehensive health status
|
|
63
|
-
status = task_service.get_health_status()
|
|
64
|
-
|
|
65
|
-
# Format and display output
|
|
66
|
-
if options["format"] == "json":
|
|
67
|
-
self._output_json(status)
|
|
68
|
-
else:
|
|
69
|
-
self._output_text(status, options["verbose"])
|
|
70
|
-
|
|
71
|
-
except ImportError:
|
|
72
|
-
raise CommandError(
|
|
73
|
-
"Dramatiq dependencies not installed. "
|
|
74
|
-
"Install with: pip install django-cfg[tasks]"
|
|
75
|
-
)
|
|
76
|
-
except Exception as e:
|
|
77
|
-
logger.exception("Failed to get task status")
|
|
78
|
-
raise CommandError(f"Failed to get status: {e}")
|
|
79
|
-
|
|
80
|
-
def _output_json(self, status: Dict[str, Any]):
|
|
81
|
-
"""Output status in JSON format."""
|
|
82
|
-
self.stdout.write(json.dumps(status, indent=2, default=str))
|
|
83
|
-
|
|
84
|
-
def _output_text(self, status: Dict[str, Any], verbose: bool):
|
|
85
|
-
"""Output status in human-readable text format."""
|
|
86
|
-
# Header
|
|
87
|
-
self.stdout.write(
|
|
88
|
-
self.style.SUCCESS("=== Django-CFG Task System Status ===")
|
|
89
|
-
)
|
|
90
|
-
self.stdout.write()
|
|
91
|
-
|
|
92
|
-
# Basic status
|
|
93
|
-
enabled = status.get("enabled", False)
|
|
94
|
-
if enabled:
|
|
95
|
-
self.stdout.write(
|
|
96
|
-
self.style.SUCCESS("✓ Task system is ENABLED")
|
|
97
|
-
)
|
|
98
|
-
else:
|
|
99
|
-
self.stdout.write(
|
|
100
|
-
self.style.ERROR("✗ Task system is DISABLED")
|
|
101
|
-
)
|
|
102
|
-
return
|
|
103
|
-
|
|
104
|
-
# Redis connection
|
|
105
|
-
redis_ok = status.get("redis_connection", False)
|
|
106
|
-
if redis_ok:
|
|
107
|
-
self.stdout.write(
|
|
108
|
-
self.style.SUCCESS("✓ Redis connection is OK")
|
|
109
|
-
)
|
|
110
|
-
else:
|
|
111
|
-
self.stdout.write(
|
|
112
|
-
self.style.ERROR("✗ Redis connection FAILED")
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
# Configuration validation
|
|
116
|
-
config_valid = status.get("configuration_valid", False)
|
|
117
|
-
if config_valid:
|
|
118
|
-
self.stdout.write(
|
|
119
|
-
self.style.SUCCESS("✓ Configuration is VALID")
|
|
120
|
-
)
|
|
121
|
-
else:
|
|
122
|
-
self.stdout.write(
|
|
123
|
-
self.style.ERROR("✗ Configuration is INVALID")
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
self.stdout.write()
|
|
127
|
-
|
|
128
|
-
# Configuration details (if verbose)
|
|
129
|
-
if verbose:
|
|
130
|
-
self._show_configuration_details()
|
|
131
|
-
|
|
132
|
-
# Queue statistics
|
|
133
|
-
queues = status.get("queues", [])
|
|
134
|
-
if queues:
|
|
135
|
-
self.stdout.write(
|
|
136
|
-
self.style.SUCCESS("=== Queue Statistics ===")
|
|
137
|
-
)
|
|
138
|
-
for queue in queues:
|
|
139
|
-
name = queue.get("name", "unknown")
|
|
140
|
-
pending = queue.get("pending", 0)
|
|
141
|
-
running = queue.get("running", 0)
|
|
142
|
-
completed = queue.get("completed", 0)
|
|
143
|
-
failed = queue.get("failed", 0)
|
|
144
|
-
|
|
145
|
-
self.stdout.write(f"Queue: {name}")
|
|
146
|
-
self.stdout.write(f" Pending: {pending}")
|
|
147
|
-
self.stdout.write(f" Running: {running}")
|
|
148
|
-
self.stdout.write(f" Completed: {completed}")
|
|
149
|
-
self.stdout.write(f" Failed: {failed}")
|
|
150
|
-
self.stdout.write()
|
|
151
|
-
|
|
152
|
-
# Worker status
|
|
153
|
-
workers = status.get("workers", [])
|
|
154
|
-
if workers:
|
|
155
|
-
self.stdout.write(
|
|
156
|
-
self.style.SUCCESS("=== Worker Status ===")
|
|
157
|
-
)
|
|
158
|
-
for worker in workers:
|
|
159
|
-
worker_id = worker.get("id", "unknown")
|
|
160
|
-
worker_status = worker.get("status", "unknown")
|
|
161
|
-
current_task = worker.get("current_task")
|
|
162
|
-
processed = worker.get("processed_tasks", 0)
|
|
163
|
-
|
|
164
|
-
status_style = (
|
|
165
|
-
self.style.SUCCESS if worker_status == "active"
|
|
166
|
-
else self.style.WARNING if worker_status == "idle"
|
|
167
|
-
else self.style.ERROR
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
self.stdout.write(f"Worker: {worker_id}")
|
|
171
|
-
self.stdout.write(f" Status: {status_style(worker_status)}")
|
|
172
|
-
if current_task:
|
|
173
|
-
self.stdout.write(f" Current Task: {current_task}")
|
|
174
|
-
self.stdout.write(f" Processed: {processed}")
|
|
175
|
-
self.stdout.write()
|
|
176
|
-
else:
|
|
177
|
-
self.stdout.write(
|
|
178
|
-
self.style.WARNING("No active workers found")
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
# Discovered modules
|
|
182
|
-
modules = status.get("discovered_modules", [])
|
|
183
|
-
if modules:
|
|
184
|
-
self.stdout.write(
|
|
185
|
-
self.style.SUCCESS("=== Discovered Task Modules ===")
|
|
186
|
-
)
|
|
187
|
-
for module in modules:
|
|
188
|
-
self.stdout.write(f" - {module}")
|
|
189
|
-
self.stdout.write()
|
|
190
|
-
else:
|
|
191
|
-
self.stdout.write(
|
|
192
|
-
self.style.WARNING("No task modules discovered")
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
# Error information
|
|
196
|
-
if "error" in status:
|
|
197
|
-
self.stdout.write(
|
|
198
|
-
self.style.ERROR(f"Error: {status['error']}")
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
def _show_configuration_details(self):
|
|
202
|
-
"""Show detailed configuration information."""
|
|
203
|
-
try:
|
|
204
|
-
from django_cfg.modules.django_tasks import get_task_service
|
|
205
|
-
|
|
206
|
-
task_service = get_task_service()
|
|
207
|
-
config = task_service.config
|
|
208
|
-
|
|
209
|
-
if not config:
|
|
210
|
-
self.stdout.write(
|
|
211
|
-
self.style.WARNING("Configuration not available")
|
|
212
|
-
)
|
|
213
|
-
return
|
|
214
|
-
|
|
215
|
-
self.stdout.write(
|
|
216
|
-
self.style.SUCCESS("=== Configuration Details ===")
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
# Basic settings
|
|
220
|
-
self.stdout.write(f"Backend: {config.backend}")
|
|
221
|
-
self.stdout.write(f"Enabled: {config.enabled}")
|
|
222
|
-
self.stdout.write(f"Auto-discover: {config.auto_discover_tasks}")
|
|
223
|
-
self.stdout.write()
|
|
224
|
-
|
|
225
|
-
# Dramatiq settings
|
|
226
|
-
dramatiq = config.dramatiq
|
|
227
|
-
self.stdout.write("Dramatiq Configuration:")
|
|
228
|
-
self.stdout.write(f" Redis DB: {dramatiq.redis_db}")
|
|
229
|
-
self.stdout.write(f" Max Retries: {dramatiq.max_retries}")
|
|
230
|
-
self.stdout.write(f" Processes: {dramatiq.processes}")
|
|
231
|
-
self.stdout.write(f" Threads: {dramatiq.threads}")
|
|
232
|
-
self.stdout.write(f" Queues: {', '.join(dramatiq.queues)}")
|
|
233
|
-
self.stdout.write(f" Time Limit: {dramatiq.time_limit_seconds}s")
|
|
234
|
-
self.stdout.write(f" Max Age: {dramatiq.max_age_seconds}s")
|
|
235
|
-
self.stdout.write()
|
|
236
|
-
|
|
237
|
-
# Worker settings
|
|
238
|
-
worker = config.worker
|
|
239
|
-
self.stdout.write("Worker Configuration:")
|
|
240
|
-
self.stdout.write(f" Log Level: {worker.log_level}")
|
|
241
|
-
self.stdout.write(f" Shutdown Timeout: {worker.shutdown_timeout}s")
|
|
242
|
-
self.stdout.write(f" Health Check: {worker.health_check_enabled}")
|
|
243
|
-
if worker.max_memory_mb:
|
|
244
|
-
self.stdout.write(f" Memory Limit: {worker.max_memory_mb}MB")
|
|
245
|
-
self.stdout.write()
|
|
246
|
-
|
|
247
|
-
# Middleware
|
|
248
|
-
if dramatiq.middleware:
|
|
249
|
-
self.stdout.write("Middleware Stack:")
|
|
250
|
-
for middleware in dramatiq.middleware:
|
|
251
|
-
self.stdout.write(f" - {middleware}")
|
|
252
|
-
self.stdout.write()
|
|
253
|
-
|
|
254
|
-
except Exception as e:
|
|
255
|
-
self.stdout.write(
|
|
256
|
-
self.style.ERROR(f"Failed to show configuration: {e}")
|
|
257
|
-
)
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Django-CFG Task Service.
|
|
3
|
-
|
|
4
|
-
Main DjangoTasks class for Dramatiq integration.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import logging
|
|
8
|
-
from typing import Any, Dict, List, Optional
|
|
9
|
-
from urllib.parse import urlparse
|
|
10
|
-
|
|
11
|
-
from django_cfg.models.django.constance import ConstanceField
|
|
12
|
-
from django_cfg.models.tasks import TaskConfig, validate_task_config
|
|
13
|
-
|
|
14
|
-
from ..base import BaseCfgModule
|
|
15
|
-
|
|
16
|
-
# Django imports
|
|
17
|
-
try:
|
|
18
|
-
from django.apps import apps
|
|
19
|
-
except ImportError:
|
|
20
|
-
apps = None
|
|
21
|
-
|
|
22
|
-
# Optional imports
|
|
23
|
-
try:
|
|
24
|
-
import dramatiq
|
|
25
|
-
except ImportError:
|
|
26
|
-
dramatiq = None
|
|
27
|
-
|
|
28
|
-
try:
|
|
29
|
-
import redis
|
|
30
|
-
except ImportError:
|
|
31
|
-
redis = None
|
|
32
|
-
|
|
33
|
-
logger = logging.getLogger(__name__)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class DjangoTasks(BaseCfgModule):
|
|
37
|
-
"""
|
|
38
|
-
Simplified Django-CFG task service.
|
|
39
|
-
|
|
40
|
-
Focuses on essential functionality:
|
|
41
|
-
- Configuration management
|
|
42
|
-
- Task discovery
|
|
43
|
-
- Health checks
|
|
44
|
-
- Constance integration
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
def __init__(self):
|
|
48
|
-
super().__init__()
|
|
49
|
-
self._config: Optional[TaskConfig] = None
|
|
50
|
-
self._redis_url: Optional[str] = None
|
|
51
|
-
|
|
52
|
-
@property
|
|
53
|
-
def config(self) -> Optional[TaskConfig]:
|
|
54
|
-
"""Get task configuration (lazy-loaded)."""
|
|
55
|
-
if self._config is None:
|
|
56
|
-
try:
|
|
57
|
-
django_config = self.get_config()
|
|
58
|
-
if django_config and hasattr(django_config, 'tasks'):
|
|
59
|
-
self._config = django_config.tasks
|
|
60
|
-
logger.debug(f"Loaded TaskConfig: enabled={self._config.enabled if self._config else False}")
|
|
61
|
-
else:
|
|
62
|
-
# Fallback: try direct import
|
|
63
|
-
try:
|
|
64
|
-
from api.config import config as api_config
|
|
65
|
-
if hasattr(api_config, 'tasks') and api_config.tasks:
|
|
66
|
-
self._config = api_config.tasks
|
|
67
|
-
logger.debug(f"Loaded TaskConfig from api.config: enabled={self._config.enabled}")
|
|
68
|
-
except ImportError:
|
|
69
|
-
logger.debug("Could not import api.config")
|
|
70
|
-
except Exception as e:
|
|
71
|
-
logger.warning(f"Failed to get task config: {e}")
|
|
72
|
-
|
|
73
|
-
return self._config
|
|
74
|
-
|
|
75
|
-
def is_enabled(self) -> bool:
|
|
76
|
-
"""Check if task system is enabled and properly configured."""
|
|
77
|
-
if not self.config or not self.config.enabled:
|
|
78
|
-
return False
|
|
79
|
-
|
|
80
|
-
if dramatiq is None:
|
|
81
|
-
logger.warning("Dramatiq not available")
|
|
82
|
-
return False
|
|
83
|
-
|
|
84
|
-
return True
|
|
85
|
-
|
|
86
|
-
def get_redis_url(self) -> Optional[str]:
|
|
87
|
-
"""Get Redis URL using the same logic as Dramatiq settings generation."""
|
|
88
|
-
if self._redis_url is None:
|
|
89
|
-
config = self.get_config()
|
|
90
|
-
|
|
91
|
-
if not config:
|
|
92
|
-
raise RuntimeError("No Django-CFG configuration available")
|
|
93
|
-
|
|
94
|
-
# Get Redis URL from cache config
|
|
95
|
-
if hasattr(config, 'cache_default') and config.cache_default:
|
|
96
|
-
self._redis_url = getattr(config.cache_default, 'redis_url', None)
|
|
97
|
-
if self._redis_url:
|
|
98
|
-
logger.debug(f"Got Redis URL from cache config: {self._redis_url}")
|
|
99
|
-
return self._redis_url
|
|
100
|
-
|
|
101
|
-
# If no cache_default, try Django cache settings
|
|
102
|
-
try:
|
|
103
|
-
from django.conf import settings
|
|
104
|
-
if hasattr(settings, 'CACHES') and 'default' in settings.CACHES:
|
|
105
|
-
cache_config = settings.CACHES['default']
|
|
106
|
-
if cache_config.get('BACKEND') == 'django_redis.cache.RedisCache':
|
|
107
|
-
self._redis_url = cache_config.get('LOCATION')
|
|
108
|
-
if self._redis_url:
|
|
109
|
-
logger.debug(f"Got Redis URL from Django cache settings: {self._redis_url}")
|
|
110
|
-
return self._redis_url
|
|
111
|
-
except Exception as e:
|
|
112
|
-
logger.debug(f"Could not get Redis URL from Django settings: {e}")
|
|
113
|
-
|
|
114
|
-
# Try DRAMATIQ_BROKER settings
|
|
115
|
-
try:
|
|
116
|
-
from django.conf import settings
|
|
117
|
-
if hasattr(settings, 'DRAMATIQ_BROKER'):
|
|
118
|
-
dramatiq_config = settings.DRAMATIQ_BROKER
|
|
119
|
-
if isinstance(dramatiq_config, dict) and 'OPTIONS' in dramatiq_config:
|
|
120
|
-
self._redis_url = dramatiq_config['OPTIONS'].get('url')
|
|
121
|
-
if self._redis_url:
|
|
122
|
-
logger.debug(f"Got Redis URL from DRAMATIQ_BROKER settings: {self._redis_url}")
|
|
123
|
-
return self._redis_url
|
|
124
|
-
except Exception as e:
|
|
125
|
-
logger.debug(f"Could not get Redis URL from DRAMATIQ_BROKER settings: {e}")
|
|
126
|
-
|
|
127
|
-
raise RuntimeError("No Redis URL found in cache configuration, Django settings, or DRAMATIQ_BROKER")
|
|
128
|
-
|
|
129
|
-
return self._redis_url
|
|
130
|
-
|
|
131
|
-
def get_redis_client(self):
|
|
132
|
-
"""Get Redis client instance."""
|
|
133
|
-
redis_url = self.get_redis_url()
|
|
134
|
-
if not redis_url or redis is None:
|
|
135
|
-
return None
|
|
136
|
-
|
|
137
|
-
try:
|
|
138
|
-
parsed = urlparse(redis_url)
|
|
139
|
-
|
|
140
|
-
# Extract database from URL path
|
|
141
|
-
db = 1 # Default
|
|
142
|
-
if parsed.path and parsed.path != "/":
|
|
143
|
-
try:
|
|
144
|
-
db = int(parsed.path.lstrip('/'))
|
|
145
|
-
except ValueError:
|
|
146
|
-
pass
|
|
147
|
-
elif self.config and self.config.dramatiq:
|
|
148
|
-
db = self.config.dramatiq.redis_db
|
|
149
|
-
|
|
150
|
-
logger.debug(f"Using Redis DB: {db} from URL: {redis_url}")
|
|
151
|
-
|
|
152
|
-
return redis.Redis(
|
|
153
|
-
host=parsed.hostname or 'localhost',
|
|
154
|
-
port=parsed.port or 6379,
|
|
155
|
-
db=db,
|
|
156
|
-
password=parsed.password,
|
|
157
|
-
socket_timeout=5
|
|
158
|
-
)
|
|
159
|
-
except Exception as e:
|
|
160
|
-
logger.error(f"Failed to create Redis client: {e}")
|
|
161
|
-
return None
|
|
162
|
-
|
|
163
|
-
def check_redis_connection(self) -> bool:
|
|
164
|
-
"""Check if Redis connection is available."""
|
|
165
|
-
redis_client = self.get_redis_client()
|
|
166
|
-
if not redis_client:
|
|
167
|
-
return False
|
|
168
|
-
|
|
169
|
-
try:
|
|
170
|
-
redis_client.ping()
|
|
171
|
-
return True
|
|
172
|
-
except Exception as e:
|
|
173
|
-
logger.error(f"Redis connection failed: {e}")
|
|
174
|
-
return False
|
|
175
|
-
|
|
176
|
-
def validate_configuration(self) -> bool:
|
|
177
|
-
"""Validate complete task system configuration."""
|
|
178
|
-
if not self.config:
|
|
179
|
-
logger.error("Task configuration not available")
|
|
180
|
-
return False
|
|
181
|
-
|
|
182
|
-
redis_url = self.get_redis_url()
|
|
183
|
-
if not redis_url:
|
|
184
|
-
logger.error("Redis URL not configured")
|
|
185
|
-
return False
|
|
186
|
-
|
|
187
|
-
return validate_task_config(self.config, redis_url)
|
|
188
|
-
|
|
189
|
-
def discover_tasks(self) -> List[str]:
|
|
190
|
-
"""Discover task modules in Django apps."""
|
|
191
|
-
if not self.config or not self.config.auto_discover_tasks:
|
|
192
|
-
return []
|
|
193
|
-
|
|
194
|
-
discovered = []
|
|
195
|
-
|
|
196
|
-
if apps is None:
|
|
197
|
-
logger.warning("Django apps not available")
|
|
198
|
-
return []
|
|
199
|
-
|
|
200
|
-
try:
|
|
201
|
-
for app_config in apps.get_app_configs():
|
|
202
|
-
for module_name in self.config.task_modules:
|
|
203
|
-
module_path = f"{app_config.name}.{module_name}"
|
|
204
|
-
try:
|
|
205
|
-
__import__(module_path)
|
|
206
|
-
discovered.append(module_path)
|
|
207
|
-
logger.debug(f"Discovered task module: {module_path}")
|
|
208
|
-
except ImportError:
|
|
209
|
-
pass
|
|
210
|
-
except Exception as e:
|
|
211
|
-
logger.warning(f"Error importing task module {module_path}: {e}")
|
|
212
|
-
except Exception as e:
|
|
213
|
-
logger.error(f"Task discovery failed: {e}")
|
|
214
|
-
|
|
215
|
-
return discovered
|
|
216
|
-
|
|
217
|
-
def get_constance_fields(self) -> List[ConstanceField]:
|
|
218
|
-
"""Get Constance fields for Dramatiq configuration."""
|
|
219
|
-
if not self.is_enabled():
|
|
220
|
-
return []
|
|
221
|
-
|
|
222
|
-
fields = [
|
|
223
|
-
ConstanceField(
|
|
224
|
-
name="DRAMATIQ_WORKER_PROCESSES",
|
|
225
|
-
default=self.config.dramatiq.processes if self.config else 2,
|
|
226
|
-
help_text="Number of worker processes for Dramatiq",
|
|
227
|
-
field_type="int",
|
|
228
|
-
group="Tasks",
|
|
229
|
-
),
|
|
230
|
-
ConstanceField(
|
|
231
|
-
name="DRAMATIQ_WORKER_THREADS",
|
|
232
|
-
default=self.config.dramatiq.threads if self.config else 4,
|
|
233
|
-
help_text="Number of threads per worker process",
|
|
234
|
-
field_type="int",
|
|
235
|
-
group="Tasks",
|
|
236
|
-
),
|
|
237
|
-
ConstanceField(
|
|
238
|
-
name="DRAMATIQ_MAX_RETRIES",
|
|
239
|
-
default=3,
|
|
240
|
-
help_text="Maximum number of retries for failed tasks",
|
|
241
|
-
field_type="int",
|
|
242
|
-
group="Tasks",
|
|
243
|
-
),
|
|
244
|
-
ConstanceField(
|
|
245
|
-
name="DRAMATIQ_TASK_TIMEOUT",
|
|
246
|
-
default=600,
|
|
247
|
-
help_text="Task timeout in seconds (10 minutes default)",
|
|
248
|
-
field_type="int",
|
|
249
|
-
group="Tasks",
|
|
250
|
-
),
|
|
251
|
-
ConstanceField(
|
|
252
|
-
name="DRAMATIQ_PROMETHEUS_ENABLED",
|
|
253
|
-
default=int(self.config.dramatiq.prometheus_enabled if self.config else False),
|
|
254
|
-
help_text="Enable Prometheus metrics for Dramatiq (0=disabled, 1=enabled)",
|
|
255
|
-
field_type="bool",
|
|
256
|
-
group="Tasks",
|
|
257
|
-
required=False,
|
|
258
|
-
),
|
|
259
|
-
]
|
|
260
|
-
|
|
261
|
-
logger.debug(f"Generated {len(fields)} Constance fields for Dramatiq")
|
|
262
|
-
return fields
|
|
263
|
-
|
|
264
|
-
def get_health_status(self) -> Dict[str, Any]:
|
|
265
|
-
"""Get comprehensive health status of task system."""
|
|
266
|
-
status = {
|
|
267
|
-
"enabled": self.is_enabled(),
|
|
268
|
-
"redis_connection": False,
|
|
269
|
-
"configuration_valid": False,
|
|
270
|
-
"discovered_modules": [],
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if self.is_enabled():
|
|
274
|
-
status["redis_connection"] = self.check_redis_connection()
|
|
275
|
-
status["configuration_valid"] = self.validate_configuration()
|
|
276
|
-
status["discovered_modules"] = self.discover_tasks()
|
|
277
|
-
|
|
278
|
-
return status
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
__all__ = ["DjangoTasks"]
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Dramatiq settings generation.
|
|
3
|
-
|
|
4
|
-
Functions to generate Dramatiq settings from DjangoConfig.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import logging
|
|
8
|
-
from typing import Any, Dict, Optional
|
|
9
|
-
|
|
10
|
-
logger = logging.getLogger(__name__)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def generate_dramatiq_settings_from_config(config=None) -> Optional[Dict[str, Any]]:
|
|
14
|
-
"""
|
|
15
|
-
Generate Dramatiq settings from DjangoConfig instance.
|
|
16
|
-
|
|
17
|
-
Args:
|
|
18
|
-
config: DjangoConfig instance (optional, will auto-discover if not provided)
|
|
19
|
-
|
|
20
|
-
Returns:
|
|
21
|
-
Dict[str, Any]: Dramatiq settings dictionary or None if not enabled
|
|
22
|
-
"""
|
|
23
|
-
# If config provided, use it directly
|
|
24
|
-
if config is not None:
|
|
25
|
-
try:
|
|
26
|
-
if not hasattr(config, "tasks") or not config.tasks or not config.tasks.enabled:
|
|
27
|
-
return None
|
|
28
|
-
|
|
29
|
-
# Get Redis URL from cache configuration
|
|
30
|
-
redis_url = None
|
|
31
|
-
if config.cache_default and hasattr(config.cache_default, 'redis_url'):
|
|
32
|
-
redis_url = config.cache_default.redis_url
|
|
33
|
-
elif config.cache_default and hasattr(config.cache_default, 'location'):
|
|
34
|
-
redis_url = config.cache_default.location
|
|
35
|
-
else:
|
|
36
|
-
redis_url = "redis://localhost:6379"
|
|
37
|
-
|
|
38
|
-
if redis_url:
|
|
39
|
-
dramatiq_settings = config.tasks.get_dramatiq_settings(redis_url)
|
|
40
|
-
logger.debug(f"Generated Dramatiq settings with Redis URL: {redis_url}")
|
|
41
|
-
return dramatiq_settings
|
|
42
|
-
else:
|
|
43
|
-
logger.warning("Tasks enabled but no Redis URL available for Dramatiq")
|
|
44
|
-
return None
|
|
45
|
-
|
|
46
|
-
except Exception as e:
|
|
47
|
-
logger.error(f"Failed to generate Dramatiq settings: {e}")
|
|
48
|
-
return None
|
|
49
|
-
|
|
50
|
-
# Auto-discover config if not provided
|
|
51
|
-
try:
|
|
52
|
-
from ..base import BaseCfgModule
|
|
53
|
-
|
|
54
|
-
base_module = BaseCfgModule()
|
|
55
|
-
config = base_module.get_config()
|
|
56
|
-
|
|
57
|
-
if not config or not hasattr(config, 'tasks') or not config.tasks or not config.tasks.enabled:
|
|
58
|
-
return None
|
|
59
|
-
|
|
60
|
-
# Get Redis URL from cache config or environment
|
|
61
|
-
redis_url = None
|
|
62
|
-
if hasattr(config, 'cache_default') and config.cache_default:
|
|
63
|
-
redis_url = getattr(config.cache_default, 'redis_url', None)
|
|
64
|
-
|
|
65
|
-
if not redis_url:
|
|
66
|
-
# Fallback to environment or default
|
|
67
|
-
import os
|
|
68
|
-
redis_url = os.getenv('REDIS_URL', 'redis://localhost:6379/1')
|
|
69
|
-
|
|
70
|
-
# Generate Dramatiq settings
|
|
71
|
-
dramatiq_settings = config.tasks.get_dramatiq_settings(redis_url)
|
|
72
|
-
|
|
73
|
-
# Ensure we only use Redis broker (no RabbitMQ)
|
|
74
|
-
if 'DRAMATIQ_BROKER' in dramatiq_settings:
|
|
75
|
-
dramatiq_settings['DRAMATIQ_BROKER']['BROKER'] = 'dramatiq.brokers.redis.RedisBroker'
|
|
76
|
-
|
|
77
|
-
logger.info(f"✅ Generated Dramatiq settings with Redis broker and {len(config.tasks.dramatiq.queues)} queues")
|
|
78
|
-
return dramatiq_settings
|
|
79
|
-
|
|
80
|
-
except Exception as e:
|
|
81
|
-
logger.error(f"Failed to generate Dramatiq settings: {e}")
|
|
82
|
-
return None
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def extend_constance_config_with_tasks():
|
|
86
|
-
"""Extend Constance configuration with Dramatiq task fields if tasks are enabled."""
|
|
87
|
-
try:
|
|
88
|
-
from .factory import get_task_service
|
|
89
|
-
|
|
90
|
-
service = get_task_service()
|
|
91
|
-
if not service.is_enabled():
|
|
92
|
-
logger.debug("Task system not enabled, skipping Constance extension")
|
|
93
|
-
return []
|
|
94
|
-
|
|
95
|
-
fields = service.get_constance_fields()
|
|
96
|
-
logger.info(f"🔧 Extended Constance with {len(fields)} task configuration fields")
|
|
97
|
-
return fields
|
|
98
|
-
|
|
99
|
-
except Exception as e:
|
|
100
|
-
logger.error(f"Failed to extend Constance config with tasks: {e}")
|
|
101
|
-
return []
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
__all__ = [
|
|
105
|
-
"generate_dramatiq_settings_from_config",
|
|
106
|
-
"extend_constance_config_with_tasks",
|
|
107
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|