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.
Files changed (181) 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/views/api/currencies.py +49 -6
  8. django_cfg/apps/payments/views/api/webhooks.py +72 -7
  9. django_cfg/apps/payments/views/overview/serializers.py +34 -1
  10. django_cfg/apps/payments/views/overview/views.py +2 -1
  11. django_cfg/apps/payments/views/serializers/payments.py +6 -6
  12. django_cfg/apps/urls.py +106 -45
  13. django_cfg/core/base/config_model.py +2 -2
  14. django_cfg/core/constants.py +1 -1
  15. django_cfg/core/generation/integration_generators/__init__.py +1 -1
  16. django_cfg/core/generation/integration_generators/api.py +72 -49
  17. django_cfg/core/integration/display/startup.py +30 -22
  18. django_cfg/core/integration/url_integration.py +15 -16
  19. django_cfg/dashboard/sections/documentation.py +391 -0
  20. django_cfg/management/commands/check_endpoints.py +11 -160
  21. django_cfg/management/commands/check_settings.py +13 -348
  22. django_cfg/management/commands/clear_constance.py +13 -201
  23. django_cfg/management/commands/create_token.py +13 -321
  24. django_cfg/management/commands/generate_clients.py +23 -0
  25. django_cfg/management/commands/list_urls.py +13 -306
  26. django_cfg/management/commands/migrate_all.py +13 -126
  27. django_cfg/management/commands/migrator.py +13 -396
  28. django_cfg/management/commands/rundramatiq.py +15 -247
  29. django_cfg/management/commands/rundramatiq_simulator.py +12 -429
  30. django_cfg/management/commands/runserver_ngrok.py +15 -160
  31. django_cfg/management/commands/script.py +12 -488
  32. django_cfg/management/commands/show_config.py +12 -215
  33. django_cfg/management/commands/show_urls.py +12 -342
  34. django_cfg/management/commands/superuser.py +15 -295
  35. django_cfg/management/commands/task_clear.py +14 -217
  36. django_cfg/management/commands/task_status.py +13 -248
  37. django_cfg/management/commands/test_email.py +15 -86
  38. django_cfg/management/commands/test_telegram.py +14 -61
  39. django_cfg/management/commands/test_twilio.py +15 -105
  40. django_cfg/management/commands/tree.py +13 -383
  41. django_cfg/management/commands/validate_openapi.py +10 -0
  42. django_cfg/middleware/README.md +1 -1
  43. django_cfg/middleware/user_activity.py +3 -3
  44. django_cfg/models/__init__.py +2 -2
  45. django_cfg/models/api/drf/spectacular.py +6 -6
  46. django_cfg/models/django/__init__.py +2 -2
  47. django_cfg/models/django/openapi.py +238 -0
  48. django_cfg/modules/django_admin/management/__init__.py +0 -0
  49. django_cfg/modules/django_admin/management/commands/__init__.py +0 -0
  50. django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
  51. django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
  52. django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
  53. django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
  54. django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
  55. django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
  56. django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
  57. django_cfg/modules/django_admin/management/commands/script.py +496 -0
  58. django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
  59. django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
  60. django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
  61. django_cfg/modules/django_admin/management/commands/tree.py +390 -0
  62. django_cfg/modules/django_client/__init__.py +20 -0
  63. django_cfg/modules/django_client/apps.py +35 -0
  64. django_cfg/modules/django_client/core/__init__.py +56 -0
  65. django_cfg/modules/django_client/core/archive/__init__.py +11 -0
  66. django_cfg/modules/django_client/core/archive/manager.py +134 -0
  67. django_cfg/modules/django_client/core/cli/__init__.py +12 -0
  68. django_cfg/modules/django_client/core/cli/main.py +235 -0
  69. django_cfg/modules/django_client/core/config/__init__.py +18 -0
  70. django_cfg/modules/django_client/core/config/config.py +188 -0
  71. django_cfg/modules/django_client/core/config/group.py +101 -0
  72. django_cfg/modules/django_client/core/config/service.py +209 -0
  73. django_cfg/modules/django_client/core/generator/__init__.py +115 -0
  74. django_cfg/modules/django_client/core/generator/base.py +767 -0
  75. django_cfg/modules/django_client/core/generator/python.py +751 -0
  76. django_cfg/modules/django_client/core/generator/templates/python/__init__.py.jinja +9 -0
  77. django_cfg/modules/django_client/core/generator/templates/python/api_wrapper.py.jinja +130 -0
  78. django_cfg/modules/django_client/core/generator/templates/python/app_init.py.jinja +6 -0
  79. django_cfg/modules/django_client/core/generator/templates/python/client/app_client.py.jinja +18 -0
  80. django_cfg/modules/django_client/core/generator/templates/python/client/flat_client.py.jinja +38 -0
  81. django_cfg/modules/django_client/core/generator/templates/python/client/main_client.py.jinja +50 -0
  82. django_cfg/modules/django_client/core/generator/templates/python/client/main_client_file.py.jinja +13 -0
  83. django_cfg/modules/django_client/core/generator/templates/python/client/operation_method.py.jinja +7 -0
  84. django_cfg/modules/django_client/core/generator/templates/python/client/sub_client.py.jinja +11 -0
  85. django_cfg/modules/django_client/core/generator/templates/python/client_file.py.jinja +13 -0
  86. django_cfg/modules/django_client/core/generator/templates/python/main_init.py.jinja +50 -0
  87. django_cfg/modules/django_client/core/generator/templates/python/models/app_models.py.jinja +17 -0
  88. django_cfg/modules/django_client/core/generator/templates/python/models/enum_class.py.jinja +15 -0
  89. django_cfg/modules/django_client/core/generator/templates/python/models/enums.py.jinja +8 -0
  90. django_cfg/modules/django_client/core/generator/templates/python/models/models.py.jinja +17 -0
  91. django_cfg/modules/django_client/core/generator/templates/python/models/schema_class.py.jinja +19 -0
  92. django_cfg/modules/django_client/core/generator/templates/python/utils/logger.py.jinja +255 -0
  93. django_cfg/modules/django_client/core/generator/templates/python/utils/schema.py.jinja +12 -0
  94. django_cfg/modules/django_client/core/generator/templates/typescript/app_index.ts.jinja +2 -0
  95. django_cfg/modules/django_client/core/generator/templates/typescript/client/app_client.ts.jinja +18 -0
  96. django_cfg/modules/django_client/core/generator/templates/typescript/client/client.ts.jinja +327 -0
  97. django_cfg/modules/django_client/core/generator/templates/typescript/client/flat_client.ts.jinja +109 -0
  98. django_cfg/modules/django_client/core/generator/templates/typescript/client/main_client_file.ts.jinja +9 -0
  99. django_cfg/modules/django_client/core/generator/templates/typescript/client/operation.ts.jinja +61 -0
  100. django_cfg/modules/django_client/core/generator/templates/typescript/client/sub_client.ts.jinja +15 -0
  101. django_cfg/modules/django_client/core/generator/templates/typescript/client_file.ts.jinja +9 -0
  102. django_cfg/modules/django_client/core/generator/templates/typescript/index.ts.jinja +5 -0
  103. django_cfg/modules/django_client/core/generator/templates/typescript/main_index.ts.jinja +206 -0
  104. django_cfg/modules/django_client/core/generator/templates/typescript/models/app_models.ts.jinja +8 -0
  105. django_cfg/modules/django_client/core/generator/templates/typescript/models/enums.ts.jinja +4 -0
  106. django_cfg/modules/django_client/core/generator/templates/typescript/models/models.ts.jinja +8 -0
  107. django_cfg/modules/django_client/core/generator/templates/typescript/utils/errors.ts.jinja +114 -0
  108. django_cfg/modules/django_client/core/generator/templates/typescript/utils/http.ts.jinja +98 -0
  109. django_cfg/modules/django_client/core/generator/templates/typescript/utils/logger.ts.jinja +251 -0
  110. django_cfg/modules/django_client/core/generator/templates/typescript/utils/schema.ts.jinja +7 -0
  111. django_cfg/modules/django_client/core/generator/templates/typescript/utils/storage.ts.jinja +114 -0
  112. django_cfg/modules/django_client/core/generator/typescript.py +872 -0
  113. django_cfg/modules/django_client/core/groups/__init__.py +13 -0
  114. django_cfg/modules/django_client/core/groups/detector.py +178 -0
  115. django_cfg/modules/django_client/core/groups/manager.py +314 -0
  116. django_cfg/modules/django_client/core/ir/__init__.py +57 -0
  117. django_cfg/modules/django_client/core/ir/context.py +387 -0
  118. django_cfg/modules/django_client/core/ir/operation.py +518 -0
  119. django_cfg/modules/django_client/core/ir/schema.py +353 -0
  120. django_cfg/modules/django_client/core/parser/__init__.py +74 -0
  121. django_cfg/modules/django_client/core/parser/base.py +648 -0
  122. django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
  123. django_cfg/modules/django_client/core/parser/models/base.py +212 -0
  124. django_cfg/modules/django_client/core/parser/models/components.py +160 -0
  125. django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
  126. django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
  127. django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
  128. django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
  129. django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
  130. django_cfg/modules/django_client/core/validation/__init__.py +22 -0
  131. django_cfg/modules/django_client/core/validation/checker.py +134 -0
  132. django_cfg/modules/django_client/core/validation/fixer.py +216 -0
  133. django_cfg/modules/django_client/core/validation/reporter.py +480 -0
  134. django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
  135. django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
  136. django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
  137. django_cfg/modules/django_client/core/validation/safety.py +266 -0
  138. django_cfg/modules/django_client/management/__init__.py +3 -0
  139. django_cfg/modules/django_client/management/commands/__init__.py +3 -0
  140. django_cfg/modules/django_client/management/commands/generate_client.py +422 -0
  141. django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
  142. django_cfg/modules/django_client/spectacular/__init__.py +9 -0
  143. django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
  144. django_cfg/modules/django_client/urls.py +72 -0
  145. django_cfg/modules/django_email/management/__init__.py +0 -0
  146. django_cfg/modules/django_email/management/commands/__init__.py +0 -0
  147. django_cfg/modules/django_email/management/commands/test_email.py +93 -0
  148. django_cfg/modules/django_logging/django_logger.py +6 -6
  149. django_cfg/modules/django_ngrok/management/__init__.py +0 -0
  150. django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
  151. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
  152. django_cfg/modules/django_tasks/management/__init__.py +0 -0
  153. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  154. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
  155. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
  156. django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
  157. django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
  158. django_cfg/modules/django_telegram/management/__init__.py +0 -0
  159. django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
  160. django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
  161. django_cfg/modules/django_twilio/management/__init__.py +0 -0
  162. django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
  163. django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
  164. django_cfg/modules/django_unfold/callbacks/main.py +16 -5
  165. django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
  166. django_cfg/pyproject.toml +2 -6
  167. django_cfg/registry/third_party.py +5 -7
  168. django_cfg/routing/callbacks.py +1 -1
  169. django_cfg/static/admin/css/prose-unfold.css +666 -0
  170. django_cfg/templates/admin/index.html +8 -0
  171. django_cfg/templates/admin/index_new.html +13 -0
  172. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
  173. django_cfg/templates/admin/sections/documentation_section.html +172 -0
  174. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
  175. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/METADATA +2 -2
  176. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/RECORD +180 -59
  177. django_cfg/management/commands/generate.py +0 -107
  178. /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
  179. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/WHEEL +0 -0
  180. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/entry_points.txt +0 -0
  181. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,422 @@
1
+ """
2
+ Django management command for client generation.
3
+
4
+ Usage:
5
+ python manage.py generate_client --groups cfg custom
6
+ python manage.py generate_client --python
7
+ python manage.py generate_client --interactive
8
+ """
9
+
10
+ from django.core.management.base import BaseCommand, CommandError
11
+ from typing import List, Optional
12
+
13
+
14
+ class Command(BaseCommand):
15
+ """Generate OpenAPI clients for configured application groups."""
16
+
17
+ help = "Generate Python and TypeScript API clients from OpenAPI schemas"
18
+
19
+ def add_arguments(self, parser):
20
+ """Add command arguments."""
21
+ # Generation options
22
+ parser.add_argument(
23
+ "--groups",
24
+ nargs="*",
25
+ help="Specific groups to generate (default: all configured groups)",
26
+ )
27
+
28
+ parser.add_argument(
29
+ "--python",
30
+ action="store_true",
31
+ help="Generate Python client only",
32
+ )
33
+
34
+ parser.add_argument(
35
+ "--typescript",
36
+ action="store_true",
37
+ help="Generate TypeScript client only",
38
+ )
39
+
40
+ parser.add_argument(
41
+ "--no-python",
42
+ action="store_true",
43
+ help="Skip Python client generation",
44
+ )
45
+
46
+ parser.add_argument(
47
+ "--no-typescript",
48
+ action="store_true",
49
+ help="Skip TypeScript client generation",
50
+ )
51
+
52
+ # Utility options
53
+ parser.add_argument(
54
+ "--dry-run",
55
+ action="store_true",
56
+ help="Dry run - validate configuration but don't generate files",
57
+ )
58
+
59
+ parser.add_argument(
60
+ "--list-groups",
61
+ action="store_true",
62
+ help="List configured application groups and exit",
63
+ )
64
+
65
+ parser.add_argument(
66
+ "--validate",
67
+ action="store_true",
68
+ help="Validate configuration and exit",
69
+ )
70
+
71
+ parser.add_argument(
72
+ "--interactive", "-i",
73
+ action="store_true",
74
+ help="Run in interactive mode",
75
+ )
76
+
77
+ def handle(self, *args, **options):
78
+ """Handle command execution."""
79
+ try:
80
+ # Import here to avoid Django import errors
81
+ from django_cfg.modules.django_client.core import get_openapi_service, GroupManager
82
+
83
+ # Get service
84
+ service = get_openapi_service()
85
+
86
+ if not service.is_enabled():
87
+ raise CommandError(
88
+ "OpenAPI client generation is not enabled. "
89
+ "Set 'openapi.enabled = True' in your django-cfg configuration."
90
+ )
91
+
92
+ # List groups
93
+ if options["list_groups"]:
94
+ self._list_groups(service)
95
+ return
96
+
97
+ # Validate
98
+ if options["validate"]:
99
+ self._validate(service)
100
+ return
101
+
102
+ # Interactive mode
103
+ if options["interactive"]:
104
+ self._interactive_mode()
105
+ return
106
+
107
+ # Generate clients
108
+ self._generate_clients(service, options)
109
+
110
+ except Exception as e:
111
+ raise CommandError(f"Client generation failed: {e}")
112
+
113
+ def _list_groups(self, service):
114
+ """List configured groups."""
115
+ groups = service.get_groups()
116
+
117
+ if not groups:
118
+ self.stdout.write(self.style.WARNING("No groups configured"))
119
+ return
120
+
121
+ self.stdout.write(self.style.SUCCESS(f"\nConfigured groups ({len(groups)}):"))
122
+
123
+ for group_name, group_config in groups.items():
124
+ self.stdout.write(f"\n • {group_name}")
125
+ self.stdout.write(f" Title: {group_config.title}")
126
+ self.stdout.write(f" Apps: {len(group_config.apps)} pattern(s)")
127
+
128
+ # Show matched apps
129
+ from django_cfg.modules.django_client.core import GroupManager
130
+ from django.apps import apps
131
+
132
+ installed_apps = [app.name for app in apps.get_app_configs()]
133
+ manager = GroupManager(service.config, installed_apps, groups=service.get_groups())
134
+ matched_apps = manager.get_group_apps(group_name)
135
+
136
+ if matched_apps:
137
+ self.stdout.write(f" Matched: {len(matched_apps)} app(s)")
138
+ for app in matched_apps[:5]: # Show first 5
139
+ self.stdout.write(f" - {app}")
140
+ if len(matched_apps) > 5:
141
+ self.stdout.write(f" ... and {len(matched_apps) - 5} more")
142
+ else:
143
+ self.stdout.write(self.style.WARNING(" Matched: 0 apps"))
144
+
145
+ def _validate(self, service):
146
+ """Validate configuration."""
147
+ self.stdout.write("Validating configuration...")
148
+
149
+ try:
150
+ service.validate_config()
151
+ self.stdout.write(self.style.SUCCESS("āœ… Configuration is valid!"))
152
+
153
+ # Show statistics
154
+ from django_cfg.modules.django_client.core import GroupManager
155
+ from django.apps import apps
156
+
157
+ installed_apps = [app.name for app in apps.get_app_configs()]
158
+ manager = GroupManager(service.config, installed_apps, groups=service.get_groups())
159
+ stats = manager.get_statistics()
160
+
161
+ self.stdout.write(f"\nStatistics:")
162
+ self.stdout.write(f" • Total groups: {stats['total_groups']}")
163
+ self.stdout.write(f" • Total apps in groups: {stats['total_apps_in_groups']}")
164
+ self.stdout.write(f" • Ungrouped apps: {stats['ungrouped_apps']}")
165
+
166
+ if stats["ungrouped_apps"] > 0:
167
+ self.stdout.write(
168
+ self.style.WARNING(
169
+ f"\nWarning: {stats['ungrouped_apps']} apps not in any group:"
170
+ )
171
+ )
172
+ for app in stats["ungrouped_apps_list"][:5]:
173
+ self.stdout.write(f" - {app}")
174
+ if len(stats["ungrouped_apps_list"]) > 5:
175
+ self.stdout.write(f" ... and {len(stats['ungrouped_apps_list']) - 5} more")
176
+
177
+ except Exception as e:
178
+ raise CommandError(f"Validation failed: {e}")
179
+
180
+ def _interactive_mode(self):
181
+ """Run interactive mode."""
182
+ try:
183
+ from django_cfg.modules.django_client.core.cli import run_cli
184
+ run_cli()
185
+ except ImportError:
186
+ raise CommandError(
187
+ "Interactive mode requires 'click' package. "
188
+ "Install with: pip install click"
189
+ )
190
+
191
+ def _generate_clients(self, service, options):
192
+ """Generate clients."""
193
+ # Determine languages
194
+ if options["python"] and not options["typescript"]:
195
+ python = True
196
+ typescript = False
197
+ elif options["typescript"] and not options["python"]:
198
+ python = False
199
+ typescript = True
200
+ else:
201
+ python = not options["no_python"]
202
+ typescript = not options["no_typescript"]
203
+
204
+ # Get groups
205
+ groups = options.get("groups")
206
+ if not groups:
207
+ groups = service.get_group_names()
208
+
209
+ if not groups:
210
+ raise CommandError("No groups to generate")
211
+
212
+ # Dry run
213
+ dry_run = options["dry_run"]
214
+
215
+ if dry_run:
216
+ self.stdout.write(self.style.WARNING("\nšŸ” DRY RUN MODE - No files will be generated\n"))
217
+
218
+ # Show what will be generated
219
+ self.stdout.write(self.style.SUCCESS(f"Generating clients for {len(groups)} group(s):\n"))
220
+
221
+ for group_name in groups:
222
+ group_config = service.get_group(group_name)
223
+ if not group_config:
224
+ self.stdout.write(self.style.WARNING(f" āš ļø Group '{group_name}' not found - skipping"))
225
+ continue
226
+
227
+ self.stdout.write(f" • {group_name} ({group_config.title})")
228
+
229
+ self.stdout.write("\nLanguages:")
230
+ if python:
231
+ self.stdout.write(" → Python")
232
+ if typescript:
233
+ self.stdout.write(" → TypeScript")
234
+
235
+ if dry_run:
236
+ self.stdout.write(self.style.WARNING("\nāœ… Dry run completed - no files generated"))
237
+ return
238
+
239
+ # Generate clients
240
+ self.stdout.write("\n" + "=" * 60)
241
+
242
+ from django_cfg.modules.django_client.core import (
243
+ GroupManager,
244
+ parse_openapi,
245
+ PythonGenerator,
246
+ TypeScriptGenerator,
247
+ ArchiveManager,
248
+ )
249
+ from django.apps import apps
250
+ from drf_spectacular.generators import SchemaGenerator
251
+ from pathlib import Path
252
+ import shutil
253
+
254
+ # Clean output folders before generation
255
+ schemas_dir = service.config.get_schemas_dir()
256
+ clients_dir = service.config.get_clients_dir()
257
+
258
+ if schemas_dir.exists():
259
+ self.stdout.write(f"\n🧹 Cleaning schemas folder: {schemas_dir}")
260
+ shutil.rmtree(schemas_dir)
261
+ schemas_dir.mkdir(parents=True, exist_ok=True)
262
+
263
+ if clients_dir.exists():
264
+ self.stdout.write(f"🧹 Cleaning clients folder: {clients_dir}")
265
+ shutil.rmtree(clients_dir)
266
+ clients_dir.mkdir(parents=True, exist_ok=True)
267
+
268
+ # Get installed apps (use app.name, not app.label)
269
+ installed_apps = [app.name for app in apps.get_app_configs()]
270
+ manager = GroupManager(service.config, installed_apps, groups=service.get_groups())
271
+
272
+ success_count = 0
273
+ error_count = 0
274
+
275
+ for group_name in groups:
276
+ group_config = service.get_group(group_name)
277
+ if not group_config:
278
+ continue
279
+
280
+ self.stdout.write(f"\nšŸ“¦ Processing group: {group_name}")
281
+
282
+ try:
283
+ # Get apps for this group
284
+ group_apps = manager.get_group_apps(group_name)
285
+ if not group_apps:
286
+ self.stdout.write(self.style.WARNING(f" āš ļø No apps matched for group '{group_name}'"))
287
+ continue
288
+
289
+ self.stdout.write(f" Apps: {', '.join(group_apps)}")
290
+
291
+ # Create dynamic URLconf for this group
292
+ urlconf_module = manager.create_urlconf_module(group_name)
293
+
294
+ # Generate OpenAPI schema
295
+ self.stdout.write(" → Generating OpenAPI schema...")
296
+
297
+ # Get app labels (not full names) for metadata
298
+ app_labels = []
299
+ for app_name in group_apps:
300
+ for config in apps.get_app_configs():
301
+ if config.name == app_name:
302
+ app_labels.append(config.label)
303
+ break
304
+
305
+ # Temporarily patch SPECTACULAR_SETTINGS to ensure COMPONENT_SPLIT_REQUEST
306
+ from django.conf import settings
307
+ original_settings = getattr(settings, 'SPECTACULAR_SETTINGS', {}).copy()
308
+ patched_settings = original_settings.copy()
309
+ patched_settings['COMPONENT_SPLIT_REQUEST'] = True
310
+ patched_settings['COMPONENT_SPLIT_PATCH'] = True
311
+ settings.SPECTACULAR_SETTINGS = patched_settings
312
+
313
+ try:
314
+ generator = SchemaGenerator(
315
+ title=group_config.title,
316
+ description=group_config.description,
317
+ version=group_config.version,
318
+ urlconf=urlconf_module,
319
+ )
320
+ schema_dict = generator.get_schema(request=None, public=True)
321
+ finally:
322
+ # Restore original settings
323
+ settings.SPECTACULAR_SETTINGS = original_settings
324
+
325
+ # Add Django metadata to schema (use app labels, not full names)
326
+ schema_dict.setdefault('info', {}).setdefault('x-django-metadata', {
327
+ 'group': group_name,
328
+ 'apps': app_labels,
329
+ 'generator': 'django-client',
330
+ 'generator_version': '1.0.0',
331
+ })
332
+
333
+ # Save schema
334
+ schema_path = service.config.get_group_schema_path(group_name)
335
+ schema_path.parent.mkdir(parents=True, exist_ok=True)
336
+
337
+ import json
338
+ with open(schema_path, 'w') as f:
339
+ json.dump(schema_dict, f, indent=2)
340
+
341
+ self.stdout.write(f" āœ… Schema saved: {schema_path}")
342
+
343
+ # Parse to IR
344
+ self.stdout.write(" → Parsing to IR...")
345
+ ir_context = parse_openapi(schema_dict)
346
+ self.stdout.write(f" āœ… Parsed: {len(ir_context.schemas)} schemas, {len(ir_context.operations)} operations")
347
+
348
+ # Generate Python client
349
+ if python:
350
+ self.stdout.write(" → Generating Python client...")
351
+ python_dir = service.config.get_group_python_dir(group_name)
352
+ python_dir.mkdir(parents=True, exist_ok=True)
353
+
354
+ py_generator = PythonGenerator(
355
+ ir_context,
356
+ client_structure=service.config.client_structure,
357
+ openapi_schema=schema_dict,
358
+ tag_prefix=f"{group_name}_",
359
+ )
360
+ py_files = py_generator.generate()
361
+
362
+ for generated_file in py_files:
363
+ full_path = python_dir / generated_file.path
364
+ full_path.parent.mkdir(parents=True, exist_ok=True)
365
+ full_path.write_text(generated_file.content)
366
+
367
+ self.stdout.write(f" āœ… Python client: {python_dir} ({len(py_files)} files)")
368
+
369
+ # Generate TypeScript client
370
+ if typescript:
371
+ self.stdout.write(" → Generating TypeScript client...")
372
+ ts_dir = service.config.get_group_typescript_dir(group_name)
373
+ ts_dir.mkdir(parents=True, exist_ok=True)
374
+
375
+ ts_generator = TypeScriptGenerator(
376
+ ir_context,
377
+ client_structure=service.config.client_structure,
378
+ openapi_schema=schema_dict,
379
+ tag_prefix=f"{group_name}_",
380
+ )
381
+ ts_files = ts_generator.generate()
382
+
383
+ for generated_file in ts_files:
384
+ full_path = ts_dir / generated_file.path
385
+ full_path.parent.mkdir(parents=True, exist_ok=True)
386
+ full_path.write_text(generated_file.content)
387
+
388
+ self.stdout.write(f" āœ… TypeScript client: {ts_dir} ({len(ts_files)} files)")
389
+
390
+ # Archive if enabled
391
+ if service.config.enable_archive:
392
+ self.stdout.write(" → Archiving...")
393
+ archive_manager = ArchiveManager(service.config.get_archive_dir())
394
+ archive_result = archive_manager.archive_clients(
395
+ group_name,
396
+ python_dir=service.config.get_group_python_dir(group_name) if python else None,
397
+ typescript_dir=service.config.get_group_typescript_dir(group_name) if typescript else None,
398
+ )
399
+ if archive_result.get('success'):
400
+ self.stdout.write(f" āœ… Archived: {archive_result['archive_path']}")
401
+
402
+ success_count += 1
403
+
404
+ except Exception as e:
405
+ error_count += 1
406
+ self.stdout.write(self.style.ERROR(f" āŒ Error: {e}"))
407
+ import traceback
408
+ traceback.print_exc()
409
+
410
+ # Summary
411
+ self.stdout.write("\n" + "=" * 60)
412
+ if error_count == 0:
413
+ self.stdout.write(self.style.SUCCESS(f"\nāœ… Successfully generated clients for {success_count} group(s)!"))
414
+ else:
415
+ self.stdout.write(self.style.WARNING(f"\nāš ļø Generated {success_count} group(s), {error_count} failed"))
416
+
417
+ # Show output paths
418
+ self.stdout.write(f"\nOutput directory: {service.get_output_dir()}")
419
+ if python:
420
+ self.stdout.write(f" Python: {service.config.get_python_clients_dir()}")
421
+ if typescript:
422
+ self.stdout.write(f" TypeScript: {service.config.get_typescript_clients_dir()}")