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,245 @@
|
|
1
|
+
"""
|
2
|
+
TypeScript Models Generator - Generates TypeScript interfaces and enums.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from __future__ import annotations
|
6
|
+
|
7
|
+
from jinja2 import Environment
|
8
|
+
from ...ir import IRSchemaObject
|
9
|
+
from ..base import GeneratedFile
|
10
|
+
|
11
|
+
|
12
|
+
class ModelsGenerator:
|
13
|
+
"""Generates TypeScript interfaces and enums."""
|
14
|
+
|
15
|
+
def __init__(self, jinja_env: Environment, context, base):
|
16
|
+
self.jinja_env = jinja_env
|
17
|
+
self.context = context
|
18
|
+
self.base = base
|
19
|
+
|
20
|
+
def generate_models_file(self):
|
21
|
+
"""Generate models.ts with all TypeScript interfaces."""
|
22
|
+
|
23
|
+
# Generate all schemas
|
24
|
+
schema_codes = []
|
25
|
+
|
26
|
+
# Response models first
|
27
|
+
for name, schema in self.base.get_response_schemas().items():
|
28
|
+
schema_codes.append(self.generate_schema(schema))
|
29
|
+
|
30
|
+
# Request models
|
31
|
+
for name, schema in self.base.get_request_schemas().items():
|
32
|
+
schema_codes.append(self.generate_schema(schema))
|
33
|
+
|
34
|
+
# Patch models
|
35
|
+
for name, schema in self.base.get_patch_schemas().items():
|
36
|
+
schema_codes.append(self.generate_schema(schema))
|
37
|
+
|
38
|
+
template = self.jinja_env.get_template('models/models.ts.jinja')
|
39
|
+
content = template.render(
|
40
|
+
has_enums=bool(self.base.get_enum_schemas()),
|
41
|
+
schemas=schema_codes
|
42
|
+
)
|
43
|
+
|
44
|
+
return GeneratedFile(
|
45
|
+
path="models.ts",
|
46
|
+
content=content,
|
47
|
+
description="TypeScript interfaces (Request/Response/Patch)",
|
48
|
+
)
|
49
|
+
|
50
|
+
def generate_enums_file(self):
|
51
|
+
"""Generate enums.ts with all enum types (flat structure)."""
|
52
|
+
|
53
|
+
enum_codes = []
|
54
|
+
for name, schema in self.base.get_enum_schemas().items():
|
55
|
+
enum_codes.append(self.generate_enum(schema))
|
56
|
+
|
57
|
+
template = self.jinja_env.get_template('models/enums.ts.jinja')
|
58
|
+
content = template.render(enums=enum_codes)
|
59
|
+
|
60
|
+
return GeneratedFile(
|
61
|
+
path="enums.ts",
|
62
|
+
content=content,
|
63
|
+
description="Enum types from x-enum-varnames",
|
64
|
+
)
|
65
|
+
|
66
|
+
def generate_shared_enums_file(self, enums: dict[str, IRSchemaObject]):
|
67
|
+
"""Generate shared enums.ts for namespaced structure (Variant 2)."""
|
68
|
+
|
69
|
+
enum_codes = []
|
70
|
+
for name, schema in enums.items():
|
71
|
+
enum_codes.append(self.generate_enum(schema))
|
72
|
+
|
73
|
+
template = self.jinja_env.get_template('models/enums.ts.jinja')
|
74
|
+
content = template.render(enums=enum_codes)
|
75
|
+
|
76
|
+
return GeneratedFile(
|
77
|
+
path="enums.ts",
|
78
|
+
content=content,
|
79
|
+
description="Shared enum types from x-enum-varnames",
|
80
|
+
)
|
81
|
+
|
82
|
+
def generate_schema(self, schema: IRSchemaObject) -> str:
|
83
|
+
"""Generate TypeScript interface for schema."""
|
84
|
+
if schema.type != "object":
|
85
|
+
# For primitive types, skip (they'll be inlined)
|
86
|
+
return ""
|
87
|
+
|
88
|
+
# Interface comment
|
89
|
+
comment_lines = []
|
90
|
+
if schema.description:
|
91
|
+
comment_lines.extend(self.base.wrap_comment(schema.description, 76))
|
92
|
+
|
93
|
+
# Add metadata about model type
|
94
|
+
if schema.is_request_model:
|
95
|
+
comment_lines.append("")
|
96
|
+
comment_lines.append("Request model (no read-only fields).")
|
97
|
+
elif schema.is_patch_model:
|
98
|
+
comment_lines.append("")
|
99
|
+
comment_lines.append("PATCH model (all fields optional).")
|
100
|
+
elif schema.is_response_model:
|
101
|
+
comment_lines.append("")
|
102
|
+
comment_lines.append("Response model (includes read-only fields).")
|
103
|
+
|
104
|
+
comment = "/**\n * " + "\n * ".join(comment_lines) + "\n */" if comment_lines else None
|
105
|
+
|
106
|
+
# Fields
|
107
|
+
field_lines = []
|
108
|
+
|
109
|
+
for prop_name, prop_schema in schema.properties.items():
|
110
|
+
field_lines.append(self._generate_field(prop_name, prop_schema, schema.required))
|
111
|
+
|
112
|
+
# Build interface
|
113
|
+
lines = []
|
114
|
+
|
115
|
+
if comment:
|
116
|
+
lines.append(comment)
|
117
|
+
|
118
|
+
lines.append(f"export interface {schema.name} {{")
|
119
|
+
|
120
|
+
if field_lines:
|
121
|
+
for field_line in field_lines:
|
122
|
+
lines.append(self.base.indent(field_line, 2))
|
123
|
+
else:
|
124
|
+
# Empty interface
|
125
|
+
pass
|
126
|
+
|
127
|
+
lines.append("}")
|
128
|
+
|
129
|
+
return "\n".join(lines)
|
130
|
+
|
131
|
+
def _generate_field(
|
132
|
+
self,
|
133
|
+
name: str,
|
134
|
+
schema: IRSchemaObject,
|
135
|
+
required_fields: list[str],
|
136
|
+
) -> str:
|
137
|
+
"""
|
138
|
+
Generate TypeScript field definition.
|
139
|
+
|
140
|
+
Examples:
|
141
|
+
id: number;
|
142
|
+
username: string;
|
143
|
+
email?: string | null;
|
144
|
+
status: Enums.StatusEnum;
|
145
|
+
"""
|
146
|
+
# Check if this field is an enum
|
147
|
+
if schema.enum and schema.name:
|
148
|
+
# Use enum type from shared enums (sanitized)
|
149
|
+
ts_type = f"Enums.{self.base.sanitize_enum_name(schema.name)}"
|
150
|
+
if schema.nullable:
|
151
|
+
ts_type = f"{ts_type} | null"
|
152
|
+
# Check if this field is a reference to an enum (via $ref)
|
153
|
+
elif schema.ref and schema.ref in self.context.schemas:
|
154
|
+
ref_schema = self.context.schemas[schema.ref]
|
155
|
+
if ref_schema.enum:
|
156
|
+
# This is a reference to an enum component (sanitized to PascalCase)
|
157
|
+
ts_type = f"Enums.{self.base.sanitize_enum_name(schema.ref)}"
|
158
|
+
if schema.nullable:
|
159
|
+
ts_type = f"{ts_type} | null"
|
160
|
+
else:
|
161
|
+
# Regular reference
|
162
|
+
ts_type = schema.typescript_type
|
163
|
+
else:
|
164
|
+
# Get TypeScript type
|
165
|
+
ts_type = schema.typescript_type
|
166
|
+
|
167
|
+
# Check if required
|
168
|
+
is_required = name in required_fields
|
169
|
+
|
170
|
+
# Optional marker
|
171
|
+
optional_marker = "" if is_required else "?"
|
172
|
+
|
173
|
+
# Comment
|
174
|
+
if schema.description:
|
175
|
+
return f"/** {schema.description} */\n{name}{optional_marker}: {ts_type};"
|
176
|
+
|
177
|
+
return f"{name}{optional_marker}: {ts_type};"
|
178
|
+
|
179
|
+
def generate_enum(self, schema: IRSchemaObject) -> str:
|
180
|
+
"""Generate TypeScript enum from x-enum-varnames."""
|
181
|
+
# Sanitize enum name (convert to PascalCase)
|
182
|
+
# "OrderDetail.status" → "OrderDetailStatus"
|
183
|
+
# "Currency.currency_type" → "CurrencyCurrencyType"
|
184
|
+
enum_name = self.base.sanitize_enum_name(schema.name)
|
185
|
+
|
186
|
+
# Enum comment
|
187
|
+
comment = None
|
188
|
+
if schema.description:
|
189
|
+
# Format enum description to split bullet points
|
190
|
+
formatted_desc = self.base.format_enum_description(schema.description)
|
191
|
+
# Split into lines and format as JSDoc comment
|
192
|
+
desc_lines = formatted_desc.split('\n')
|
193
|
+
comment = "/**\n * " + "\n * ".join(desc_lines) + "\n */"
|
194
|
+
|
195
|
+
# Enum members
|
196
|
+
member_lines = []
|
197
|
+
for var_name, value in zip(schema.enum_var_names, schema.enum):
|
198
|
+
# Skip empty values (from blank=True in Django)
|
199
|
+
if not var_name or (isinstance(value, str) and value == ''):
|
200
|
+
continue
|
201
|
+
|
202
|
+
if isinstance(value, str):
|
203
|
+
member_lines.append(f'{var_name} = "{value}",')
|
204
|
+
else:
|
205
|
+
member_lines.append(f"{var_name} = {value},")
|
206
|
+
|
207
|
+
# Build enum
|
208
|
+
lines = []
|
209
|
+
|
210
|
+
if comment:
|
211
|
+
lines.append(comment)
|
212
|
+
|
213
|
+
lines.append(f"export enum {enum_name} {{")
|
214
|
+
|
215
|
+
for member_line in member_lines:
|
216
|
+
lines.append(self.base.indent(member_line, 2))
|
217
|
+
|
218
|
+
lines.append("}")
|
219
|
+
|
220
|
+
return "\n".join(lines)
|
221
|
+
|
222
|
+
def generate_app_models_file(self, tag: str, schemas: dict[str, IRSchemaObject], operations: list):
|
223
|
+
"""Generate models.ts for a specific app."""
|
224
|
+
|
225
|
+
# Check if we have enums in schemas
|
226
|
+
app_enums = self.base._collect_enums_from_schemas(schemas)
|
227
|
+
has_enums = len(app_enums) > 0
|
228
|
+
|
229
|
+
# Generate schemas
|
230
|
+
schema_codes = []
|
231
|
+
for name, schema in schemas.items():
|
232
|
+
schema_codes.append(self.generate_schema(schema))
|
233
|
+
|
234
|
+
template = self.jinja_env.get_template('models/app_models.ts.jinja')
|
235
|
+
content = template.render(
|
236
|
+
has_enums=has_enums,
|
237
|
+
schemas=schema_codes
|
238
|
+
)
|
239
|
+
|
240
|
+
folder_name = self.base.tag_and_app_to_folder_name(tag, operations)
|
241
|
+
return GeneratedFile(
|
242
|
+
path=f"{folder_name}/models.ts",
|
243
|
+
content=content,
|
244
|
+
description=f"TypeScript interfaces for {tag}",
|
245
|
+
)
|
@@ -0,0 +1,298 @@
|
|
1
|
+
"""
|
2
|
+
TypeScript Operations Generator - Generates TypeScript async operation methods.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from __future__ import annotations
|
6
|
+
|
7
|
+
from jinja2 import Environment
|
8
|
+
from ...ir import IROperationObject
|
9
|
+
|
10
|
+
|
11
|
+
class OperationsGenerator:
|
12
|
+
"""Generates TypeScript async operation methods."""
|
13
|
+
|
14
|
+
def __init__(self, jinja_env: Environment, context, base):
|
15
|
+
self.jinja_env = jinja_env
|
16
|
+
self.context = context
|
17
|
+
self.base = base
|
18
|
+
|
19
|
+
def generate_operation(self, operation: IROperationObject, remove_tag_prefix: bool = False, in_subclient: bool = False) -> str:
|
20
|
+
"""Generate async method for operation."""
|
21
|
+
# Get method name
|
22
|
+
operation_id = operation.operation_id
|
23
|
+
if remove_tag_prefix and operation.tags:
|
24
|
+
# Remove tag prefix using base class method
|
25
|
+
tag = operation.tags[0]
|
26
|
+
operation_id = self.base.remove_tag_prefix(operation_id, tag)
|
27
|
+
|
28
|
+
# Convert snake_case to camelCase
|
29
|
+
method_name = self._to_camel_case(operation_id)
|
30
|
+
|
31
|
+
# Request method prefix
|
32
|
+
request_prefix = "this.client" if in_subclient else "this"
|
33
|
+
|
34
|
+
# Method parameters
|
35
|
+
params = []
|
36
|
+
|
37
|
+
# Add path parameters
|
38
|
+
for param in operation.path_parameters:
|
39
|
+
param_type = self._map_param_type(param.schema_type)
|
40
|
+
params.append(f"{param.name}: {param_type}")
|
41
|
+
|
42
|
+
# Check if this is a file upload operation
|
43
|
+
is_multipart = (
|
44
|
+
operation.request_body
|
45
|
+
and operation.request_body.content_type == "multipart/form-data"
|
46
|
+
)
|
47
|
+
|
48
|
+
# Add request body parameter
|
49
|
+
if operation.request_body:
|
50
|
+
if is_multipart:
|
51
|
+
# For multipart, get schema properties and add as individual File parameters
|
52
|
+
schema_name = operation.request_body.schema_name
|
53
|
+
if schema_name and schema_name in self.context.schemas:
|
54
|
+
schema = self.context.schemas[schema_name]
|
55
|
+
for prop_name, prop in schema.properties.items():
|
56
|
+
# Check if it's a file field (format: binary)
|
57
|
+
if prop.format == "binary":
|
58
|
+
params.append(f"{prop_name}: File | Blob")
|
59
|
+
else:
|
60
|
+
# Regular field in multipart
|
61
|
+
prop_type = self._map_param_type(prop.type)
|
62
|
+
if prop_name in schema.required:
|
63
|
+
params.append(f"{prop_name}: {prop_type}")
|
64
|
+
else:
|
65
|
+
params.append(f"{prop_name}?: {prop_type}")
|
66
|
+
else:
|
67
|
+
# Inline schema - use FormData
|
68
|
+
params.append("data: FormData")
|
69
|
+
else:
|
70
|
+
# JSON request body
|
71
|
+
schema_name = operation.request_body.schema_name
|
72
|
+
if schema_name and schema_name in self.context.schemas:
|
73
|
+
params.append(f"data: Models.{schema_name}")
|
74
|
+
else:
|
75
|
+
# Inline schema - use any
|
76
|
+
params.append("data: any")
|
77
|
+
elif operation.patch_request_body:
|
78
|
+
schema_name = operation.patch_request_body.schema_name
|
79
|
+
if schema_name and schema_name in self.context.schemas:
|
80
|
+
params.append(f"data?: Models.{schema_name}")
|
81
|
+
else:
|
82
|
+
params.append("data?: any")
|
83
|
+
|
84
|
+
# Add query parameters (old style - separate params)
|
85
|
+
query_params_list = []
|
86
|
+
for param in operation.query_parameters:
|
87
|
+
param_type = self._map_param_type(param.schema_type)
|
88
|
+
if not param.required:
|
89
|
+
params.append(f"{param.name}?: {param_type}")
|
90
|
+
else:
|
91
|
+
params.append(f"{param.name}: {param_type}")
|
92
|
+
query_params_list.append((param.name, param_type, param.required))
|
93
|
+
|
94
|
+
# Return type
|
95
|
+
primary_response = operation.primary_success_response
|
96
|
+
if primary_response and primary_response.schema_name:
|
97
|
+
if operation.is_list_operation:
|
98
|
+
return_type = f"Models.{primary_response.schema_name}[]"
|
99
|
+
else:
|
100
|
+
return_type = f"Models.{primary_response.schema_name}"
|
101
|
+
else:
|
102
|
+
return_type = "void"
|
103
|
+
|
104
|
+
# Build overload signatures if has query params
|
105
|
+
overload_signatures = []
|
106
|
+
use_rest_params = False
|
107
|
+
|
108
|
+
if query_params_list:
|
109
|
+
# Overload 1: separate parameters (current/backward compatible style)
|
110
|
+
overload_signatures.append(f"async {method_name}({', '.join(params)}): Promise<{return_type}>")
|
111
|
+
|
112
|
+
# Overload 2: params object (new style)
|
113
|
+
# Build params object signature
|
114
|
+
params_obj = [p for p in params if not any(p.startswith(f"{pn}:") or p.startswith(f"{pn}?:") for pn, _, _ in query_params_list)]
|
115
|
+
query_fields = []
|
116
|
+
for param_name, param_type, required in query_params_list:
|
117
|
+
optional = "?" if not required else ""
|
118
|
+
query_fields.append(f"{param_name}{optional}: {param_type}")
|
119
|
+
if query_fields:
|
120
|
+
params_obj.append(f"params?: {{ {'; '.join(query_fields)} }}")
|
121
|
+
overload_signatures.append(f"async {method_name}({', '.join(params_obj)}): Promise<{return_type}>")
|
122
|
+
|
123
|
+
# Implementation signature - use rest params for compatibility
|
124
|
+
use_rest_params = True
|
125
|
+
|
126
|
+
# Build implementation signature
|
127
|
+
if use_rest_params:
|
128
|
+
signature = f"async {method_name}(...args: any[]): Promise<{return_type}> {{"
|
129
|
+
else:
|
130
|
+
signature = f"async {method_name}({', '.join(params)}): Promise<{return_type}> {{"
|
131
|
+
|
132
|
+
# Comment
|
133
|
+
comment_lines = []
|
134
|
+
if operation.summary:
|
135
|
+
comment_lines.append(operation.summary)
|
136
|
+
if operation.description:
|
137
|
+
if comment_lines:
|
138
|
+
comment_lines.append("")
|
139
|
+
comment_lines.extend(self.base.wrap_comment(operation.description, 72))
|
140
|
+
|
141
|
+
comment = "/**\n * " + "\n * ".join(comment_lines) + "\n */" if comment_lines else None
|
142
|
+
|
143
|
+
# Method body
|
144
|
+
body_lines = []
|
145
|
+
|
146
|
+
# Handle overloaded parameters if has query params (using rest params)
|
147
|
+
if use_rest_params and query_params_list:
|
148
|
+
# Extract parameters from args array
|
149
|
+
path_params_count = len(operation.path_parameters)
|
150
|
+
body_params_count = 1 if (operation.request_body or operation.patch_request_body) else 0
|
151
|
+
first_query_pos = path_params_count + body_params_count
|
152
|
+
|
153
|
+
# Extract path parameters
|
154
|
+
for i, param in enumerate(operation.path_parameters):
|
155
|
+
body_lines.append(f"const {param.name} = args[{i}];")
|
156
|
+
|
157
|
+
# Extract body/data parameter
|
158
|
+
if operation.request_body or operation.patch_request_body:
|
159
|
+
body_lines.append(f"const data = args[{path_params_count}];")
|
160
|
+
|
161
|
+
# Check if first query arg is object (params style) or primitive (old style)
|
162
|
+
body_lines.append(f"const isParamsObject = args.length === {first_query_pos + 1} && typeof args[{first_query_pos}] === 'object' && args[{first_query_pos}] !== null && !Array.isArray(args[{first_query_pos}]);")
|
163
|
+
body_lines.append("")
|
164
|
+
|
165
|
+
# Build path
|
166
|
+
path_expr = f'"{operation.path}"'
|
167
|
+
if operation.path_parameters:
|
168
|
+
# Replace {id} with ${id}
|
169
|
+
path_with_vars = operation.path
|
170
|
+
for param in operation.path_parameters:
|
171
|
+
path_with_vars = path_with_vars.replace(f"{{{param.name}}}", f"${{{param.name}}}")
|
172
|
+
path_expr = f'`{path_with_vars}`'
|
173
|
+
|
174
|
+
# Build request options
|
175
|
+
request_opts = []
|
176
|
+
|
177
|
+
# Query params
|
178
|
+
if query_params_list:
|
179
|
+
param_names = [param_name for param_name, _, _ in query_params_list]
|
180
|
+
|
181
|
+
if use_rest_params:
|
182
|
+
# Extract params from args array - handle both calling styles
|
183
|
+
path_params_count = len(operation.path_parameters)
|
184
|
+
body_params_count = 1 if (operation.request_body or operation.patch_request_body) else 0
|
185
|
+
first_query_pos = path_params_count + body_params_count
|
186
|
+
|
187
|
+
body_lines.append("let params;")
|
188
|
+
body_lines.append("if (isParamsObject) {")
|
189
|
+
# Params object style
|
190
|
+
body_lines.append(f" params = args[{first_query_pos}];")
|
191
|
+
body_lines.append("} else {")
|
192
|
+
# Separate params style - collect from individual args
|
193
|
+
param_extractions = []
|
194
|
+
for i, param_name in enumerate(param_names):
|
195
|
+
param_extractions.append(f"{param_name}: args[{first_query_pos + i}]")
|
196
|
+
body_lines.append(f" params = {{ {', '.join(param_extractions)} }};")
|
197
|
+
body_lines.append("}")
|
198
|
+
else:
|
199
|
+
# No overloads - standard query params
|
200
|
+
query_items = ", ".join(param_names)
|
201
|
+
body_lines.append(f"const params = {{ {query_items} }};")
|
202
|
+
|
203
|
+
request_opts.append("params")
|
204
|
+
|
205
|
+
# Body / FormData
|
206
|
+
if operation.request_body or operation.patch_request_body:
|
207
|
+
if is_multipart and operation.request_body:
|
208
|
+
# Build FormData for multipart upload
|
209
|
+
schema_name = operation.request_body.schema_name
|
210
|
+
if schema_name and schema_name in self.context.schemas:
|
211
|
+
schema = self.context.schemas[schema_name]
|
212
|
+
body_lines.append("const formData = new FormData();")
|
213
|
+
for prop_name, prop in schema.properties.items():
|
214
|
+
if prop.format == "binary":
|
215
|
+
# Append file
|
216
|
+
body_lines.append(f"formData.append('{prop_name}', {prop_name});")
|
217
|
+
elif prop_name in schema.required or True: # Append all non-undefined fields
|
218
|
+
# Append other fields (wrap in if check for optional)
|
219
|
+
if prop_name not in schema.required:
|
220
|
+
body_lines.append(f"if ({prop_name} !== undefined) formData.append('{prop_name}', String({prop_name}));")
|
221
|
+
else:
|
222
|
+
body_lines.append(f"formData.append('{prop_name}', String({prop_name}));")
|
223
|
+
request_opts.append("formData")
|
224
|
+
else:
|
225
|
+
# Inline schema - data is already FormData
|
226
|
+
request_opts.append("body: data")
|
227
|
+
else:
|
228
|
+
# JSON body
|
229
|
+
request_opts.append("body: data")
|
230
|
+
|
231
|
+
# Make request (no type argument when client is 'any')
|
232
|
+
if request_opts:
|
233
|
+
request_line = f"const response = await {request_prefix}.request('{operation.http_method}', {path_expr}, {{ {', '.join(request_opts)} }});"
|
234
|
+
else:
|
235
|
+
request_line = f"const response = await {request_prefix}.request('{operation.http_method}', {path_expr});"
|
236
|
+
|
237
|
+
body_lines.append(request_line)
|
238
|
+
|
239
|
+
# Handle response
|
240
|
+
if operation.is_list_operation and primary_response:
|
241
|
+
# Extract results from paginated response
|
242
|
+
body_lines.append("return (response as any).results || [];")
|
243
|
+
elif return_type != "void":
|
244
|
+
body_lines.append("return response;")
|
245
|
+
else:
|
246
|
+
body_lines.append("return;")
|
247
|
+
|
248
|
+
# Build method with proper class-level indentation (2 spaces)
|
249
|
+
lines = []
|
250
|
+
|
251
|
+
# Add overload signatures first (if any)
|
252
|
+
if overload_signatures:
|
253
|
+
for overload_sig in overload_signatures:
|
254
|
+
lines.append(" " + overload_sig + ";")
|
255
|
+
lines.append("") # Empty line between overloads and implementation
|
256
|
+
|
257
|
+
# Add comment with indentation
|
258
|
+
if comment:
|
259
|
+
comment_lines_formatted = []
|
260
|
+
for line in comment.split('\n'):
|
261
|
+
comment_lines_formatted.append(" " + line)
|
262
|
+
lines.extend(comment_lines_formatted)
|
263
|
+
|
264
|
+
# Add signature with indentation
|
265
|
+
lines.append(" " + signature)
|
266
|
+
|
267
|
+
# Add body with indentation (4 spaces total: 2 for class + 2 for method body)
|
268
|
+
for line in body_lines:
|
269
|
+
lines.append(" " + line)
|
270
|
+
|
271
|
+
# Add closing brace with indentation
|
272
|
+
lines.append(" " + "}")
|
273
|
+
|
274
|
+
return "\n".join(lines)
|
275
|
+
|
276
|
+
def _map_param_type(self, schema_type: str) -> str:
|
277
|
+
"""Map parameter schema type to TypeScript type."""
|
278
|
+
type_map = {
|
279
|
+
"string": "string",
|
280
|
+
"integer": "number",
|
281
|
+
"number": "number",
|
282
|
+
"boolean": "boolean",
|
283
|
+
"array": "any[]",
|
284
|
+
}
|
285
|
+
return type_map.get(schema_type, "any")
|
286
|
+
|
287
|
+
def _to_camel_case(self, snake_str: str) -> str:
|
288
|
+
"""
|
289
|
+
Convert snake_case to camelCase.
|
290
|
+
|
291
|
+
Examples:
|
292
|
+
>>> self._to_camel_case("users_list")
|
293
|
+
'usersList'
|
294
|
+
>>> self._to_camel_case("users_partial_update")
|
295
|
+
'usersPartialUpdate'
|
296
|
+
"""
|
297
|
+
components = snake_str.split("_")
|
298
|
+
return components[0] + "".join(x.title() for x in components[1:])
|