django-cfg 1.4.62__py3-none-any.whl → 1.4.64__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 (181) 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 +582 -0
  62. django_cfg/apps/centrifugo/services/client/config.py +236 -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 +380 -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.62.dist-info → django_cfg-1.4.64.dist-info}/METADATA +1 -1
  139. {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/RECORD +142 -70
  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 -232
  145. django_cfg/apps/ipc/apps.py +0 -98
  146. django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
  147. django_cfg/apps/ipc/migrations/0002_rpclog_is_event.py +0 -23
  148. django_cfg/apps/ipc/migrations/__init__.py +0 -0
  149. django_cfg/apps/ipc/models.py +0 -229
  150. django_cfg/apps/ipc/serializers/__init__.py +0 -29
  151. django_cfg/apps/ipc/serializers/serializers.py +0 -343
  152. django_cfg/apps/ipc/services/__init__.py +0 -7
  153. django_cfg/apps/ipc/services/client/__init__.py +0 -23
  154. django_cfg/apps/ipc/services/client/client.py +0 -621
  155. django_cfg/apps/ipc/services/client/config.py +0 -214
  156. django_cfg/apps/ipc/services/client/exceptions.py +0 -201
  157. django_cfg/apps/ipc/services/logging.py +0 -239
  158. django_cfg/apps/ipc/services/monitor.py +0 -466
  159. django_cfg/apps/ipc/services/rpc_log_consumer.py +0 -330
  160. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
  161. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
  162. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
  163. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
  164. django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
  165. django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
  166. django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
  167. django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
  168. django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
  169. django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
  170. django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
  171. django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
  172. django_cfg/apps/ipc/urls.py +0 -23
  173. django_cfg/apps/ipc/views/__init__.py +0 -13
  174. django_cfg/apps/ipc/views/dashboard.py +0 -15
  175. django_cfg/apps/ipc/views/monitoring.py +0 -251
  176. django_cfg/apps/ipc/views/testing.py +0 -285
  177. django_cfg/static/js/api/ipc/client.mjs +0 -114
  178. django_cfg/static/js/api/ipc/index.mjs +0 -13
  179. {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/WHEEL +0 -0
  180. {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/entry_points.txt +0 -0
  181. {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.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."""