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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/services/otp_service.py +3 -14
- django_cfg/apps/centrifugo/__init__.py +57 -0
- django_cfg/apps/centrifugo/admin/__init__.py +13 -0
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
- django_cfg/apps/centrifugo/admin/config.py +82 -0
- django_cfg/apps/centrifugo/apps.py +31 -0
- django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
- django_cfg/apps/centrifugo/codegen/README.md +242 -0
- django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
- django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
- django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
- django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
- django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
- django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
- django_cfg/apps/centrifugo/decorators.py +137 -0
- django_cfg/apps/centrifugo/management/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
- django_cfg/apps/centrifugo/managers/__init__.py +12 -0
- django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
- django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
- django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
- django_cfg/apps/centrifugo/models/__init__.py +11 -0
- django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
- django_cfg/apps/centrifugo/registry.py +106 -0
- django_cfg/apps/centrifugo/router.py +125 -0
- django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
- django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
- django_cfg/apps/centrifugo/serializers/channels.py +26 -0
- django_cfg/apps/centrifugo/serializers/health.py +17 -0
- django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
- django_cfg/apps/centrifugo/serializers/stats.py +21 -0
- django_cfg/apps/centrifugo/services/__init__.py +12 -0
- django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
- django_cfg/apps/centrifugo/services/client/client.py +577 -0
- django_cfg/apps/centrifugo/services/client/config.py +228 -0
- django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
- django_cfg/apps/centrifugo/services/config_helper.py +63 -0
- django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
- django_cfg/apps/centrifugo/services/logging.py +677 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
- django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
- django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
- django_cfg/apps/centrifugo/urls.py +31 -0
- django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
- django_cfg/apps/centrifugo/views/__init__.py +15 -0
- django_cfg/apps/centrifugo/views/admin_api.py +374 -0
- django_cfg/apps/centrifugo/views/dashboard.py +15 -0
- django_cfg/apps/centrifugo/views/monitoring.py +286 -0
- django_cfg/apps/centrifugo/views/testing_api.py +422 -0
- django_cfg/apps/support/utils/support_email_service.py +5 -18
- django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
- django_cfg/apps/urls.py +5 -5
- django_cfg/core/base/config_model.py +4 -44
- django_cfg/core/builders/apps_builder.py +2 -2
- django_cfg/core/generation/integration_generators/third_party.py +8 -8
- django_cfg/core/utils/__init__.py +5 -0
- django_cfg/core/utils/url_helpers.py +73 -0
- django_cfg/modules/base.py +7 -7
- django_cfg/modules/django_client/core/__init__.py +2 -1
- django_cfg/modules/django_client/core/config/config.py +8 -0
- django_cfg/modules/django_client/core/generator/__init__.py +42 -2
- django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
- django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
- django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
- django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
- django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
- django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
- django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
- django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
- django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
- django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
- django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
- django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
- django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
- django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
- django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
- django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
- django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
- django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
- django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
- django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
- django_cfg/modules/django_client/system/schema_parser.py +5 -1
- django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
- django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
- django_cfg/modules/django_unfold/dashboard.py +25 -19
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/core.py +2 -0
- django_cfg/registry/modules.py +2 -2
- django_cfg/static/js/api/centrifugo/client.mjs +164 -0
- django_cfg/static/js/api/centrifugo/index.mjs +13 -0
- django_cfg/static/js/api/index.mjs +5 -5
- django_cfg/static/js/api/types.mjs +89 -26
- {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/METADATA +1 -1
- {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/RECORD +142 -68
- django_cfg/apps/ipc/README.md +0 -346
- django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
- django_cfg/apps/ipc/TESTING.md +0 -539
- django_cfg/apps/ipc/__init__.py +0 -60
- django_cfg/apps/ipc/admin.py +0 -212
- django_cfg/apps/ipc/apps.py +0 -28
- django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
- django_cfg/apps/ipc/migrations/__init__.py +0 -0
- django_cfg/apps/ipc/models.py +0 -221
- django_cfg/apps/ipc/serializers/__init__.py +0 -29
- django_cfg/apps/ipc/serializers/serializers.py +0 -343
- django_cfg/apps/ipc/services/__init__.py +0 -7
- django_cfg/apps/ipc/services/client/__init__.py +0 -23
- django_cfg/apps/ipc/services/client/client.py +0 -621
- django_cfg/apps/ipc/services/client/config.py +0 -214
- django_cfg/apps/ipc/services/client/exceptions.py +0 -201
- django_cfg/apps/ipc/services/logging.py +0 -239
- django_cfg/apps/ipc/services/monitor.py +0 -466
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
- django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
- django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
- django_cfg/apps/ipc/urls.py +0 -23
- django_cfg/apps/ipc/views/__init__.py +0 -13
- django_cfg/apps/ipc/views/dashboard.py +0 -15
- django_cfg/apps/ipc/views/monitoring.py +0 -251
- django_cfg/apps/ipc/views/testing.py +0 -285
- django_cfg/static/js/api/ipc/client.mjs +0 -114
- django_cfg/static/js/api/ipc/index.mjs +0 -13
- {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/entry_points.txt +0 -0
- {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."""
|