django-cfg 1.4.11__py3-none-any.whl → 1.4.14__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 (109) hide show
  1. django_cfg/apps/urls.py +120 -108
  2. django_cfg/core/generation/integration_generators/api.py +2 -1
  3. django_cfg/core/integration/url_integration.py +5 -10
  4. django_cfg/models/django/openapi.py +15 -128
  5. django_cfg/modules/django_client/core/archive/manager.py +2 -2
  6. django_cfg/modules/django_client/core/config/config.py +20 -0
  7. django_cfg/modules/django_client/core/config/service.py +1 -1
  8. django_cfg/modules/django_client/core/generator/__init__.py +4 -4
  9. django_cfg/modules/django_client/core/generator/base.py +71 -0
  10. django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
  11. django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
  12. django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
  13. django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
  14. django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
  15. django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
  16. django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
  17. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/api_wrapper.py.jinja +25 -2
  18. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/main_client.py.jinja +24 -6
  19. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/main_client_file.py.jinja +1 -0
  20. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/operation_method.py.jinja +3 -1
  21. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/sub_client.py.jinja +8 -1
  22. django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
  23. django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
  24. django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
  25. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/main_init.py.jinja +2 -0
  26. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/enum_class.py.jinja +3 -1
  27. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/schema_class.py.jinja +3 -1
  28. django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
  29. django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
  30. django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
  31. django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
  32. django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
  33. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
  34. django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
  35. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +539 -0
  36. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
  37. django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
  38. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
  39. django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
  40. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/app_client.ts.jinja +1 -1
  41. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/client.ts.jinja +77 -1
  42. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/main_client_file.ts.jinja +1 -0
  43. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/sub_client.ts.jinja +3 -3
  44. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
  45. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
  46. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/main_index.ts.jinja +73 -11
  47. django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
  48. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
  49. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
  50. django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
  51. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/errors.ts.jinja +3 -1
  52. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/logger.ts.jinja +9 -1
  53. django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
  54. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/storage.ts.jinja +54 -10
  55. django_cfg/modules/django_client/management/commands/generate_client.py +5 -0
  56. django_cfg/modules/django_client/pytest.ini +30 -0
  57. django_cfg/modules/django_client/spectacular/__init__.py +3 -2
  58. django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
  59. django_cfg/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
  60. django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
  61. django_cfg/modules/django_unfold/callbacks/main.py +6 -6
  62. django_cfg/modules/django_unfold/dashboard.py +6 -6
  63. django_cfg/pyproject.toml +1 -1
  64. {django_cfg-1.4.11.dist-info → django_cfg-1.4.14.dist-info}/METADATA +1 -1
  65. {django_cfg-1.4.11.dist-info → django_cfg-1.4.14.dist-info}/RECORD +100 -78
  66. django_cfg/dashboard/DEBUG_README.md +0 -105
  67. django_cfg/dashboard/REFACTORING_SUMMARY.md +0 -237
  68. django_cfg/modules/django_client/core/generator/python.py +0 -751
  69. django_cfg/modules/django_client/core/generator/typescript.py +0 -872
  70. django_cfg/modules/django_drf_theme/CHANGELOG.md +0 -210
  71. django_cfg/modules/django_drf_theme/EXAMPLE.md +0 -465
  72. django_cfg/modules/django_drf_theme/IMPLEMENTATION.md +0 -232
  73. django_cfg/modules/django_drf_theme/README.md +0 -207
  74. django_cfg/modules/django_drf_theme/TAILWIND_CDN_GUIDE.md +0 -274
  75. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/__init__.py.jinja +0 -0
  76. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/app_init.py.jinja +0 -0
  77. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/app_client.py.jinja +0 -0
  78. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/flat_client.py.jinja +0 -0
  79. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client_file.py.jinja +0 -0
  80. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/app_models.py.jinja +0 -0
  81. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/enums.py.jinja +0 -0
  82. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/models.py.jinja +0 -0
  83. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/utils/logger.py.jinja +0 -0
  84. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/utils/schema.py.jinja +0 -0
  85. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/app_index.ts.jinja +0 -0
  86. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/flat_client.ts.jinja +0 -0
  87. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/operation.ts.jinja +0 -0
  88. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client_file.ts.jinja +0 -0
  89. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/index.ts.jinja +0 -0
  90. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/app_models.ts.jinja +0 -0
  91. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/enums.ts.jinja +0 -0
  92. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/models.ts.jinja +0 -0
  93. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/http.ts.jinja +0 -0
  94. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/schema.ts.jinja +0 -0
  95. /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
  96. /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
  97. /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
  98. /django_cfg/{dashboard → modules/django_dashboard}/management/__init__.py +0 -0
  99. /django_cfg/{dashboard → modules/django_dashboard}/management/commands/__init__.py +0 -0
  100. /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
  101. /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
  102. /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
  103. /django_cfg/{dashboard → modules/django_dashboard}/sections/documentation.py +0 -0
  104. /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
  105. /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
  106. /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
  107. {django_cfg-1.4.11.dist-info → django_cfg-1.4.14.dist-info}/WHEEL +0 -0
  108. {django_cfg-1.4.11.dist-info → django_cfg-1.4.14.dist-info}/entry_points.txt +0 -0
  109. {django_cfg-1.4.11.dist-info → django_cfg-1.4.14.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:])