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.
Files changed (225) hide show
  1. django_cfg/apps/agents/management/commands/create_agent.py +1 -1
  2. django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
  3. django_cfg/apps/newsletter/serializers.py +40 -3
  4. django_cfg/apps/newsletter/views/campaigns.py +12 -3
  5. django_cfg/apps/newsletter/views/emails.py +14 -3
  6. django_cfg/apps/newsletter/views/subscriptions.py +12 -2
  7. django_cfg/apps/payments/views/api/currencies.py +49 -6
  8. django_cfg/apps/payments/views/api/webhooks.py +72 -7
  9. django_cfg/apps/payments/views/overview/serializers.py +34 -1
  10. django_cfg/apps/payments/views/overview/views.py +2 -1
  11. django_cfg/apps/payments/views/serializers/payments.py +6 -6
  12. django_cfg/apps/urls.py +106 -45
  13. django_cfg/core/base/config_model.py +2 -2
  14. django_cfg/core/constants.py +1 -1
  15. django_cfg/core/generation/integration_generators/__init__.py +1 -1
  16. django_cfg/core/generation/integration_generators/api.py +73 -49
  17. django_cfg/core/integration/display/startup.py +30 -22
  18. django_cfg/core/integration/url_integration.py +15 -16
  19. django_cfg/management/commands/check_endpoints.py +11 -160
  20. django_cfg/management/commands/check_settings.py +13 -348
  21. django_cfg/management/commands/clear_constance.py +13 -201
  22. django_cfg/management/commands/create_token.py +13 -321
  23. django_cfg/management/commands/generate_clients.py +23 -0
  24. django_cfg/management/commands/list_urls.py +13 -306
  25. django_cfg/management/commands/migrate_all.py +13 -126
  26. django_cfg/management/commands/migrator.py +13 -396
  27. django_cfg/management/commands/rundramatiq.py +15 -247
  28. django_cfg/management/commands/rundramatiq_simulator.py +12 -429
  29. django_cfg/management/commands/runserver_ngrok.py +15 -160
  30. django_cfg/management/commands/script.py +12 -488
  31. django_cfg/management/commands/show_config.py +12 -215
  32. django_cfg/management/commands/show_urls.py +12 -342
  33. django_cfg/management/commands/superuser.py +15 -295
  34. django_cfg/management/commands/task_clear.py +14 -217
  35. django_cfg/management/commands/task_status.py +13 -248
  36. django_cfg/management/commands/test_email.py +15 -86
  37. django_cfg/management/commands/test_telegram.py +14 -61
  38. django_cfg/management/commands/test_twilio.py +15 -105
  39. django_cfg/management/commands/tree.py +13 -383
  40. django_cfg/management/commands/validate_openapi.py +10 -0
  41. django_cfg/middleware/README.md +1 -1
  42. django_cfg/middleware/user_activity.py +3 -3
  43. django_cfg/models/__init__.py +2 -2
  44. django_cfg/models/api/drf/spectacular.py +6 -6
  45. django_cfg/models/django/__init__.py +2 -2
  46. django_cfg/models/django/openapi.py +162 -0
  47. django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
  48. django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
  49. django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
  50. django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
  51. django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
  52. django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
  53. django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
  54. django_cfg/modules/django_admin/management/commands/script.py +496 -0
  55. django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
  56. django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
  57. django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
  58. django_cfg/modules/django_admin/management/commands/tree.py +390 -0
  59. django_cfg/modules/django_client/__init__.py +20 -0
  60. django_cfg/modules/django_client/apps.py +35 -0
  61. django_cfg/modules/django_client/core/__init__.py +56 -0
  62. django_cfg/modules/django_client/core/archive/__init__.py +11 -0
  63. django_cfg/modules/django_client/core/archive/manager.py +134 -0
  64. django_cfg/modules/django_client/core/cli/__init__.py +12 -0
  65. django_cfg/modules/django_client/core/cli/main.py +235 -0
  66. django_cfg/modules/django_client/core/config/__init__.py +18 -0
  67. django_cfg/modules/django_client/core/config/config.py +208 -0
  68. django_cfg/modules/django_client/core/config/group.py +101 -0
  69. django_cfg/modules/django_client/core/config/service.py +209 -0
  70. django_cfg/modules/django_client/core/generator/__init__.py +115 -0
  71. django_cfg/modules/django_client/core/generator/base.py +838 -0
  72. django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
  73. django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
  74. django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
  75. django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
  76. django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
  77. django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
  78. django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
  79. django_cfg/modules/django_client/core/generator/python/templates/__init__.py.jinja +9 -0
  80. django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +153 -0
  81. django_cfg/modules/django_client/core/generator/python/templates/app_init.py.jinja +6 -0
  82. django_cfg/modules/django_client/core/generator/python/templates/client/app_client.py.jinja +18 -0
  83. django_cfg/modules/django_client/core/generator/python/templates/client/flat_client.py.jinja +38 -0
  84. django_cfg/modules/django_client/core/generator/python/templates/client/main_client.py.jinja +68 -0
  85. django_cfg/modules/django_client/core/generator/python/templates/client/main_client_file.py.jinja +14 -0
  86. django_cfg/modules/django_client/core/generator/python/templates/client/operation_method.py.jinja +9 -0
  87. django_cfg/modules/django_client/core/generator/python/templates/client/sub_client.py.jinja +18 -0
  88. django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
  89. django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
  90. django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
  91. django_cfg/modules/django_client/core/generator/python/templates/client_file.py.jinja +13 -0
  92. django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +52 -0
  93. django_cfg/modules/django_client/core/generator/python/templates/models/app_models.py.jinja +17 -0
  94. django_cfg/modules/django_client/core/generator/python/templates/models/enum_class.py.jinja +17 -0
  95. django_cfg/modules/django_client/core/generator/python/templates/models/enums.py.jinja +8 -0
  96. django_cfg/modules/django_client/core/generator/python/templates/models/models.py.jinja +17 -0
  97. django_cfg/modules/django_client/core/generator/python/templates/models/schema_class.py.jinja +21 -0
  98. django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
  99. django_cfg/modules/django_client/core/generator/python/templates/utils/logger.py.jinja +255 -0
  100. django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
  101. django_cfg/modules/django_client/core/generator/python/templates/utils/schema.py.jinja +12 -0
  102. django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
  103. django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
  104. django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
  105. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
  106. django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
  107. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +536 -0
  108. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
  109. django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
  110. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
  111. django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
  112. django_cfg/modules/django_client/core/generator/typescript/templates/app_index.ts.jinja +2 -0
  113. django_cfg/modules/django_client/core/generator/typescript/templates/client/app_client.ts.jinja +18 -0
  114. django_cfg/modules/django_client/core/generator/typescript/templates/client/client.ts.jinja +403 -0
  115. django_cfg/modules/django_client/core/generator/typescript/templates/client/flat_client.ts.jinja +109 -0
  116. django_cfg/modules/django_client/core/generator/typescript/templates/client/main_client_file.ts.jinja +10 -0
  117. django_cfg/modules/django_client/core/generator/typescript/templates/client/operation.ts.jinja +61 -0
  118. django_cfg/modules/django_client/core/generator/typescript/templates/client/sub_client.ts.jinja +15 -0
  119. django_cfg/modules/django_client/core/generator/typescript/templates/client_file.ts.jinja +9 -0
  120. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
  121. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
  122. django_cfg/modules/django_client/core/generator/typescript/templates/index.ts.jinja +5 -0
  123. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +268 -0
  124. django_cfg/modules/django_client/core/generator/typescript/templates/models/app_models.ts.jinja +8 -0
  125. django_cfg/modules/django_client/core/generator/typescript/templates/models/enums.ts.jinja +4 -0
  126. django_cfg/modules/django_client/core/generator/typescript/templates/models/models.ts.jinja +8 -0
  127. django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
  128. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
  129. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
  130. django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
  131. django_cfg/modules/django_client/core/generator/typescript/templates/utils/errors.ts.jinja +116 -0
  132. django_cfg/modules/django_client/core/generator/typescript/templates/utils/http.ts.jinja +98 -0
  133. django_cfg/modules/django_client/core/generator/typescript/templates/utils/logger.ts.jinja +259 -0
  134. django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
  135. django_cfg/modules/django_client/core/generator/typescript/templates/utils/schema.ts.jinja +7 -0
  136. django_cfg/modules/django_client/core/generator/typescript/templates/utils/storage.ts.jinja +158 -0
  137. django_cfg/modules/django_client/core/groups/__init__.py +13 -0
  138. django_cfg/modules/django_client/core/groups/detector.py +178 -0
  139. django_cfg/modules/django_client/core/groups/manager.py +314 -0
  140. django_cfg/modules/django_client/core/ir/__init__.py +57 -0
  141. django_cfg/modules/django_client/core/ir/context.py +387 -0
  142. django_cfg/modules/django_client/core/ir/operation.py +518 -0
  143. django_cfg/modules/django_client/core/ir/schema.py +353 -0
  144. django_cfg/modules/django_client/core/parser/__init__.py +74 -0
  145. django_cfg/modules/django_client/core/parser/base.py +648 -0
  146. django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
  147. django_cfg/modules/django_client/core/parser/models/base.py +212 -0
  148. django_cfg/modules/django_client/core/parser/models/components.py +160 -0
  149. django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
  150. django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
  151. django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
  152. django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
  153. django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
  154. django_cfg/modules/django_client/core/validation/__init__.py +22 -0
  155. django_cfg/modules/django_client/core/validation/checker.py +134 -0
  156. django_cfg/modules/django_client/core/validation/fixer.py +216 -0
  157. django_cfg/modules/django_client/core/validation/reporter.py +480 -0
  158. django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
  159. django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
  160. django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
  161. django_cfg/modules/django_client/core/validation/safety.py +266 -0
  162. django_cfg/modules/django_client/management/__init__.py +3 -0
  163. django_cfg/modules/django_client/management/commands/__init__.py +3 -0
  164. django_cfg/modules/django_client/management/commands/generate_client.py +427 -0
  165. django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
  166. django_cfg/modules/django_client/pytest.ini +30 -0
  167. django_cfg/modules/django_client/spectacular/__init__.py +10 -0
  168. django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
  169. django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
  170. django_cfg/modules/django_client/urls.py +72 -0
  171. django_cfg/{dashboard → modules/django_dashboard}/DEBUG_README.md +2 -2
  172. django_cfg/{dashboard → modules/django_dashboard}/REFACTORING_SUMMARY.md +1 -1
  173. django_cfg/modules/django_dashboard/management/__init__.py +0 -0
  174. django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
  175. django_cfg/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
  176. django_cfg/modules/django_dashboard/sections/documentation.py +391 -0
  177. django_cfg/modules/django_email/management/__init__.py +0 -0
  178. django_cfg/modules/django_email/management/commands/__init__.py +0 -0
  179. django_cfg/modules/django_email/management/commands/test_email.py +93 -0
  180. django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
  181. django_cfg/modules/django_logging/django_logger.py +6 -6
  182. django_cfg/modules/django_ngrok/management/__init__.py +0 -0
  183. django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
  184. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
  185. django_cfg/modules/django_tasks/management/__init__.py +0 -0
  186. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  187. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
  188. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
  189. django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
  190. django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
  191. django_cfg/modules/django_telegram/management/__init__.py +0 -0
  192. django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
  193. django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
  194. django_cfg/modules/django_twilio/management/__init__.py +0 -0
  195. django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
  196. django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
  197. django_cfg/modules/django_unfold/callbacks/main.py +21 -10
  198. django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
  199. django_cfg/pyproject.toml +2 -6
  200. django_cfg/registry/third_party.py +5 -7
  201. django_cfg/routing/callbacks.py +1 -1
  202. django_cfg/static/admin/css/prose-unfold.css +666 -0
  203. django_cfg/templates/admin/index.html +8 -0
  204. django_cfg/templates/admin/index_new.html +13 -0
  205. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
  206. django_cfg/templates/admin/sections/documentation_section.html +172 -0
  207. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
  208. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/METADATA +2 -2
  209. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/RECORD +224 -74
  210. django_cfg/management/commands/generate.py +0 -107
  211. /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
  212. /django_cfg/{dashboard → modules/django_admin}/management/__init__.py +0 -0
  213. /django_cfg/{dashboard → modules/django_admin}/management/commands/__init__.py +0 -0
  214. /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
  215. /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
  216. /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
  217. /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
  218. /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
  219. /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
  220. /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
  221. /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
  222. /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
  223. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/WHEEL +0 -0
  224. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/entry_points.txt +0 -0
  225. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,536 @@
1
+ """
2
+ SWR Hooks Generator - Generates React hooks for data fetching.
3
+
4
+ This generator creates SWR-based React hooks from IR:
5
+ - Query hooks (GET operations) using useSWR
6
+ - Mutation hooks (POST/PUT/PATCH/DELETE) using useSWRConfig
7
+ - Automatic key generation
8
+ - Type-safe parameters and responses
9
+ - Optimistic updates support
10
+
11
+ Architecture:
12
+ - Query hooks: useSWR with automatic key management
13
+ - Mutation hooks: Custom hooks with revalidation
14
+ - Works only in React client components
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from jinja2 import Environment
20
+ from ..base import BaseGenerator, GeneratedFile
21
+ from ...ir import IROperationObject, IRContext
22
+
23
+
24
+ class HooksGenerator:
25
+ """
26
+ SWR hooks generator for React.
27
+
28
+ Generates:
29
+ - useResource() hooks for GET operations
30
+ - useCreateResource() hooks for POST
31
+ - useUpdateResource() hooks for PUT/PATCH
32
+ - useDeleteResource() hooks for DELETE
33
+ """
34
+
35
+ def __init__(self, jinja_env: Environment, context: IRContext, base: BaseGenerator):
36
+ self.jinja_env = jinja_env
37
+ self.context = context
38
+ self.base = base
39
+
40
+ def generate_query_hook(self, operation: IROperationObject) -> str:
41
+ """
42
+ Generate useSWR hook for GET operation.
43
+
44
+ Examples:
45
+ >>> generate_query_hook(users_list)
46
+ export function useShopProducts(params?: { page?: number }) {
47
+ return useSWR(
48
+ params ? ['shop-products', params] : 'shop-products',
49
+ () => Fetchers.getShopProducts(params)
50
+ )
51
+ }
52
+ """
53
+ # Get hook name
54
+ hook_name = self._operation_to_hook_name(operation)
55
+
56
+ # Get fetcher function name
57
+ fetcher_name = self._operation_to_fetcher_name(operation)
58
+
59
+ # Get parameters
60
+ param_info = self._get_param_info(operation)
61
+
62
+ # Get response type
63
+ response_type = self._get_response_type(operation)
64
+
65
+ # Get SWR key
66
+ swr_key = self._generate_swr_key(operation)
67
+
68
+ # Build hook
69
+ lines = []
70
+
71
+ # JSDoc
72
+ lines.append("/**")
73
+ if operation.summary:
74
+ lines.append(f" * {operation.summary}")
75
+ lines.append(" *")
76
+ lines.append(f" * @method {operation.http_method}")
77
+ lines.append(f" * @path {operation.path}")
78
+ lines.append(" */")
79
+
80
+ # Hook signature
81
+ if param_info['func_params']:
82
+ lines.append(f"export function {hook_name}({param_info['func_params']}) {{")
83
+ else:
84
+ lines.append(f"export function {hook_name}() {{")
85
+
86
+ # useSWR call
87
+ fetcher_params = param_info['fetcher_params']
88
+ if fetcher_params:
89
+ lines.append(f" return useSWR<{response_type}>(")
90
+ lines.append(f" {swr_key},")
91
+ lines.append(f" () => Fetchers.{fetcher_name}({fetcher_params})")
92
+ lines.append(" )")
93
+ else:
94
+ lines.append(f" return useSWR<{response_type}>(")
95
+ lines.append(f" {swr_key},")
96
+ lines.append(f" () => Fetchers.{fetcher_name}()")
97
+ lines.append(" )")
98
+
99
+ lines.append("}")
100
+
101
+ return "\n".join(lines)
102
+
103
+ def generate_mutation_hook(self, operation: IROperationObject) -> str:
104
+ """
105
+ Generate mutation hook for POST/PUT/PATCH/DELETE.
106
+
107
+ Examples:
108
+ >>> generate_mutation_hook(users_create)
109
+ export function useCreateShopProduct() {
110
+ const { mutate } = useSWRConfig()
111
+
112
+ return async (data: ProductCreateRequest) => {
113
+ const result = await Fetchers.createShopProduct(data)
114
+ mutate('shop-products')
115
+ return result
116
+ }
117
+ }
118
+ """
119
+ # Get hook name
120
+ hook_name = self._operation_to_hook_name(operation)
121
+
122
+ # Get fetcher function name
123
+ fetcher_name = self._operation_to_fetcher_name(operation)
124
+
125
+ # Get parameters
126
+ param_info = self._get_param_info(operation)
127
+
128
+ # Get response type
129
+ response_type = self._get_response_type(operation)
130
+
131
+ # Get revalidation keys
132
+ revalidation_keys = self._get_revalidation_keys(operation)
133
+
134
+ # Build hook
135
+ lines = []
136
+
137
+ # JSDoc
138
+ lines.append("/**")
139
+ if operation.summary:
140
+ lines.append(f" * {operation.summary}")
141
+ lines.append(" *")
142
+ lines.append(f" * @method {operation.http_method}")
143
+ lines.append(f" * @path {operation.path}")
144
+ lines.append(" */")
145
+
146
+ # Hook signature
147
+ lines.append(f"export function {hook_name}() {{")
148
+ lines.append(" const { mutate } = useSWRConfig()")
149
+ lines.append("")
150
+
151
+ # Return async function
152
+ if param_info['func_params']:
153
+ lines.append(f" return async ({param_info['func_params']}): Promise<{response_type}> => {{")
154
+ else:
155
+ lines.append(f" return async (): Promise<{response_type}> => {{")
156
+
157
+ # Call fetcher
158
+ fetcher_params = param_info['fetcher_params']
159
+ if fetcher_params:
160
+ lines.append(f" const result = await Fetchers.{fetcher_name}({fetcher_params})")
161
+ else:
162
+ lines.append(f" const result = await Fetchers.{fetcher_name}()")
163
+
164
+ # Revalidate
165
+ if revalidation_keys:
166
+ lines.append("")
167
+ lines.append(" // Revalidate related queries")
168
+ for key in revalidation_keys:
169
+ lines.append(f" mutate('{key}')")
170
+
171
+ lines.append("")
172
+ lines.append(" return result")
173
+ lines.append(" }")
174
+ lines.append("}")
175
+
176
+ return "\n".join(lines)
177
+
178
+ def _operation_to_hook_name(self, operation: IROperationObject) -> str:
179
+ """
180
+ Convert operation to hook name.
181
+
182
+ Examples:
183
+ users_list (GET) -> useUsers
184
+ users_retrieve (GET) -> useUser
185
+ users_create (POST) -> useCreateUser
186
+ users_update (PUT) -> useUpdateUser
187
+ users_partial_update (PATCH) -> useUpdateUser
188
+ users_destroy (DELETE) -> useDeleteUser
189
+ """
190
+ op_id = operation.operation_id
191
+
192
+ if op_id.endswith("_list"):
193
+ resource = op_id.replace("_list", "")
194
+ # Plural form
195
+ return f"use{self._to_pascal_case(resource)}"
196
+ elif op_id.endswith("_retrieve"):
197
+ resource = op_id.replace("_retrieve", "")
198
+ # Singular form (remove trailing 's')
199
+ resource_singular = resource.rstrip('s') if resource.endswith('s') and len(resource) > 1 else resource
200
+ return f"use{self._to_pascal_case(resource_singular)}"
201
+ elif op_id.endswith("_create"):
202
+ resource = op_id.removesuffix("_create")
203
+ return f"useCreate{self._to_pascal_case(resource)}"
204
+ elif op_id.endswith("_partial_update"):
205
+ resource = op_id.removesuffix("_partial_update")
206
+ return f"usePartialUpdate{self._to_pascal_case(resource)}"
207
+ elif op_id.endswith("_update"):
208
+ resource = op_id.removesuffix("_update")
209
+ return f"useUpdate{self._to_pascal_case(resource)}"
210
+ elif op_id.endswith("_destroy"):
211
+ resource = op_id.removesuffix("_destroy")
212
+ return f"useDelete{self._to_pascal_case(resource)}"
213
+ else:
214
+ # Custom action
215
+ return f"use{self._to_pascal_case(op_id)}"
216
+
217
+ def _operation_to_fetcher_name(self, operation: IROperationObject) -> str:
218
+ """Get corresponding fetcher function name."""
219
+ op_id = operation.operation_id
220
+
221
+ # Remove only suffix, not all occurrences (same logic as fetchers_generator)
222
+ if op_id.endswith("_list"):
223
+ resource = op_id.removesuffix("_list")
224
+ return f"get{self._to_pascal_case(resource)}"
225
+ elif op_id.endswith("_retrieve"):
226
+ resource = op_id.removesuffix("_retrieve")
227
+ # Singular
228
+ resource_singular = resource.rstrip('s') if resource.endswith('s') else resource
229
+ return f"get{self._to_pascal_case(resource_singular)}"
230
+ elif op_id.endswith("_create"):
231
+ resource = op_id.removesuffix("_create")
232
+ return f"create{self._to_pascal_case(resource)}"
233
+ elif op_id.endswith("_partial_update"):
234
+ resource = op_id.removesuffix("_partial_update")
235
+ return f"partialUpdate{self._to_pascal_case(resource)}"
236
+ elif op_id.endswith("_update"):
237
+ resource = op_id.removesuffix("_update")
238
+ return f"update{self._to_pascal_case(resource)}"
239
+ elif op_id.endswith("_destroy"):
240
+ resource = op_id.removesuffix("_destroy")
241
+ return f"delete{self._to_pascal_case(resource)}"
242
+ else:
243
+ return f"{operation.http_method.lower()}{self._to_pascal_case(op_id)}"
244
+
245
+ def _get_param_info(self, operation: IROperationObject) -> dict:
246
+ """
247
+ Get parameter info for hook.
248
+
249
+ Returns:
250
+ {
251
+ 'func_params': Function parameters for hook signature
252
+ 'fetcher_params': Parameters to pass to fetcher
253
+ }
254
+ """
255
+ func_params = []
256
+ fetcher_params = []
257
+
258
+ # Path parameters
259
+ if operation.path_parameters:
260
+ for param in operation.path_parameters:
261
+ param_type = self._map_param_type(param.schema_type)
262
+ func_params.append(f"{param.name}: {param_type}")
263
+ fetcher_params.append(param.name)
264
+
265
+ # Query parameters
266
+ if operation.query_parameters:
267
+ query_fields = []
268
+ all_required = all(param.required for param in operation.query_parameters)
269
+
270
+ for param in operation.query_parameters:
271
+ param_type = self._map_param_type(param.schema_type)
272
+ optional = "?" if not param.required else ""
273
+ query_fields.append(f"{param.name}{optional}: {param_type}")
274
+
275
+ if query_fields:
276
+ params_optional = "" if all_required else "?"
277
+ func_params.append(f"params{params_optional}: {{ {'; '.join(query_fields)} }}")
278
+ fetcher_params.append("params")
279
+
280
+ # Request body
281
+ if operation.request_body:
282
+ schema_name = operation.request_body.schema_name
283
+ # Use schema only if it exists as a component (not inline)
284
+ if schema_name and schema_name in self.context.schemas:
285
+ body_type = schema_name
286
+ else:
287
+ body_type = "any"
288
+ func_params.append(f"data: {body_type}")
289
+ fetcher_params.append("data")
290
+
291
+ return {
292
+ 'func_params': ", ".join(func_params) if func_params else "",
293
+ 'fetcher_params': ", ".join(fetcher_params) if fetcher_params else ""
294
+ }
295
+
296
+ def _map_param_type(self, param_type: str) -> str:
297
+ """Map OpenAPI param type to TypeScript type."""
298
+ type_map = {
299
+ "integer": "number",
300
+ "number": "number",
301
+ "string": "string",
302
+ "boolean": "boolean",
303
+ "array": "any[]",
304
+ "object": "any",
305
+ }
306
+ return type_map.get(param_type, "any")
307
+
308
+ def _get_response_type(self, operation: IROperationObject) -> str:
309
+ """Get response type for hook."""
310
+ # Get 2xx response
311
+ for status_code in [200, 201, 202, 204]:
312
+ if status_code in operation.responses:
313
+ response = operation.responses[status_code]
314
+ if response.schema_name:
315
+ return response.schema_name
316
+
317
+ # No response or void
318
+ if 204 in operation.responses or operation.http_method == "DELETE":
319
+ return "void"
320
+
321
+ return "any"
322
+
323
+ def _generate_swr_key(self, operation: IROperationObject) -> str:
324
+ """
325
+ Generate SWR key for query.
326
+
327
+ Examples:
328
+ GET /products/ -> 'shop-products'
329
+ GET /products/{id}/ -> ['shop-product', id]
330
+ GET /products/?category=5 -> ['shop-products', params]
331
+ """
332
+ # Get resource name from operation_id
333
+ op_id = operation.operation_id
334
+
335
+ # Determine if list or detail
336
+ is_list = op_id.endswith("_list")
337
+ is_detail = op_id.endswith("_retrieve")
338
+
339
+ # Remove common suffixes
340
+ resource = op_id.replace("_list", "").replace("_retrieve", "")
341
+
342
+ # For detail views, use singular form
343
+ if is_detail:
344
+ resource = resource.rstrip('s') if resource.endswith('s') and len(resource) > 1 else resource
345
+
346
+ # Convert to kebab-case
347
+ key_base = resource.replace("_", "-")
348
+
349
+ # Check if has path params or query params
350
+ has_path_params = bool(operation.path_parameters)
351
+ has_query_params = bool(operation.query_parameters)
352
+
353
+ if has_path_params:
354
+ # Single resource: ['shop-product', id]
355
+ param_name = operation.path_parameters[0].name
356
+ return f"['{key_base}', {param_name}]"
357
+ elif has_query_params:
358
+ # List with params: params ? ['shop-products', params] : 'shop-products'
359
+ return f"params ? ['{key_base}', params] : '{key_base}'"
360
+ else:
361
+ # Simple key: 'shop-products'
362
+ return f"'{key_base}'"
363
+
364
+ def _get_revalidation_keys(self, operation: IROperationObject) -> list[str]:
365
+ """
366
+ Get SWR keys that should be revalidated after mutation.
367
+
368
+ Examples:
369
+ POST /products/ -> ['shop-products']
370
+ PUT /products/{id}/ -> ['shop-products', 'shop-product']
371
+ DELETE /products/{id}/ -> ['shop-products']
372
+ """
373
+ keys = []
374
+
375
+ op_id = operation.operation_id
376
+ resource = op_id.replace("_create", "").replace("_update", "").replace("_partial_update", "").replace("_destroy", "")
377
+
378
+ # List key (for revalidating lists)
379
+ list_key = f"{resource.replace('_', '-')}"
380
+ keys.append(list_key)
381
+
382
+ # Detail key (for update/delete operations)
383
+ if operation.http_method in ("PUT", "PATCH", "DELETE"):
384
+ detail_key = f"{resource.replace('_', '-').rstrip('s')}"
385
+ if detail_key != list_key:
386
+ keys.append(detail_key)
387
+
388
+ return keys
389
+
390
+ def _to_pascal_case(self, snake_str: str) -> str:
391
+ """Convert snake_case to PascalCase."""
392
+ return ''.join(word.capitalize() for word in snake_str.split('_'))
393
+
394
+ def generate_tag_hooks_file(
395
+ self,
396
+ tag: str,
397
+ operations: list[IROperationObject],
398
+ ) -> GeneratedFile:
399
+ """
400
+ Generate hooks file for a specific tag/resource.
401
+
402
+ Args:
403
+ tag: Tag name (e.g., "shop_products")
404
+ operations: List of operations for this tag
405
+
406
+ Returns:
407
+ GeneratedFile with hooks
408
+ """
409
+ # Separate queries and mutations & collect schema names
410
+ query_hooks = []
411
+ mutation_hooks = []
412
+ schema_names = set()
413
+
414
+ for operation in operations:
415
+ # Collect schemas used in this operation (only if they exist as components)
416
+ if operation.request_body and operation.request_body.schema_name:
417
+ if operation.request_body.schema_name in self.context.schemas:
418
+ schema_names.add(operation.request_body.schema_name)
419
+ if operation.patch_request_body and operation.patch_request_body.schema_name:
420
+ if operation.patch_request_body.schema_name in self.context.schemas:
421
+ schema_names.add(operation.patch_request_body.schema_name)
422
+
423
+ # Get response schema
424
+ response = operation.primary_success_response
425
+ if response and response.schema_name:
426
+ schema_names.add(response.schema_name)
427
+
428
+ # Generate hook
429
+ if operation.http_method == "GET":
430
+ query_hooks.append(self.generate_query_hook(operation))
431
+ else:
432
+ mutation_hooks.append(self.generate_mutation_hook(operation))
433
+
434
+ # Get display name for documentation
435
+ tag_display_name = self.base.tag_to_display_name(tag)
436
+
437
+ # Build file content
438
+ lines = []
439
+
440
+ # Header
441
+ lines.append("/**")
442
+ lines.append(f" * SWR Hooks for {tag_display_name}")
443
+ lines.append(" *")
444
+ lines.append(" * Auto-generated React hooks for data fetching with SWR.")
445
+ lines.append(" *")
446
+ lines.append(" * Setup:")
447
+ lines.append(" * ```typescript")
448
+ lines.append(" * // Configure API once (in your app root)")
449
+ lines.append(" * import { configureAPI } from '../../api-instance'")
450
+ lines.append(" * configureAPI({ baseUrl: 'https://api.example.com' })")
451
+ lines.append(" * ```")
452
+ lines.append(" *")
453
+ lines.append(" * Usage:")
454
+ lines.append(" * ```typescript")
455
+ lines.append(" * // Query hook")
456
+ lines.append(" * const { data, error, mutate } = useShopProducts({ page: 1 })")
457
+ lines.append(" *")
458
+ lines.append(" * // Mutation hook")
459
+ lines.append(" * const createProduct = useCreateShopProduct()")
460
+ lines.append(" * await createProduct({ name: 'Product', price: 99 })")
461
+ lines.append(" * ```")
462
+ lines.append(" */")
463
+
464
+ # Import types from schemas
465
+ for schema_name in sorted(schema_names):
466
+ lines.append(f"import type {{ {schema_name} }} from '../schemas/{schema_name}.schema'")
467
+
468
+ lines.append("import useSWR from 'swr'")
469
+ lines.append("import { useSWRConfig } from 'swr'")
470
+ lines.append("import * as Fetchers from '../fetchers'")
471
+ lines.append("")
472
+
473
+ # Query hooks
474
+ if query_hooks:
475
+ lines.append("// ===== Query Hooks (GET) =====")
476
+ lines.append("")
477
+ for hook in query_hooks:
478
+ lines.append(hook)
479
+ lines.append("")
480
+
481
+ # Mutation hooks
482
+ if mutation_hooks:
483
+ lines.append("// ===== Mutation Hooks (POST/PUT/PATCH/DELETE) =====")
484
+ lines.append("")
485
+ for hook in mutation_hooks:
486
+ lines.append(hook)
487
+ lines.append("")
488
+
489
+ content = "\n".join(lines)
490
+
491
+ # Get file path (use same naming as APIClient)
492
+ folder_name = self.base.tag_and_app_to_folder_name(tag, operations)
493
+ file_path = f"_utils/hooks/{folder_name}.ts"
494
+
495
+ return GeneratedFile(
496
+ path=file_path,
497
+ content=content,
498
+ description=f"SWR hooks for {tag_display_name}",
499
+ )
500
+
501
+ def generate_hooks_index_file(self, module_names: list[str]) -> GeneratedFile:
502
+ """Generate index.ts for hooks folder."""
503
+ lines = []
504
+
505
+ lines.append("/**")
506
+ lines.append(" * SWR Hooks - React hooks for data fetching")
507
+ lines.append(" *")
508
+ lines.append(" * Auto-generated from OpenAPI specification.")
509
+ lines.append(" * These hooks use SWR for data fetching and caching.")
510
+ lines.append(" *")
511
+ lines.append(" * Usage:")
512
+ lines.append(" * ```typescript")
513
+ lines.append(" * import { useShopProducts } from './_utils/hooks'")
514
+ lines.append(" *")
515
+ lines.append(" * function ProductsPage() {")
516
+ lines.append(" * const { data, error } = useShopProducts({ page: 1 })")
517
+ lines.append(" * if (error) return <Error />")
518
+ lines.append(" * if (!data) return <Loading />")
519
+ lines.append(" * return <ProductList products={data.results} />")
520
+ lines.append(" * }")
521
+ lines.append(" * ```")
522
+ lines.append(" */")
523
+ lines.append("")
524
+
525
+ for module_name in module_names:
526
+ lines.append(f"export * from './{module_name}'")
527
+
528
+ lines.append("")
529
+
530
+ content = "\n".join(lines)
531
+
532
+ return GeneratedFile(
533
+ path="_utils/hooks/index.ts",
534
+ content=content,
535
+ description="Index file for SWR hooks",
536
+ )