django-cfg 1.4.11__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.
Files changed (101) hide show
  1. django_cfg/core/generation/integration_generators/api.py +2 -1
  2. django_cfg/models/django/openapi.py +4 -80
  3. django_cfg/modules/django_client/core/archive/manager.py +2 -2
  4. django_cfg/modules/django_client/core/config/config.py +20 -0
  5. django_cfg/modules/django_client/core/config/service.py +1 -1
  6. django_cfg/modules/django_client/core/generator/__init__.py +4 -4
  7. django_cfg/modules/django_client/core/generator/base.py +71 -0
  8. django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
  9. django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
  10. django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
  11. django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
  12. django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
  13. django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
  14. django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
  15. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/api_wrapper.py.jinja +25 -2
  16. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/main_client.py.jinja +24 -6
  17. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/main_client_file.py.jinja +1 -0
  18. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/operation_method.py.jinja +3 -1
  19. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/sub_client.py.jinja +8 -1
  20. django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
  21. django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
  22. django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
  23. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/main_init.py.jinja +2 -0
  24. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/enum_class.py.jinja +3 -1
  25. django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/schema_class.py.jinja +3 -1
  26. django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
  27. django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
  28. django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
  29. django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
  30. django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
  31. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
  32. django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
  33. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +536 -0
  34. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
  35. django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
  36. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
  37. django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
  38. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/app_client.ts.jinja +1 -1
  39. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/client.ts.jinja +77 -1
  40. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/main_client_file.ts.jinja +1 -0
  41. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/sub_client.ts.jinja +3 -3
  42. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
  43. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
  44. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/main_index.ts.jinja +73 -11
  45. django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
  46. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
  47. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
  48. django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
  49. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/errors.ts.jinja +3 -1
  50. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/logger.ts.jinja +9 -1
  51. django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
  52. django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/storage.ts.jinja +54 -10
  53. django_cfg/modules/django_client/management/commands/generate_client.py +5 -0
  54. django_cfg/modules/django_client/pytest.ini +30 -0
  55. django_cfg/modules/django_client/spectacular/__init__.py +3 -2
  56. django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
  57. django_cfg/{dashboard → modules/django_dashboard}/DEBUG_README.md +2 -2
  58. django_cfg/{dashboard → modules/django_dashboard}/REFACTORING_SUMMARY.md +1 -1
  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/pyproject.toml +1 -1
  63. {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/METADATA +1 -1
  64. {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/RECORD +99 -70
  65. django_cfg/modules/django_client/core/generator/python.py +0 -751
  66. django_cfg/modules/django_client/core/generator/typescript.py +0 -872
  67. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/__init__.py.jinja +0 -0
  68. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/app_init.py.jinja +0 -0
  69. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/app_client.py.jinja +0 -0
  70. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client/flat_client.py.jinja +0 -0
  71. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/client_file.py.jinja +0 -0
  72. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/app_models.py.jinja +0 -0
  73. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/enums.py.jinja +0 -0
  74. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/models/models.py.jinja +0 -0
  75. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/utils/logger.py.jinja +0 -0
  76. /django_cfg/modules/django_client/core/generator/{templates/python → python/templates}/utils/schema.py.jinja +0 -0
  77. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/app_index.ts.jinja +0 -0
  78. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/flat_client.ts.jinja +0 -0
  79. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client/operation.ts.jinja +0 -0
  80. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/client_file.ts.jinja +0 -0
  81. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/index.ts.jinja +0 -0
  82. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/app_models.ts.jinja +0 -0
  83. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/enums.ts.jinja +0 -0
  84. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/models/models.ts.jinja +0 -0
  85. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/http.ts.jinja +0 -0
  86. /django_cfg/modules/django_client/core/generator/{templates/typescript → typescript/templates}/utils/schema.ts.jinja +0 -0
  87. /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
  88. /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
  89. /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
  90. /django_cfg/{dashboard → modules/django_dashboard}/management/__init__.py +0 -0
  91. /django_cfg/{dashboard → modules/django_dashboard}/management/commands/__init__.py +0 -0
  92. /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
  93. /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
  94. /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
  95. /django_cfg/{dashboard → modules/django_dashboard}/sections/documentation.py +0 -0
  96. /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
  97. /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
  98. /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
  99. {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/WHEEL +0 -0
  100. {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/entry_points.txt +0 -0
  101. {django_cfg-1.4.11.dist-info → django_cfg-1.4.13.dist-info}/licenses/LICENSE +0 -0
@@ -152,9 +152,10 @@ class APIFrameworksGenerator:
152
152
  },
153
153
  "COMPONENT_SPLIT_REQUEST": True,
154
154
  "COMPONENT_SPLIT_PATCH": True,
155
- # Auto-fix enum naming collisions
155
+ # Postprocessing hooks
156
156
  "POSTPROCESSING_HOOKS": [
157
157
  "django_cfg.modules.django_client.spectacular.auto_fix_enum_names",
158
+ "django_cfg.modules.django_client.spectacular.mark_async_operations",
158
159
  ],
159
160
  }
160
161
 
@@ -8,11 +8,11 @@ This replaces django-revolution with a cleaner, faster, type-safe implementation
8
8
  """
9
9
 
10
10
  from typing import Dict, Any, Optional
11
- from pydantic import BaseModel, Field
11
+ from pydantic import Field
12
12
  from django_cfg.modules.django_client.core.config import OpenAPIConfig, OpenAPIGroupConfig
13
13
 
14
14
 
15
- class ExtendedOpenAPIConfig(OpenAPIConfig):
15
+ class OpenAPIClientConfig(OpenAPIConfig):
16
16
  """
17
17
  Extended OpenAPI configuration with DRF parameters for django-cfg integration.
18
18
 
@@ -21,9 +21,9 @@ class ExtendedOpenAPIConfig(OpenAPIConfig):
21
21
 
22
22
  Example:
23
23
  ```python
24
- from django_cfg import ExtendedOpenAPIConfig, OpenAPIGroupConfig
24
+ from django_cfg import OpenAPIClientConfig, OpenAPIGroupConfig
25
25
 
26
- config = ExtendedOpenAPIConfig(
26
+ config = OpenAPIClientConfig(
27
27
  enabled=True,
28
28
  groups=[
29
29
  OpenAPIGroupConfig(
@@ -153,86 +153,10 @@ class ExtendedOpenAPIConfig(OpenAPIConfig):
153
153
  description="All django-cfg built-in applications",
154
154
  )
155
155
 
156
- # Skip individual cfg_* groups - use unified 'cfg' instead
157
156
  return groups_dict
158
157
 
159
- # Add Support group if enabled
160
- if support_enabled and 'cfg_support' not in groups_dict:
161
- groups_dict['cfg_support'] = OpenAPIGroupConfig(
162
- name="cfg_support",
163
- apps=["django_cfg.apps.support"],
164
- title="Support API",
165
- description="Support tickets and messages API",
166
- )
167
-
168
- # Add Accounts group if enabled
169
- if accounts_enabled and 'cfg_accounts' not in groups_dict:
170
- groups_dict['cfg_accounts'] = OpenAPIGroupConfig(
171
- name="cfg_accounts",
172
- apps=["django_cfg.apps.accounts"],
173
- title="Accounts API",
174
- description="User management, OTP, profiles, and activity tracking API",
175
- )
176
-
177
- # Add Newsletter group if enabled
178
- if newsletter_enabled and 'cfg_newsletter' not in groups_dict:
179
- groups_dict['cfg_newsletter'] = OpenAPIGroupConfig(
180
- name="cfg_newsletter",
181
- apps=["django_cfg.apps.newsletter"],
182
- title="Newsletter API",
183
- description="Email campaigns, subscriptions, and newsletter management API",
184
- )
185
-
186
- # Add Leads group if enabled
187
- if leads_enabled and 'cfg_leads' not in groups_dict:
188
- groups_dict['cfg_leads'] = OpenAPIGroupConfig(
189
- name="cfg_leads",
190
- apps=["django_cfg.apps.leads"],
191
- title="Leads API",
192
- description="Lead collection, contact forms, and CRM integration API",
193
- )
194
-
195
- # Add Knowbase group if enabled
196
- if knowbase_enabled and 'cfg_knowbase' not in groups_dict:
197
- groups_dict['cfg_knowbase'] = OpenAPIGroupConfig(
198
- name="cfg_knowbase",
199
- apps=["django_cfg.apps.knowbase"],
200
- title="Knowbase API",
201
- description="Knowledge base, AI chat, embeddings, and search API",
202
- )
203
-
204
- # Add Agents group if enabled
205
- if agents_enabled and 'cfg_agents' not in groups_dict:
206
- groups_dict['cfg_agents'] = OpenAPIGroupConfig(
207
- name="cfg_agents",
208
- apps=["django_cfg.apps.agents"],
209
- title="Agents API",
210
- description="Agent definitions, executions, workflows, and tools API",
211
- )
212
-
213
- # Add Tasks group if enabled
214
- if tasks_enabled and 'cfg_tasks' not in groups_dict:
215
- groups_dict['cfg_tasks'] = OpenAPIGroupConfig(
216
- name="cfg_tasks",
217
- apps=["django_cfg.apps.tasks"],
218
- title="Tasks API",
219
- description="Tasks, workflows, and automation API",
220
- )
221
-
222
- # Add Payments group if enabled
223
- if payments_enabled and 'cfg_payments' not in groups_dict:
224
- groups_dict['cfg_payments'] = OpenAPIGroupConfig(
225
- name="cfg_payments",
226
- apps=["django_cfg.apps.payments"],
227
- title="Payments API",
228
- description="Payments, subscriptions, and billing API",
229
- )
230
-
231
158
  except Exception:
232
159
  pass
233
160
 
234
161
  return groups_dict
235
162
 
236
-
237
- # Alias for easier import
238
- OpenAPIClientConfig = ExtendedOpenAPIConfig
@@ -57,12 +57,12 @@ class ArchiveManager:
57
57
 
58
58
  if python_dir and python_dir.exists():
59
59
  dest = archive_path / "python"
60
- shutil.copytree(python_dir, dest)
60
+ shutil.copytree(python_dir, dest, dirs_exist_ok=True)
61
61
  copied["python"] = str(dest)
62
62
 
63
63
  if typescript_dir and typescript_dir.exists():
64
64
  dest = archive_path / "typescript"
65
- shutil.copytree(typescript_dir, dest)
65
+ shutil.copytree(typescript_dir, dest, dirs_exist_ok=True)
66
66
  copied["typescript"] = str(dest)
67
67
 
68
68
  # Create metadata
@@ -76,6 +76,26 @@ class OpenAPIConfig(BaseModel):
76
76
  description="Generate TypeScript client",
77
77
  )
78
78
 
79
+ generate_package_files: bool = Field(
80
+ default=False,
81
+ description="Generate package.json (TypeScript) and pyproject.toml (Python)",
82
+ )
83
+
84
+ generate_zod_schemas: bool = Field(
85
+ default=False,
86
+ description="Generate Zod schemas for runtime validation (TypeScript only)",
87
+ )
88
+
89
+ generate_fetchers: bool = Field(
90
+ default=False,
91
+ description="Generate typed fetcher functions (TypeScript only, requires Zod schemas)",
92
+ )
93
+
94
+ generate_swr_hooks: bool = Field(
95
+ default=False,
96
+ description="Generate SWR hooks for React (TypeScript only, requires fetchers)",
97
+ )
98
+
79
99
  client_structure: Literal["flat", "namespaced"] = Field(
80
100
  default="namespaced",
81
101
  description=(
@@ -59,7 +59,7 @@ class DjangoOpenAPI:
59
59
  if not self.config:
60
60
  return {}
61
61
 
62
- # Use get_groups_with_defaults if available (ExtendedOpenAPIConfig)
62
+ # Use get_groups_with_defaults if available (OpenAPIClientConfig)
63
63
  if hasattr(self.config, 'get_groups_with_defaults'):
64
64
  return self.config.get_groups_with_defaults()
65
65
 
@@ -22,10 +22,10 @@ Usage:
22
22
  from pathlib import Path
23
23
  from typing import Literal
24
24
 
25
- from django_cfg.modules.django_client.core.generator.base import GeneratedFile
26
- from django_cfg.modules.django_client.core.generator.python import PythonGenerator
27
- from django_cfg.modules.django_client.core.generator.typescript import TypeScriptGenerator
28
- from django_cfg.modules.django_client.core.ir import IRContext
25
+ from .base import GeneratedFile
26
+ from .python import PythonGenerator
27
+ from .typescript import TypeScriptGenerator
28
+ from ..ir import IRContext
29
29
 
30
30
  __all__ = [
31
31
  "PythonGenerator",
@@ -48,6 +48,11 @@ class BaseGenerator(ABC):
48
48
  client_structure: str = "namespaced",
49
49
  openapi_schema: dict | None = None,
50
50
  tag_prefix: str = "",
51
+ package_config: dict | None = None,
52
+ generate_package_files: bool = False,
53
+ generate_zod_schemas: bool = False,
54
+ generate_fetchers: bool = False,
55
+ generate_swr_hooks: bool = False,
51
56
  ):
52
57
  """
53
58
  Initialize generator with IR context.
@@ -57,11 +62,21 @@ class BaseGenerator(ABC):
57
62
  client_structure: Client structure ("flat" or "namespaced")
58
63
  openapi_schema: OpenAPI schema dict (for embedding in client)
59
64
  tag_prefix: Prefix to add to all tag names (e.g., "cfg_")
65
+ package_config: Package configuration (name, version, author, etc.)
66
+ generate_package_files: Whether to generate package.json/pyproject.toml
67
+ generate_zod_schemas: Whether to generate Zod schemas (TypeScript only)
68
+ generate_fetchers: Whether to generate typed fetchers (TypeScript only)
69
+ generate_swr_hooks: Whether to generate SWR hooks (TypeScript only, React)
60
70
  """
61
71
  self.context = context
62
72
  self.client_structure = client_structure
63
73
  self.openapi_schema = openapi_schema
64
74
  self.tag_prefix = tag_prefix
75
+ self.package_config = package_config or {}
76
+ self.generate_package_files = generate_package_files
77
+ self.generate_zod_schemas = generate_zod_schemas
78
+ self.generate_fetchers = generate_fetchers
79
+ self.generate_swr_hooks = generate_swr_hooks
65
80
 
66
81
  # ===== Namespaced Structure Helpers =====
67
82
 
@@ -765,3 +780,59 @@ class BaseGenerator(ABC):
765
780
  lines.append(" ".join(current_line))
766
781
 
767
782
  return lines
783
+
784
+ def format_enum_description(self, text: str) -> str:
785
+ """
786
+ Format enum description by splitting bullet points.
787
+
788
+ Enum descriptions from OpenAPI often have the format:
789
+ "* `value1` - Desc1 * `value2` - Desc2"
790
+
791
+ This method splits them into separate lines:
792
+ "* `value1` - Desc1\n* `value2` - Desc2"
793
+
794
+ Args:
795
+ text: Enum description text
796
+
797
+ Returns:
798
+ Formatted description with proper line breaks
799
+ """
800
+ if not text:
801
+ return text
802
+
803
+ # Split by " * `" pattern (preserving the first *)
804
+ import re
805
+ # Replace " * `" with newline + "* `"
806
+ formatted = re.sub(r'\s+\*\s+`', '\n* `', text.strip())
807
+
808
+ return formatted
809
+
810
+ def sanitize_enum_name(self, name: str) -> str:
811
+ """
812
+ Sanitize enum name by converting to PascalCase.
813
+
814
+ Examples:
815
+ "OrderDetail.status" -> "OrderDetailStatus"
816
+ "Currency.currency_type" -> "CurrencyCurrencyType"
817
+ "CurrencyList.currency_type" -> "CurrencyListCurrencyType"
818
+ "User.role" -> "UserRole"
819
+
820
+ Args:
821
+ name: Original enum name (may contain dots, underscores)
822
+
823
+ Returns:
824
+ Sanitized PascalCase name
825
+ """
826
+ # Replace dots with underscores, then split and convert to PascalCase
827
+ parts = name.replace('.', '_').split('_')
828
+ result = []
829
+ for word in parts:
830
+ if not word:
831
+ continue
832
+ # If word is already PascalCase/camelCase, keep it as is
833
+ # Otherwise capitalize first letter only
834
+ if word[0].isupper():
835
+ result.append(word)
836
+ else:
837
+ result.append(word[0].upper() + word[1:] if len(word) > 1 else word.upper())
838
+ return ''.join(result)
@@ -0,0 +1,16 @@
1
+ """
2
+ Python Generator - Generates Python client (Pydantic 2 + httpx).
3
+
4
+ This generator creates a complete Python API client from IR:
5
+ - Pydantic 2 models (Request/Response/Patch splits)
6
+ - Enum classes from x-enum-varnames
7
+ - httpx.AsyncClient for async HTTP
8
+ - Django CSRF/session handling
9
+ - Type-safe (MyPy strict mode compatible)
10
+
11
+ Reference: https://docs.pydantic.dev/latest/
12
+ """
13
+
14
+ from .generator import PythonGenerator
15
+
16
+ __all__ = ['PythonGenerator']
@@ -0,0 +1,174 @@
1
+ """
2
+ Async Client Generator - Generates async Python clients.
3
+
4
+ Handles:
5
+ - Main APIClient class (httpx.AsyncClient)
6
+ - Sub-client classes (per tag/app)
7
+ - Flat vs namespaced structures
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from jinja2 import Environment
13
+
14
+ from ..base import GeneratedFile
15
+ from ...ir import IROperationObject
16
+
17
+
18
+ class AsyncClientGenerator:
19
+ """Generates async Python client files."""
20
+
21
+ def __init__(self, jinja_env: Environment, context, base_generator, operations_gen):
22
+ """
23
+ Initialize async client generator.
24
+
25
+ Args:
26
+ jinja_env: Jinja2 environment for templates
27
+ context: Generation context
28
+ base_generator: Reference to base generator
29
+ operations_gen: Operations generator instance
30
+ """
31
+ self.jinja_env = jinja_env
32
+ self.context = context
33
+ self.base = base_generator
34
+ self.operations_gen = operations_gen
35
+
36
+ def generate_client_file(self) -> GeneratedFile:
37
+ """Generate client.py with AsyncClient."""
38
+ # Client class
39
+ client_code = self._generate_client_class()
40
+
41
+ template = self.jinja_env.get_template('client_file.py.jinja')
42
+ content = template.render(
43
+ has_enums=bool(self.base.get_enum_schemas()),
44
+ client_code=client_code
45
+ )
46
+
47
+ return GeneratedFile(
48
+ path="client.py",
49
+ content=content,
50
+ description="AsyncClient with httpx",
51
+ )
52
+
53
+ def _generate_client_class(self) -> str:
54
+ """Generate APIClient class."""
55
+ if self.base.client_structure == "namespaced":
56
+ return self._generate_namespaced_client()
57
+ else:
58
+ return self._generate_flat_client()
59
+
60
+ def _generate_flat_client(self) -> str:
61
+ """Generate flat APIClient (all methods in one class)."""
62
+ # Generate all operation methods
63
+ method_codes = []
64
+ for op_id, operation in self.context.operations.items():
65
+ method_codes.append(self.operations_gen.generate_async_operation(operation))
66
+
67
+ template = self.jinja_env.get_template('client/flat_client.py.jinja')
68
+ return template.render(
69
+ api_title=self.context.openapi_info.title,
70
+ operations=method_codes
71
+ )
72
+
73
+ def _generate_namespaced_client(self) -> str:
74
+ """Generate namespaced APIClient (sub-clients per tag)."""
75
+ # Group operations by tag (using base class method)
76
+ ops_by_tag = self.base.group_operations_by_tag()
77
+
78
+ # Generate sub-client classes
79
+ sub_client_classes = []
80
+ for tag, operations in sorted(ops_by_tag.items()):
81
+ sub_client_classes.append(self._generate_sub_client_class(tag, operations))
82
+
83
+ sub_clients_code = "\n\n\n".join(sub_client_classes)
84
+
85
+ # Generate main APIClient
86
+ main_client_code = self._generate_main_client_class(ops_by_tag)
87
+
88
+ return f"{sub_clients_code}\n\n\n{main_client_code}"
89
+
90
+ def _generate_sub_client_class(self, tag: str, operations: list) -> str:
91
+ """Generate sub-client class for a specific tag."""
92
+ class_name = self.base.tag_to_class_name(tag)
93
+
94
+ # Generate methods for this tag
95
+ method_codes = []
96
+ for operation in operations:
97
+ method_codes.append(self.operations_gen.generate_async_operation(operation, remove_tag_prefix=True))
98
+
99
+ template = self.jinja_env.get_template('client/sub_client.py.jinja')
100
+ return template.render(
101
+ tag=self.base.tag_to_display_name(tag),
102
+ class_name=class_name,
103
+ operations=method_codes
104
+ )
105
+
106
+ def _generate_main_client_class(self, ops_by_tag: dict) -> str:
107
+ """Generate main APIClient with sub-clients."""
108
+ tags = sorted(ops_by_tag.keys())
109
+
110
+ # Prepare tags data for template
111
+ tags_data = [
112
+ {
113
+ "class_name": self.base.tag_to_class_name(tag),
114
+ "property": self.base.tag_to_property_name(tag),
115
+ }
116
+ for tag in tags
117
+ ]
118
+
119
+ template = self.jinja_env.get_template('client/main_client.py.jinja')
120
+ return template.render(
121
+ api_title=self.context.openapi_info.title,
122
+ tags=tags_data
123
+ )
124
+
125
+ def generate_app_client_file(self, tag: str, operations: list[IROperationObject]) -> GeneratedFile:
126
+ """Generate client.py for a specific app."""
127
+ class_name = self.base.tag_to_class_name(tag)
128
+
129
+ # Generate methods
130
+ method_codes = []
131
+ for operation in operations:
132
+ method_codes.append(self.operations_gen.generate_async_operation(operation, remove_tag_prefix=True))
133
+
134
+ template = self.jinja_env.get_template('client/app_client.py.jinja')
135
+ content = template.render(
136
+ tag=self.base.tag_to_display_name(tag),
137
+ class_name=class_name,
138
+ operations=method_codes
139
+ )
140
+
141
+ folder_name = self.base.tag_and_app_to_folder_name(tag, operations)
142
+ return GeneratedFile(
143
+ path=f"{folder_name}/client.py",
144
+ content=content,
145
+ description=f"API client for {tag}",
146
+ )
147
+
148
+ def generate_main_client_file(self, ops_by_tag: dict) -> GeneratedFile:
149
+ """Generate main client.py with APIClient."""
150
+ tags = sorted(ops_by_tag.keys())
151
+
152
+ # Prepare tags data for template
153
+ tags_data = [
154
+ {
155
+ "class_name": self.base.tag_to_class_name(tag),
156
+ "slug": self.base.tag_and_app_to_folder_name(tag, ops_by_tag[tag]),
157
+ }
158
+ for tag in tags
159
+ ]
160
+
161
+ # Generate main APIClient class
162
+ client_code = self._generate_main_client_class(ops_by_tag)
163
+
164
+ template = self.jinja_env.get_template('client/main_client_file.py.jinja')
165
+ content = template.render(
166
+ tags=tags_data,
167
+ client_code=client_code
168
+ )
169
+
170
+ return GeneratedFile(
171
+ path="client.py",
172
+ content=content,
173
+ description="Main API client",
174
+ )
@@ -0,0 +1,180 @@
1
+ """
2
+ Files Generator - Generates auxiliary files (__init__.py, logger.py, schema.py).
3
+
4
+ Handles:
5
+ - Package __init__.py files
6
+ - Logger configuration
7
+ - OpenAPI schema embedding
8
+ - API wrapper classes
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import re
15
+
16
+ from jinja2 import Environment
17
+
18
+ from ..base import GeneratedFile
19
+ from ...ir import IROperationObject
20
+
21
+
22
+ class FilesGenerator:
23
+ """Generates auxiliary Python files."""
24
+
25
+ def __init__(self, jinja_env: Environment, context, base_generator):
26
+ """
27
+ Initialize files generator.
28
+
29
+ Args:
30
+ jinja_env: Jinja2 environment for templates
31
+ context: Generation context
32
+ base_generator: Reference to base generator
33
+ """
34
+ self.jinja_env = jinja_env
35
+ self.context = context
36
+ self.base = base_generator
37
+
38
+ def generate_init_file(self) -> GeneratedFile:
39
+ """Generate __init__.py with exports (flat structure)."""
40
+ template = self.jinja_env.get_template('__init__.py.jinja')
41
+ content = template.render(
42
+ has_enums=bool(self.base.get_enum_schemas())
43
+ )
44
+
45
+ return GeneratedFile(
46
+ path="__init__.py",
47
+ content=content,
48
+ description="Package exports",
49
+ )
50
+
51
+ def generate_app_init_file(self, tag: str, operations: list[IROperationObject]) -> GeneratedFile:
52
+ """Generate __init__.py for a specific app."""
53
+ class_name = self.base.tag_to_class_name(tag)
54
+
55
+ template = self.jinja_env.get_template('app_init.py.jinja')
56
+ content = template.render(class_name=class_name)
57
+
58
+ folder_name = self.base.tag_and_app_to_folder_name(tag, operations)
59
+ return GeneratedFile(
60
+ path=f"{folder_name}/__init__.py",
61
+ content=content,
62
+ description=f"Package exports for {tag}",
63
+ )
64
+
65
+ def generate_main_init_file(self) -> GeneratedFile:
66
+ """Generate main __init__.py with API class and JWT management."""
67
+ ops_by_tag = self.base.group_operations_by_tag()
68
+ tags = sorted(ops_by_tag.keys())
69
+
70
+ # Prepare tags data for template
71
+ tags_data = [
72
+ {
73
+ "class_name": self.base.tag_to_class_name(tag),
74
+ "slug": self.base.tag_and_app_to_folder_name(tag, ops_by_tag[tag]),
75
+ }
76
+ for tag in tags
77
+ ]
78
+
79
+ # Check if we have enums
80
+ all_schemas = self.context.schemas
81
+ all_enums = self.base._collect_enums_from_schemas(all_schemas)
82
+
83
+ # API class
84
+ api_class = self.generate_api_wrapper_class_python(tags)
85
+
86
+ template = self.jinja_env.get_template('main_init.py.jinja')
87
+ content = template.render(
88
+ api_title=self.context.openapi_info.title,
89
+ tags=tags_data,
90
+ has_enums=bool(all_enums),
91
+ enum_names=sorted(all_enums.keys()) if all_enums else [],
92
+ api_class=api_class
93
+ )
94
+
95
+ return GeneratedFile(
96
+ path="__init__.py",
97
+ content=content,
98
+ description="Package exports with API class and JWT management",
99
+ )
100
+
101
+ def generate_api_wrapper_class_python(self, tags: list[str]) -> str:
102
+ """Generate API wrapper class with JWT management for Python."""
103
+ # Prepare property data
104
+ properties_data = []
105
+ for tag in tags:
106
+ properties_data.append({
107
+ "tag": tag,
108
+ "class_name": self.base.tag_to_class_name(tag),
109
+ "property": self.base.tag_to_property_name(tag),
110
+ })
111
+
112
+ template = self.jinja_env.get_template('api_wrapper.py.jinja')
113
+ return template.render(properties=properties_data)
114
+
115
+ def generate_logger_file(self) -> GeneratedFile:
116
+ """Generate logger.py with Rich integration."""
117
+ template = self.jinja_env.get_template('utils/logger.py.jinja')
118
+ content = template.render()
119
+
120
+ return GeneratedFile(
121
+ path="logger.py",
122
+ content=content,
123
+ description="API Logger with Rich",
124
+ )
125
+
126
+ def generate_retry_file(self) -> GeneratedFile:
127
+ """Generate retry.py with tenacity integration."""
128
+ template = self.jinja_env.get_template('utils/retry.py.jinja')
129
+ content = template.render()
130
+
131
+ return GeneratedFile(
132
+ path="retry.py",
133
+ content=content,
134
+ description="Retry utilities with tenacity",
135
+ )
136
+
137
+ def generate_pyproject_toml_file(self, package_config: dict = None) -> GeneratedFile:
138
+ """Generate pyproject.toml for Poetry/PyPI publishing."""
139
+ if package_config is None:
140
+ package_config = {}
141
+
142
+ # Default configuration
143
+ defaults = {
144
+ "package_name": package_config.get("name", "api-client"),
145
+ "version": package_config.get("version", "1.0.0"),
146
+ "description": package_config.get("description") or f"Auto-generated Python client for {self.context.openapi_info.title}",
147
+ "authors": package_config.get("authors", ["Author <author@example.com>"]),
148
+ "license": package_config.get("license", "MIT"),
149
+ "repository_url": package_config.get("repository_url"),
150
+ "keywords": package_config.get("keywords", ["api", "client", "python", "openapi"]),
151
+ "python_version": package_config.get("python_version", "^3.12"),
152
+ }
153
+
154
+ template = self.jinja_env.get_template('pyproject.toml.jinja')
155
+ content = template.render(**defaults)
156
+
157
+ return GeneratedFile(
158
+ path="pyproject.toml",
159
+ content=content,
160
+ description="Poetry package configuration",
161
+ )
162
+
163
+ def generate_schema_file(self, openapi_schema: dict) -> GeneratedFile:
164
+ """Generate schema.py with OpenAPI schema as dict."""
165
+ # First, convert to pretty JSON
166
+ schema_json = json.dumps(openapi_schema, indent=4, ensure_ascii=False)
167
+
168
+ # Convert JSON literals to Python literals
169
+ schema_json = re.sub(r'\btrue\b', 'True', schema_json)
170
+ schema_json = re.sub(r'\bfalse\b', 'False', schema_json)
171
+ schema_json = re.sub(r'\bnull\b', 'None', schema_json)
172
+
173
+ template = self.jinja_env.get_template('utils/schema.py.jinja')
174
+ content = template.render(schema_dict=schema_json)
175
+
176
+ return GeneratedFile(
177
+ path="schema.py",
178
+ content=content,
179
+ description="OpenAPI Schema",
180
+ )