django-cfg 1.4.107__py3-none-any.whl → 1.4.109__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.109.dist-info}/METADATA +3 -3
- {django_cfg-1.4.107.dist-info → django_cfg-1.4.109.dist-info}/RECORD +70 -117
- 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_client/system/__init__.py +0 -24
- django_cfg/modules/django_client/system/base_generator.py +0 -123
- django_cfg/modules/django_client/system/generate_mjs_clients.py +0 -176
- django_cfg/modules/django_client/system/mjs_generator.py +0 -219
- django_cfg/modules/django_client/system/schema_parser.py +0 -199
- django_cfg/modules/django_client/system/templates/api_client.js.j2 +0 -87
- django_cfg/modules/django_client/system/templates/app_index.js.j2 +0 -13
- django_cfg/modules/django_client/system/templates/base_client.js.j2 +0 -166
- django_cfg/modules/django_client/system/templates/main_index.js.j2 +0 -80
- django_cfg/modules/django_client/system/templates/types.js.j2 +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.109.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.107.dist-info → django_cfg-1.4.109.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.107.dist-info → django_cfg-1.4.109.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,253 +1,122 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Backend-specific configurations.
|
|
3
3
|
|
|
4
|
-
Contains
|
|
5
|
-
Size: ~
|
|
4
|
+
Contains ReArq configuration model.
|
|
5
|
+
Size: ~100 lines (focused on backend settings)
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
-
import
|
|
10
|
-
from typing import List, Literal, Optional
|
|
9
|
+
from typing import Any, Dict
|
|
11
10
|
|
|
12
11
|
from pydantic import BaseModel, Field, field_validator
|
|
13
12
|
|
|
14
13
|
logger = logging.getLogger(__name__)
|
|
15
14
|
|
|
16
15
|
|
|
17
|
-
class
|
|
16
|
+
class RearqConfig(BaseModel):
|
|
18
17
|
"""
|
|
19
|
-
|
|
18
|
+
ReArq-specific configuration with production-ready defaults.
|
|
20
19
|
|
|
21
|
-
This model provides comprehensive configuration for
|
|
20
|
+
This model provides comprehensive configuration for ReArq async background
|
|
22
21
|
task processing, including Redis settings, worker configuration,
|
|
23
|
-
|
|
22
|
+
and job retry policies.
|
|
24
23
|
|
|
25
24
|
Example:
|
|
26
25
|
```python
|
|
27
|
-
from django_cfg.models.tasks import
|
|
26
|
+
from django_cfg.models.tasks import RearqConfig
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
33
|
)
|
|
34
34
|
```
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
# ===
|
|
38
|
-
|
|
39
|
-
default=
|
|
40
|
-
|
|
41
|
-
le=15,
|
|
42
|
-
description="Redis database number for tasks (separate from cache)"
|
|
37
|
+
# === Core Settings ===
|
|
38
|
+
redis_url: str = Field(
|
|
39
|
+
default="redis://localhost:6379/0",
|
|
40
|
+
description="Redis connection URL for task queue"
|
|
43
41
|
)
|
|
44
|
-
|
|
45
|
-
default="
|
|
46
|
-
description="
|
|
42
|
+
db_url: str = Field(
|
|
43
|
+
default="sqlite://./rearq.db",
|
|
44
|
+
description="Database URL for job persistence (Tortoise ORM)"
|
|
47
45
|
)
|
|
48
46
|
|
|
49
|
-
# ===
|
|
50
|
-
|
|
51
|
-
default=
|
|
52
|
-
ge=
|
|
53
|
-
le=
|
|
54
|
-
description="
|
|
55
|
-
)
|
|
56
|
-
default_priority: int = Field(
|
|
57
|
-
default=5,
|
|
58
|
-
ge=0,
|
|
59
|
-
le=10,
|
|
60
|
-
description="Default task priority (0=highest, 10=lowest)"
|
|
61
|
-
)
|
|
62
|
-
max_age_seconds: int = Field(
|
|
63
|
-
default=3600,
|
|
64
|
-
ge=60,
|
|
65
|
-
description="Maximum age for tasks before they expire"
|
|
47
|
+
# === Worker Settings ===
|
|
48
|
+
max_jobs: int = Field(
|
|
49
|
+
default=10,
|
|
50
|
+
ge=1,
|
|
51
|
+
le=100,
|
|
52
|
+
description="Maximum concurrent jobs per worker"
|
|
66
53
|
)
|
|
67
|
-
|
|
68
|
-
default=
|
|
54
|
+
job_timeout: int = Field(
|
|
55
|
+
default=300,
|
|
69
56
|
ge=30,
|
|
70
|
-
|
|
57
|
+
le=3600,
|
|
58
|
+
description="Default job timeout in seconds"
|
|
71
59
|
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
le=32,
|
|
78
|
-
description="Number of worker processes"
|
|
60
|
+
job_retry: int = Field(
|
|
61
|
+
default=3,
|
|
62
|
+
ge=0,
|
|
63
|
+
le=10,
|
|
64
|
+
description="Default number of retries for failed jobs"
|
|
79
65
|
)
|
|
80
|
-
|
|
81
|
-
default=
|
|
66
|
+
job_retry_after: int = Field(
|
|
67
|
+
default=60,
|
|
82
68
|
ge=1,
|
|
83
|
-
|
|
84
|
-
description="Number of threads per worker process"
|
|
85
|
-
)
|
|
86
|
-
queues: List[str] = Field(
|
|
87
|
-
default=["default", "high", "low"],
|
|
88
|
-
description="Available task queues"
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
# === Middleware Stack ===
|
|
92
|
-
middleware: List[str] = Field(
|
|
93
|
-
default=[
|
|
94
|
-
"dramatiq.middleware.AgeLimit",
|
|
95
|
-
"dramatiq.middleware.TimeLimit",
|
|
96
|
-
"dramatiq.middleware.Callbacks",
|
|
97
|
-
"dramatiq.middleware.Retries",
|
|
98
|
-
# NOTE: Results middleware requires backend configuration
|
|
99
|
-
# "dramatiq.results.middleware.Results",
|
|
100
|
-
"dramatiq.middleware.Prometheus",
|
|
101
|
-
"django_dramatiq.middleware.AdminMiddleware",
|
|
102
|
-
"django_dramatiq.middleware.DbConnectionsMiddleware",
|
|
103
|
-
],
|
|
104
|
-
description="Middleware stack for task processing"
|
|
69
|
+
description="Delay in seconds before retrying failed job"
|
|
105
70
|
)
|
|
106
71
|
|
|
107
|
-
# ===
|
|
108
|
-
|
|
109
|
-
default=
|
|
110
|
-
description="Enable Prometheus metrics collection"
|
|
111
|
-
)
|
|
112
|
-
admin_enabled: bool = Field(
|
|
113
|
-
default=True,
|
|
114
|
-
description="Enable Django admin interface integration"
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
# === Performance Tuning ===
|
|
118
|
-
prefetch_multiplier: int = Field(
|
|
119
|
-
default=2,
|
|
72
|
+
# === Cleanup Settings ===
|
|
73
|
+
keep_job_days: int | None = Field(
|
|
74
|
+
default=7,
|
|
120
75
|
ge=1,
|
|
121
|
-
|
|
122
|
-
description="Message prefetch multiplier for workers"
|
|
123
|
-
)
|
|
124
|
-
max_memory_mb: Optional[int] = Field(
|
|
125
|
-
default=512,
|
|
126
|
-
ge=128,
|
|
127
|
-
description="Maximum memory usage per worker (MB)"
|
|
76
|
+
description="Days to keep job history (None = forever)"
|
|
128
77
|
)
|
|
129
78
|
|
|
130
|
-
@field_validator("
|
|
79
|
+
@field_validator("redis_url")
|
|
131
80
|
@classmethod
|
|
132
|
-
def
|
|
133
|
-
"""
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if v > max_recommended:
|
|
138
|
-
logger.warning(
|
|
139
|
-
f"Process count ({v}) exceeds recommended maximum ({max_recommended}). "
|
|
140
|
-
f"Consider reducing to avoid resource contention."
|
|
141
|
-
)
|
|
142
|
-
|
|
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://")
|
|
143
85
|
return v
|
|
144
86
|
|
|
145
|
-
@field_validator("
|
|
87
|
+
@field_validator("db_url")
|
|
146
88
|
@classmethod
|
|
147
|
-
def
|
|
148
|
-
"""
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
# Ensure 'default' queue exists
|
|
153
|
-
if "default" not in v:
|
|
154
|
-
v.append("default")
|
|
155
|
-
|
|
156
|
-
# Validate queue names (alphanumeric + underscore/hyphen)
|
|
157
|
-
import re
|
|
158
|
-
pattern = re.compile(r'^[a-zA-Z0-9_-]+$')
|
|
159
|
-
|
|
160
|
-
for queue in v:
|
|
161
|
-
if not pattern.match(queue):
|
|
162
|
-
raise ValueError(f"Invalid queue name: {queue}. Use only alphanumeric, underscore, and hyphen.")
|
|
163
|
-
|
|
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}")
|
|
164
94
|
return v
|
|
165
95
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
and health monitoring settings.
|
|
189
|
-
|
|
190
|
-
Example:
|
|
191
|
-
```python
|
|
192
|
-
from django_cfg.models.tasks import WorkerConfig
|
|
193
|
-
|
|
194
|
-
worker = WorkerConfig(
|
|
195
|
-
shutdown_timeout=30,
|
|
196
|
-
max_memory_mb=512,
|
|
197
|
-
health_check_enabled=True,
|
|
198
|
-
)
|
|
199
|
-
```
|
|
200
|
-
"""
|
|
201
|
-
|
|
202
|
-
# === Process Management ===
|
|
203
|
-
shutdown_timeout: int = Field(
|
|
204
|
-
default=30,
|
|
205
|
-
ge=5,
|
|
206
|
-
le=300,
|
|
207
|
-
description="Graceful shutdown timeout in seconds"
|
|
208
|
-
)
|
|
209
|
-
heartbeat_interval: int = Field(
|
|
210
|
-
default=5,
|
|
211
|
-
ge=1,
|
|
212
|
-
le=60,
|
|
213
|
-
description="Worker heartbeat interval in seconds"
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
# === Resource Limits ===
|
|
217
|
-
max_memory_mb: Optional[int] = Field(
|
|
218
|
-
default=512,
|
|
219
|
-
ge=128,
|
|
220
|
-
description="Maximum memory per worker process (MB)"
|
|
221
|
-
)
|
|
222
|
-
max_cpu_percent: Optional[float] = Field(
|
|
223
|
-
default=80.0,
|
|
224
|
-
ge=10.0,
|
|
225
|
-
le=100.0,
|
|
226
|
-
description="Maximum CPU usage per worker (%)"
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
# === Health Monitoring ===
|
|
230
|
-
health_check_enabled: bool = Field(
|
|
231
|
-
default=True,
|
|
232
|
-
description="Enable worker health monitoring"
|
|
233
|
-
)
|
|
234
|
-
restart_on_memory_limit: bool = Field(
|
|
235
|
-
default=True,
|
|
236
|
-
description="Restart worker if memory limit exceeded"
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
# === Logging ===
|
|
240
|
-
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = Field(
|
|
241
|
-
default="INFO",
|
|
242
|
-
description="Worker log level"
|
|
243
|
-
)
|
|
244
|
-
log_format: str = Field(
|
|
245
|
-
default="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
246
|
-
description="Log message format"
|
|
247
|
-
)
|
|
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
|
+
}
|
|
248
118
|
|
|
249
119
|
|
|
250
120
|
__all__ = [
|
|
251
|
-
"
|
|
252
|
-
"WorkerConfig",
|
|
121
|
+
"RearqConfig",
|
|
253
122
|
]
|
|
@@ -19,8 +19,7 @@ logger = logging.getLogger(__name__)
|
|
|
19
19
|
|
|
20
20
|
class TaskBackend(str, Enum):
|
|
21
21
|
"""Supported task backends."""
|
|
22
|
-
|
|
23
|
-
# Future: CELERY = "celery"
|
|
22
|
+
REARQ = "rearq"
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
class QueuePriority(str, Enum):
|
|
@@ -56,7 +55,7 @@ class TaskConfig(BaseModel, BaseCfgAutoModule):
|
|
|
56
55
|
description="Enable background task processing"
|
|
57
56
|
)
|
|
58
57
|
backend: TaskBackend = Field(
|
|
59
|
-
default=TaskBackend.
|
|
58
|
+
default=TaskBackend.REARQ,
|
|
60
59
|
description="Task processing backend"
|
|
61
60
|
)
|
|
62
61
|
|
|
@@ -67,23 +66,16 @@ class TaskConfig(BaseModel, BaseCfgAutoModule):
|
|
|
67
66
|
self._config = None
|
|
68
67
|
|
|
69
68
|
# === Backend-Specific Configuration ===
|
|
70
|
-
|
|
69
|
+
rearq: 'RearqConfig' = Field(
|
|
71
70
|
default_factory=lambda: None,
|
|
72
|
-
description="
|
|
73
|
-
)
|
|
74
|
-
worker: 'WorkerConfig' = Field(
|
|
75
|
-
default_factory=lambda: None,
|
|
76
|
-
description="Worker configuration"
|
|
71
|
+
description="ReArq-specific configuration"
|
|
77
72
|
)
|
|
78
73
|
|
|
79
74
|
def model_post_init(self, __context: Any) -> None:
|
|
80
75
|
"""Initialize backend configs with defaults after model creation."""
|
|
81
|
-
if self.
|
|
82
|
-
from .backends import
|
|
83
|
-
self.
|
|
84
|
-
if self.worker is None:
|
|
85
|
-
from .backends import WorkerConfig
|
|
86
|
-
self.worker = WorkerConfig()
|
|
76
|
+
if self.rearq is None:
|
|
77
|
+
from .backends import RearqConfig
|
|
78
|
+
self.rearq = RearqConfig()
|
|
87
79
|
|
|
88
80
|
# === Environment-Specific Overrides ===
|
|
89
81
|
dev_processes: Optional[int] = Field(
|
|
@@ -120,124 +112,26 @@ class TaskConfig(BaseModel, BaseCfgAutoModule):
|
|
|
120
112
|
|
|
121
113
|
return v
|
|
122
114
|
|
|
123
|
-
def
|
|
124
|
-
"""
|
|
125
|
-
Get effective number of processes based on environment.
|
|
126
|
-
|
|
127
|
-
Args:
|
|
128
|
-
debug: Whether in debug mode
|
|
129
|
-
|
|
130
|
-
Returns:
|
|
131
|
-
Number of worker processes to use
|
|
132
|
-
|
|
133
|
-
Example:
|
|
134
|
-
>>> config = TaskConfig()
|
|
135
|
-
>>> config.get_effective_processes(debug=True)
|
|
136
|
-
2
|
|
137
|
-
"""
|
|
138
|
-
if debug and self.dev_processes is not None:
|
|
139
|
-
return self.dev_processes
|
|
140
|
-
elif not debug and self.prod_processes is not None:
|
|
141
|
-
return self.prod_processes
|
|
142
|
-
else:
|
|
143
|
-
return self.dramatiq.processes
|
|
144
|
-
|
|
145
|
-
def get_effective_queues(self) -> List[str]:
|
|
146
|
-
"""
|
|
147
|
-
Get effective queue configuration.
|
|
148
|
-
|
|
149
|
-
Returns:
|
|
150
|
-
List of queue names
|
|
151
|
-
|
|
152
|
-
Example:
|
|
153
|
-
>>> config = TaskConfig()
|
|
154
|
-
>>> config.get_effective_queues()
|
|
155
|
-
['default', 'high', 'low']
|
|
156
|
-
"""
|
|
157
|
-
return self.dramatiq.queues
|
|
158
|
-
|
|
159
|
-
def get_redis_config(self, redis_url: str) -> Dict[str, Any]:
|
|
115
|
+
def to_django_settings(self) -> Dict[str, Any]:
|
|
160
116
|
"""
|
|
161
|
-
Generate
|
|
162
|
-
|
|
163
|
-
Args:
|
|
164
|
-
redis_url: Redis connection URL
|
|
117
|
+
Generate Django settings for task system.
|
|
165
118
|
|
|
166
119
|
Returns:
|
|
167
|
-
Dictionary with
|
|
120
|
+
Dictionary with task configuration for Django settings
|
|
168
121
|
|
|
169
122
|
Example:
|
|
170
123
|
>>> config = TaskConfig()
|
|
171
|
-
>>> config.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
from urllib.parse import urlparse
|
|
175
|
-
|
|
176
|
-
# Parse Redis URL
|
|
177
|
-
parsed = urlparse(redis_url)
|
|
178
|
-
|
|
179
|
-
# Build Redis config
|
|
180
|
-
config = {
|
|
181
|
-
"host": parsed.hostname or "localhost",
|
|
182
|
-
"port": parsed.port or 6379,
|
|
183
|
-
"db": self.dramatiq.redis_db,
|
|
184
|
-
"password": parsed.password,
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
# Add SSL if specified
|
|
188
|
-
if parsed.scheme == "rediss":
|
|
189
|
-
config["ssl"] = True
|
|
190
|
-
|
|
191
|
-
return config
|
|
192
|
-
|
|
193
|
-
def get_dramatiq_settings(self, redis_url: str) -> Dict[str, Any]:
|
|
124
|
+
>>> settings = config.to_django_settings()
|
|
125
|
+
>>> "REARQ_REDIS_URL" in settings
|
|
126
|
+
True
|
|
194
127
|
"""
|
|
195
|
-
|
|
128
|
+
if not self.enabled:
|
|
129
|
+
return {}
|
|
196
130
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
Returns:
|
|
201
|
-
Dictionary with complete Dramatiq configuration
|
|
131
|
+
if self.backend == TaskBackend.REARQ:
|
|
132
|
+
return self.rearq.to_django_settings()
|
|
202
133
|
|
|
203
|
-
|
|
204
|
-
>>> config = TaskConfig()
|
|
205
|
-
>>> settings = config.get_dramatiq_settings("redis://localhost:6379/1")
|
|
206
|
-
>>> "DRAMATIQ_BROKER" in settings
|
|
207
|
-
True
|
|
208
|
-
"""
|
|
209
|
-
from urllib.parse import urlparse
|
|
210
|
-
|
|
211
|
-
redis_config = self.get_redis_config(redis_url)
|
|
212
|
-
parsed = urlparse(redis_url)
|
|
213
|
-
|
|
214
|
-
# Build Redis URL with correct database
|
|
215
|
-
redis_url_with_db = redis_url
|
|
216
|
-
if parsed.path and parsed.path != "/":
|
|
217
|
-
# Replace existing database in URL
|
|
218
|
-
redis_url_with_db = redis_url.replace(parsed.path, f"/{self.dramatiq.redis_db}")
|
|
219
|
-
else:
|
|
220
|
-
# Add database to URL
|
|
221
|
-
redis_url_with_db = f"{redis_url.rstrip('/')}/{self.dramatiq.redis_db}"
|
|
222
|
-
|
|
223
|
-
return {
|
|
224
|
-
"DRAMATIQ_BROKER": {
|
|
225
|
-
"BROKER": "dramatiq.brokers.redis.RedisBroker",
|
|
226
|
-
"OPTIONS": {
|
|
227
|
-
"url": redis_url_with_db,
|
|
228
|
-
**redis_config
|
|
229
|
-
},
|
|
230
|
-
},
|
|
231
|
-
"DRAMATIQ_RESULT_BACKEND": {
|
|
232
|
-
"BACKEND": "dramatiq.results.backends.redis.RedisBackend",
|
|
233
|
-
"BACKEND_OPTIONS": {
|
|
234
|
-
"url": redis_url_with_db,
|
|
235
|
-
**redis_config
|
|
236
|
-
},
|
|
237
|
-
},
|
|
238
|
-
"DRAMATIQ_MIDDLEWARE": self.dramatiq.middleware,
|
|
239
|
-
"DRAMATIQ_QUEUES": self.dramatiq.queues,
|
|
240
|
-
}
|
|
134
|
+
return {}
|
|
241
135
|
|
|
242
136
|
def get_smart_defaults(self):
|
|
243
137
|
"""
|
|
@@ -303,7 +197,7 @@ class TaskConfig(BaseModel, BaseCfgAutoModule):
|
|
|
303
197
|
|
|
304
198
|
|
|
305
199
|
# Resolve forward references for Pydantic v2
|
|
306
|
-
from .backends import
|
|
200
|
+
from .backends import RearqConfig
|
|
307
201
|
|
|
308
202
|
TaskConfig.model_rebuild()
|
|
309
203
|
|
|
@@ -311,6 +205,5 @@ __all__ = [
|
|
|
311
205
|
"TaskConfig",
|
|
312
206
|
"TaskBackend",
|
|
313
207
|
"QueuePriority",
|
|
314
|
-
"
|
|
315
|
-
"WorkerConfig",
|
|
208
|
+
"RearqConfig",
|
|
316
209
|
]
|
django_cfg/models/tasks/utils.py
CHANGED
|
@@ -84,41 +84,30 @@ def get_default_task_config(debug: bool = False) -> 'TaskConfig':
|
|
|
84
84
|
|
|
85
85
|
Example:
|
|
86
86
|
>>> config = get_default_task_config(debug=True)
|
|
87
|
-
>>> config.
|
|
88
|
-
|
|
87
|
+
>>> config.rearq.max_jobs
|
|
88
|
+
10
|
|
89
89
|
"""
|
|
90
|
-
from .backends import
|
|
90
|
+
from .backends import RearqConfig
|
|
91
91
|
from .config import TaskConfig
|
|
92
92
|
|
|
93
|
-
smart_queues = get_smart_queues(debug)
|
|
94
|
-
|
|
95
93
|
if debug:
|
|
96
94
|
# Development defaults
|
|
97
95
|
return TaskConfig(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
),
|
|
104
|
-
worker=WorkerConfig(
|
|
105
|
-
log_level="DEBUG",
|
|
106
|
-
health_check_enabled=False,
|
|
96
|
+
rearq=RearqConfig(
|
|
97
|
+
redis_url="redis://localhost:6379/0",
|
|
98
|
+
db_url="sqlite://./rearq.db",
|
|
99
|
+
max_jobs=5,
|
|
100
|
+
job_timeout=300,
|
|
107
101
|
)
|
|
108
102
|
)
|
|
109
103
|
else:
|
|
110
104
|
# Production defaults
|
|
111
105
|
return TaskConfig(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
),
|
|
118
|
-
worker=WorkerConfig(
|
|
119
|
-
log_level="INFO",
|
|
120
|
-
health_check_enabled=True,
|
|
121
|
-
restart_on_memory_limit=True,
|
|
106
|
+
rearq=RearqConfig(
|
|
107
|
+
redis_url="redis://localhost:6379/0",
|
|
108
|
+
db_url="postgresql://localhost/rearq",
|
|
109
|
+
max_jobs=20,
|
|
110
|
+
job_timeout=600,
|
|
122
111
|
)
|
|
123
112
|
)
|
|
124
113
|
|
|
@@ -138,7 +127,7 @@ def validate_task_config(config: 'TaskConfig', redis_url: Optional[str] = None)
|
|
|
138
127
|
|
|
139
128
|
Example:
|
|
140
129
|
>>> config = get_default_task_config()
|
|
141
|
-
>>> validate_task_config(config, "redis://localhost:6379/
|
|
130
|
+
>>> validate_task_config(config, "redis://localhost:6379/0")
|
|
142
131
|
True
|
|
143
132
|
"""
|
|
144
133
|
if not config.enabled:
|
|
@@ -156,12 +145,11 @@ def validate_task_config(config: 'TaskConfig', redis_url: Optional[str] = None)
|
|
|
156
145
|
logger.error(f"Invalid Redis URL: {e}")
|
|
157
146
|
return False
|
|
158
147
|
|
|
159
|
-
# Check if
|
|
148
|
+
# Check if ReArq is available
|
|
160
149
|
try:
|
|
161
|
-
import
|
|
162
|
-
import dramatiq
|
|
150
|
+
import rearq
|
|
163
151
|
except ImportError as e:
|
|
164
|
-
logger.error(f"
|
|
152
|
+
logger.error(f"ReArq dependencies not available: {e}")
|
|
165
153
|
return False
|
|
166
154
|
|
|
167
155
|
return True
|
|
@@ -74,6 +74,12 @@ class Command(BaseCommand):
|
|
|
74
74
|
)
|
|
75
75
|
|
|
76
76
|
# Utility options
|
|
77
|
+
parser.add_argument(
|
|
78
|
+
"--no-build",
|
|
79
|
+
action="store_true",
|
|
80
|
+
help="Skip Next.js admin build (useful when calling from Makefile)",
|
|
81
|
+
)
|
|
82
|
+
|
|
77
83
|
parser.add_argument(
|
|
78
84
|
"--dry-run",
|
|
79
85
|
action="store_true",
|
|
@@ -515,7 +521,13 @@ class Command(BaseCommand):
|
|
|
515
521
|
# First copy API clients
|
|
516
522
|
self._copy_to_nextjs_admin(service)
|
|
517
523
|
# Then build Next.js (so clients are included in build)
|
|
518
|
-
|
|
524
|
+
# Skip build if --no-build flag is set
|
|
525
|
+
if not options.get("no_build"):
|
|
526
|
+
self._build_nextjs_admin()
|
|
527
|
+
else:
|
|
528
|
+
self.stdout.write(self.style.WARNING(
|
|
529
|
+
"\n⏭️ Skipping Next.js build (--no-build flag set)"
|
|
530
|
+
))
|
|
519
531
|
|
|
520
532
|
# Summary
|
|
521
533
|
self.stdout.write("\n" + "=" * 60)
|