django-cfg 1.5.1__py3-none-any.whl → 1.5.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 django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/dashboard/TRANSACTION_FIX.md +73 -0
- django_cfg/apps/dashboard/serializers/__init__.py +0 -12
- django_cfg/apps/dashboard/serializers/activity.py +1 -1
- django_cfg/apps/dashboard/services/__init__.py +0 -2
- django_cfg/apps/dashboard/services/charts_service.py +4 -3
- django_cfg/apps/dashboard/services/statistics_service.py +11 -2
- django_cfg/apps/dashboard/services/system_health_service.py +64 -106
- django_cfg/apps/dashboard/urls.py +0 -2
- django_cfg/apps/dashboard/views/__init__.py +0 -2
- django_cfg/apps/dashboard/views/commands_views.py +3 -6
- django_cfg/apps/dashboard/views/overview_views.py +14 -13
- django_cfg/apps/knowbase/apps.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +2 -3
- django_cfg/apps/newsletter/admin/newsletter_admin.py +12 -11
- django_cfg/apps/rq/__init__.py +9 -0
- django_cfg/apps/rq/apps.py +80 -0
- django_cfg/apps/rq/management/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/__init__.py +1 -0
- django_cfg/apps/rq/management/commands/rqscheduler.py +31 -0
- django_cfg/apps/rq/management/commands/rqstats.py +33 -0
- django_cfg/apps/rq/management/commands/rqworker.py +31 -0
- django_cfg/apps/rq/management/commands/rqworker_pool.py +27 -0
- django_cfg/apps/rq/serializers/__init__.py +40 -0
- django_cfg/apps/rq/serializers/health.py +60 -0
- django_cfg/apps/rq/serializers/job.py +100 -0
- django_cfg/apps/rq/serializers/queue.py +80 -0
- django_cfg/apps/rq/serializers/schedule.py +178 -0
- django_cfg/apps/rq/serializers/testing.py +139 -0
- django_cfg/apps/rq/serializers/worker.py +58 -0
- django_cfg/apps/rq/services/__init__.py +25 -0
- django_cfg/apps/rq/services/config_helper.py +233 -0
- django_cfg/apps/rq/services/models/README.md +417 -0
- django_cfg/apps/rq/services/models/__init__.py +30 -0
- django_cfg/apps/rq/services/models/event.py +123 -0
- django_cfg/apps/rq/services/models/job.py +99 -0
- django_cfg/apps/rq/services/models/queue.py +92 -0
- django_cfg/apps/rq/services/models/worker.py +104 -0
- django_cfg/apps/rq/services/rq_converters.py +183 -0
- django_cfg/apps/rq/tasks/__init__.py +23 -0
- django_cfg/apps/rq/tasks/demo_tasks.py +284 -0
- django_cfg/apps/rq/urls.py +54 -0
- django_cfg/apps/rq/views/__init__.py +19 -0
- django_cfg/apps/rq/views/jobs.py +882 -0
- django_cfg/apps/rq/views/monitoring.py +248 -0
- django_cfg/apps/rq/views/queues.py +261 -0
- django_cfg/apps/rq/views/schedule.py +400 -0
- django_cfg/apps/rq/views/testing.py +761 -0
- django_cfg/apps/rq/views/workers.py +195 -0
- django_cfg/apps/urls.py +6 -7
- django_cfg/core/base/config_model.py +10 -26
- django_cfg/core/builders/apps_builder.py +4 -11
- django_cfg/core/generation/integration_generators/__init__.py +3 -6
- django_cfg/core/generation/integration_generators/django_rq.py +80 -0
- django_cfg/core/generation/orchestrator.py +9 -19
- django_cfg/core/integration/display/startup.py +6 -20
- django_cfg/mixins/__init__.py +2 -0
- django_cfg/mixins/superadmin_api.py +59 -0
- django_cfg/models/__init__.py +3 -3
- django_cfg/models/django/__init__.py +3 -3
- django_cfg/models/django/django_rq.py +621 -0
- django_cfg/models/django/revolution_legacy.py +1 -1
- django_cfg/modules/base.py +4 -6
- django_cfg/modules/django_admin/config/background_task_config.py +4 -4
- django_cfg/modules/django_admin/utils/html/composition.py +9 -2
- django_cfg/modules/django_unfold/navigation.py +1 -26
- django_cfg/pyproject.toml +4 -4
- django_cfg/registry/core.py +4 -7
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/constance/includes/results_list.html +73 -0
- django_cfg/templates/admin/index.html +187 -62
- django_cfg/templatetags/django_cfg.py +61 -1
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/METADATA +5 -6
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/RECORD +77 -82
- django_cfg/apps/dashboard/permissions.py +0 -48
- django_cfg/apps/dashboard/serializers/django_q2.py +0 -50
- django_cfg/apps/dashboard/services/django_q2_service.py +0 -159
- django_cfg/apps/dashboard/views/django_q2_views.py +0 -79
- django_cfg/apps/tasks/__init__.py +0 -64
- django_cfg/apps/tasks/admin/__init__.py +0 -4
- django_cfg/apps/tasks/admin/config.py +0 -98
- django_cfg/apps/tasks/admin/task_log.py +0 -238
- django_cfg/apps/tasks/apps.py +0 -15
- django_cfg/apps/tasks/filters/__init__.py +0 -10
- django_cfg/apps/tasks/filters/task_log.py +0 -121
- django_cfg/apps/tasks/migrations/0001_initial.py +0 -196
- django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +0 -16
- django_cfg/apps/tasks/migrations/__init__.py +0 -0
- django_cfg/apps/tasks/models/__init__.py +0 -4
- django_cfg/apps/tasks/models/task_log.py +0 -246
- django_cfg/apps/tasks/serializers/__init__.py +0 -28
- django_cfg/apps/tasks/serializers/task_log.py +0 -249
- django_cfg/apps/tasks/services/__init__.py +0 -10
- django_cfg/apps/tasks/services/client/__init__.py +0 -7
- django_cfg/apps/tasks/services/client/client.py +0 -234
- django_cfg/apps/tasks/services/config_helper.py +0 -63
- django_cfg/apps/tasks/services/sync.py +0 -204
- django_cfg/apps/tasks/urls.py +0 -16
- django_cfg/apps/tasks/views/__init__.py +0 -10
- django_cfg/apps/tasks/views/task_log.py +0 -41
- django_cfg/apps/tasks/views/task_log_base.py +0 -41
- django_cfg/apps/tasks/views/task_log_overview.py +0 -100
- django_cfg/apps/tasks/views/task_log_related.py +0 -41
- django_cfg/apps/tasks/views/task_log_stats.py +0 -91
- django_cfg/apps/tasks/views/task_log_timeline.py +0 -81
- django_cfg/core/generation/integration_generators/django_q2.py +0 -133
- django_cfg/core/generation/integration_generators/tasks.py +0 -88
- django_cfg/models/django/django_q2.py +0 -514
- django_cfg/models/tasks/__init__.py +0 -49
- django_cfg/models/tasks/backends.py +0 -122
- django_cfg/models/tasks/config.py +0 -209
- django_cfg/models/tasks/utils.py +0 -162
- django_cfg/modules/django_q2/README.md +0 -140
- django_cfg/modules/django_q2/__init__.py +0 -8
- django_cfg/modules/django_q2/apps.py +0 -107
- django_cfg/modules/django_q2/management/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/__init__.py +0 -0
- django_cfg/modules/django_q2/management/commands/sync_django_q_schedules.py +0 -74
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.1.dist-info → django_cfg-1.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,514 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Django-Q2 Configuration for django-cfg.
|
|
3
|
-
|
|
4
|
-
Type-safe configuration for django-q2 (modern fork of django-q) with automatic
|
|
5
|
-
Django settings generation and support for scheduled tasks.
|
|
6
|
-
|
|
7
|
-
Django-Q2 is the actively maintained fork: https://github.com/django-q2/django-q2
|
|
8
|
-
|
|
9
|
-
Features:
|
|
10
|
-
- Type-safe scheduled task definitions
|
|
11
|
-
- Django management command support
|
|
12
|
-
- Cron-style and interval-based scheduling
|
|
13
|
-
- Redis/database broker configuration
|
|
14
|
-
- Queue management
|
|
15
|
-
- Task result storage
|
|
16
|
-
- Monitoring and logging
|
|
17
|
-
- Built-in admin interface
|
|
18
|
-
- Python 3.8+ and Django 3.2+ support
|
|
19
|
-
|
|
20
|
-
Migration from django-crontab to django-q2:
|
|
21
|
-
1. Replace django-crontab with django-q2 in requirements
|
|
22
|
-
2. Convert CrontabConfig to DjangoQ2Config
|
|
23
|
-
3. Cron expressions stay the same
|
|
24
|
-
4. Add additional features like intervals, hooks, retries
|
|
25
|
-
5. Built-in admin interface for monitoring
|
|
26
|
-
6. No need to run 'crontab add' - uses Django's own scheduler
|
|
27
|
-
|
|
28
|
-
Example:
|
|
29
|
-
```python
|
|
30
|
-
# Old django-crontab
|
|
31
|
-
crontab_config = CrontabConfig(
|
|
32
|
-
jobs=[
|
|
33
|
-
CrontabJobConfig(
|
|
34
|
-
name="Sync balances",
|
|
35
|
-
minute="0",
|
|
36
|
-
hour="*/1", # Every hour
|
|
37
|
-
command="sync_account_balances",
|
|
38
|
-
)
|
|
39
|
-
]
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
# New django-q2
|
|
43
|
-
django_q2_config = DjangoQ2Config(
|
|
44
|
-
schedules=[
|
|
45
|
-
DjangoQ2ScheduleConfig(
|
|
46
|
-
name="Sync balances",
|
|
47
|
-
schedule_type="hourly", # Simpler!
|
|
48
|
-
command="sync_account_balances",
|
|
49
|
-
)
|
|
50
|
-
]
|
|
51
|
-
)
|
|
52
|
-
```
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
from typing import Any, Dict, List, Literal, Optional, Union
|
|
56
|
-
|
|
57
|
-
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class DjangoQ2ScheduleConfig(BaseModel):
|
|
61
|
-
"""
|
|
62
|
-
Configuration for a single Django-Q2 scheduled task.
|
|
63
|
-
|
|
64
|
-
Supports both cron-style and interval-based scheduling.
|
|
65
|
-
|
|
66
|
-
Schedule types:
|
|
67
|
-
- cron: Traditional cron expression (e.g., "0 0 * * *")
|
|
68
|
-
- minutes: Every N minutes (e.g., minutes=15)
|
|
69
|
-
- hourly: Every hour (at minute 0)
|
|
70
|
-
- daily: Every day (at midnight)
|
|
71
|
-
- weekly: Every week (Sunday at midnight)
|
|
72
|
-
- monthly: Every month (1st at midnight)
|
|
73
|
-
- yearly: Every year (Jan 1st at midnight)
|
|
74
|
-
- once: Run once at next scheduled time
|
|
75
|
-
"""
|
|
76
|
-
|
|
77
|
-
model_config = ConfigDict(
|
|
78
|
-
validate_assignment=True,
|
|
79
|
-
extra="forbid",
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
# Task identification
|
|
83
|
-
name: str = Field(
|
|
84
|
-
...,
|
|
85
|
-
description="Human-readable task name (unique identifier)",
|
|
86
|
-
min_length=1,
|
|
87
|
-
max_length=100,
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
# Schedule type
|
|
91
|
-
schedule_type: Literal["cron", "minutes", "hourly", "daily", "weekly", "monthly", "yearly", "once"] = Field(
|
|
92
|
-
default="cron",
|
|
93
|
-
description="Schedule type: cron, minutes, hourly, daily, weekly, monthly, yearly, once",
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
# Cron-style schedule (when schedule_type="cron")
|
|
97
|
-
cron: Optional[str] = Field(
|
|
98
|
-
default=None,
|
|
99
|
-
description="Cron expression (e.g., '0 0 * * *' for daily at midnight)",
|
|
100
|
-
pattern=r"^[\d\*\-\,\/\s]+$",
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
# Interval-based schedule (when schedule_type="minutes")
|
|
104
|
-
minutes: Optional[int] = Field(
|
|
105
|
-
default=None,
|
|
106
|
-
ge=1,
|
|
107
|
-
le=525600, # Max 1 year in minutes
|
|
108
|
-
description="Run every N minutes (when schedule_type='minutes')",
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
# Task execution configuration
|
|
112
|
-
func: Optional[str] = Field(
|
|
113
|
-
default=None,
|
|
114
|
-
description="Function path (e.g., 'django.core.management.call_command' or 'myapp.tasks.my_task'). Auto-set when 'command' is provided.",
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
# Function arguments
|
|
118
|
-
args: Optional[List[Any]] = Field(
|
|
119
|
-
default=None,
|
|
120
|
-
description="Positional arguments for the function",
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
kwargs: Optional[Dict[str, Any]] = Field(
|
|
124
|
-
default=None,
|
|
125
|
-
description="Keyword arguments for the function",
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
# Management command support (shortcut)
|
|
129
|
-
command: Optional[str] = Field(
|
|
130
|
-
default=None,
|
|
131
|
-
description="Django management command name (auto-sets func to call_command)",
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
command_args: Optional[List[str]] = Field(
|
|
135
|
-
default=None,
|
|
136
|
-
description="Management command arguments",
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
command_kwargs: Optional[Dict[str, Any]] = Field(
|
|
140
|
-
default=None,
|
|
141
|
-
description="Management command keyword arguments",
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
# Task options
|
|
145
|
-
enabled: bool = Field(
|
|
146
|
-
default=True,
|
|
147
|
-
description="Whether task is enabled",
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
queue: Optional[str] = Field(
|
|
151
|
-
default=None,
|
|
152
|
-
description="Queue name (None = default queue)",
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
timeout: Optional[int] = Field(
|
|
156
|
-
default=None,
|
|
157
|
-
ge=1,
|
|
158
|
-
le=86400, # Max 24 hours
|
|
159
|
-
description="Task timeout in seconds",
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
repeats: int = Field(
|
|
163
|
-
default=-1,
|
|
164
|
-
description="Number of times to repeat (-1 = infinite)",
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
hook: Optional[str] = Field(
|
|
168
|
-
default=None,
|
|
169
|
-
description="Hook function to call after task completion",
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
cluster: Optional[str] = Field(
|
|
173
|
-
default=None,
|
|
174
|
-
description="Cluster name (for multi-cluster setups)",
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
def model_post_init(self, __context: Any) -> None:
|
|
178
|
-
"""Validate and setup configuration after initialization."""
|
|
179
|
-
# Validate that either func or command is provided
|
|
180
|
-
if not self.func and not self.command:
|
|
181
|
-
raise ValueError("Either 'func' or 'command' must be provided")
|
|
182
|
-
|
|
183
|
-
# Setup management command
|
|
184
|
-
if self.command:
|
|
185
|
-
self.func = "django.core.management.call_command"
|
|
186
|
-
args = [self.command]
|
|
187
|
-
if self.command_args:
|
|
188
|
-
args.extend(self.command_args)
|
|
189
|
-
self.args = args
|
|
190
|
-
if self.command_kwargs:
|
|
191
|
-
self.kwargs = self.command_kwargs
|
|
192
|
-
|
|
193
|
-
# Validate schedule configuration
|
|
194
|
-
if self.schedule_type == "cron" and not self.cron:
|
|
195
|
-
raise ValueError("'cron' must be set when schedule_type is 'cron'")
|
|
196
|
-
|
|
197
|
-
if self.schedule_type == "minutes" and not self.minutes:
|
|
198
|
-
raise ValueError("'minutes' must be set when schedule_type is 'minutes'")
|
|
199
|
-
|
|
200
|
-
def to_django_q_format(self) -> Dict[str, Any]:
|
|
201
|
-
"""
|
|
202
|
-
Convert to Django-Q Schedule format.
|
|
203
|
-
|
|
204
|
-
Returns:
|
|
205
|
-
Dictionary for ORM.create() or schedule creation
|
|
206
|
-
"""
|
|
207
|
-
from django.utils import timezone
|
|
208
|
-
from datetime import timedelta
|
|
209
|
-
|
|
210
|
-
# Map our schedule types to Django-Q2 constants
|
|
211
|
-
type_mapping = {
|
|
212
|
-
"once": "O",
|
|
213
|
-
"minutes": "I",
|
|
214
|
-
"hourly": "H",
|
|
215
|
-
"daily": "D",
|
|
216
|
-
"weekly": "W",
|
|
217
|
-
"monthly": "M",
|
|
218
|
-
"yearly": "Y",
|
|
219
|
-
"cron": "C",
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
config = {
|
|
223
|
-
"name": self.name,
|
|
224
|
-
"func": self.func,
|
|
225
|
-
"schedule_type": type_mapping.get(self.schedule_type, self.schedule_type.upper()),
|
|
226
|
-
# Set next_run to NOW + 10 seconds for immediate execution on qcluster start
|
|
227
|
-
"next_run": timezone.now() + timedelta(seconds=10),
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
# Convert args list to tuple for Django-Q2 scheduler compatibility
|
|
231
|
-
# Django-Q2 scheduler.py:66-69 expects tuple format, not list
|
|
232
|
-
# If list is provided, scheduler wraps it: (list,) instead of converting list -> tuple
|
|
233
|
-
if self.args:
|
|
234
|
-
config["args"] = tuple(self.args) if isinstance(self.args, list) else self.args
|
|
235
|
-
|
|
236
|
-
if self.kwargs:
|
|
237
|
-
config["kwargs"] = self.kwargs
|
|
238
|
-
|
|
239
|
-
if self.schedule_type == "cron" and self.cron:
|
|
240
|
-
config["cron"] = self.cron
|
|
241
|
-
|
|
242
|
-
if self.schedule_type == "minutes" and self.minutes:
|
|
243
|
-
config["minutes"] = self.minutes
|
|
244
|
-
|
|
245
|
-
if self.queue:
|
|
246
|
-
config["queue"] = self.queue
|
|
247
|
-
|
|
248
|
-
if self.timeout:
|
|
249
|
-
config["timeout"] = self.timeout
|
|
250
|
-
|
|
251
|
-
if self.repeats != -1:
|
|
252
|
-
config["repeats"] = self.repeats
|
|
253
|
-
|
|
254
|
-
if self.hook:
|
|
255
|
-
config["hook"] = self.hook
|
|
256
|
-
|
|
257
|
-
if self.cluster:
|
|
258
|
-
config["cluster"] = self.cluster
|
|
259
|
-
|
|
260
|
-
return config
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
class DjangoQ2Config(BaseModel):
|
|
264
|
-
"""
|
|
265
|
-
Complete Django-Q2 configuration container.
|
|
266
|
-
|
|
267
|
-
Integrates with django-q2 (modern fork) for scheduled and async task execution.
|
|
268
|
-
Automatically adds django_q to INSTALLED_APPS when enabled.
|
|
269
|
-
|
|
270
|
-
Installation:
|
|
271
|
-
pip install django-q2[redis]
|
|
272
|
-
|
|
273
|
-
Example:
|
|
274
|
-
```python
|
|
275
|
-
# MAGIC: broker_url automatically uses config.redis_url! 🎉
|
|
276
|
-
# Just set redis_url once in your DjangoConfig:
|
|
277
|
-
# redis_url: Optional[str] = env.redis_url
|
|
278
|
-
|
|
279
|
-
django_q2_config = DjangoQ2Config(
|
|
280
|
-
enabled=True,
|
|
281
|
-
# broker_url is auto-detected from config.redis_url!
|
|
282
|
-
schedules=[
|
|
283
|
-
DjangoQ2ScheduleConfig(
|
|
284
|
-
name="Sync balances every hour",
|
|
285
|
-
schedule_type="hourly",
|
|
286
|
-
command="sync_account_balances",
|
|
287
|
-
),
|
|
288
|
-
DjangoQ2ScheduleConfig(
|
|
289
|
-
name="Cleanup old data daily",
|
|
290
|
-
schedule_type="cron",
|
|
291
|
-
cron="0 2 * * *", # 2 AM daily
|
|
292
|
-
command="cleanup_old_data",
|
|
293
|
-
command_kwargs={"days": 30},
|
|
294
|
-
),
|
|
295
|
-
DjangoQ2ScheduleConfig(
|
|
296
|
-
name="Quick check every 5 minutes",
|
|
297
|
-
schedule_type="minutes",
|
|
298
|
-
minutes=5,
|
|
299
|
-
command="health_check",
|
|
300
|
-
),
|
|
301
|
-
],
|
|
302
|
-
)
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
Schedule Synchronization (AUTOMATIC):
|
|
306
|
-
When Django-Q2 is enabled, schedules are automatically synced after migrations.
|
|
307
|
-
|
|
308
|
-
The module 'django_cfg.modules.django_q2' is automatically added to INSTALLED_APPS
|
|
309
|
-
when django_q2.enabled=True, so you don't need to add it manually.
|
|
310
|
-
|
|
311
|
-
It uses Django's post_migrate signal to sync schedules from config to database.
|
|
312
|
-
|
|
313
|
-
Manual sync (optional):
|
|
314
|
-
```bash
|
|
315
|
-
python manage.py sync_django_q_schedules
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
Admin interface:
|
|
319
|
-
- Visit /admin/django_q/ to view tasks and schedules
|
|
320
|
-
- Monitor task execution, failures, and performance
|
|
321
|
-
- Manually trigger scheduled tasks
|
|
322
|
-
- View task results and logs
|
|
323
|
-
"""
|
|
324
|
-
|
|
325
|
-
model_config = ConfigDict(
|
|
326
|
-
validate_assignment=True,
|
|
327
|
-
extra="forbid",
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
enabled: bool = Field(
|
|
331
|
-
default=True,
|
|
332
|
-
description="Enable Django-Q (auto-adds django_q to INSTALLED_APPS)",
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
schedules: List[DjangoQ2ScheduleConfig] = Field(
|
|
336
|
-
default_factory=list,
|
|
337
|
-
description="List of scheduled tasks",
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
# Django-Q broker configuration
|
|
341
|
-
broker_url: str = Field(
|
|
342
|
-
default="redis://localhost:6379/0",
|
|
343
|
-
description="Broker URL (Redis recommended for production)",
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
broker_class: Literal["redis", "orm"] = Field(
|
|
347
|
-
default="redis",
|
|
348
|
-
description="Broker backend class (redis or orm)",
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
# Queue configuration
|
|
352
|
-
queue_limit: Optional[int] = Field(
|
|
353
|
-
default=50,
|
|
354
|
-
ge=1,
|
|
355
|
-
description="Maximum tasks in queue before rejecting new ones",
|
|
356
|
-
)
|
|
357
|
-
|
|
358
|
-
workers: int = Field(
|
|
359
|
-
default=4,
|
|
360
|
-
ge=1,
|
|
361
|
-
le=32,
|
|
362
|
-
description="Number of worker processes",
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
timeout: int = Field(
|
|
366
|
-
default=300,
|
|
367
|
-
ge=1,
|
|
368
|
-
le=86400, # Max 24 hours
|
|
369
|
-
description="Default task timeout in seconds",
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
retry: int = Field(
|
|
373
|
-
default=3600,
|
|
374
|
-
ge=0,
|
|
375
|
-
description="Seconds to wait before retrying failed tasks (0 = no retry)",
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
# Task result configuration
|
|
379
|
-
save_limit: int = Field(
|
|
380
|
-
default=250,
|
|
381
|
-
ge=0,
|
|
382
|
-
description="Maximum number of successful tasks to save (0 = unlimited)",
|
|
383
|
-
)
|
|
384
|
-
|
|
385
|
-
cached: int = Field(
|
|
386
|
-
default=500,
|
|
387
|
-
ge=0,
|
|
388
|
-
description="Maximum number of tasks to cache (0 = disabled)",
|
|
389
|
-
)
|
|
390
|
-
|
|
391
|
-
# Monitoring
|
|
392
|
-
monitor_interval: int = Field(
|
|
393
|
-
default=30,
|
|
394
|
-
ge=1,
|
|
395
|
-
description="Seconds between monitor checks",
|
|
396
|
-
)
|
|
397
|
-
|
|
398
|
-
# Logging
|
|
399
|
-
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = Field(
|
|
400
|
-
default="INFO",
|
|
401
|
-
description="Django-Q log level",
|
|
402
|
-
)
|
|
403
|
-
|
|
404
|
-
# Advanced options
|
|
405
|
-
compress: bool = Field(
|
|
406
|
-
default=False,
|
|
407
|
-
description="Compress task data",
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
catch_up: bool = Field(
|
|
411
|
-
default=True,
|
|
412
|
-
description="Run missed scheduled tasks immediately",
|
|
413
|
-
)
|
|
414
|
-
|
|
415
|
-
sync: bool = Field(
|
|
416
|
-
default=False,
|
|
417
|
-
description="Run tasks synchronously (for testing)",
|
|
418
|
-
)
|
|
419
|
-
|
|
420
|
-
def get_enabled_schedules(self) -> List[DjangoQ2ScheduleConfig]:
|
|
421
|
-
"""Get list of enabled schedules."""
|
|
422
|
-
return [schedule for schedule in self.schedules if schedule.enabled]
|
|
423
|
-
|
|
424
|
-
def to_django_settings(self, parent_config: Optional[Any] = None) -> Dict[str, Any]:
|
|
425
|
-
"""
|
|
426
|
-
Convert to Django settings dictionary.
|
|
427
|
-
|
|
428
|
-
Generates Q_CLUSTER configuration for Django-Q2.
|
|
429
|
-
|
|
430
|
-
Args:
|
|
431
|
-
parent_config: Optional parent DjangoConfig for accessing redis_url
|
|
432
|
-
|
|
433
|
-
Note: Schedules are created via Django ORM, not settings.
|
|
434
|
-
Use management command: python manage.py qcluster
|
|
435
|
-
"""
|
|
436
|
-
if not self.enabled:
|
|
437
|
-
return {}
|
|
438
|
-
|
|
439
|
-
# Auto-detect redis_url from parent config if not explicitly set
|
|
440
|
-
broker_url = self.broker_url
|
|
441
|
-
if broker_url == "redis://localhost:6379/0" and parent_config:
|
|
442
|
-
# Use redis_url from parent config if available
|
|
443
|
-
if hasattr(parent_config, 'redis_url') and parent_config.redis_url:
|
|
444
|
-
broker_url = parent_config.redis_url
|
|
445
|
-
|
|
446
|
-
# Map short broker names to full class paths
|
|
447
|
-
broker_class_map = {
|
|
448
|
-
"redis": "django_q.brokers.redis_broker.Redis",
|
|
449
|
-
"orm": "django_q.brokers.orm.ORM",
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
cluster_config = {
|
|
453
|
-
# Broker
|
|
454
|
-
"name": "django_cfg_cluster",
|
|
455
|
-
"broker": broker_url,
|
|
456
|
-
"broker_class": broker_class_map.get(self.broker_class, self.broker_class),
|
|
457
|
-
|
|
458
|
-
# Queue
|
|
459
|
-
"queue_limit": self.queue_limit,
|
|
460
|
-
"workers": self.workers,
|
|
461
|
-
"timeout": self.timeout,
|
|
462
|
-
"retry": self.retry,
|
|
463
|
-
|
|
464
|
-
# Results
|
|
465
|
-
"save_limit": self.save_limit,
|
|
466
|
-
"cached": self.cached,
|
|
467
|
-
|
|
468
|
-
# Monitoring
|
|
469
|
-
"monitor": self.monitor_interval,
|
|
470
|
-
|
|
471
|
-
# Logging
|
|
472
|
-
"log_level": self.log_level,
|
|
473
|
-
|
|
474
|
-
# Advanced
|
|
475
|
-
"compress": self.compress,
|
|
476
|
-
"catch_up": self.catch_up,
|
|
477
|
-
"sync": self.sync,
|
|
478
|
-
|
|
479
|
-
# Django integration
|
|
480
|
-
"orm": "default",
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
# CRITICAL FIX: Django-Q2 uses 'redis' parameter, NOT 'broker'!
|
|
484
|
-
# The 'broker' parameter is ignored by django-q2.
|
|
485
|
-
# We must set 'redis' parameter to the broker_url string.
|
|
486
|
-
if self.broker_class == "redis":
|
|
487
|
-
# Set redis parameter to broker_url (Django-Q2 accepts redis:// URL string)
|
|
488
|
-
cluster_config["redis"] = broker_url
|
|
489
|
-
|
|
490
|
-
settings = {
|
|
491
|
-
"Q_CLUSTER": cluster_config
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
return settings
|
|
495
|
-
|
|
496
|
-
def get_schedule_by_name(self, name: str) -> Optional[DjangoQ2ScheduleConfig]:
|
|
497
|
-
"""Get schedule by name."""
|
|
498
|
-
for schedule in self.schedules:
|
|
499
|
-
if schedule.name == name:
|
|
500
|
-
return schedule
|
|
501
|
-
return None
|
|
502
|
-
|
|
503
|
-
def get_schedules_by_command(self, command: str) -> List[DjangoQ2ScheduleConfig]:
|
|
504
|
-
"""Get all schedules for a specific command."""
|
|
505
|
-
return [
|
|
506
|
-
schedule for schedule in self.schedules
|
|
507
|
-
if schedule.command == command
|
|
508
|
-
]
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
__all__ = [
|
|
512
|
-
"DjangoQ2ScheduleConfig",
|
|
513
|
-
"DjangoQ2Config",
|
|
514
|
-
]
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Task processing configuration models for Django-CFG.
|
|
3
|
-
|
|
4
|
-
This module provides type-safe Pydantic models for configuring background task
|
|
5
|
-
processing with ReArq, including worker management, queue configuration,
|
|
6
|
-
and monitoring settings.
|
|
7
|
-
|
|
8
|
-
Architecture:
|
|
9
|
-
config.py - Main TaskConfig and enums
|
|
10
|
-
backends.py - RearqConfig
|
|
11
|
-
utils.py - Utility functions
|
|
12
|
-
|
|
13
|
-
Example:
|
|
14
|
-
```python
|
|
15
|
-
from django_cfg.models.tasks import TaskConfig, RearqConfig
|
|
16
|
-
|
|
17
|
-
# Basic configuration
|
|
18
|
-
tasks = TaskConfig(
|
|
19
|
-
enabled=True,
|
|
20
|
-
rearq=RearqConfig(
|
|
21
|
-
redis_url="redis://localhost:6379/0",
|
|
22
|
-
max_jobs=10,
|
|
23
|
-
)
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
# Get environment-aware defaults
|
|
27
|
-
from django_cfg.models.tasks import get_default_task_config
|
|
28
|
-
tasks = get_default_task_config(debug=True)
|
|
29
|
-
```
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
from .backends import RearqConfig
|
|
33
|
-
from .config import QueuePriority, TaskBackend, TaskConfig
|
|
34
|
-
from .utils import get_default_task_config, get_smart_queues, validate_task_config
|
|
35
|
-
|
|
36
|
-
__all__ = [
|
|
37
|
-
# Main configuration
|
|
38
|
-
"TaskConfig",
|
|
39
|
-
"TaskBackend",
|
|
40
|
-
"QueuePriority",
|
|
41
|
-
|
|
42
|
-
# Backend configurations
|
|
43
|
-
"RearqConfig",
|
|
44
|
-
|
|
45
|
-
# Utility functions
|
|
46
|
-
"get_default_task_config",
|
|
47
|
-
"validate_task_config",
|
|
48
|
-
"get_smart_queues",
|
|
49
|
-
]
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Backend-specific configurations.
|
|
3
|
-
|
|
4
|
-
Contains ReArq configuration model.
|
|
5
|
-
Size: ~100 lines (focused on backend settings)
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import logging
|
|
9
|
-
from typing import Any, Dict
|
|
10
|
-
|
|
11
|
-
from pydantic import BaseModel, Field, field_validator
|
|
12
|
-
|
|
13
|
-
logger = logging.getLogger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class RearqConfig(BaseModel):
|
|
17
|
-
"""
|
|
18
|
-
ReArq-specific configuration with production-ready defaults.
|
|
19
|
-
|
|
20
|
-
This model provides comprehensive configuration for ReArq async background
|
|
21
|
-
task processing, including Redis settings, worker configuration,
|
|
22
|
-
and job retry policies.
|
|
23
|
-
|
|
24
|
-
Example:
|
|
25
|
-
```python
|
|
26
|
-
from django_cfg.models.tasks import RearqConfig
|
|
27
|
-
|
|
28
|
-
rearq = RearqConfig(
|
|
29
|
-
redis_url="redis://localhost:6379/0",
|
|
30
|
-
db_url="sqlite://./rearq.db",
|
|
31
|
-
max_jobs=10,
|
|
32
|
-
job_timeout=300,
|
|
33
|
-
)
|
|
34
|
-
```
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
# === Core Settings ===
|
|
38
|
-
redis_url: str = Field(
|
|
39
|
-
default="redis://localhost:6379/0",
|
|
40
|
-
description="Redis connection URL for task queue"
|
|
41
|
-
)
|
|
42
|
-
db_url: str = Field(
|
|
43
|
-
default="sqlite://./rearq.db",
|
|
44
|
-
description="Database URL for job persistence (Tortoise ORM)"
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
# === Worker Settings ===
|
|
48
|
-
max_jobs: int = Field(
|
|
49
|
-
default=10,
|
|
50
|
-
ge=1,
|
|
51
|
-
le=100,
|
|
52
|
-
description="Maximum concurrent jobs per worker"
|
|
53
|
-
)
|
|
54
|
-
job_timeout: int = Field(
|
|
55
|
-
default=300,
|
|
56
|
-
ge=30,
|
|
57
|
-
le=3600,
|
|
58
|
-
description="Default job timeout in seconds"
|
|
59
|
-
)
|
|
60
|
-
job_retry: int = Field(
|
|
61
|
-
default=3,
|
|
62
|
-
ge=0,
|
|
63
|
-
le=10,
|
|
64
|
-
description="Default number of retries for failed jobs"
|
|
65
|
-
)
|
|
66
|
-
job_retry_after: int = Field(
|
|
67
|
-
default=60,
|
|
68
|
-
ge=1,
|
|
69
|
-
description="Delay in seconds before retrying failed job"
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
# === Cleanup Settings ===
|
|
73
|
-
keep_job_days: int | None = Field(
|
|
74
|
-
default=7,
|
|
75
|
-
ge=1,
|
|
76
|
-
description="Days to keep job history (None = forever)"
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
@field_validator("redis_url")
|
|
80
|
-
@classmethod
|
|
81
|
-
def validate_redis_url(cls, v: str) -> str:
|
|
82
|
-
"""Validate Redis URL format."""
|
|
83
|
-
if not v.startswith(("redis://", "rediss://")):
|
|
84
|
-
raise ValueError("Redis URL must start with redis:// or rediss://")
|
|
85
|
-
return v
|
|
86
|
-
|
|
87
|
-
@field_validator("db_url")
|
|
88
|
-
@classmethod
|
|
89
|
-
def validate_db_url(cls, v: str) -> str:
|
|
90
|
-
"""Validate database URL format."""
|
|
91
|
-
valid_schemes = ("sqlite://", "postgres://", "postgresql://", "mysql://")
|
|
92
|
-
if not v.startswith(valid_schemes):
|
|
93
|
-
raise ValueError(f"Database URL must start with one of: {valid_schemes}")
|
|
94
|
-
return v
|
|
95
|
-
|
|
96
|
-
def to_django_settings(self) -> Dict[str, Any]:
|
|
97
|
-
"""
|
|
98
|
-
Generate Django settings dictionary.
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
Dictionary with ReArq configuration for Django settings
|
|
102
|
-
|
|
103
|
-
Example:
|
|
104
|
-
>>> config = RearqConfig()
|
|
105
|
-
>>> settings = config.to_django_settings()
|
|
106
|
-
>>> "REARQ_REDIS_URL" in settings
|
|
107
|
-
True
|
|
108
|
-
"""
|
|
109
|
-
return {
|
|
110
|
-
"REARQ_REDIS_URL": self.redis_url,
|
|
111
|
-
"REARQ_DB_URL": self.db_url,
|
|
112
|
-
"REARQ_MAX_JOBS": self.max_jobs,
|
|
113
|
-
"REARQ_JOB_TIMEOUT": self.job_timeout,
|
|
114
|
-
"REARQ_JOB_RETRY": self.job_retry,
|
|
115
|
-
"REARQ_JOB_RETRY_AFTER": self.job_retry_after,
|
|
116
|
-
"REARQ_KEEP_JOB_DAYS": self.keep_job_days,
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
__all__ = [
|
|
121
|
-
"RearqConfig",
|
|
122
|
-
]
|