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
@@ -1,437 +1,20 @@
1
1
  """
2
- Django management command for simulating Dramatiq tasks and workers.
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 --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
8
+ python manage.py rundramatiq_simulator
9
9
  """
10
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
-
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
- # Web execution metadata
289
- web_executable = False
290
- requires_input = False
291
- is_destructive = False
14
+ class Command(SimulatorCommand):
15
+ """
16
+ Alias for rundramatiq_simulator command.
292
17
 
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})")
18
+ Simply inherits from SimulatorCommand without any changes.
19
+ """
20
+ pass
@@ -1,167 +1,22 @@
1
1
  """
2
- Management command to run Django development server with ngrok tunnel.
2
+ Django-CFG wrapper for runserver_ngrok command.
3
3
 
4
- Simple implementation following KISS principle.
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
- class Command(RunServerCommand):
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
- # Update ALLOWED_HOSTS if needed
111
- self._update_allowed_hosts(tunnel_url)
16
+ class Command(NgrokCommand):
17
+ """
18
+ Alias for runserver_ngrok command.
112
19
 
113
- # Brief success message - detailed info will be shown by startup_display
114
- self.stdout.write(
115
- self.style.SUCCESS(f"✅ Ngrok tunnel ready: {tunnel_url}")
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