django-cfg 1.4.10__py3-none-any.whl → 1.4.11__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/apps/agents/management/commands/create_agent.py +1 -1
- django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
- django_cfg/apps/newsletter/serializers.py +40 -3
- django_cfg/apps/newsletter/views/campaigns.py +12 -3
- django_cfg/apps/newsletter/views/emails.py +14 -3
- django_cfg/apps/newsletter/views/subscriptions.py +12 -2
- django_cfg/apps/payments/views/api/currencies.py +49 -6
- django_cfg/apps/payments/views/api/webhooks.py +72 -7
- django_cfg/apps/payments/views/overview/serializers.py +34 -1
- django_cfg/apps/payments/views/overview/views.py +2 -1
- django_cfg/apps/payments/views/serializers/payments.py +6 -6
- django_cfg/apps/urls.py +106 -45
- django_cfg/core/base/config_model.py +2 -2
- django_cfg/core/constants.py +1 -1
- django_cfg/core/generation/integration_generators/__init__.py +1 -1
- django_cfg/core/generation/integration_generators/api.py +72 -49
- django_cfg/core/integration/display/startup.py +30 -22
- django_cfg/core/integration/url_integration.py +15 -16
- django_cfg/dashboard/sections/documentation.py +391 -0
- django_cfg/management/commands/check_endpoints.py +11 -160
- django_cfg/management/commands/check_settings.py +13 -348
- django_cfg/management/commands/clear_constance.py +13 -201
- django_cfg/management/commands/create_token.py +13 -321
- django_cfg/management/commands/generate_clients.py +23 -0
- django_cfg/management/commands/list_urls.py +13 -306
- django_cfg/management/commands/migrate_all.py +13 -126
- django_cfg/management/commands/migrator.py +13 -396
- django_cfg/management/commands/rundramatiq.py +15 -247
- django_cfg/management/commands/rundramatiq_simulator.py +12 -429
- django_cfg/management/commands/runserver_ngrok.py +15 -160
- django_cfg/management/commands/script.py +12 -488
- django_cfg/management/commands/show_config.py +12 -215
- django_cfg/management/commands/show_urls.py +12 -342
- django_cfg/management/commands/superuser.py +15 -295
- django_cfg/management/commands/task_clear.py +14 -217
- django_cfg/management/commands/task_status.py +13 -248
- django_cfg/management/commands/test_email.py +15 -86
- django_cfg/management/commands/test_telegram.py +14 -61
- django_cfg/management/commands/test_twilio.py +15 -105
- django_cfg/management/commands/tree.py +13 -383
- django_cfg/management/commands/validate_openapi.py +10 -0
- django_cfg/middleware/README.md +1 -1
- django_cfg/middleware/user_activity.py +3 -3
- django_cfg/models/__init__.py +2 -2
- django_cfg/models/api/drf/spectacular.py +6 -6
- django_cfg/models/django/__init__.py +2 -2
- django_cfg/models/django/openapi.py +238 -0
- django_cfg/modules/django_admin/management/__init__.py +0 -0
- django_cfg/modules/django_admin/management/commands/__init__.py +0 -0
- django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
- django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
- django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
- django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
- django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
- django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
- django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
- django_cfg/modules/django_admin/management/commands/script.py +496 -0
- django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
- django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
- django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
- django_cfg/modules/django_admin/management/commands/tree.py +390 -0
- django_cfg/modules/django_client/__init__.py +20 -0
- django_cfg/modules/django_client/apps.py +35 -0
- django_cfg/modules/django_client/core/__init__.py +56 -0
- django_cfg/modules/django_client/core/archive/__init__.py +11 -0
- django_cfg/modules/django_client/core/archive/manager.py +134 -0
- django_cfg/modules/django_client/core/cli/__init__.py +12 -0
- django_cfg/modules/django_client/core/cli/main.py +235 -0
- django_cfg/modules/django_client/core/config/__init__.py +18 -0
- django_cfg/modules/django_client/core/config/config.py +188 -0
- django_cfg/modules/django_client/core/config/group.py +101 -0
- django_cfg/modules/django_client/core/config/service.py +209 -0
- django_cfg/modules/django_client/core/generator/__init__.py +115 -0
- django_cfg/modules/django_client/core/generator/base.py +767 -0
- django_cfg/modules/django_client/core/generator/python.py +751 -0
- django_cfg/modules/django_client/core/generator/templates/python/__init__.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/templates/python/api_wrapper.py.jinja +130 -0
- django_cfg/modules/django_client/core/generator/templates/python/app_init.py.jinja +6 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/app_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/flat_client.py.jinja +38 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/main_client.py.jinja +50 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/main_client_file.py.jinja +13 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/operation_method.py.jinja +7 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/sub_client.py.jinja +11 -0
- django_cfg/modules/django_client/core/generator/templates/python/client_file.py.jinja +13 -0
- django_cfg/modules/django_client/core/generator/templates/python/main_init.py.jinja +50 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/app_models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/enum_class.py.jinja +15 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/enums.py.jinja +8 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/schema_class.py.jinja +19 -0
- django_cfg/modules/django_client/core/generator/templates/python/utils/logger.py.jinja +255 -0
- django_cfg/modules/django_client/core/generator/templates/python/utils/schema.py.jinja +12 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/app_index.ts.jinja +2 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/app_client.ts.jinja +18 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/client.ts.jinja +327 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/flat_client.ts.jinja +109 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/main_client_file.ts.jinja +9 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/operation.ts.jinja +61 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/sub_client.ts.jinja +15 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client_file.ts.jinja +9 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/index.ts.jinja +5 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/main_index.ts.jinja +206 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/models/app_models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/models/enums.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/models/models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/errors.ts.jinja +114 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/http.ts.jinja +98 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/logger.ts.jinja +251 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/schema.ts.jinja +7 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/storage.ts.jinja +114 -0
- django_cfg/modules/django_client/core/generator/typescript.py +872 -0
- django_cfg/modules/django_client/core/groups/__init__.py +13 -0
- django_cfg/modules/django_client/core/groups/detector.py +178 -0
- django_cfg/modules/django_client/core/groups/manager.py +314 -0
- django_cfg/modules/django_client/core/ir/__init__.py +57 -0
- django_cfg/modules/django_client/core/ir/context.py +387 -0
- django_cfg/modules/django_client/core/ir/operation.py +518 -0
- django_cfg/modules/django_client/core/ir/schema.py +353 -0
- django_cfg/modules/django_client/core/parser/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/base.py +648 -0
- django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/models/base.py +212 -0
- django_cfg/modules/django_client/core/parser/models/components.py +160 -0
- django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
- django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
- django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
- django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
- django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
- django_cfg/modules/django_client/core/validation/__init__.py +22 -0
- django_cfg/modules/django_client/core/validation/checker.py +134 -0
- django_cfg/modules/django_client/core/validation/fixer.py +216 -0
- django_cfg/modules/django_client/core/validation/reporter.py +480 -0
- django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
- django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
- django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
- django_cfg/modules/django_client/core/validation/safety.py +266 -0
- django_cfg/modules/django_client/management/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +422 -0
- django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
- django_cfg/modules/django_client/spectacular/__init__.py +9 -0
- django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
- django_cfg/modules/django_client/urls.py +72 -0
- django_cfg/modules/django_email/management/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/test_email.py +93 -0
- django_cfg/modules/django_logging/django_logger.py +6 -6
- django_cfg/modules/django_ngrok/management/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
- django_cfg/modules/django_tasks/management/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
- django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
- django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
- django_cfg/modules/django_telegram/management/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
- django_cfg/modules/django_twilio/management/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
- django_cfg/modules/django_unfold/callbacks/main.py +16 -5
- django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
- django_cfg/pyproject.toml +2 -6
- django_cfg/registry/third_party.py +5 -7
- django_cfg/routing/callbacks.py +1 -1
- django_cfg/static/admin/css/prose-unfold.css +666 -0
- django_cfg/templates/admin/index.html +8 -0
- django_cfg/templates/admin/index_new.html +13 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
- django_cfg/templates/admin/sections/documentation_section.html +172 -0
- django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/METADATA +2 -2
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/RECORD +180 -59
- django_cfg/management/commands/generate.py +0 -107
- /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/licenses/LICENSE +0 -0
@@ -1,437 +1,20 @@
|
|
1
1
|
"""
|
2
|
-
Django
|
2
|
+
Django-CFG wrapper for rundramatiq_simulator command.
|
3
|
+
|
4
|
+
This is a simple alias for django_tasks.management.commands.rundramatiq_simulator.
|
5
|
+
All logic is in django_tasks module.
|
3
6
|
|
4
7
|
Usage:
|
5
|
-
python manage.py rundramatiq_simulator
|
6
|
-
python manage.py rundramatiq_simulator --workers 5
|
7
|
-
python manage.py rundramatiq_simulator --clear-only
|
8
|
-
python manage.py rundramatiq_simulator --show-keys
|
8
|
+
python manage.py rundramatiq_simulator
|
9
9
|
"""
|
10
10
|
|
11
|
-
import
|
12
|
-
import json
|
13
|
-
import time
|
14
|
-
from datetime import datetime, timezone
|
15
|
-
import random
|
16
|
-
from typing import Dict, Any, Optional
|
17
|
-
|
18
|
-
from django.core.management.base import BaseCommand, CommandError
|
19
|
-
from django.conf import settings
|
20
|
-
|
21
|
-
from django_cfg.modules.django_tasks import DjangoTasks
|
22
|
-
|
23
|
-
|
24
|
-
class TaskSimulator:
|
25
|
-
"""Task data simulator for Tasks Dashboard."""
|
26
|
-
|
27
|
-
def __init__(self):
|
28
|
-
"""Initialize the simulator."""
|
29
|
-
self.tasks_service = DjangoTasks()
|
30
|
-
|
31
|
-
# Get Redis client using the same logic as DjangoTasks
|
32
|
-
try:
|
33
|
-
redis_url = self.tasks_service.get_redis_url()
|
34
|
-
if not redis_url:
|
35
|
-
raise RuntimeError("No Redis URL available")
|
36
|
-
|
37
|
-
# Parse URL for connection
|
38
|
-
from urllib.parse import urlparse
|
39
|
-
parsed = urlparse(redis_url)
|
40
|
-
|
41
|
-
self.redis_client = redis.Redis(
|
42
|
-
host=parsed.hostname or 'localhost',
|
43
|
-
port=parsed.port or 6379,
|
44
|
-
db=int(parsed.path.lstrip('/')) if parsed.path else 1,
|
45
|
-
decode_responses=True
|
46
|
-
)
|
47
|
-
|
48
|
-
except Exception as e:
|
49
|
-
raise CommandError(f"Failed to connect to Redis: {e}")
|
50
|
-
|
51
|
-
# Get queue configuration
|
52
|
-
try:
|
53
|
-
config = self.tasks_service.get_config()
|
54
|
-
self.queues = config.tasks.dramatiq.queues
|
55
|
-
except Exception as e:
|
56
|
-
# Use default queues if we can't get configuration
|
57
|
-
self.queues = ['critical', 'high', 'default', 'low', 'background', 'payments', 'agents', 'knowbase']
|
58
|
-
|
59
|
-
def clear_all_data(self) -> int:
|
60
|
-
"""
|
61
|
-
Clear all test data.
|
62
|
-
|
63
|
-
Returns:
|
64
|
-
Number of deleted keys
|
65
|
-
"""
|
66
|
-
keys = self.redis_client.keys("dramatiq:*")
|
67
|
-
if keys:
|
68
|
-
deleted = self.redis_client.delete(*keys)
|
69
|
-
return deleted
|
70
|
-
return 0
|
71
|
-
|
72
|
-
def simulate_queues(self, pending_tasks_per_queue=None, failed_tasks_per_queue=None) -> Dict[str, Dict[str, int]]:
|
73
|
-
"""
|
74
|
-
Simulate queues with tasks.
|
75
|
-
|
76
|
-
Args:
|
77
|
-
pending_tasks_per_queue: Dict[str, int] - number of pending tasks per queue
|
78
|
-
failed_tasks_per_queue: Dict[str, int] - number of failed tasks per queue
|
79
|
-
|
80
|
-
Returns:
|
81
|
-
Dict with information about created tasks
|
82
|
-
"""
|
83
|
-
if pending_tasks_per_queue is None:
|
84
|
-
pending_tasks_per_queue = {
|
85
|
-
'critical': 2,
|
86
|
-
'high': 5,
|
87
|
-
'default': 12,
|
88
|
-
'low': 8,
|
89
|
-
'background': 15,
|
90
|
-
'payments': 3,
|
91
|
-
'agents': 7,
|
92
|
-
'knowbase': 4
|
93
|
-
}
|
94
|
-
|
95
|
-
if failed_tasks_per_queue is None:
|
96
|
-
failed_tasks_per_queue = {
|
97
|
-
'critical': 0,
|
98
|
-
'high': 1,
|
99
|
-
'default': 3,
|
100
|
-
'low': 2,
|
101
|
-
'background': 1,
|
102
|
-
'payments': 0,
|
103
|
-
'agents': 1,
|
104
|
-
'knowbase': 0
|
105
|
-
}
|
106
|
-
|
107
|
-
results = {}
|
108
|
-
|
109
|
-
for queue_name in self.queues:
|
110
|
-
queue_results = {'pending': 0, 'failed': 0}
|
111
|
-
|
112
|
-
# Pending tasks
|
113
|
-
pending_count = pending_tasks_per_queue.get(queue_name, 0)
|
114
|
-
if pending_count > 0:
|
115
|
-
queue_key = f"dramatiq:default.DQ.{queue_name}"
|
116
|
-
|
117
|
-
# Add fake tasks to queue
|
118
|
-
for i in range(pending_count):
|
119
|
-
task_data = {
|
120
|
-
"queue_name": queue_name,
|
121
|
-
"actor_name": f"process_{queue_name}_task",
|
122
|
-
"args": [f"task_{i}"],
|
123
|
-
"kwargs": {},
|
124
|
-
"options": {},
|
125
|
-
"message_id": f"msg_{queue_name}_{i}_{int(time.time())}",
|
126
|
-
"message_timestamp": int(time.time() * 1000)
|
127
|
-
}
|
128
|
-
self.redis_client.lpush(queue_key, json.dumps(task_data))
|
129
|
-
|
130
|
-
queue_results['pending'] = pending_count
|
131
|
-
|
132
|
-
# Failed tasks
|
133
|
-
failed_count = failed_tasks_per_queue.get(queue_name, 0)
|
134
|
-
if failed_count > 0:
|
135
|
-
failed_key = f"dramatiq:default.DQ.{queue_name}.failed"
|
136
|
-
|
137
|
-
# Add fake failed tasks
|
138
|
-
for i in range(failed_count):
|
139
|
-
failed_task_data = {
|
140
|
-
"queue_name": queue_name,
|
141
|
-
"actor_name": f"failed_{queue_name}_task",
|
142
|
-
"args": [f"failed_task_{i}"],
|
143
|
-
"kwargs": {},
|
144
|
-
"options": {},
|
145
|
-
"message_id": f"failed_msg_{queue_name}_{i}_{int(time.time())}",
|
146
|
-
"message_timestamp": int(time.time() * 1000),
|
147
|
-
"error": f"Simulated error for {queue_name} task {i}"
|
148
|
-
}
|
149
|
-
self.redis_client.lpush(failed_key, json.dumps(failed_task_data))
|
150
|
-
|
151
|
-
queue_results['failed'] = failed_count
|
152
|
-
|
153
|
-
if queue_results['pending'] > 0 or queue_results['failed'] > 0:
|
154
|
-
results[queue_name] = queue_results
|
155
|
-
|
156
|
-
return results
|
157
|
-
|
158
|
-
def simulate_workers(self, worker_count=3) -> list:
|
159
|
-
"""
|
160
|
-
Симулировать активных воркеров.
|
161
|
-
|
162
|
-
Args:
|
163
|
-
worker_count: Количество воркеров для симуляции
|
164
|
-
|
165
|
-
Returns:
|
166
|
-
Список ID созданных воркеров
|
167
|
-
"""
|
168
|
-
worker_ids = []
|
169
|
-
|
170
|
-
for i in range(worker_count):
|
171
|
-
worker_id = f"worker_{i}_{int(time.time())}"
|
172
|
-
worker_key = f"dramatiq:worker:{worker_id}"
|
173
|
-
|
174
|
-
worker_data = {
|
175
|
-
"worker_id": worker_id,
|
176
|
-
"hostname": f"localhost",
|
177
|
-
"pid": 1000 + i,
|
178
|
-
"queues": self.queues,
|
179
|
-
"started_at": datetime.now(timezone.utc).isoformat(),
|
180
|
-
"last_heartbeat": datetime.now(timezone.utc).isoformat(),
|
181
|
-
"status": "active"
|
182
|
-
}
|
183
|
-
|
184
|
-
# Устанавливаем данные воркера с TTL
|
185
|
-
self.redis_client.setex(
|
186
|
-
worker_key,
|
187
|
-
300, # 5 минут TTL
|
188
|
-
json.dumps(worker_data)
|
189
|
-
)
|
190
|
-
|
191
|
-
worker_ids.append(worker_id)
|
192
|
-
|
193
|
-
return worker_ids
|
194
|
-
|
195
|
-
def simulate_task_statistics(self) -> Dict[str, Any]:
|
196
|
-
"""
|
197
|
-
Симулировать статистику задач.
|
198
|
-
|
199
|
-
Returns:
|
200
|
-
Созданная статистика
|
201
|
-
"""
|
202
|
-
stats_data = {
|
203
|
-
"total_processed": random.randint(1000, 2000),
|
204
|
-
"total_failed": random.randint(30, 80),
|
205
|
-
"total_retried": random.randint(15, 40),
|
206
|
-
"processing_time_avg": round(random.uniform(1.5, 4.0), 2),
|
207
|
-
"last_updated": datetime.now(timezone.utc).isoformat()
|
208
|
-
}
|
209
|
-
|
210
|
-
stats_key = "dramatiq:stats"
|
211
|
-
self.redis_client.setex(stats_key, 3600, json.dumps(stats_data))
|
212
|
-
|
213
|
-
return stats_data
|
214
|
-
|
215
|
-
def run_simulation(self, workers=3, clear_first=True) -> Dict[str, Any]:
|
216
|
-
"""
|
217
|
-
Запустить полную симуляцию.
|
218
|
-
|
219
|
-
Args:
|
220
|
-
workers: Количество воркеров
|
221
|
-
clear_first: Очистить данные перед симуляцией
|
222
|
-
|
223
|
-
Returns:
|
224
|
-
Результаты симуляции
|
225
|
-
"""
|
226
|
-
results = {
|
227
|
-
'cleared_keys': 0,
|
228
|
-
'queues': {},
|
229
|
-
'workers': [],
|
230
|
-
'statistics': {}
|
231
|
-
}
|
232
|
-
|
233
|
-
if clear_first:
|
234
|
-
results['cleared_keys'] = self.clear_all_data()
|
235
|
-
|
236
|
-
results['queues'] = self.simulate_queues()
|
237
|
-
results['workers'] = self.simulate_workers(workers)
|
238
|
-
results['statistics'] = self.simulate_task_statistics()
|
239
|
-
|
240
|
-
return results
|
241
|
-
|
242
|
-
def get_redis_summary(self) -> Dict[str, Any]:
|
243
|
-
"""Получить сводку по данным в Redis."""
|
244
|
-
summary = {
|
245
|
-
'total_keys': 0,
|
246
|
-
'queues': {},
|
247
|
-
'workers': 0,
|
248
|
-
'statistics': None
|
249
|
-
}
|
250
|
-
|
251
|
-
# Подсчитываем все ключи
|
252
|
-
all_keys = self.redis_client.keys("dramatiq:*")
|
253
|
-
summary['total_keys'] = len(all_keys)
|
254
|
-
|
255
|
-
# Анализируем очереди
|
256
|
-
for queue_name in self.queues:
|
257
|
-
pending_key = f"dramatiq:default.DQ.{queue_name}"
|
258
|
-
failed_key = f"dramatiq:default.DQ.{queue_name}.failed"
|
259
|
-
|
260
|
-
pending = self.redis_client.llen(pending_key)
|
261
|
-
failed = self.redis_client.llen(failed_key)
|
262
|
-
|
263
|
-
if pending > 0 or failed > 0:
|
264
|
-
summary['queues'][queue_name] = {
|
265
|
-
'pending': pending,
|
266
|
-
'failed': failed
|
267
|
-
}
|
268
|
-
|
269
|
-
# Подсчитываем воркеров
|
270
|
-
worker_keys = self.redis_client.keys("dramatiq:worker:*")
|
271
|
-
summary['workers'] = len(worker_keys)
|
272
|
-
|
273
|
-
# Получаем статистику
|
274
|
-
stats_key = "dramatiq:stats"
|
275
|
-
if self.redis_client.exists(stats_key):
|
276
|
-
try:
|
277
|
-
stats_data = self.redis_client.get(stats_key)
|
278
|
-
summary['statistics'] = json.loads(stats_data)
|
279
|
-
except:
|
280
|
-
pass
|
281
|
-
|
282
|
-
return summary
|
283
|
-
|
11
|
+
from django_cfg.modules.django_tasks.management.commands.rundramatiq_simulator import Command as SimulatorCommand
|
284
12
|
|
285
|
-
class Command(BaseCommand):
|
286
|
-
"""Django management command для симуляции Dramatiq данных."""
|
287
13
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
is_destructive = False
|
14
|
+
class Command(SimulatorCommand):
|
15
|
+
"""
|
16
|
+
Alias for rundramatiq_simulator command.
|
292
17
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
"""Добавить аргументы команды."""
|
297
|
-
parser.add_argument(
|
298
|
-
'--workers',
|
299
|
-
type=int,
|
300
|
-
default=3,
|
301
|
-
help='Number of workers to simulate (default: 3)'
|
302
|
-
)
|
303
|
-
|
304
|
-
parser.add_argument(
|
305
|
-
'--no-clear',
|
306
|
-
action='store_true',
|
307
|
-
help='Do not clear existing data before simulation'
|
308
|
-
)
|
309
|
-
|
310
|
-
parser.add_argument(
|
311
|
-
'--clear-only',
|
312
|
-
action='store_true',
|
313
|
-
help='Only clear data, do not simulate'
|
314
|
-
)
|
315
|
-
|
316
|
-
parser.add_argument(
|
317
|
-
'--show-keys',
|
318
|
-
action='store_true',
|
319
|
-
help='Show Redis keys after operation'
|
320
|
-
)
|
321
|
-
|
322
|
-
parser.add_argument(
|
323
|
-
'--summary',
|
324
|
-
action='store_true',
|
325
|
-
help='Show summary of current Redis data'
|
326
|
-
)
|
327
|
-
|
328
|
-
def handle(self, *args, **options):
|
329
|
-
"""Выполнить команду."""
|
330
|
-
try:
|
331
|
-
simulator = TaskSimulator()
|
332
|
-
|
333
|
-
# Показать только сводку
|
334
|
-
if options['summary']:
|
335
|
-
self.show_summary(simulator)
|
336
|
-
return
|
337
|
-
|
338
|
-
# Только очистка
|
339
|
-
if options['clear_only']:
|
340
|
-
self.stdout.write("🧹 Clearing all test data...")
|
341
|
-
cleared = simulator.clear_all_data()
|
342
|
-
self.stdout.write(
|
343
|
-
self.style.SUCCESS(f"✅ Cleared {cleared} Redis keys")
|
344
|
-
)
|
345
|
-
return
|
346
|
-
|
347
|
-
# Полная симуляция
|
348
|
-
self.stdout.write("🎭 Starting Dramatiq Task Simulation")
|
349
|
-
self.stdout.write("=" * 50)
|
350
|
-
|
351
|
-
results = simulator.run_simulation(
|
352
|
-
workers=options['workers'],
|
353
|
-
clear_first=not options['no_clear']
|
354
|
-
)
|
355
|
-
|
356
|
-
# Показываем результаты
|
357
|
-
if results['cleared_keys'] > 0:
|
358
|
-
self.stdout.write(f"🧹 Cleared {results['cleared_keys']} existing keys")
|
359
|
-
|
360
|
-
self.stdout.write(f"📋 Created queues:")
|
361
|
-
total_pending = 0
|
362
|
-
total_failed = 0
|
363
|
-
|
364
|
-
for queue_name, counts in results['queues'].items():
|
365
|
-
pending = counts['pending']
|
366
|
-
failed = counts['failed']
|
367
|
-
total_pending += pending
|
368
|
-
total_failed += failed
|
369
|
-
|
370
|
-
self.stdout.write(f" {queue_name}: {pending} pending, {failed} failed")
|
371
|
-
|
372
|
-
self.stdout.write(f"👷 Created {len(results['workers'])} workers")
|
373
|
-
self.stdout.write(f"📊 Added task statistics")
|
374
|
-
|
375
|
-
self.stdout.write("=" * 50)
|
376
|
-
self.stdout.write(self.style.SUCCESS("✅ Simulation completed!"))
|
377
|
-
|
378
|
-
self.stdout.write(f"\n📊 Summary:")
|
379
|
-
active_queues = len(results['queues'])
|
380
|
-
self.stdout.write(f" Active Queues: {active_queues}")
|
381
|
-
self.stdout.write(f" Active Workers: {len(results['workers'])}")
|
382
|
-
self.stdout.write(f" Pending Tasks: {total_pending}")
|
383
|
-
self.stdout.write(f" Failed Tasks: {total_failed}")
|
384
|
-
|
385
|
-
self.stdout.write(f"\n🌐 Dashboard URL: http://localhost:8000/cfg/admin/django_cfg_tasks/admin/dashboard/")
|
386
|
-
|
387
|
-
# Показать ключи если запрошено
|
388
|
-
if options['show_keys']:
|
389
|
-
self.show_redis_keys(simulator)
|
390
|
-
|
391
|
-
except Exception as e:
|
392
|
-
raise CommandError(f"Simulation failed: {e}")
|
393
|
-
|
394
|
-
def show_summary(self, simulator: TaskSimulator):
|
395
|
-
"""Показать сводку текущих данных."""
|
396
|
-
self.stdout.write("📊 Current Redis Data Summary")
|
397
|
-
self.stdout.write("=" * 40)
|
398
|
-
|
399
|
-
summary = simulator.get_redis_summary()
|
400
|
-
|
401
|
-
self.stdout.write(f"Total Redis keys: {summary['total_keys']}")
|
402
|
-
self.stdout.write(f"Active workers: {summary['workers']}")
|
403
|
-
|
404
|
-
if summary['queues']:
|
405
|
-
self.stdout.write("\nQueues:")
|
406
|
-
for queue_name, counts in summary['queues'].items():
|
407
|
-
self.stdout.write(f" {queue_name}: {counts['pending']} pending, {counts['failed']} failed")
|
408
|
-
else:
|
409
|
-
self.stdout.write("\nNo active queues found")
|
410
|
-
|
411
|
-
if summary['statistics']:
|
412
|
-
stats = summary['statistics']
|
413
|
-
self.stdout.write(f"\nStatistics:")
|
414
|
-
self.stdout.write(f" Total processed: {stats.get('total_processed', 'N/A')}")
|
415
|
-
self.stdout.write(f" Total failed: {stats.get('total_failed', 'N/A')}")
|
416
|
-
self.stdout.write(f" Avg processing time: {stats.get('processing_time_avg', 'N/A')}s")
|
417
|
-
|
418
|
-
def show_redis_keys(self, simulator: TaskSimulator):
|
419
|
-
"""Показать все Redis ключи."""
|
420
|
-
self.stdout.write("\n🔍 Redis Keys:")
|
421
|
-
keys = simulator.redis_client.keys("dramatiq:*")
|
422
|
-
|
423
|
-
if not keys:
|
424
|
-
self.stdout.write(" No Dramatiq keys found")
|
425
|
-
return
|
426
|
-
|
427
|
-
for key in sorted(keys):
|
428
|
-
key_type = simulator.redis_client.type(key)
|
429
|
-
if key_type == 'list':
|
430
|
-
length = simulator.redis_client.llen(key)
|
431
|
-
self.stdout.write(f" {key} (list): {length} items")
|
432
|
-
elif key_type == 'string':
|
433
|
-
ttl = simulator.redis_client.ttl(key)
|
434
|
-
ttl_str = f"TTL {ttl}s" if ttl > 0 else "no TTL"
|
435
|
-
self.stdout.write(f" {key} (string): {ttl_str}")
|
436
|
-
else:
|
437
|
-
self.stdout.write(f" {key} ({key_type})")
|
18
|
+
Simply inherits from SimulatorCommand without any changes.
|
19
|
+
"""
|
20
|
+
pass
|
@@ -1,167 +1,22 @@
|
|
1
1
|
"""
|
2
|
-
|
2
|
+
Django-CFG wrapper for runserver_ngrok command.
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
import os
|
8
|
-
import time
|
9
|
-
from django.core.management.commands.runserver import Command as RunServerCommand
|
10
|
-
from django_cfg.modules.django_ngrok import get_ngrok_service
|
11
|
-
from django_cfg.modules.django_logging import get_logger
|
12
|
-
|
13
|
-
logger = get_logger('runserver_ngrok')
|
4
|
+
This is a simple alias for django_ngrok.management.commands.runserver_ngrok.
|
5
|
+
All logic is in django_ngrok module.
|
14
6
|
|
7
|
+
Usage:
|
8
|
+
python manage.py runserver_ngrok
|
9
|
+
python manage.py runserver_ngrok --domain example.com
|
10
|
+
python manage.py runserver_ngrok --no-ngrok
|
11
|
+
"""
|
15
12
|
|
16
|
-
|
17
|
-
"""Enhanced runserver command with ngrok tunnel support."""
|
18
|
-
|
19
|
-
# Web execution metadata
|
20
|
-
web_executable = False
|
21
|
-
requires_input = False
|
22
|
-
is_destructive = False
|
13
|
+
from django_cfg.modules.django_ngrok.management.commands.runserver_ngrok import Command as NgrokCommand
|
23
14
|
|
24
|
-
help = f'{RunServerCommand.help.rstrip(".")} with ngrok tunnel.'
|
25
|
-
|
26
|
-
def add_arguments(self, parser):
|
27
|
-
super().add_arguments(parser)
|
28
|
-
parser.add_argument(
|
29
|
-
'--domain',
|
30
|
-
help='Custom ngrok domain (requires paid plan)'
|
31
|
-
)
|
32
|
-
parser.add_argument(
|
33
|
-
'--no-ngrok',
|
34
|
-
action='store_true',
|
35
|
-
help='Disable ngrok tunnel even if configured'
|
36
|
-
)
|
37
|
-
|
38
|
-
def handle(self, *args, **options):
|
39
|
-
"""Handle the command with ngrok integration."""
|
40
|
-
|
41
|
-
# Check if ngrok should be disabled
|
42
|
-
if options.get('no_ngrok'):
|
43
|
-
self.stdout.write("Ngrok disabled by --no-ngrok flag")
|
44
|
-
return super().handle(*args, **options)
|
45
|
-
|
46
|
-
# Get ngrok service
|
47
|
-
try:
|
48
|
-
ngrok_service = get_ngrok_service()
|
49
|
-
|
50
|
-
# Check if ngrok is configured and enabled
|
51
|
-
config = ngrok_service.get_config()
|
52
|
-
if not config or not hasattr(config, 'ngrok') or not config.ngrok or not config.ngrok.enabled:
|
53
|
-
self.stdout.write("Ngrok not configured or disabled")
|
54
|
-
return super().handle(*args, **options)
|
55
|
-
except Exception as e:
|
56
|
-
self.stdout.write(f"Error accessing ngrok configuration: {e}")
|
57
|
-
return super().handle(*args, **options)
|
58
|
-
|
59
|
-
# Override domain if provided
|
60
|
-
if options.get('domain'):
|
61
|
-
config.ngrok.tunnel.domain = options['domain']
|
62
|
-
|
63
|
-
# Start the server normally first
|
64
|
-
self.stdout.write("Starting Django development server...")
|
65
|
-
|
66
|
-
# Call parent handle but intercept the server start
|
67
|
-
return super().handle(*args, **options)
|
68
|
-
|
69
|
-
def on_bind(self, server_port):
|
70
|
-
"""Called when server binds to port - start ngrok tunnel here."""
|
71
|
-
super().on_bind(server_port)
|
72
|
-
|
73
|
-
# Start ngrok tunnel
|
74
|
-
ngrok_service = get_ngrok_service()
|
75
|
-
|
76
|
-
self.stdout.write("🚇 Starting ngrok tunnel...")
|
77
|
-
logger.info(f"Starting ngrok tunnel for port {server_port}")
|
78
|
-
|
79
|
-
tunnel_url = ngrok_service.start_tunnel(server_port)
|
80
|
-
|
81
|
-
if tunnel_url:
|
82
|
-
# Wait for tunnel to be fully established
|
83
|
-
self.stdout.write("⏳ Waiting for tunnel to be established...")
|
84
|
-
logger.info("Waiting for ngrok tunnel to be fully established")
|
85
|
-
|
86
|
-
max_retries = 10
|
87
|
-
retry_count = 0
|
88
|
-
tunnel_ready = False
|
89
|
-
|
90
|
-
while retry_count < max_retries and not tunnel_ready:
|
91
|
-
time.sleep(1)
|
92
|
-
retry_count += 1
|
93
|
-
|
94
|
-
# Check if tunnel is actually accessible
|
95
|
-
try:
|
96
|
-
current_url = ngrok_service.get_tunnel_url()
|
97
|
-
if current_url and current_url == tunnel_url:
|
98
|
-
tunnel_ready = True
|
99
|
-
logger.info(f"Ngrok tunnel established successfully: {tunnel_url}")
|
100
|
-
break
|
101
|
-
except Exception as e:
|
102
|
-
logger.warning(f"Tunnel check attempt {retry_count} failed: {e}")
|
103
|
-
|
104
|
-
self.stdout.write(f"⏳ Tunnel check {retry_count}/{max_retries}...")
|
105
|
-
|
106
|
-
if tunnel_ready:
|
107
|
-
# Set environment variables for ngrok URL
|
108
|
-
self._set_ngrok_env_vars(tunnel_url)
|
109
15
|
|
110
|
-
|
111
|
-
|
16
|
+
class Command(NgrokCommand):
|
17
|
+
"""
|
18
|
+
Alias for runserver_ngrok command.
|
112
19
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
)
|
117
|
-
logger.info(f"Ngrok tunnel fully ready: {tunnel_url}")
|
118
|
-
else:
|
119
|
-
self.stdout.write(
|
120
|
-
self.style.WARNING("⚠️ Ngrok tunnel started but may not be fully ready")
|
121
|
-
)
|
122
|
-
logger.warning("Ngrok tunnel started but readiness check failed")
|
123
|
-
else:
|
124
|
-
error_msg = "Failed to start ngrok tunnel"
|
125
|
-
self.stdout.write(self.style.ERROR(f"❌ {error_msg}"))
|
126
|
-
logger.error(error_msg)
|
127
|
-
|
128
|
-
def _set_ngrok_env_vars(self, tunnel_url: str):
|
129
|
-
"""Set environment variables with ngrok URL for easy access."""
|
130
|
-
try:
|
131
|
-
from urllib.parse import urlparse
|
132
|
-
|
133
|
-
# Set main ngrok URL
|
134
|
-
os.environ['NGROK_URL'] = tunnel_url
|
135
|
-
os.environ['DJANGO_NGROK_URL'] = tunnel_url
|
136
|
-
|
137
|
-
# Parse URL components
|
138
|
-
parsed = urlparse(tunnel_url)
|
139
|
-
os.environ['NGROK_HOST'] = parsed.netloc
|
140
|
-
os.environ['NGROK_SCHEME'] = parsed.scheme
|
141
|
-
|
142
|
-
# Set API URL (same as tunnel URL for most cases)
|
143
|
-
os.environ['NGROK_API_URL'] = tunnel_url
|
144
|
-
|
145
|
-
# Environment variables set - no need for verbose output
|
146
|
-
logger.info(f"Set ngrok environment variables: {tunnel_url}")
|
147
|
-
|
148
|
-
except Exception as e:
|
149
|
-
logger.warning(f"Could not set ngrok environment variables: {e}")
|
150
|
-
|
151
|
-
def _update_allowed_hosts(self, tunnel_url: str):
|
152
|
-
"""Update ALLOWED_HOSTS with ngrok domain."""
|
153
|
-
try:
|
154
|
-
from django.conf import settings
|
155
|
-
from urllib.parse import urlparse
|
156
|
-
|
157
|
-
parsed = urlparse(tunnel_url)
|
158
|
-
ngrok_host = parsed.netloc
|
159
|
-
|
160
|
-
# Add to ALLOWED_HOSTS if not already present
|
161
|
-
if hasattr(settings, 'ALLOWED_HOSTS'):
|
162
|
-
if ngrok_host not in settings.ALLOWED_HOSTS:
|
163
|
-
settings.ALLOWED_HOSTS.append(ngrok_host)
|
164
|
-
logger.info(f"Added {ngrok_host} to ALLOWED_HOSTS")
|
165
|
-
|
166
|
-
except Exception as e:
|
167
|
-
logger.warning(f"Could not update ALLOWED_HOSTS: {e}")
|
20
|
+
Simply inherits from NgrokCommand without any changes.
|
21
|
+
"""
|
22
|
+
pass
|