django-cfg 1.4.9__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.
Files changed (193) hide show
  1. django_cfg/apps/agents/management/commands/create_agent.py +1 -1
  2. django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
  3. django_cfg/apps/newsletter/serializers.py +40 -3
  4. django_cfg/apps/newsletter/views/campaigns.py +12 -3
  5. django_cfg/apps/newsletter/views/emails.py +14 -3
  6. django_cfg/apps/newsletter/views/subscriptions.py +12 -2
  7. django_cfg/apps/payments/middleware/api_access.py +6 -2
  8. django_cfg/apps/payments/middleware/rate_limiting.py +2 -1
  9. django_cfg/apps/payments/middleware/usage_tracking.py +5 -1
  10. django_cfg/apps/payments/models/managers/api_key_managers.py +0 -1
  11. django_cfg/apps/payments/models/managers/subscription_managers.py +0 -1
  12. django_cfg/apps/payments/services/core/balance_service.py +5 -5
  13. django_cfg/apps/payments/services/core/subscription_service.py +1 -2
  14. django_cfg/apps/payments/views/api/balances.py +8 -7
  15. django_cfg/apps/payments/views/api/base.py +10 -6
  16. django_cfg/apps/payments/views/api/currencies.py +53 -10
  17. django_cfg/apps/payments/views/api/payments.py +3 -1
  18. django_cfg/apps/payments/views/api/subscriptions.py +2 -5
  19. django_cfg/apps/payments/views/api/webhooks.py +72 -7
  20. django_cfg/apps/payments/views/overview/serializers.py +34 -1
  21. django_cfg/apps/payments/views/overview/views.py +2 -1
  22. django_cfg/apps/payments/views/serializers/payments.py +6 -6
  23. django_cfg/apps/urls.py +106 -45
  24. django_cfg/core/base/config_model.py +2 -2
  25. django_cfg/core/constants.py +1 -1
  26. django_cfg/core/generation/integration_generators/__init__.py +1 -1
  27. django_cfg/core/generation/integration_generators/api.py +82 -41
  28. django_cfg/core/integration/display/startup.py +30 -22
  29. django_cfg/core/integration/url_integration.py +15 -16
  30. django_cfg/dashboard/sections/documentation.py +391 -0
  31. django_cfg/management/commands/check_endpoints.py +11 -160
  32. django_cfg/management/commands/check_settings.py +13 -265
  33. django_cfg/management/commands/clear_constance.py +13 -201
  34. django_cfg/management/commands/create_token.py +13 -321
  35. django_cfg/management/commands/generate_clients.py +23 -0
  36. django_cfg/management/commands/list_urls.py +13 -306
  37. django_cfg/management/commands/migrate_all.py +13 -126
  38. django_cfg/management/commands/migrator.py +13 -396
  39. django_cfg/management/commands/rundramatiq.py +15 -247
  40. django_cfg/management/commands/rundramatiq_simulator.py +12 -429
  41. django_cfg/management/commands/runserver_ngrok.py +15 -160
  42. django_cfg/management/commands/script.py +12 -488
  43. django_cfg/management/commands/show_config.py +12 -215
  44. django_cfg/management/commands/show_urls.py +12 -342
  45. django_cfg/management/commands/superuser.py +15 -295
  46. django_cfg/management/commands/task_clear.py +14 -217
  47. django_cfg/management/commands/task_status.py +13 -248
  48. django_cfg/management/commands/test_email.py +15 -86
  49. django_cfg/management/commands/test_telegram.py +14 -61
  50. django_cfg/management/commands/test_twilio.py +15 -105
  51. django_cfg/management/commands/tree.py +13 -383
  52. django_cfg/management/commands/validate_openapi.py +10 -0
  53. django_cfg/middleware/README.md +1 -1
  54. django_cfg/middleware/user_activity.py +3 -3
  55. django_cfg/models/__init__.py +2 -2
  56. django_cfg/models/api/drf/spectacular.py +6 -6
  57. django_cfg/models/django/__init__.py +2 -2
  58. django_cfg/models/django/openapi.py +238 -0
  59. django_cfg/models/django/{revolution.py → revolution_legacy.py} +8 -0
  60. django_cfg/modules/django_admin/management/__init__.py +0 -0
  61. django_cfg/modules/django_admin/management/commands/__init__.py +0 -0
  62. django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
  63. django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
  64. django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
  65. django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
  66. django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
  67. django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
  68. django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
  69. django_cfg/modules/django_admin/management/commands/script.py +496 -0
  70. django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
  71. django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
  72. django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
  73. django_cfg/modules/django_admin/management/commands/tree.py +390 -0
  74. django_cfg/modules/django_client/__init__.py +20 -0
  75. django_cfg/modules/django_client/apps.py +35 -0
  76. django_cfg/modules/django_client/core/__init__.py +56 -0
  77. django_cfg/modules/django_client/core/archive/__init__.py +11 -0
  78. django_cfg/modules/django_client/core/archive/manager.py +134 -0
  79. django_cfg/modules/django_client/core/cli/__init__.py +12 -0
  80. django_cfg/modules/django_client/core/cli/main.py +235 -0
  81. django_cfg/modules/django_client/core/config/__init__.py +18 -0
  82. django_cfg/modules/django_client/core/config/config.py +188 -0
  83. django_cfg/modules/django_client/core/config/group.py +101 -0
  84. django_cfg/modules/django_client/core/config/service.py +209 -0
  85. django_cfg/modules/django_client/core/generator/__init__.py +115 -0
  86. django_cfg/modules/django_client/core/generator/base.py +767 -0
  87. django_cfg/modules/django_client/core/generator/python.py +751 -0
  88. django_cfg/modules/django_client/core/generator/templates/python/__init__.py.jinja +9 -0
  89. django_cfg/modules/django_client/core/generator/templates/python/api_wrapper.py.jinja +130 -0
  90. django_cfg/modules/django_client/core/generator/templates/python/app_init.py.jinja +6 -0
  91. django_cfg/modules/django_client/core/generator/templates/python/client/app_client.py.jinja +18 -0
  92. django_cfg/modules/django_client/core/generator/templates/python/client/flat_client.py.jinja +38 -0
  93. django_cfg/modules/django_client/core/generator/templates/python/client/main_client.py.jinja +50 -0
  94. django_cfg/modules/django_client/core/generator/templates/python/client/main_client_file.py.jinja +13 -0
  95. django_cfg/modules/django_client/core/generator/templates/python/client/operation_method.py.jinja +7 -0
  96. django_cfg/modules/django_client/core/generator/templates/python/client/sub_client.py.jinja +11 -0
  97. django_cfg/modules/django_client/core/generator/templates/python/client_file.py.jinja +13 -0
  98. django_cfg/modules/django_client/core/generator/templates/python/main_init.py.jinja +50 -0
  99. django_cfg/modules/django_client/core/generator/templates/python/models/app_models.py.jinja +17 -0
  100. django_cfg/modules/django_client/core/generator/templates/python/models/enum_class.py.jinja +15 -0
  101. django_cfg/modules/django_client/core/generator/templates/python/models/enums.py.jinja +8 -0
  102. django_cfg/modules/django_client/core/generator/templates/python/models/models.py.jinja +17 -0
  103. django_cfg/modules/django_client/core/generator/templates/python/models/schema_class.py.jinja +19 -0
  104. django_cfg/modules/django_client/core/generator/templates/python/utils/logger.py.jinja +255 -0
  105. django_cfg/modules/django_client/core/generator/templates/python/utils/schema.py.jinja +12 -0
  106. django_cfg/modules/django_client/core/generator/templates/typescript/app_index.ts.jinja +2 -0
  107. django_cfg/modules/django_client/core/generator/templates/typescript/client/app_client.ts.jinja +18 -0
  108. django_cfg/modules/django_client/core/generator/templates/typescript/client/client.ts.jinja +327 -0
  109. django_cfg/modules/django_client/core/generator/templates/typescript/client/flat_client.ts.jinja +109 -0
  110. django_cfg/modules/django_client/core/generator/templates/typescript/client/main_client_file.ts.jinja +9 -0
  111. django_cfg/modules/django_client/core/generator/templates/typescript/client/operation.ts.jinja +61 -0
  112. django_cfg/modules/django_client/core/generator/templates/typescript/client/sub_client.ts.jinja +15 -0
  113. django_cfg/modules/django_client/core/generator/templates/typescript/client_file.ts.jinja +9 -0
  114. django_cfg/modules/django_client/core/generator/templates/typescript/index.ts.jinja +5 -0
  115. django_cfg/modules/django_client/core/generator/templates/typescript/main_index.ts.jinja +206 -0
  116. django_cfg/modules/django_client/core/generator/templates/typescript/models/app_models.ts.jinja +8 -0
  117. django_cfg/modules/django_client/core/generator/templates/typescript/models/enums.ts.jinja +4 -0
  118. django_cfg/modules/django_client/core/generator/templates/typescript/models/models.ts.jinja +8 -0
  119. django_cfg/modules/django_client/core/generator/templates/typescript/utils/errors.ts.jinja +114 -0
  120. django_cfg/modules/django_client/core/generator/templates/typescript/utils/http.ts.jinja +98 -0
  121. django_cfg/modules/django_client/core/generator/templates/typescript/utils/logger.ts.jinja +251 -0
  122. django_cfg/modules/django_client/core/generator/templates/typescript/utils/schema.ts.jinja +7 -0
  123. django_cfg/modules/django_client/core/generator/templates/typescript/utils/storage.ts.jinja +114 -0
  124. django_cfg/modules/django_client/core/generator/typescript.py +872 -0
  125. django_cfg/modules/django_client/core/groups/__init__.py +13 -0
  126. django_cfg/modules/django_client/core/groups/detector.py +178 -0
  127. django_cfg/modules/django_client/core/groups/manager.py +314 -0
  128. django_cfg/modules/django_client/core/ir/__init__.py +57 -0
  129. django_cfg/modules/django_client/core/ir/context.py +387 -0
  130. django_cfg/modules/django_client/core/ir/operation.py +518 -0
  131. django_cfg/modules/django_client/core/ir/schema.py +353 -0
  132. django_cfg/modules/django_client/core/parser/__init__.py +74 -0
  133. django_cfg/modules/django_client/core/parser/base.py +648 -0
  134. django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
  135. django_cfg/modules/django_client/core/parser/models/base.py +212 -0
  136. django_cfg/modules/django_client/core/parser/models/components.py +160 -0
  137. django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
  138. django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
  139. django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
  140. django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
  141. django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
  142. django_cfg/modules/django_client/core/validation/__init__.py +22 -0
  143. django_cfg/modules/django_client/core/validation/checker.py +134 -0
  144. django_cfg/modules/django_client/core/validation/fixer.py +216 -0
  145. django_cfg/modules/django_client/core/validation/reporter.py +480 -0
  146. django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
  147. django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
  148. django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
  149. django_cfg/modules/django_client/core/validation/safety.py +266 -0
  150. django_cfg/modules/django_client/management/__init__.py +3 -0
  151. django_cfg/modules/django_client/management/commands/__init__.py +3 -0
  152. django_cfg/modules/django_client/management/commands/generate_client.py +422 -0
  153. django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
  154. django_cfg/modules/django_client/spectacular/__init__.py +9 -0
  155. django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
  156. django_cfg/modules/django_client/urls.py +72 -0
  157. django_cfg/modules/django_email/management/__init__.py +0 -0
  158. django_cfg/modules/django_email/management/commands/__init__.py +0 -0
  159. django_cfg/modules/django_email/management/commands/test_email.py +93 -0
  160. django_cfg/modules/django_logging/django_logger.py +6 -6
  161. django_cfg/modules/django_ngrok/management/__init__.py +0 -0
  162. django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
  163. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
  164. django_cfg/modules/django_tasks/management/__init__.py +0 -0
  165. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  166. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
  167. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
  168. django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
  169. django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
  170. django_cfg/modules/django_telegram/management/__init__.py +0 -0
  171. django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
  172. django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
  173. django_cfg/modules/django_twilio/management/__init__.py +0 -0
  174. django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
  175. django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
  176. django_cfg/modules/django_unfold/callbacks/main.py +16 -5
  177. django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
  178. django_cfg/modules/django_unfold/dashboard.py +1 -1
  179. django_cfg/pyproject.toml +2 -6
  180. django_cfg/registry/third_party.py +5 -7
  181. django_cfg/routing/callbacks.py +1 -1
  182. django_cfg/static/admin/css/prose-unfold.css +666 -0
  183. django_cfg/templates/admin/index.html +8 -0
  184. django_cfg/templates/admin/index_new.html +13 -0
  185. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
  186. django_cfg/templates/admin/sections/documentation_section.html +172 -0
  187. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
  188. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/METADATA +2 -2
  189. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/RECORD +192 -71
  190. django_cfg/management/commands/generate.py +0 -107
  191. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/WHEEL +0 -0
  192. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/entry_points.txt +0 -0
  193. {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,437 @@
1
+ """
2
+ Django management command for simulating Dramatiq tasks and workers.
3
+
4
+ Usage:
5
+ python manage.py rundramatiq_simulator --help
6
+ python manage.py rundramatiq_simulator --workers 5
7
+ python manage.py rundramatiq_simulator --clear-only
8
+ python manage.py rundramatiq_simulator --show-keys
9
+ """
10
+
11
+ import redis
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
+
284
+
285
+ class Command(BaseCommand):
286
+ """Django management command для симуляции Dramatiq данных."""
287
+
288
+ # Web execution metadata
289
+ web_executable = False
290
+ requires_input = False
291
+ is_destructive = False
292
+
293
+ help = 'Simulate Dramatiq tasks and workers for dashboard testing'
294
+
295
+ def add_arguments(self, parser):
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})")
@@ -0,0 +1,226 @@
1
+ """
2
+ Django management command for clearing task queues.
3
+
4
+ This command provides utilities for clearing failed tasks,
5
+ specific queues, or all tasks from the Dramatiq system.
6
+ """
7
+
8
+ from django.core.management.base import BaseCommand, CommandError
9
+ from django.conf import settings
10
+ from typing import Any, Optional, List
11
+ import logging
12
+ from django_cfg.modules.django_logging import get_logger
13
+
14
+ logger = get_logger('task_clear')
15
+
16
+
17
+ class Command(BaseCommand):
18
+ """
19
+ Clear tasks from Dramatiq queues.
20
+
21
+ Provides options to:
22
+ - Clear all tasks from specific queues
23
+ - Clear only failed tasks
24
+ - Clear all tasks from all queues
25
+ """
26
+
27
+ # Web execution metadata
28
+ web_executable = False
29
+ requires_input = True
30
+ is_destructive = True
31
+
32
+ help = "Clear tasks from Dramatiq queues"
33
+
34
+ def add_arguments(self, parser):
35
+ """Add command line arguments."""
36
+ parser.add_argument(
37
+ "--queue",
38
+ type=str,
39
+ help="Specific queue to clear (default: all queues)",
40
+ )
41
+ parser.add_argument(
42
+ "--failed-only",
43
+ action="store_true",
44
+ help="Clear only failed tasks",
45
+ )
46
+ parser.add_argument(
47
+ "--confirm",
48
+ action="store_true",
49
+ help="Skip confirmation prompt",
50
+ )
51
+ parser.add_argument(
52
+ "--dry-run",
53
+ action="store_true",
54
+ help="Show what would be cleared without actually clearing",
55
+ )
56
+
57
+ def handle(self, *args, **options):
58
+ """Handle the command execution."""
59
+ logger.info("Starting task_clear command")
60
+
61
+ try:
62
+ # Import here to avoid issues if dramatiq is not installed
63
+ from django_cfg.modules.django_tasks import get_task_service
64
+
65
+ # Get task service
66
+ task_service = get_task_service()
67
+
68
+ # Check if task system is enabled
69
+ if not task_service.is_enabled():
70
+ raise CommandError(
71
+ "Task system is not enabled. "
72
+ "Please configure 'tasks' in your Django-CFG configuration."
73
+ )
74
+
75
+ # Get task manager
76
+ manager = task_service.manager
77
+ if not manager:
78
+ raise CommandError("Task manager not available")
79
+
80
+ # Get configuration
81
+ config = task_service.config
82
+ if not config:
83
+ raise CommandError("Task configuration not available")
84
+
85
+ # Determine queues to clear
86
+ if options.get("queue"):
87
+ queues_to_clear = [options["queue"]]
88
+ # Validate queue exists
89
+ if options["queue"] not in config.get_effective_queues():
90
+ self.stdout.write(
91
+ self.style.WARNING(
92
+ f"Queue '{options['queue']}' not in configured queues: "
93
+ f"{', '.join(config.get_effective_queues())}"
94
+ )
95
+ )
96
+ else:
97
+ queues_to_clear = config.get_effective_queues()
98
+
99
+ # Show what will be cleared
100
+ self._show_clear_plan(queues_to_clear, options)
101
+
102
+ if options["dry_run"]:
103
+ self.stdout.write(
104
+ self.style.SUCCESS("Dry run completed - no tasks were cleared")
105
+ )
106
+ return
107
+
108
+ # Confirm action
109
+ if not options["confirm"]:
110
+ if not self._confirm_clear(queues_to_clear, options):
111
+ self.stdout.write("Operation cancelled")
112
+ return
113
+
114
+ # Perform clearing
115
+ self._clear_queues(manager, queues_to_clear, options)
116
+
117
+ except ImportError:
118
+ raise CommandError(
119
+ "Dramatiq dependencies not installed. "
120
+ "Install with: pip install django-cfg[tasks]"
121
+ )
122
+ except Exception as e:
123
+ logger.exception("Failed to clear tasks")
124
+ raise CommandError(f"Failed to clear tasks: {e}")
125
+
126
+ def _show_clear_plan(self, queues: List[str], options):
127
+ """Show what will be cleared."""
128
+ self.stdout.write(
129
+ self.style.SUCCESS("=== Clear Plan ===")
130
+ )
131
+
132
+ if options.get("failed_only"):
133
+ self.stdout.write("Action: Clear FAILED tasks only")
134
+ else:
135
+ self.stdout.write("Action: Clear ALL tasks")
136
+
137
+ self.stdout.write(f"Queues: {', '.join(queues)}")
138
+
139
+ # Get current queue statistics
140
+ try:
141
+ from django_cfg.modules.django_tasks import get_task_service
142
+ task_service = get_task_service()
143
+ manager = task_service.manager
144
+
145
+ if manager:
146
+ queue_stats = manager.get_queue_stats()
147
+
148
+ self.stdout.write("\nCurrent Queue Statistics:")
149
+ for stat in queue_stats:
150
+ if stat["name"] in queues:
151
+ name = stat["name"]
152
+ pending = stat.get("pending", 0)
153
+ failed = stat.get("failed", 0)
154
+
155
+ if options.get("failed_only"):
156
+ self.stdout.write(f" {name}: {failed} failed tasks")
157
+ else:
158
+ total = pending + stat.get("running", 0) + failed
159
+ self.stdout.write(f" {name}: {total} total tasks")
160
+ except Exception as e:
161
+ self.stdout.write(f"Could not get queue statistics: {e}")
162
+
163
+ self.stdout.write()
164
+
165
+ def _confirm_clear(self, queues: List[str], options) -> bool:
166
+ """Confirm the clear operation with user."""
167
+ if options.get("failed_only"):
168
+ action = "clear FAILED tasks"
169
+ else:
170
+ action = "clear ALL tasks"
171
+
172
+ queue_list = ", ".join(queues)
173
+
174
+ self.stdout.write(
175
+ self.style.WARNING(
176
+ f"This will {action} from queues: {queue_list}"
177
+ )
178
+ )
179
+
180
+ response = input("Are you sure? [y/N]: ").lower().strip()
181
+ return response in ["y", "yes"]
182
+
183
+ def _clear_queues(self, manager, queues: List[str], options):
184
+ """Clear the specified queues."""
185
+ cleared_count = 0
186
+
187
+ self.stdout.write(
188
+ self.style.SUCCESS("Starting queue clearing...")
189
+ )
190
+
191
+ for queue_name in queues:
192
+ try:
193
+ self.stdout.write(f"Clearing queue: {queue_name}")
194
+
195
+ if options.get("failed_only"):
196
+ # Clear only failed tasks
197
+ # TODO: Implement failed task clearing
198
+ # This would require specific Dramatiq broker methods
199
+ count = 0 # Placeholder
200
+ self.stdout.write(f" Cleared {count} failed tasks")
201
+ else:
202
+ # Clear all tasks
203
+ success = manager.clear_queue(queue_name)
204
+ if success:
205
+ # TODO: Get actual count of cleared tasks
206
+ count = 0 # Placeholder
207
+ self.stdout.write(f" Cleared {count} tasks")
208
+ cleared_count += count
209
+ else:
210
+ self.stdout.write(
211
+ self.style.ERROR(f" Failed to clear queue: {queue_name}")
212
+ )
213
+
214
+ except Exception as e:
215
+ self.stdout.write(
216
+ self.style.ERROR(f" Error clearing queue {queue_name}: {e}")
217
+ )
218
+
219
+ if cleared_count > 0:
220
+ self.stdout.write(
221
+ self.style.SUCCESS(f"Successfully cleared {cleared_count} tasks")
222
+ )
223
+ else:
224
+ self.stdout.write(
225
+ self.style.WARNING("No tasks were cleared")
226
+ )