django-cfg 1.4.10__py3-none-any.whl → 1.4.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_cfg/apps/agents/management/commands/create_agent.py +1 -1
- django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
- django_cfg/apps/newsletter/serializers.py +40 -3
- django_cfg/apps/newsletter/views/campaigns.py +12 -3
- django_cfg/apps/newsletter/views/emails.py +14 -3
- django_cfg/apps/newsletter/views/subscriptions.py +12 -2
- django_cfg/apps/payments/views/api/currencies.py +49 -6
- django_cfg/apps/payments/views/api/webhooks.py +72 -7
- django_cfg/apps/payments/views/overview/serializers.py +34 -1
- django_cfg/apps/payments/views/overview/views.py +2 -1
- django_cfg/apps/payments/views/serializers/payments.py +6 -6
- django_cfg/apps/urls.py +106 -45
- django_cfg/core/base/config_model.py +2 -2
- django_cfg/core/constants.py +1 -1
- django_cfg/core/generation/integration_generators/__init__.py +1 -1
- django_cfg/core/generation/integration_generators/api.py +73 -49
- django_cfg/core/integration/display/startup.py +30 -22
- django_cfg/core/integration/url_integration.py +15 -16
- django_cfg/management/commands/check_endpoints.py +11 -160
- django_cfg/management/commands/check_settings.py +13 -348
- django_cfg/management/commands/clear_constance.py +13 -201
- django_cfg/management/commands/create_token.py +13 -321
- django_cfg/management/commands/generate_clients.py +23 -0
- django_cfg/management/commands/list_urls.py +13 -306
- django_cfg/management/commands/migrate_all.py +13 -126
- django_cfg/management/commands/migrator.py +13 -396
- django_cfg/management/commands/rundramatiq.py +15 -247
- django_cfg/management/commands/rundramatiq_simulator.py +12 -429
- django_cfg/management/commands/runserver_ngrok.py +15 -160
- django_cfg/management/commands/script.py +12 -488
- django_cfg/management/commands/show_config.py +12 -215
- django_cfg/management/commands/show_urls.py +12 -342
- django_cfg/management/commands/superuser.py +15 -295
- django_cfg/management/commands/task_clear.py +14 -217
- django_cfg/management/commands/task_status.py +13 -248
- django_cfg/management/commands/test_email.py +15 -86
- django_cfg/management/commands/test_telegram.py +14 -61
- django_cfg/management/commands/test_twilio.py +15 -105
- django_cfg/management/commands/tree.py +13 -383
- django_cfg/management/commands/validate_openapi.py +10 -0
- django_cfg/middleware/README.md +1 -1
- django_cfg/middleware/user_activity.py +3 -3
- django_cfg/models/__init__.py +2 -2
- django_cfg/models/api/drf/spectacular.py +6 -6
- django_cfg/models/django/__init__.py +2 -2
- django_cfg/models/django/openapi.py +162 -0
- django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
- django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
- django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
- django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
- django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
- django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
- django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
- django_cfg/modules/django_admin/management/commands/script.py +496 -0
- django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
- django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
- django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
- django_cfg/modules/django_admin/management/commands/tree.py +390 -0
- django_cfg/modules/django_client/__init__.py +20 -0
- django_cfg/modules/django_client/apps.py +35 -0
- django_cfg/modules/django_client/core/__init__.py +56 -0
- django_cfg/modules/django_client/core/archive/__init__.py +11 -0
- django_cfg/modules/django_client/core/archive/manager.py +134 -0
- django_cfg/modules/django_client/core/cli/__init__.py +12 -0
- django_cfg/modules/django_client/core/cli/main.py +235 -0
- django_cfg/modules/django_client/core/config/__init__.py +18 -0
- django_cfg/modules/django_client/core/config/config.py +208 -0
- django_cfg/modules/django_client/core/config/group.py +101 -0
- django_cfg/modules/django_client/core/config/service.py +209 -0
- django_cfg/modules/django_client/core/generator/__init__.py +115 -0
- django_cfg/modules/django_client/core/generator/base.py +838 -0
- django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
- django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
- django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
- django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
- django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
- django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
- django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
- django_cfg/modules/django_client/core/generator/python/templates/__init__.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +153 -0
- django_cfg/modules/django_client/core/generator/python/templates/app_init.py.jinja +6 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/app_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/flat_client.py.jinja +38 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/main_client.py.jinja +68 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/main_client_file.py.jinja +14 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/operation_method.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sub_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/python/templates/client_file.py.jinja +13 -0
- django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +52 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/app_models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/enum_class.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/enums.py.jinja +8 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/schema_class.py.jinja +21 -0
- django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
- django_cfg/modules/django_client/core/generator/python/templates/utils/logger.py.jinja +255 -0
- django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
- django_cfg/modules/django_client/core/generator/python/templates/utils/schema.py.jinja +12 -0
- django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
- django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
- django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
- django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
- django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +536 -0
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
- django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/app_index.ts.jinja +2 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/app_client.ts.jinja +18 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/client.ts.jinja +403 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/flat_client.ts.jinja +109 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/main_client_file.ts.jinja +10 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/operation.ts.jinja +61 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/sub_client.ts.jinja +15 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client_file.ts.jinja +9 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/index.ts.jinja +5 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +268 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/models/app_models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/models/enums.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/models/models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/errors.ts.jinja +116 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/http.ts.jinja +98 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/logger.ts.jinja +259 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/schema.ts.jinja +7 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/storage.ts.jinja +158 -0
- django_cfg/modules/django_client/core/groups/__init__.py +13 -0
- django_cfg/modules/django_client/core/groups/detector.py +178 -0
- django_cfg/modules/django_client/core/groups/manager.py +314 -0
- django_cfg/modules/django_client/core/ir/__init__.py +57 -0
- django_cfg/modules/django_client/core/ir/context.py +387 -0
- django_cfg/modules/django_client/core/ir/operation.py +518 -0
- django_cfg/modules/django_client/core/ir/schema.py +353 -0
- django_cfg/modules/django_client/core/parser/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/base.py +648 -0
- django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/models/base.py +212 -0
- django_cfg/modules/django_client/core/parser/models/components.py +160 -0
- django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
- django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
- django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
- django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
- django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
- django_cfg/modules/django_client/core/validation/__init__.py +22 -0
- django_cfg/modules/django_client/core/validation/checker.py +134 -0
- django_cfg/modules/django_client/core/validation/fixer.py +216 -0
- django_cfg/modules/django_client/core/validation/reporter.py +480 -0
- django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
- django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
- django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
- django_cfg/modules/django_client/core/validation/safety.py +266 -0
- django_cfg/modules/django_client/management/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +427 -0
- django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
- django_cfg/modules/django_client/pytest.ini +30 -0
- django_cfg/modules/django_client/spectacular/__init__.py +10 -0
- django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
- django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
- django_cfg/modules/django_client/urls.py +72 -0
- django_cfg/{dashboard → modules/django_dashboard}/DEBUG_README.md +2 -2
- django_cfg/{dashboard → modules/django_dashboard}/REFACTORING_SUMMARY.md +1 -1
- django_cfg/modules/django_dashboard/management/__init__.py +0 -0
- django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
- django_cfg/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
- django_cfg/modules/django_dashboard/sections/documentation.py +391 -0
- django_cfg/modules/django_email/management/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/test_email.py +93 -0
- django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
- django_cfg/modules/django_logging/django_logger.py +6 -6
- django_cfg/modules/django_ngrok/management/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
- django_cfg/modules/django_tasks/management/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
- django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
- django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
- django_cfg/modules/django_telegram/management/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
- django_cfg/modules/django_twilio/management/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
- django_cfg/modules/django_unfold/callbacks/main.py +21 -10
- django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
- django_cfg/pyproject.toml +2 -6
- django_cfg/registry/third_party.py +5 -7
- django_cfg/routing/callbacks.py +1 -1
- django_cfg/static/admin/css/prose-unfold.css +666 -0
- django_cfg/templates/admin/index.html +8 -0
- django_cfg/templates/admin/index_new.html +13 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
- django_cfg/templates/admin/sections/documentation_section.html +172 -0
- django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/METADATA +2 -2
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/RECORD +224 -74
- django_cfg/management/commands/generate.py +0 -107
- /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
- /django_cfg/{dashboard → modules/django_admin}/management/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_admin}/management/commands/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,427 @@
|
|
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
|
+
generate_package_files=service.config.generate_package_files,
|
360
|
+
)
|
361
|
+
py_files = py_generator.generate()
|
362
|
+
|
363
|
+
for generated_file in py_files:
|
364
|
+
full_path = python_dir / generated_file.path
|
365
|
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
366
|
+
full_path.write_text(generated_file.content)
|
367
|
+
|
368
|
+
self.stdout.write(f" ✅ Python client: {python_dir} ({len(py_files)} files)")
|
369
|
+
|
370
|
+
# Generate TypeScript client
|
371
|
+
if typescript:
|
372
|
+
self.stdout.write(" → Generating TypeScript client...")
|
373
|
+
ts_dir = service.config.get_group_typescript_dir(group_name)
|
374
|
+
ts_dir.mkdir(parents=True, exist_ok=True)
|
375
|
+
|
376
|
+
ts_generator = TypeScriptGenerator(
|
377
|
+
ir_context,
|
378
|
+
client_structure=service.config.client_structure,
|
379
|
+
openapi_schema=schema_dict,
|
380
|
+
tag_prefix=f"{group_name}_",
|
381
|
+
generate_package_files=service.config.generate_package_files,
|
382
|
+
generate_zod_schemas=service.config.generate_zod_schemas,
|
383
|
+
generate_fetchers=service.config.generate_fetchers,
|
384
|
+
generate_swr_hooks=service.config.generate_swr_hooks,
|
385
|
+
)
|
386
|
+
ts_files = ts_generator.generate()
|
387
|
+
|
388
|
+
for generated_file in ts_files:
|
389
|
+
full_path = ts_dir / generated_file.path
|
390
|
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
391
|
+
full_path.write_text(generated_file.content)
|
392
|
+
|
393
|
+
self.stdout.write(f" ✅ TypeScript client: {ts_dir} ({len(ts_files)} files)")
|
394
|
+
|
395
|
+
# Archive if enabled
|
396
|
+
if service.config.enable_archive:
|
397
|
+
self.stdout.write(" → Archiving...")
|
398
|
+
archive_manager = ArchiveManager(service.config.get_archive_dir())
|
399
|
+
archive_result = archive_manager.archive_clients(
|
400
|
+
group_name,
|
401
|
+
python_dir=service.config.get_group_python_dir(group_name) if python else None,
|
402
|
+
typescript_dir=service.config.get_group_typescript_dir(group_name) if typescript else None,
|
403
|
+
)
|
404
|
+
if archive_result.get('success'):
|
405
|
+
self.stdout.write(f" ✅ Archived: {archive_result['archive_path']}")
|
406
|
+
|
407
|
+
success_count += 1
|
408
|
+
|
409
|
+
except Exception as e:
|
410
|
+
error_count += 1
|
411
|
+
self.stdout.write(self.style.ERROR(f" ❌ Error: {e}"))
|
412
|
+
import traceback
|
413
|
+
traceback.print_exc()
|
414
|
+
|
415
|
+
# Summary
|
416
|
+
self.stdout.write("\n" + "=" * 60)
|
417
|
+
if error_count == 0:
|
418
|
+
self.stdout.write(self.style.SUCCESS(f"\n✅ Successfully generated clients for {success_count} group(s)!"))
|
419
|
+
else:
|
420
|
+
self.stdout.write(self.style.WARNING(f"\n⚠️ Generated {success_count} group(s), {error_count} failed"))
|
421
|
+
|
422
|
+
# Show output paths
|
423
|
+
self.stdout.write(f"\nOutput directory: {service.get_output_dir()}")
|
424
|
+
if python:
|
425
|
+
self.stdout.write(f" Python: {service.config.get_python_clients_dir()}")
|
426
|
+
if typescript:
|
427
|
+
self.stdout.write(f" TypeScript: {service.config.get_typescript_clients_dir()}")
|