django-cfg 1.4.61__py3-none-any.whl → 1.4.63__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.

Potentially problematic release.


This version of django-cfg might be problematic. Click here for more details.

Files changed (179) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/services/otp_service.py +3 -14
  3. django_cfg/apps/centrifugo/__init__.py +57 -0
  4. django_cfg/apps/centrifugo/admin/__init__.py +13 -0
  5. django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
  6. django_cfg/apps/centrifugo/admin/config.py +82 -0
  7. django_cfg/apps/centrifugo/apps.py +31 -0
  8. django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
  9. django_cfg/apps/centrifugo/codegen/README.md +242 -0
  10. django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
  11. django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
  12. django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
  13. django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
  14. django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
  15. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
  16. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
  17. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
  18. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
  19. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
  20. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
  21. django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
  22. django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
  23. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
  24. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
  25. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
  26. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
  27. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
  28. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
  29. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
  30. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
  31. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
  32. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
  33. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
  34. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
  35. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
  36. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
  37. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
  38. django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
  39. django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
  40. django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
  41. django_cfg/apps/centrifugo/decorators.py +137 -0
  42. django_cfg/apps/centrifugo/management/__init__.py +1 -0
  43. django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
  44. django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
  45. django_cfg/apps/centrifugo/managers/__init__.py +12 -0
  46. django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
  47. django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
  48. django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
  49. django_cfg/apps/centrifugo/models/__init__.py +11 -0
  50. django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
  51. django_cfg/apps/centrifugo/registry.py +106 -0
  52. django_cfg/apps/centrifugo/router.py +125 -0
  53. django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
  54. django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
  55. django_cfg/apps/centrifugo/serializers/channels.py +26 -0
  56. django_cfg/apps/centrifugo/serializers/health.py +17 -0
  57. django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
  58. django_cfg/apps/centrifugo/serializers/stats.py +21 -0
  59. django_cfg/apps/centrifugo/services/__init__.py +12 -0
  60. django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
  61. django_cfg/apps/centrifugo/services/client/client.py +577 -0
  62. django_cfg/apps/centrifugo/services/client/config.py +228 -0
  63. django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
  64. django_cfg/apps/centrifugo/services/config_helper.py +63 -0
  65. django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
  66. django_cfg/apps/centrifugo/services/logging.py +677 -0
  67. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
  68. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
  69. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
  70. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
  71. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
  72. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
  73. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
  74. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
  75. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
  76. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
  77. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
  78. django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
  79. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
  80. django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
  81. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
  82. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
  83. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
  84. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
  85. django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
  86. django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
  87. django_cfg/apps/centrifugo/urls.py +31 -0
  88. django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
  89. django_cfg/apps/centrifugo/views/__init__.py +15 -0
  90. django_cfg/apps/centrifugo/views/admin_api.py +374 -0
  91. django_cfg/apps/centrifugo/views/dashboard.py +15 -0
  92. django_cfg/apps/centrifugo/views/monitoring.py +286 -0
  93. django_cfg/apps/centrifugo/views/testing_api.py +422 -0
  94. django_cfg/apps/support/utils/support_email_service.py +5 -18
  95. django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
  96. django_cfg/apps/urls.py +5 -5
  97. django_cfg/core/base/config_model.py +4 -44
  98. django_cfg/core/builders/apps_builder.py +2 -2
  99. django_cfg/core/generation/integration_generators/third_party.py +8 -8
  100. django_cfg/core/utils/__init__.py +5 -0
  101. django_cfg/core/utils/url_helpers.py +73 -0
  102. django_cfg/modules/base.py +7 -7
  103. django_cfg/modules/django_client/core/__init__.py +2 -1
  104. django_cfg/modules/django_client/core/config/config.py +8 -0
  105. django_cfg/modules/django_client/core/generator/__init__.py +42 -2
  106. django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
  107. django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
  108. django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
  109. django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
  110. django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
  111. django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
  112. django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
  113. django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
  114. django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
  115. django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
  116. django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
  117. django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
  118. django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
  119. django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
  120. django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
  121. django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
  122. django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
  123. django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
  124. django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
  125. django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
  126. django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
  127. django_cfg/modules/django_client/system/schema_parser.py +5 -1
  128. django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
  129. django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
  130. django_cfg/modules/django_unfold/dashboard.py +25 -19
  131. django_cfg/pyproject.toml +1 -1
  132. django_cfg/registry/core.py +2 -0
  133. django_cfg/registry/modules.py +2 -2
  134. django_cfg/static/js/api/centrifugo/client.mjs +164 -0
  135. django_cfg/static/js/api/centrifugo/index.mjs +13 -0
  136. django_cfg/static/js/api/index.mjs +5 -5
  137. django_cfg/static/js/api/types.mjs +89 -26
  138. {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/METADATA +1 -1
  139. {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/RECORD +142 -68
  140. django_cfg/apps/ipc/README.md +0 -346
  141. django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
  142. django_cfg/apps/ipc/TESTING.md +0 -539
  143. django_cfg/apps/ipc/__init__.py +0 -60
  144. django_cfg/apps/ipc/admin.py +0 -212
  145. django_cfg/apps/ipc/apps.py +0 -28
  146. django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
  147. django_cfg/apps/ipc/migrations/__init__.py +0 -0
  148. django_cfg/apps/ipc/models.py +0 -221
  149. django_cfg/apps/ipc/serializers/__init__.py +0 -29
  150. django_cfg/apps/ipc/serializers/serializers.py +0 -343
  151. django_cfg/apps/ipc/services/__init__.py +0 -7
  152. django_cfg/apps/ipc/services/client/__init__.py +0 -23
  153. django_cfg/apps/ipc/services/client/client.py +0 -621
  154. django_cfg/apps/ipc/services/client/config.py +0 -214
  155. django_cfg/apps/ipc/services/client/exceptions.py +0 -201
  156. django_cfg/apps/ipc/services/logging.py +0 -239
  157. django_cfg/apps/ipc/services/monitor.py +0 -466
  158. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
  159. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
  160. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
  161. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
  162. django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
  163. django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
  164. django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
  165. django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
  166. django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
  167. django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
  168. django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
  169. django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
  170. django_cfg/apps/ipc/urls.py +0 -23
  171. django_cfg/apps/ipc/views/__init__.py +0 -13
  172. django_cfg/apps/ipc/views/dashboard.py +0 -15
  173. django_cfg/apps/ipc/views/monitoring.py +0 -251
  174. django_cfg/apps/ipc/views/testing.py +0 -285
  175. django_cfg/static/js/api/ipc/client.mjs +0 -114
  176. django_cfg/static/js/api/ipc/index.mjs +0 -13
  177. {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/WHEEL +0 -0
  178. {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/entry_points.txt +0 -0
  179. {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,349 @@
1
+ """
2
+ Type conversion utilities for code generation.
3
+
4
+ Converts Pydantic models to TypeScript interfaces and Python type hints.
5
+ Adapted from unrealon-openapi/generators/common/utils.py
6
+ """
7
+
8
+ import logging
9
+ from typing import Any, Dict, Type, List, get_args, get_origin
10
+ from pydantic import BaseModel
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def convert_json_schema_to_typescript(field_info: Dict[str, Any]) -> str:
16
+ """
17
+ Convert JSON schema field to TypeScript type.
18
+
19
+ Args:
20
+ field_info: JSON schema field information
21
+
22
+ Returns:
23
+ str: TypeScript type string
24
+ """
25
+ # Handle anyOf (union types)
26
+ if "anyOf" in field_info:
27
+ types = [convert_json_schema_to_typescript(t) for t in field_info["anyOf"]]
28
+ return " | ".join(types)
29
+
30
+ field_type = field_info.get("type", "any")
31
+
32
+ if field_type == "string":
33
+ return "string"
34
+ elif field_type == "integer":
35
+ return "number"
36
+ elif field_type == "number":
37
+ return "number"
38
+ elif field_type == "boolean":
39
+ return "boolean"
40
+ elif field_type == "array":
41
+ items = field_info.get("items", {})
42
+ item_type = convert_json_schema_to_typescript(items)
43
+ return f"{item_type}[]"
44
+ elif field_type == "object":
45
+ return "Record<string, any>"
46
+ elif field_type == "null":
47
+ return "null"
48
+ else:
49
+ return "any"
50
+
51
+
52
+ def convert_json_schema_to_python(field_info: Dict[str, Any]) -> str:
53
+ """
54
+ Convert JSON schema field to Python type.
55
+
56
+ Args:
57
+ field_info: JSON schema field information
58
+
59
+ Returns:
60
+ str: Python type string
61
+ """
62
+ field_type = field_info.get("type", "any")
63
+
64
+ if field_type == "string":
65
+ return "str"
66
+ elif field_type == "integer":
67
+ return "int"
68
+ elif field_type == "number":
69
+ return "float"
70
+ elif field_type == "boolean":
71
+ return "bool"
72
+ elif field_type == "array":
73
+ items = field_info.get("items", {})
74
+ item_type = convert_json_schema_to_python(items)
75
+ return f"List[{item_type}]"
76
+ elif field_type == "object":
77
+ return "Dict[str, Any]"
78
+ elif field_type == "null":
79
+ return "None"
80
+ else:
81
+ return "Any"
82
+
83
+
84
+ def pydantic_to_typescript(model: Type[BaseModel]) -> str:
85
+ """
86
+ Convert Pydantic model to TypeScript interface.
87
+
88
+ Args:
89
+ model: Pydantic model class
90
+
91
+ Returns:
92
+ str: TypeScript interface definition
93
+ """
94
+ if not issubclass(model, BaseModel):
95
+ return "any"
96
+
97
+ try:
98
+ schema = model.model_json_schema()
99
+ properties = schema.get('properties', {})
100
+ required = schema.get('required', [])
101
+
102
+ ts_fields = []
103
+ for field_name, field_info in properties.items():
104
+ ts_type = convert_json_schema_to_typescript(field_info)
105
+ optional = '?' if field_name not in required else ''
106
+
107
+ # Add description as comment if available
108
+ description = field_info.get('description')
109
+ if description:
110
+ ts_fields.append(f" /** {description} */")
111
+
112
+ ts_fields.append(f" {field_name}{optional}: {ts_type};")
113
+
114
+ interface_code = f"export interface {model.__name__} {{\n"
115
+ interface_code += "\n".join(ts_fields)
116
+ interface_code += "\n}"
117
+
118
+ return interface_code
119
+
120
+ except Exception as e:
121
+ logger.error(f"Failed to convert {model.__name__} to TypeScript: {e}")
122
+ return f"export interface {model.__name__} {{ [key: string]: any; }}"
123
+
124
+
125
+ def pydantic_to_python(model: Type[BaseModel]) -> str:
126
+ """
127
+ Convert Pydantic model to Python class definition.
128
+
129
+ Args:
130
+ model: Pydantic model class
131
+
132
+ Returns:
133
+ str: Python class definition
134
+ """
135
+ if not issubclass(model, BaseModel):
136
+ return "Any"
137
+
138
+ try:
139
+ schema = model.model_json_schema()
140
+ properties = schema.get('properties', {})
141
+ required = schema.get('required', [])
142
+
143
+ py_fields = []
144
+ for field_name, field_info in properties.items():
145
+ py_type = convert_json_schema_to_python(field_info)
146
+
147
+ # Add description as docstring comment
148
+ description = field_info.get('description')
149
+ if description:
150
+ py_fields.append(f' """{description}"""')
151
+
152
+ if field_name in required:
153
+ py_fields.append(f" {field_name}: {py_type}")
154
+ else:
155
+ py_fields.append(f" {field_name}: Optional[{py_type}] = None")
156
+
157
+ doc = model.__doc__ or f"{model.__name__} model"
158
+
159
+ class_code = f"class {model.__name__}(BaseModel):\n"
160
+ class_code += f' """{doc}"""\n\n'
161
+ class_code += "\n".join(py_fields) if py_fields else " pass"
162
+
163
+ return class_code
164
+
165
+ except Exception as e:
166
+ logger.error(f"Failed to convert {model.__name__} to Python: {e}")
167
+ return f"class {model.__name__}(BaseModel):\n pass"
168
+
169
+
170
+ def generate_typescript_types(models: List[Type[BaseModel]]) -> str:
171
+ """
172
+ Generate TypeScript type definitions for multiple Pydantic models.
173
+
174
+ Args:
175
+ models: List of Pydantic model classes
176
+
177
+ Returns:
178
+ str: Complete TypeScript type definitions
179
+ """
180
+ lines = []
181
+ lines.append("// Generated TypeScript Types")
182
+ lines.append("// Auto-generated from Pydantic models - DO NOT EDIT")
183
+ lines.append("")
184
+
185
+ for model in models:
186
+ interface = pydantic_to_typescript(model)
187
+ lines.append(interface)
188
+ lines.append("")
189
+
190
+ return "\n".join(lines)
191
+
192
+
193
+ def generate_python_types(models: List[Type[BaseModel]]) -> str:
194
+ """
195
+ Generate Python type definitions for multiple Pydantic models.
196
+
197
+ Args:
198
+ models: List of Pydantic model classes
199
+
200
+ Returns:
201
+ str: Complete Python type definitions
202
+ """
203
+ lines = []
204
+ lines.append('"""Generated Python Types"""')
205
+ lines.append('"""Auto-generated from Pydantic models - DO NOT EDIT"""')
206
+ lines.append("")
207
+ lines.append("from typing import Optional, List, Dict, Any")
208
+ lines.append("from pydantic import BaseModel")
209
+ lines.append("")
210
+
211
+ for model in models:
212
+ class_def = pydantic_to_python(model)
213
+ lines.append(class_def)
214
+ lines.append("")
215
+
216
+ return "\n".join(lines)
217
+
218
+
219
+ def convert_json_schema_to_go(field_info: Dict[str, Any]) -> str:
220
+ """
221
+ Convert JSON schema field to Go type.
222
+
223
+ Args:
224
+ field_info: JSON schema field information
225
+
226
+ Returns:
227
+ str: Go type string
228
+ """
229
+ # Handle anyOf (union types) - Go doesn't have union types, use interface{}
230
+ if "anyOf" in field_info:
231
+ return "interface{}"
232
+
233
+ field_type = field_info.get("type", "any")
234
+
235
+ if field_type == "string":
236
+ return "string"
237
+ elif field_type == "integer":
238
+ return "int64"
239
+ elif field_type == "number":
240
+ return "float64"
241
+ elif field_type == "boolean":
242
+ return "bool"
243
+ elif field_type == "array":
244
+ items = field_info.get("items", {})
245
+ item_type = convert_json_schema_to_go(items)
246
+ return f"[]{item_type}"
247
+ elif field_type == "object":
248
+ return "map[string]interface{}"
249
+ elif field_type == "null":
250
+ return "interface{}"
251
+ else:
252
+ return "interface{}"
253
+
254
+
255
+ def pydantic_to_go(model: Type[BaseModel]) -> Dict[str, Any]:
256
+ """
257
+ Convert Pydantic model to Go struct definition.
258
+
259
+ Args:
260
+ model: Pydantic model class
261
+
262
+ Returns:
263
+ dict: Go struct information with name, fields, and doc
264
+ """
265
+ if not issubclass(model, BaseModel):
266
+ return {
267
+ "name": "UnknownStruct",
268
+ "fields": [],
269
+ "doc": "",
270
+ }
271
+
272
+ try:
273
+ schema = model.model_json_schema()
274
+ properties = schema.get('properties', {})
275
+ required = schema.get('required', [])
276
+
277
+ fields = []
278
+ for field_name, field_info in properties.items():
279
+ go_type = convert_json_schema_to_go(field_info)
280
+
281
+ # Convert snake_case to PascalCase for Go field names
282
+ go_field_name = ''.join(word.capitalize() for word in field_name.split('_'))
283
+
284
+ # Pointer types for optional fields
285
+ is_optional = field_name not in required
286
+ if is_optional and go_type not in ["interface{}", "map[string]interface{}"]:
287
+ go_type = f"*{go_type}"
288
+
289
+ # JSON tag
290
+ json_tag = f'`json:"{field_name}"`'
291
+
292
+ # Description
293
+ description = field_info.get('description', '')
294
+
295
+ fields.append({
296
+ "name": go_field_name,
297
+ "type": go_type,
298
+ "json_tag": json_tag,
299
+ "description": description,
300
+ })
301
+
302
+ doc = model.__doc__ or f"{model.__name__} struct"
303
+
304
+ return {
305
+ "name": model.__name__,
306
+ "fields": fields,
307
+ "doc": doc,
308
+ }
309
+
310
+ except Exception as e:
311
+ logger.error(f"Failed to convert {model.__name__} to Go: {e}")
312
+ return {
313
+ "name": model.__name__,
314
+ "fields": [],
315
+ "doc": f"{model.__name__} struct",
316
+ }
317
+
318
+
319
+ def generate_go_types(models: List[Type[BaseModel]]) -> str:
320
+ """
321
+ Generate Go type definitions for multiple Pydantic models.
322
+
323
+ Args:
324
+ models: List of Pydantic model classes
325
+
326
+ Returns:
327
+ str: Complete Go type definitions
328
+ """
329
+ lines = []
330
+ lines.append("// Generated Go Types")
331
+ lines.append("// Auto-generated from Pydantic models - DO NOT EDIT")
332
+ lines.append("")
333
+
334
+ for model in models:
335
+ struct_info = pydantic_to_go(model)
336
+
337
+ # Add doc comment
338
+ lines.append(f"// {struct_info['doc']}")
339
+ lines.append(f"type {struct_info['name']} struct {{")
340
+
341
+ for field in struct_info['fields']:
342
+ if field['description']:
343
+ lines.append(f"\t// {field['description']}")
344
+ lines.append(f"\t{field['name']} {field['type']} {field['json_tag']}")
345
+
346
+ lines.append("}")
347
+ lines.append("")
348
+
349
+ return "\n".join(lines)
@@ -0,0 +1,137 @@
1
+ """
2
+ Decorators for Centrifugo RPC handlers.
3
+ """
4
+
5
+ import logging
6
+ import inspect
7
+ from typing import Callable, Optional, Type, get_type_hints
8
+ from pydantic import BaseModel
9
+
10
+ from .router import get_global_router
11
+ from .registry import get_global_registry
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def websocket_rpc(method_name: str):
17
+ """
18
+ Decorator to register WebSocket RPC handler.
19
+
20
+ Registers handler with both MessageRouter (for runtime)
21
+ and RPCRegistry (for code generation).
22
+
23
+ Args:
24
+ method_name: RPC method name (e.g., "tasks.get_stats")
25
+
26
+ Returns:
27
+ Decorator function
28
+
29
+ Example:
30
+ >>> from pydantic import BaseModel
31
+ >>>
32
+ >>> class TaskStatsParams(BaseModel):
33
+ ... date_from: str
34
+ ... date_to: str
35
+ >>>
36
+ >>> class TaskStatsResult(BaseModel):
37
+ ... total: int
38
+ ... completed: int
39
+ >>>
40
+ >>> @websocket_rpc("tasks.get_stats")
41
+ >>> async def get_stats(conn, params: TaskStatsParams) -> TaskStatsResult:
42
+ ... # Business logic here
43
+ ... return TaskStatsResult(total=100, completed=50)
44
+
45
+ Notes:
46
+ - Handler must be async function
47
+ - Handler signature: async def handler(conn, params: ParamsModel) -> ResultModel
48
+ - ParamsModel and ResultModel must be Pydantic BaseModel subclasses
49
+ - Type hints are required for code generation
50
+ """
51
+ def decorator(handler_func: Callable) -> Callable:
52
+ # Validate handler is async
53
+ if not inspect.iscoroutinefunction(handler_func):
54
+ raise TypeError(f"Handler '{method_name}' must be async function")
55
+
56
+ # Extract type hints
57
+ try:
58
+ hints = get_type_hints(handler_func)
59
+ except Exception as e:
60
+ logger.warning(f"Could not extract type hints for '{method_name}': {e}")
61
+ hints = {}
62
+
63
+ # Extract parameter type (second parameter after conn)
64
+ param_type: Optional[Type[BaseModel]] = None
65
+ signature = inspect.signature(handler_func)
66
+ params_list = list(signature.parameters.values())
67
+
68
+ if len(params_list) >= 2:
69
+ params_param = params_list[1]
70
+ param_type_hint = hints.get(params_param.name)
71
+
72
+ if param_type_hint and _is_pydantic_model(param_type_hint):
73
+ param_type = param_type_hint
74
+ elif param_type_hint:
75
+ logger.warning(
76
+ f"⚠️ Handler '{method_name}' uses '{param_type_hint}' for params. "
77
+ f"Pydantic models recommended for type-safe client generation."
78
+ )
79
+
80
+ # Extract return type
81
+ return_type: Optional[Type[BaseModel]] = None
82
+ return_type_hint = hints.get("return")
83
+
84
+ if return_type_hint and _is_pydantic_model(return_type_hint):
85
+ return_type = return_type_hint
86
+ elif return_type_hint:
87
+ logger.warning(
88
+ f"⚠️ Handler '{method_name}' returns '{return_type_hint}'. "
89
+ f"Pydantic models recommended for type-safe client generation."
90
+ )
91
+
92
+ # Get docstring
93
+ docstring = inspect.getdoc(handler_func)
94
+
95
+ # Register with MessageRouter (for runtime)
96
+ router = get_global_router()
97
+ router.register(method_name)(handler_func)
98
+
99
+ # Register with RPCRegistry (for codegen)
100
+ registry = get_global_registry()
101
+ registry.register(
102
+ name=method_name,
103
+ handler=handler_func,
104
+ param_type=param_type,
105
+ return_type=return_type,
106
+ docstring=docstring,
107
+ )
108
+
109
+ logger.info(f"✅ Registered WebSocket RPC: {method_name}")
110
+
111
+ return handler_func
112
+
113
+ return decorator
114
+
115
+
116
+ def _is_pydantic_model(type_hint) -> bool:
117
+ """
118
+ Check if type hint is a Pydantic model.
119
+
120
+ Args:
121
+ type_hint: Type hint to check
122
+
123
+ Returns:
124
+ True if it's a Pydantic BaseModel subclass
125
+ """
126
+ try:
127
+ return (
128
+ inspect.isclass(type_hint)
129
+ and issubclass(type_hint, BaseModel)
130
+ )
131
+ except (TypeError, AttributeError):
132
+ return False
133
+
134
+
135
+ __all__ = [
136
+ "websocket_rpc",
137
+ ]
@@ -0,0 +1 @@
1
+ """Django management commands for Centrifugo."""
@@ -0,0 +1 @@
1
+ """Centrifugo management commands."""