django-cfg 1.1.61__py3-none-any.whl → 1.1.62__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_cfg/__init__.py +1 -1
- django_cfg/management/commands/rundramatiq.py +174 -202
- django_cfg/modules/django_tasks.py +54 -428
- django_cfg/modules/dramatiq_setup.py +16 -0
- {django_cfg-1.1.61.dist-info → django_cfg-1.1.62.dist-info}/METADATA +1 -1
- {django_cfg-1.1.61.dist-info → django_cfg-1.1.62.dist-info}/RECORD +9 -27
- django_cfg/apps/accounts/tests/__init__.py +0 -1
- django_cfg/apps/accounts/tests/test_models.py +0 -412
- django_cfg/apps/accounts/tests/test_otp_views.py +0 -143
- django_cfg/apps/accounts/tests/test_serializers.py +0 -331
- django_cfg/apps/accounts/tests/test_services.py +0 -401
- django_cfg/apps/accounts/tests/test_signals.py +0 -110
- django_cfg/apps/accounts/tests/test_views.py +0 -255
- django_cfg/apps/newsletter/tests/__init__.py +0 -1
- django_cfg/apps/newsletter/tests/run_tests.py +0 -47
- django_cfg/apps/newsletter/tests/test_email_integration.py +0 -256
- django_cfg/apps/newsletter/tests/test_email_tracking.py +0 -332
- django_cfg/apps/newsletter/tests/test_newsletter_manager.py +0 -83
- django_cfg/apps/newsletter/tests/test_newsletter_models.py +0 -157
- django_cfg/apps/support/tests/__init__.py +0 -0
- django_cfg/apps/support/tests/test_models.py +0 -106
- django_cfg/apps/tasks/@docs/CONFIGURATION.md +0 -663
- django_cfg/apps/tasks/@docs/README.md +0 -195
- django_cfg/apps/tasks/@docs/TASKS_QUEUES.md +0 -423
- django_cfg/apps/tasks/@docs/TROUBLESHOOTING.md +0 -506
- {django_cfg-1.1.61.dist-info → django_cfg-1.1.62.dist-info}/WHEEL +0 -0
- {django_cfg-1.1.61.dist-info → django_cfg-1.1.62.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.1.61.dist-info → django_cfg-1.1.62.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
@@ -38,7 +38,7 @@ default_app_config = "django_cfg.apps.DjangoCfgConfig"
|
|
38
38
|
from typing import TYPE_CHECKING
|
39
39
|
|
40
40
|
# Version information
|
41
|
-
__version__ = "1.1.
|
41
|
+
__version__ = "1.1.62"
|
42
42
|
__author__ = "Unrealos Team"
|
43
43
|
__email__ = "info@unrealos.com"
|
44
44
|
__license__ = "MIT"
|
@@ -1,263 +1,235 @@
|
|
1
1
|
"""
|
2
2
|
Django management command for running Dramatiq workers.
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
Based on django_dramatiq.management.commands.rundramatiq with Django-CFG integration.
|
5
|
+
Simple, clean, and working approach.
|
6
6
|
"""
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
import logging
|
12
|
-
import sys
|
8
|
+
import argparse
|
9
|
+
import importlib
|
10
|
+
import multiprocessing
|
13
11
|
import os
|
12
|
+
import sys
|
13
|
+
|
14
|
+
from django.apps import apps
|
15
|
+
from django.conf import settings
|
16
|
+
from django.core.management.base import BaseCommand
|
17
|
+
from django.utils.module_loading import module_has_submodule
|
14
18
|
|
15
19
|
from django_cfg.modules.django_tasks import get_task_service
|
16
20
|
|
17
|
-
|
21
|
+
|
22
|
+
# Default values
|
23
|
+
NPROCS = multiprocessing.cpu_count()
|
24
|
+
NTHREADS = 8
|
18
25
|
|
19
26
|
|
20
27
|
class Command(BaseCommand):
|
21
|
-
""
|
22
|
-
|
23
|
-
|
24
|
-
This command starts Dramatiq workers using the configuration
|
25
|
-
defined in Django-CFG TaskConfig, with support for custom
|
26
|
-
process counts, queue selection, and worker options.
|
27
|
-
"""
|
28
|
-
|
29
|
-
help = "Run Dramatiq workers for background task processing"
|
30
|
-
|
28
|
+
help = "Run Dramatiq workers with Django-CFG configuration."
|
29
|
+
|
31
30
|
def add_arguments(self, parser):
|
32
|
-
|
31
|
+
parser.formatter_class = argparse.ArgumentDefaultsHelpFormatter
|
32
|
+
|
33
33
|
parser.add_argument(
|
34
|
-
"--processes",
|
34
|
+
"--processes", "-p",
|
35
|
+
default=NPROCS,
|
35
36
|
type=int,
|
36
|
-
help="
|
37
|
+
help="The number of processes to run",
|
37
38
|
)
|
38
39
|
parser.add_argument(
|
39
|
-
"--threads",
|
40
|
+
"--threads", "-t",
|
41
|
+
default=NTHREADS,
|
40
42
|
type=int,
|
41
|
-
help="
|
43
|
+
help="The number of threads per process to use",
|
42
44
|
)
|
43
45
|
parser.add_argument(
|
44
|
-
"--queues",
|
46
|
+
"--queues", "-Q",
|
47
|
+
nargs="*",
|
45
48
|
type=str,
|
46
|
-
help="
|
49
|
+
help="Listen to a subset of queues, or all when empty",
|
47
50
|
)
|
48
51
|
parser.add_argument(
|
49
|
-
"--
|
50
|
-
|
51
|
-
help="
|
52
|
+
"--watch",
|
53
|
+
dest="watch_dir",
|
54
|
+
help="Reload workers when changes are detected in the given directory",
|
52
55
|
)
|
53
56
|
parser.add_argument(
|
54
|
-
"--
|
57
|
+
"--pid-file",
|
55
58
|
type=str,
|
56
|
-
help="
|
59
|
+
help="Write the PID of the master process to this file",
|
57
60
|
)
|
58
61
|
parser.add_argument(
|
59
|
-
"--
|
62
|
+
"--log-file",
|
60
63
|
type=str,
|
61
|
-
help="Write
|
64
|
+
help="Write all logs to a file, or stderr when empty",
|
65
|
+
)
|
66
|
+
parser.add_argument(
|
67
|
+
"--worker-shutdown-timeout",
|
68
|
+
type=int,
|
69
|
+
default=600000,
|
70
|
+
help="Timeout for worker shutdown, in milliseconds"
|
62
71
|
)
|
63
72
|
parser.add_argument(
|
64
73
|
"--dry-run",
|
65
74
|
action="store_true",
|
66
75
|
help="Show configuration without starting workers",
|
67
76
|
)
|
68
|
-
|
69
|
-
def handle(self,
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
77
|
+
|
78
|
+
def handle(self, watch_dir, processes, threads, verbosity, queues,
|
79
|
+
pid_file, log_file, worker_shutdown_timeout, dry_run, **options):
|
80
|
+
|
81
|
+
# Get task service and validate
|
82
|
+
task_service = get_task_service()
|
83
|
+
if not task_service.is_enabled():
|
84
|
+
self.stdout.write(
|
85
|
+
self.style.ERROR("Task system is not enabled in Django-CFG configuration")
|
86
|
+
)
|
87
|
+
return
|
88
|
+
|
89
|
+
# Discover task modules
|
90
|
+
tasks_modules = self._discover_tasks_modules()
|
91
|
+
|
92
|
+
# Show configuration info
|
93
|
+
self.stdout.write(self.style.SUCCESS("Dramatiq Worker Configuration:"))
|
94
|
+
self.stdout.write(f"Processes: {processes}")
|
95
|
+
self.stdout.write(f"Threads: {threads}")
|
96
|
+
if queues:
|
97
|
+
self.stdout.write(f"Queues: {', '.join(queues)}")
|
98
|
+
else:
|
99
|
+
self.stdout.write("Queues: all")
|
100
|
+
|
101
|
+
self.stdout.write(f"\nDiscovered task modules:")
|
102
|
+
for module in tasks_modules:
|
103
|
+
self.stdout.write(f" - {module}")
|
104
|
+
|
105
|
+
# If dry run, show command and exit
|
106
|
+
if dry_run:
|
107
|
+
executable_name = "dramatiq"
|
108
|
+
process_args = [
|
109
|
+
executable_name,
|
110
|
+
"--processes", str(processes),
|
111
|
+
"--threads", str(threads),
|
112
|
+
"--worker-shutdown-timeout", str(worker_shutdown_timeout),
|
113
|
+
]
|
74
114
|
|
75
|
-
|
76
|
-
|
77
|
-
raise CommandError(
|
78
|
-
"Task system is not enabled. "
|
79
|
-
"Please configure 'tasks' in your Django-CFG configuration."
|
80
|
-
)
|
115
|
+
if watch_dir:
|
116
|
+
process_args.extend(["--watch", watch_dir])
|
81
117
|
|
82
|
-
|
83
|
-
|
84
|
-
raise CommandError(
|
85
|
-
"Task system configuration is invalid. "
|
86
|
-
"Please check your Redis connection and task settings."
|
87
|
-
)
|
118
|
+
verbosity_args = ["-v"] * (verbosity - 1)
|
119
|
+
process_args.extend(verbosity_args)
|
88
120
|
|
89
|
-
|
90
|
-
|
91
|
-
if not config:
|
92
|
-
raise CommandError("Task configuration not available")
|
121
|
+
if queues:
|
122
|
+
process_args.extend(["--queues", *queues])
|
93
123
|
|
94
|
-
|
95
|
-
|
124
|
+
if pid_file:
|
125
|
+
process_args.extend(["--pid-file", pid_file])
|
96
126
|
|
97
|
-
if
|
98
|
-
|
99
|
-
return
|
127
|
+
if log_file:
|
128
|
+
process_args.extend(["--log-file", log_file])
|
100
129
|
|
101
|
-
|
102
|
-
self._start_workers(worker_args, options)
|
130
|
+
process_args.extend(tasks_modules)
|
103
131
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
"Install with: pip install django-cfg[tasks]"
|
108
|
-
)
|
109
|
-
except Exception as e:
|
110
|
-
logger.exception("Failed to start Dramatiq workers")
|
111
|
-
raise CommandError(f"Failed to start workers: {e}")
|
112
|
-
|
113
|
-
def _build_worker_args(self, config, options) -> List[str]:
|
114
|
-
"""Build command line arguments for Dramatiq workers."""
|
115
|
-
args = ["dramatiq"]
|
116
|
-
|
117
|
-
# Process count
|
118
|
-
processes = options.get("processes") or config.get_effective_processes()
|
119
|
-
args.extend(["--processes", str(processes)])
|
132
|
+
self.stdout.write(f"\nCommand that would be executed:")
|
133
|
+
self.stdout.write(f' {" ".join(process_args)}')
|
134
|
+
return
|
120
135
|
|
121
|
-
#
|
122
|
-
|
123
|
-
args.extend(["--threads", str(threads)])
|
136
|
+
# Show startup info
|
137
|
+
self.stdout.write(self.style.SUCCESS("\nStarting Dramatiq workers..."))
|
124
138
|
|
125
|
-
#
|
126
|
-
|
127
|
-
|
128
|
-
else:
|
129
|
-
queues = config.get_effective_queues()
|
139
|
+
# Build dramatiq command
|
140
|
+
executable_name = "dramatiq"
|
141
|
+
executable_path = self._resolve_executable(executable_name)
|
130
142
|
|
131
|
-
|
132
|
-
|
143
|
+
# Build process arguments exactly like django_dramatiq
|
144
|
+
process_args = [
|
145
|
+
executable_name,
|
146
|
+
"--processes", str(processes),
|
147
|
+
"--threads", str(threads),
|
148
|
+
"--worker-shutdown-timeout", str(worker_shutdown_timeout),
|
149
|
+
]
|
133
150
|
|
134
|
-
#
|
135
|
-
|
136
|
-
|
151
|
+
# Add watch directory if specified
|
152
|
+
if watch_dir:
|
153
|
+
process_args.extend(["--watch", watch_dir])
|
137
154
|
|
138
|
-
#
|
139
|
-
|
140
|
-
|
155
|
+
# Add verbosity
|
156
|
+
verbosity_args = ["-v"] * (verbosity - 1)
|
157
|
+
process_args.extend(verbosity_args)
|
141
158
|
|
142
|
-
#
|
143
|
-
if
|
144
|
-
|
159
|
+
# Add queues if specified
|
160
|
+
if queues:
|
161
|
+
process_args.extend(["--queues", *queues])
|
145
162
|
|
146
|
-
#
|
163
|
+
# Add PID file if specified
|
164
|
+
if pid_file:
|
165
|
+
process_args.extend(["--pid-file", pid_file])
|
147
166
|
|
148
|
-
# Add
|
149
|
-
|
150
|
-
|
151
|
-
args.append(module)
|
167
|
+
# Add log file if specified
|
168
|
+
if log_file:
|
169
|
+
process_args.extend(["--log-file", log_file])
|
152
170
|
|
153
|
-
#
|
154
|
-
|
155
|
-
# Add common task module patterns
|
156
|
-
for app in settings.INSTALLED_APPS:
|
157
|
-
if not app.startswith("django.") and not app.startswith("django_"):
|
158
|
-
args.append(f"{app}.tasks")
|
171
|
+
# Add task modules (broker module first, then discovered modules)
|
172
|
+
process_args.extend(tasks_modules)
|
159
173
|
|
160
|
-
|
161
|
-
|
162
|
-
def _show_configuration(self, config, worker_args):
|
163
|
-
"""Show worker configuration without starting."""
|
164
|
-
self.stdout.write(
|
165
|
-
self.style.SUCCESS("Dramatiq Worker Configuration:")
|
166
|
-
)
|
174
|
+
self.stdout.write(f'Running dramatiq: "{" ".join(process_args)}"\n')
|
167
175
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
176
|
+
# Ensure DJANGO_SETTINGS_MODULE is set for worker processes
|
177
|
+
if not os.environ.get('DJANGO_SETTINGS_MODULE'):
|
178
|
+
if hasattr(settings, 'SETTINGS_MODULE'):
|
179
|
+
os.environ['DJANGO_SETTINGS_MODULE'] = settings.SETTINGS_MODULE
|
180
|
+
else:
|
181
|
+
# Try to detect from manage.py or current settings
|
182
|
+
import django
|
183
|
+
from django.conf import settings as django_settings
|
184
|
+
if hasattr(django_settings, '_wrapped') and hasattr(django_settings._wrapped, '__module__'):
|
185
|
+
module_name = django_settings._wrapped.__module__
|
186
|
+
os.environ['DJANGO_SETTINGS_MODULE'] = module_name
|
187
|
+
else:
|
188
|
+
self.stdout.write(
|
189
|
+
self.style.WARNING("Could not detect DJANGO_SETTINGS_MODULE")
|
190
|
+
)
|
191
|
+
|
192
|
+
# Use os.execvp like django_dramatiq to preserve environment
|
193
|
+
if sys.platform == "win32":
|
194
|
+
import subprocess
|
195
|
+
command = [executable_path] + process_args[1:]
|
196
|
+
sys.exit(subprocess.run(command))
|
181
197
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
self.style.SUCCESS("Starting Dramatiq workers...")
|
189
|
-
)
|
198
|
+
os.execvp(executable_path, process_args)
|
199
|
+
|
200
|
+
def _discover_tasks_modules(self):
|
201
|
+
"""Discover task modules like django_dramatiq does."""
|
202
|
+
# Always include our broker setup module first
|
203
|
+
tasks_modules = ["django_cfg.modules.dramatiq_setup"]
|
190
204
|
|
191
|
-
#
|
205
|
+
# Get task service for configuration
|
192
206
|
task_service = get_task_service()
|
193
|
-
config = task_service.config
|
194
|
-
|
195
|
-
processes = options.get("processes") or config.get_effective_processes()
|
196
|
-
threads = options.get("threads") or config.dramatiq.threads
|
197
|
-
queues = config.get_effective_queues()
|
198
|
-
if options.get("queues"):
|
199
|
-
queues = [q.strip() for q in options["queues"].split(",")]
|
200
|
-
|
201
|
-
self.stdout.write(f"Processes: {processes}")
|
202
|
-
self.stdout.write(f"Threads: {threads}")
|
203
|
-
self.stdout.write(f"Queues: {', '.join(queues)}")
|
204
207
|
|
205
|
-
#
|
206
|
-
if
|
207
|
-
|
208
|
-
|
209
|
-
|
208
|
+
# Try to get task modules from Django-CFG config
|
209
|
+
if task_service.config and task_service.config.auto_discover_tasks:
|
210
|
+
discovered = task_service.discover_tasks()
|
211
|
+
for module_name in discovered:
|
212
|
+
self.stdout.write(f"Discovered tasks module: '{module_name}'")
|
213
|
+
tasks_modules.append(module_name)
|
210
214
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
executable_path = self._resolve_executable(executable_name)
|
215
|
-
|
216
|
-
# Discover task modules (including django_dramatiq.setup)
|
217
|
-
discovered_modules = self._discover_task_modules()
|
215
|
+
# Fallback: use django_dramatiq discovery logic
|
216
|
+
if len(tasks_modules) == 1: # Only broker module found
|
217
|
+
task_module_names = getattr(settings, "DRAMATIQ_AUTODISCOVER_MODULES", ("tasks",))
|
218
218
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
# Use subprocess like django_dramatiq does
|
235
|
-
import subprocess
|
236
|
-
if sys.platform == "win32":
|
237
|
-
command = [executable_path] + process_args[1:]
|
238
|
-
sys.exit(subprocess.run(command))
|
239
|
-
else:
|
240
|
-
# Use os.execvp like django_dramatiq
|
241
|
-
os.execvp(executable_path, process_args)
|
242
|
-
|
243
|
-
except KeyboardInterrupt:
|
244
|
-
self.stdout.write("\nShutting down workers...")
|
245
|
-
except Exception as e:
|
246
|
-
logger.exception("Worker execution failed")
|
247
|
-
raise CommandError(f"Worker execution failed: {e}")
|
248
|
-
|
249
|
-
def _discover_task_modules(self):
|
250
|
-
"""Discover task modules like django_dramatiq does."""
|
251
|
-
task_modules = ["django_dramatiq.setup"] # Always include setup module
|
252
|
-
|
253
|
-
# Add discovered modules from django-cfg
|
254
|
-
discovered_modules = get_task_service().discover_tasks()
|
255
|
-
for module_name in discovered_modules:
|
256
|
-
self.stdout.write(f"Discovered tasks module: {module_name}")
|
257
|
-
task_modules.append(module_name)
|
258
|
-
|
259
|
-
return task_modules
|
260
|
-
|
219
|
+
for app_config in apps.get_app_configs():
|
220
|
+
for task_module in task_module_names:
|
221
|
+
if module_has_submodule(app_config.module, task_module):
|
222
|
+
module_name = f"{app_config.name}.{task_module}"
|
223
|
+
try:
|
224
|
+
importlib.import_module(module_name)
|
225
|
+
self.stdout.write(f"Discovered tasks module: '{module_name}'")
|
226
|
+
tasks_modules.append(module_name)
|
227
|
+
except ImportError:
|
228
|
+
# Module exists but has import errors, skip it
|
229
|
+
pass
|
230
|
+
|
231
|
+
return tasks_modules
|
232
|
+
|
261
233
|
def _resolve_executable(self, exec_name):
|
262
234
|
"""Resolve executable path like django_dramatiq does."""
|
263
235
|
bin_dir = os.path.dirname(sys.executable)
|
@@ -266,4 +238,4 @@ class Command(BaseCommand):
|
|
266
238
|
exec_path = os.path.join(d, exec_name)
|
267
239
|
if os.path.isfile(exec_path):
|
268
240
|
return exec_path
|
269
|
-
return exec_name
|
241
|
+
return exec_name
|