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,380 @@
1
+ """
2
+ Type mapping from OpenAPI/IR to Go types.
3
+
4
+ Handles conversion of IR schemas to Go types with proper handling of:
5
+ - Primitive types (string, integer, number, boolean)
6
+ - Optional fields (pointers)
7
+ - Arrays and slices
8
+ - Nested objects (struct references)
9
+ - Enums (custom types)
10
+ - Format-specific types (time.Time, uuid, etc.)
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import TYPE_CHECKING
16
+
17
+ from .naming import sanitize_go_identifier, to_pascal_case
18
+
19
+ if TYPE_CHECKING:
20
+ from ...ir import IRSchemaObject
21
+
22
+
23
+ class GoTypeMapper:
24
+ """
25
+ Maps OpenAPI/IR types to Go types.
26
+
27
+ Handles:
28
+ - Primitive types (string → string, integer → int64, etc.)
29
+ - Optional fields (User.email? → *string)
30
+ - Arrays ([]string, []int64)
31
+ - Nested objects (references to other structs)
32
+ - Enums (custom types with constants)
33
+ """
34
+
35
+ # Primitive type mapping
36
+ PRIMITIVE_TYPES = {
37
+ "string": "string",
38
+ "integer": "int64",
39
+ "number": "float64",
40
+ "boolean": "bool",
41
+ "object": "map[string]interface{}",
42
+ "null": "interface{}",
43
+ }
44
+
45
+ # Format-specific type overrides
46
+ FORMAT_OVERRIDES = {
47
+ "int32": "int32",
48
+ "int64": "int64",
49
+ "float": "float32",
50
+ "double": "float64",
51
+ "date-time": "time.Time",
52
+ "date": "string", # YYYY-MM-DD format
53
+ "uuid": "string", # Or use google/uuid package
54
+ "binary": "[]byte",
55
+ "byte": "[]byte",
56
+ "email": "string",
57
+ "uri": "string",
58
+ "url": "string",
59
+ "hostname": "string",
60
+ "ipv4": "string",
61
+ "ipv6": "string",
62
+ }
63
+
64
+ def __init__(self, use_types_package: bool = False):
65
+ """Initialize type mapper.
66
+
67
+ Args:
68
+ use_types_package: If True, prefix enum types with "types."
69
+ """
70
+ self._imports_needed = set()
71
+ self.use_types_package = use_types_package
72
+
73
+ def get_imports(self) -> set[str]:
74
+ """
75
+ Get required imports for generated code.
76
+
77
+ Returns:
78
+ Set of import paths (e.g., {"time", "encoding/json"})
79
+ """
80
+ return self._imports_needed
81
+
82
+ def ir_schema_to_go_type(
83
+ self,
84
+ schema: IRSchemaObject,
85
+ required: bool = True,
86
+ parent_schema: IRSchemaObject | None = None,
87
+ ) -> str:
88
+ """
89
+ Convert IR schema to Go type.
90
+
91
+ Args:
92
+ schema: IRSchemaObject to convert
93
+ required: Whether field is required (affects pointer usage)
94
+ parent_schema: Parent schema (for context)
95
+
96
+ Returns:
97
+ Go type string
98
+
99
+ Examples:
100
+ >>> mapper.ir_schema_to_go_type(
101
+ ... IRSchemaObject(name="email", type="string"),
102
+ ... required=False
103
+ ... )
104
+ '*string'
105
+
106
+ >>> mapper.ir_schema_to_go_type(
107
+ ... IRSchemaObject(name="items", type="array", items=IRSchemaObject(type="string"))
108
+ ... )
109
+ '[]string'
110
+
111
+ >>> mapper.ir_schema_to_go_type(
112
+ ... IRSchemaObject(name="created_at", type="string", format="date-time")
113
+ ... )
114
+ 'time.Time'
115
+ """
116
+ # Handle $ref
117
+ if schema.ref:
118
+ type_name = schema.ref.split('/')[-1]
119
+ # Struct references are already pointers in Go when optional
120
+ if not required:
121
+ return f"*{type_name}"
122
+ return type_name
123
+
124
+ # Handle enums
125
+ if schema.enum:
126
+ enum_type = self._get_enum_type_name(schema)
127
+ if self.use_types_package:
128
+ enum_type = f"types.{enum_type}"
129
+ return f"*{enum_type}" if not required else enum_type
130
+
131
+ # Handle arrays
132
+ if schema.type == "array" and schema.items:
133
+ item_type = self.ir_schema_to_go_type(schema.items, required=True)
134
+ # Arrays are always non-nil in Go, but can be empty
135
+ # omitempty in JSON tag handles the serialization
136
+ return f"[]{item_type}"
137
+
138
+ # Handle format overrides
139
+ if schema.format and schema.format in self.FORMAT_OVERRIDES:
140
+ go_type = self.FORMAT_OVERRIDES[schema.format]
141
+
142
+ # Track imports
143
+ if go_type == "time.Time":
144
+ self._imports_needed.add("time")
145
+
146
+ # time.Time is a struct, so we use pointer for optionals
147
+ if not required:
148
+ return f"*{go_type}"
149
+ return go_type
150
+
151
+ # Handle primitive types
152
+ if schema.type in self.PRIMITIVE_TYPES:
153
+ go_type = self.PRIMITIVE_TYPES[schema.type]
154
+
155
+ # Optionals become pointers (except for maps and interfaces)
156
+ if not required and go_type not in ["interface{}", "map[string]interface{}"]:
157
+ return f"*{go_type}"
158
+
159
+ return go_type
160
+
161
+ # Fallback to interface{} for unknown types
162
+ return "interface{}"
163
+
164
+ def ir_schema_to_struct(
165
+ self,
166
+ schema: IRSchemaObject,
167
+ ) -> dict:
168
+ """
169
+ Convert IR schema to Go struct definition.
170
+
171
+ Args:
172
+ schema: IRSchemaObject to convert
173
+
174
+ Returns:
175
+ Dictionary with struct definition:
176
+ {
177
+ "name": "User",
178
+ "fields": [
179
+ {
180
+ "name": "ID",
181
+ "type": "int64",
182
+ "json_tag": '`json:"id"`',
183
+ "description": "User ID",
184
+ "required": True
185
+ },
186
+ ...
187
+ ],
188
+ "doc": "User represents a registered user.",
189
+ "needs_time_import": False,
190
+ }
191
+
192
+ Examples:
193
+ >>> schema = IRSchemaObject(
194
+ ... name="User",
195
+ ... type="object",
196
+ ... properties={
197
+ ... "id": IRSchemaObject(name="id", type="integer"),
198
+ ... "username": IRSchemaObject(name="username", type="string"),
199
+ ... "email": IRSchemaObject(name="email", type="string"),
200
+ ... },
201
+ ... required=["id", "username"],
202
+ ... )
203
+ >>> struct = mapper.ir_schema_to_struct(schema)
204
+ >>> struct["name"]
205
+ 'User'
206
+ >>> len(struct["fields"])
207
+ 3
208
+ """
209
+ fields = []
210
+
211
+ # Reset imports for this struct
212
+ self._imports_needed = set()
213
+
214
+ # Process properties
215
+ for prop_name, prop_schema in (schema.properties or {}).items():
216
+ is_required = prop_name in (schema.required or [])
217
+
218
+ # Get Go type
219
+ go_type = self.ir_schema_to_go_type(prop_schema, is_required, parent_schema=schema)
220
+
221
+ # Build JSON tag
222
+ if not is_required:
223
+ json_tag = f'`json:"{prop_name},omitempty"`'
224
+ else:
225
+ json_tag = f'`json:"{prop_name}"`'
226
+
227
+ # Create field definition
228
+ field = {
229
+ "name": to_pascal_case(prop_name),
230
+ "type": go_type,
231
+ "json_tag": json_tag,
232
+ "description": prop_schema.description or "",
233
+ "required": is_required,
234
+ "deprecated": prop_schema.deprecated,
235
+ }
236
+ fields.append(field)
237
+
238
+ return {
239
+ "name": schema.name,
240
+ "fields": fields,
241
+ "doc": schema.description or f"{schema.name} model.",
242
+ "needs_time_import": "time" in self._imports_needed,
243
+ }
244
+
245
+ def _get_enum_type_name(self, schema: IRSchemaObject) -> str:
246
+ """
247
+ Get enum type name from schema.
248
+
249
+ Args:
250
+ schema: IRSchemaObject with enum
251
+
252
+ Returns:
253
+ Enum type name (PascalCase)
254
+
255
+ Examples:
256
+ >>> schema = IRSchemaObject(name="status", type="integer", enum=[1, 2, 3])
257
+ >>> mapper._get_enum_type_name(schema)
258
+ 'Status'
259
+
260
+ >>> schema = IRSchemaObject(name="User.role", type="string", enum=["admin", "user"])
261
+ >>> mapper._get_enum_type_name(schema)
262
+ 'UserRole'
263
+ """
264
+ if not schema.name:
265
+ return "Enum"
266
+
267
+ # Handle nested enum names (e.g., "User.role" → "UserRole")
268
+ if '.' in schema.name:
269
+ parts = schema.name.split('.')
270
+ return ''.join(to_pascal_case(p) for p in parts)
271
+
272
+ return to_pascal_case(schema.name)
273
+
274
+ def get_enum_base_type(self, schema: IRSchemaObject) -> str:
275
+ """
276
+ Get Go base type for enum.
277
+
278
+ Args:
279
+ schema: IRSchemaObject with enum
280
+
281
+ Returns:
282
+ Go base type ("int64", "string", etc.)
283
+
284
+ Examples:
285
+ >>> schema = IRSchemaObject(name="status", type="integer", enum=[1, 2, 3])
286
+ >>> mapper.get_enum_base_type(schema)
287
+ 'int64'
288
+
289
+ >>> schema = IRSchemaObject(name="role", type="string", enum=["admin", "user"])
290
+ >>> mapper.get_enum_base_type(schema)
291
+ 'string'
292
+ """
293
+ if schema.type == "integer":
294
+ # Check format for int32 vs int64
295
+ if schema.format == "int32":
296
+ return "int32"
297
+ return "int64"
298
+ elif schema.type == "number":
299
+ if schema.format == "float":
300
+ return "float32"
301
+ return "float64"
302
+ elif schema.type == "string":
303
+ return "string"
304
+
305
+ # Default to int64
306
+ return "int64"
307
+
308
+ def generate_enum_definition(self, schema: IRSchemaObject) -> dict:
309
+ """
310
+ Generate enum type definition for Go.
311
+
312
+ Args:
313
+ schema: IRSchemaObject with enum + enum_var_names
314
+
315
+ Returns:
316
+ Dictionary with enum definition:
317
+ {
318
+ "name": "StatusEnum",
319
+ "base_type": "int64",
320
+ "values": [
321
+ {"name": "StatusNew", "value": 1, "description": "New user"},
322
+ {"name": "StatusActive", "value": 2, "description": "Active user"},
323
+ ],
324
+ "doc": "StatusEnum represents user status.",
325
+ }
326
+
327
+ Examples:
328
+ >>> schema = IRSchemaObject(
329
+ ... name="status",
330
+ ... type="integer",
331
+ ... enum=[1, 2, 3],
332
+ ... enum_var_names=["STATUS_NEW", "STATUS_ACTIVE", "STATUS_COMPLETE"],
333
+ ... )
334
+ >>> enum_def = mapper.generate_enum_definition(schema)
335
+ >>> enum_def["name"]
336
+ 'Status'
337
+ >>> len(enum_def["values"])
338
+ 3
339
+ """
340
+ enum_type_name = self._get_enum_type_name(schema)
341
+ base_type = self.get_enum_base_type(schema)
342
+
343
+ # Build enum values
344
+ values = []
345
+ enum_values = schema.enum or []
346
+ enum_var_names = schema.enum_var_names or []
347
+
348
+ for i, enum_value in enumerate(enum_values):
349
+ # Get variable name
350
+ if i < len(enum_var_names):
351
+ # Even if var name is provided, sanitize it for Go
352
+ var_name = sanitize_go_identifier(enum_var_names[i])
353
+ else:
354
+ # Auto-generate from value
355
+ if isinstance(enum_value, str):
356
+ # Use sanitize_go_identifier to handle dots, hyphens, spaces, etc.
357
+ var_name = sanitize_go_identifier(enum_value)
358
+ else:
359
+ var_name = f"VALUE_{enum_value}"
360
+
361
+ # var_name is now always PascalCase from sanitize_go_identifier
362
+ go_var_name = var_name
363
+
364
+ # Add enum type prefix if not present
365
+ if not go_var_name.startswith(enum_type_name):
366
+ go_var_name = enum_type_name + go_var_name
367
+
368
+ values.append({
369
+ "name": go_var_name,
370
+ "value": enum_value,
371
+ "description": "", # Could extract from x-choices if available
372
+ })
373
+
374
+ return {
375
+ "name": enum_type_name,
376
+ "base_type": base_type,
377
+ "values": values,
378
+ "doc": schema.description or f"{enum_type_name} enum.",
379
+ "is_string_enum": schema.type == "string",
380
+ }
@@ -14,7 +14,7 @@ from django.core.management.base import BaseCommand, CommandError
14
14
  class Command(BaseCommand):
15
15
  """Generate OpenAPI clients for configured application groups."""
16
16
 
17
- help = "Generate Python and TypeScript API clients from OpenAPI schemas"
17
+ help = "Generate Python, TypeScript, and Go API clients from OpenAPI schemas"
18
18
 
19
19
  def add_arguments(self, parser):
20
20
  """Add command arguments."""
@@ -37,6 +37,12 @@ class Command(BaseCommand):
37
37
  help="Generate TypeScript client only",
38
38
  )
39
39
 
40
+ parser.add_argument(
41
+ "--go",
42
+ action="store_true",
43
+ help="Generate Go client only",
44
+ )
45
+
40
46
  parser.add_argument(
41
47
  "--no-python",
42
48
  action="store_true",
@@ -49,6 +55,12 @@ class Command(BaseCommand):
49
55
  help="Skip TypeScript client generation",
50
56
  )
51
57
 
58
+ parser.add_argument(
59
+ "--no-go",
60
+ action="store_true",
61
+ help="Skip Go client generation",
62
+ )
63
+
52
64
  # Utility options
53
65
  parser.add_argument(
54
66
  "--dry-run",
@@ -193,15 +205,22 @@ class Command(BaseCommand):
193
205
  def _generate_clients(self, service, options):
194
206
  """Generate clients."""
195
207
  # Determine languages
196
- if options["python"] and not options["typescript"]:
208
+ if options["python"] and not options["typescript"] and not options["go"]:
197
209
  python = True
198
210
  typescript = False
199
- elif options["typescript"] and not options["python"]:
211
+ go = False
212
+ elif options["typescript"] and not options["python"] and not options["go"]:
200
213
  python = False
201
214
  typescript = True
215
+ go = False
216
+ elif options["go"] and not options["python"] and not options["typescript"]:
217
+ python = False
218
+ typescript = False
219
+ go = True
202
220
  else:
203
221
  python = not options["no_python"]
204
222
  typescript = not options["no_typescript"]
223
+ go = not options["no_go"]
205
224
 
206
225
  # Get groups
207
226
  groups = options.get("groups")
@@ -233,6 +252,8 @@ class Command(BaseCommand):
233
252
  self.stdout.write(" → Python")
234
253
  if typescript:
235
254
  self.stdout.write(" → TypeScript")
255
+ if go:
256
+ self.stdout.write(" → Go")
236
257
 
237
258
  if dry_run:
238
259
  self.stdout.write(self.style.WARNING("\n✅ Dry run completed - no files generated"))
@@ -248,6 +269,7 @@ class Command(BaseCommand):
248
269
 
249
270
  from django_cfg.modules.django_client.core import (
250
271
  ArchiveManager,
272
+ GoGenerator,
251
273
  GroupManager,
252
274
  PythonGenerator,
253
275
  TypeScriptGenerator,
@@ -395,6 +417,32 @@ class Command(BaseCommand):
395
417
 
396
418
  self.stdout.write(f" ✅ TypeScript client: {ts_dir} ({len(ts_files)} files)")
397
419
 
420
+ # Generate Go client
421
+ if go:
422
+ self.stdout.write(" → Generating Go client...")
423
+ go_dir = service.config.get_group_go_dir(group_name)
424
+ go_dir.mkdir(parents=True, exist_ok=True)
425
+
426
+ go_generator = GoGenerator(
427
+ ir_context,
428
+ client_structure=service.config.client_structure,
429
+ openapi_schema=schema_dict,
430
+ tag_prefix=f"{group_name}_",
431
+ generate_package_files=service.config.generate_package_files,
432
+ package_config={
433
+ "module_name": f"djangocfg__{group_name}",
434
+ "version": "v1.0.0",
435
+ },
436
+ )
437
+ go_files = go_generator.generate()
438
+
439
+ for generated_file in go_files:
440
+ full_path = go_dir / generated_file.path
441
+ full_path.parent.mkdir(parents=True, exist_ok=True)
442
+ full_path.write_text(generated_file.content)
443
+
444
+ self.stdout.write(f" ✅ Go client: {go_dir} ({len(go_files)} files)")
445
+
398
446
  # Archive if enabled
399
447
  if service.config.enable_archive:
400
448
  self.stdout.write(" → Archiving...")
@@ -428,3 +476,5 @@ class Command(BaseCommand):
428
476
  self.stdout.write(f" Python: {service.config.get_python_clients_dir()}")
429
477
  if typescript:
430
478
  self.stdout.write(f" TypeScript: {service.config.get_typescript_clients_dir()}")
479
+ if go:
480
+ self.stdout.write(f" Go: {service.config.get_go_clients_dir()}")
@@ -54,7 +54,9 @@ def main():
54
54
  django_cfg_dir = modules_dir.parent # django_cfg
55
55
  src_dir = django_cfg_dir.parent # src
56
56
  dev_dir = src_dir.parent # django-cfg-dev
57
- example_django_dir = dev_dir.parent / "solution" / "projects" / "django"
57
+ projects_dir = dev_dir.parent # projects
58
+ root_dir = projects_dir.parent # root (django-cfg)
59
+ example_django_dir = root_dir / "solution" / "projects" / "django"
58
60
 
59
61
  # Output directory for MJS clients
60
62
  if args.output:
@@ -120,7 +120,11 @@ class SchemaParser:
120
120
  return 'Object'
121
121
  # Check for additionalProperties (dictionary-like)
122
122
  if 'additionalProperties' in schema:
123
- value_type = self.get_js_type(schema['additionalProperties'])
123
+ additional = schema['additionalProperties']
124
+ # additionalProperties can be bool or schema object
125
+ if isinstance(additional, bool):
126
+ return 'Record<string, any>' if additional else 'Object'
127
+ value_type = self.get_js_type(additional)
124
128
  return f'Record<string, {value_type}>'
125
129
  return 'Object'
126
130
 
@@ -13,6 +13,7 @@
13
13
 
14
14
  <!-- Alpine.js for interactivity -->
15
15
  <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
16
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
16
17
 
17
18
  <style>
18
19
  /* Prevent flash of unstyled content */
@@ -17,6 +17,7 @@ from django.template.loader import render_to_string
17
17
  from sendgrid import SendGridAPIClient
18
18
  from sendgrid.helpers.mail import Mail
19
19
 
20
+ from django_cfg.core.utils import get_otp_url
20
21
  from django_cfg.modules.base import BaseCfgModule
21
22
  from django_cfg.modules.django_twilio.exceptions import (
22
23
  TwilioConfigurationError,
@@ -305,6 +306,9 @@ class SendGridService(BaseCfgModule):
305
306
  # Prepare subject
306
307
  subject = f"Your {cfg.project_name if cfg else 'Django CFG'} verification code"
307
308
 
309
+ # Generate OTP link using utility function
310
+ otp_link = get_otp_url(otp_code)
311
+
308
312
  # Prepare template data matching sendgrid_otp_email.html template
309
313
  dynamic_data = {
310
314
  'site_name': cfg.project_name if cfg else 'Django CFG',
@@ -317,7 +321,7 @@ class SendGridService(BaseCfgModule):
317
321
  'email': email
318
322
  },
319
323
  'otp_code': otp_code, # This is what the template expects!
320
- 'otp_link': cfg.get_otp_url(otp_code) if cfg else None, # Add OTP verification link
324
+ 'otp_link': otp_link, # Add OTP verification link
321
325
  'expires_minutes': 10,
322
326
  'subject': subject, # Add subject to template data
323
327
  **config.custom_template_data,
@@ -370,9 +374,8 @@ class SendGridService(BaseCfgModule):
370
374
  "user": {"username": email.split("@")[0]}, # Fallback user object
371
375
  }
372
376
 
373
- # Add OTP link if config is available
374
- if current_config:
375
- context["otp_link"] = current_config.get_otp_url(otp_code)
377
+ # Add OTP link using utility function
378
+ context["otp_link"] = get_otp_url(otp_code)
376
379
 
377
380
  # Render Django templates
378
381
  try:
@@ -68,16 +68,22 @@ class DashboardManager(BaseCfgModule):
68
68
  ),
69
69
  ]
70
70
 
71
+ # Centrifugo Dashboard (if enabled)
72
+ if self.is_centrifugo_enabled():
73
+ navigation_sections.append(
74
+ NavigationSection(
75
+ title="Centrifugo",
76
+ separator=True,
77
+ collapsible=True,
78
+ items=[NavigationItem(title="Dashboard", icon=Icons.MONITOR_HEART, link="/cfg/centrifugo/admin/"),
79
+ NavigationItem(title="Logs", icon=Icons.LIST_ALT, link=str(reverse_lazy("admin:django_cfg_centrifugo_centrifugolog_changelist"))),
80
+ ]
81
+ )
82
+ )
83
+
71
84
  # Add Operations section (System & Monitoring tools)
72
85
  operations_items = []
73
86
 
74
- # RPC Dashboard (if enabled)
75
- if self.is_rpc_enabled():
76
- operations_items.extend([
77
- NavigationItem(title="IPC/RPC Dashboard", icon=Icons.MONITOR_HEART, link="/cfg/ipc/admin/"),
78
- NavigationItem(title="RPC Logs", icon=Icons.LIST_ALT, link=str(reverse_lazy("admin:django_cfg_ipc_rpclog_changelist"))),
79
- ])
80
-
81
87
  # Background Tasks (if enabled)
82
88
  if self.should_enable_tasks():
83
89
  operations_items.extend([
@@ -327,38 +333,38 @@ class DashboardManager(BaseCfgModule):
327
333
  )
328
334
  widgets.append(system_overview_widget.to_dict())
329
335
 
330
- # Add RPC monitoring widget if IPC is enabled
331
- if self.is_rpc_enabled():
332
- rpc_monitoring_widget = StatsCardsWidget(
333
- title="IPC/RPC Monitoring",
336
+ # Add Centrifugo monitoring widget if enabled
337
+ if self.is_centrifugo_enabled():
338
+ centrifugo_monitoring_widget = StatsCardsWidget(
339
+ title="Centrifugo Monitoring",
334
340
  cards=[
335
341
  StatCard(
336
- title="Total Calls",
337
- value="{{ rpc_total_calls }}",
342
+ title="Total Publishes",
343
+ value="{{ centrifugo_total_publishes }}",
338
344
  icon=Icons.API,
339
345
  color="blue",
340
346
  ),
341
347
  StatCard(
342
348
  title="Success Rate",
343
- value="{{ rpc_success_rate }}%",
349
+ value="{{ centrifugo_success_rate }}%",
344
350
  icon=Icons.CHECK_CIRCLE,
345
351
  color="green",
346
352
  ),
347
353
  StatCard(
348
- title="Avg Response Time",
349
- value="{{ rpc_avg_duration }}ms",
354
+ title="Avg Duration",
355
+ value="{{ centrifugo_avg_duration }}ms",
350
356
  icon=Icons.SPEED,
351
357
  color="purple",
352
358
  ),
353
359
  StatCard(
354
- title="Failed Calls",
355
- value="{{ rpc_failed_calls }}",
360
+ title="Failed Publishes",
361
+ value="{{ centrifugo_failed_publishes }}",
356
362
  icon=Icons.ERROR,
357
363
  color="red",
358
364
  ),
359
365
  ]
360
366
  )
361
- widgets.append(rpc_monitoring_widget.to_dict())
367
+ widgets.append(centrifugo_monitoring_widget.to_dict())
362
368
 
363
369
  # Convert to dictionaries for Unfold
364
370
  return widgets
django_cfg/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "django-cfg"
7
- version = "1.4.62"
7
+ version = "1.4.64"
8
8
  description = "Django AI framework with built-in agents, type-safe Pydantic v2 configuration, and 8 enterprise apps. Replace settings.py, validate at startup, 90% less code. Production-ready AI workflows for Django."
9
9
  readme = "README.md"
10
10
  keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "ai-agents", "enterprise-django", "django-settings", "type-safe-config",]
@@ -61,6 +61,8 @@ CORE_REGISTRY = {
61
61
 
62
62
  # Utils
63
63
  "version_check": ("django_cfg.utils.version_check", "version_check"),
64
+ "get_ticket_url": ("django_cfg.core.utils.url_helpers", "get_ticket_url"),
65
+ "get_otp_url": ("django_cfg.core.utils.url_helpers", "get_otp_url"),
64
66
 
65
67
  # Routing
66
68
  "DynamicRouter": ("django_cfg.routing.routers", "DynamicRouter"),
@@ -10,8 +10,8 @@ MODULES_REGISTRY = {
10
10
  # Configuration utilities
11
11
  "set_current_config": ("django_cfg.core.config", "set_current_config"),
12
12
 
13
- # IPC/RPC module
14
- "DjangoCfgRPCConfig": ("django_cfg.apps.ipc", "DjangoCfgRPCConfig"),
13
+ # Centrifugo module
14
+ "DjangoCfgCentrifugoConfig": ("django_cfg.apps.centrifugo.services.client.config", "DjangoCfgCentrifugoConfig"),
15
15
 
16
16
  # Import/Export integration (simple re-exports)
17
17
  "ImportForm": ("django_cfg.modules.django_import_export", "ImportForm"),