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.
- 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 +582 -0
- django_cfg/apps/centrifugo/services/client/config.py +236 -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 +380 -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.62.dist-info → django_cfg-1.4.64.dist-info}/METADATA +1 -1
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/RECORD +142 -70
- 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 -232
- django_cfg/apps/ipc/apps.py +0 -98
- django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
- django_cfg/apps/ipc/migrations/0002_rpclog_is_event.py +0 -23
- django_cfg/apps/ipc/migrations/__init__.py +0 -0
- django_cfg/apps/ipc/models.py +0 -229
- 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/services/rpc_log_consumer.py +0 -330
- 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.62.dist-info → django_cfg-1.4.64.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
# Centrifugo Client Code Generation - Usage Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Автоматическая генерация type-safe клиентов для Centrifugo WebSocket RPC из Python обработчиков.
|
|
6
|
+
|
|
7
|
+
**Архитектура:**
|
|
8
|
+
- Используется Pydantic как единый источник правды
|
|
9
|
+
- Декоратор `@websocket_rpc` регистрирует обработчики
|
|
10
|
+
- Discovery извлекает типы из сигнатур функций
|
|
11
|
+
- Генераторы создают тонкие обёртки над базовыми RPC клиентами
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### 1. Создайте RPC Handler
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
# myapp/handlers.py
|
|
19
|
+
from pydantic import BaseModel, Field
|
|
20
|
+
from django_cfg.apps.centrifugo.decorators import websocket_rpc
|
|
21
|
+
|
|
22
|
+
class TaskStatsParams(BaseModel):
|
|
23
|
+
user_id: str = Field(..., description="User ID")
|
|
24
|
+
limit: int = Field(10, description="Max results")
|
|
25
|
+
|
|
26
|
+
class TaskStatsResult(BaseModel):
|
|
27
|
+
total: int = Field(..., description="Total tasks")
|
|
28
|
+
completed: int = Field(..., description="Completed tasks")
|
|
29
|
+
|
|
30
|
+
@websocket_rpc("tasks.get_stats")
|
|
31
|
+
async def get_task_stats(conn, params: TaskStatsParams) -> TaskStatsResult:
|
|
32
|
+
"""Get task statistics for user."""
|
|
33
|
+
# Ваша бизнес-логика
|
|
34
|
+
return TaskStatsResult(total=100, completed=75)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Импортируйте Handlers в AppConfig
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
# myapp/apps.py
|
|
41
|
+
class MyAppConfig(AppConfig):
|
|
42
|
+
def ready(self):
|
|
43
|
+
# Импортируйте handlers чтобы декораторы сработали
|
|
44
|
+
from . import handlers
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 3. Сгенерируйте Клиенты
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Все языки
|
|
51
|
+
python manage.py generate_centrifugo_clients --output ./clients --all
|
|
52
|
+
|
|
53
|
+
# Только Python
|
|
54
|
+
python manage.py generate_centrifugo_clients --output ./clients --python
|
|
55
|
+
|
|
56
|
+
# Только TypeScript
|
|
57
|
+
python manage.py generate_centrifugo_clients --output ./clients --typescript
|
|
58
|
+
|
|
59
|
+
# Только Go
|
|
60
|
+
python manage.py generate_centrifugo_clients --output ./clients --go
|
|
61
|
+
|
|
62
|
+
# С verbose выводом
|
|
63
|
+
python manage.py generate_centrifugo_clients --output ./clients --all --verbose
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 4. Используйте Сгенерированные Клиенты
|
|
67
|
+
|
|
68
|
+
**Python:**
|
|
69
|
+
```python
|
|
70
|
+
from clients.python import CentrifugoRPCClient, APIClient
|
|
71
|
+
from clients.python.models import TaskStatsParams
|
|
72
|
+
|
|
73
|
+
async def main():
|
|
74
|
+
rpc = CentrifugoRPCClient(
|
|
75
|
+
url='ws://localhost:8000/connection/websocket',
|
|
76
|
+
token='jwt-token',
|
|
77
|
+
user_id='user-123'
|
|
78
|
+
)
|
|
79
|
+
await rpc.connect()
|
|
80
|
+
|
|
81
|
+
api = APIClient(rpc)
|
|
82
|
+
result = await api.tasks_get_stats(
|
|
83
|
+
TaskStatsParams(user_id='user-123', limit=10)
|
|
84
|
+
)
|
|
85
|
+
print(f"Total: {result.total}, Completed: {result.completed}")
|
|
86
|
+
|
|
87
|
+
await rpc.disconnect()
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**TypeScript:**
|
|
91
|
+
```typescript
|
|
92
|
+
import { CentrifugoRPCClient, APIClient } from './clients/typescript';
|
|
93
|
+
import type { TaskStatsParams } from './clients/typescript';
|
|
94
|
+
|
|
95
|
+
const rpc = new CentrifugoRPCClient(
|
|
96
|
+
'ws://localhost:8000/connection/websocket',
|
|
97
|
+
'jwt-token',
|
|
98
|
+
'user-123'
|
|
99
|
+
);
|
|
100
|
+
await rpc.connect();
|
|
101
|
+
|
|
102
|
+
const api = new APIClient(rpc);
|
|
103
|
+
const result = await api.tasksGetStats({
|
|
104
|
+
user_id: 'user-123',
|
|
105
|
+
limit: 10
|
|
106
|
+
});
|
|
107
|
+
console.log(`Total: ${result.total}, Completed: ${result.completed}`);
|
|
108
|
+
|
|
109
|
+
await rpc.disconnect();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Go:**
|
|
113
|
+
```go
|
|
114
|
+
import (
|
|
115
|
+
"context"
|
|
116
|
+
client "path/to/clients/go"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
func main() {
|
|
120
|
+
ctx := context.Background()
|
|
121
|
+
api := client.NewAPIClient(
|
|
122
|
+
"ws://localhost:8000/connection/websocket",
|
|
123
|
+
"jwt-token",
|
|
124
|
+
"user-123",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if err := api.Connect(ctx); err != nil {
|
|
128
|
+
log.Fatal(err)
|
|
129
|
+
}
|
|
130
|
+
defer api.Disconnect()
|
|
131
|
+
|
|
132
|
+
result, err := api.TasksGetStats(ctx, client.TaskStatsParams{
|
|
133
|
+
UserId: "user-123",
|
|
134
|
+
Limit: 10,
|
|
135
|
+
})
|
|
136
|
+
if err != nil {
|
|
137
|
+
log.Fatal(err)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fmt.Printf("Total: %d, Completed: %d\n", result.Total, result.Completed)
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Декоратор @websocket_rpc
|
|
145
|
+
|
|
146
|
+
### Базовое Использование
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from django_cfg.apps.centrifugo.decorators import websocket_rpc
|
|
150
|
+
from pydantic import BaseModel
|
|
151
|
+
|
|
152
|
+
class MyParams(BaseModel):
|
|
153
|
+
name: str
|
|
154
|
+
|
|
155
|
+
class MyResult(BaseModel):
|
|
156
|
+
message: str
|
|
157
|
+
|
|
158
|
+
@websocket_rpc("my.method")
|
|
159
|
+
async def my_handler(conn, params: MyParams) -> MyResult:
|
|
160
|
+
"""Handler docstring (будет в генерируемых клиентах)."""
|
|
161
|
+
return MyResult(message=f"Hello {params.name}")
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Регистрация
|
|
165
|
+
|
|
166
|
+
Декоратор автоматически регистрирует обработчик в:
|
|
167
|
+
1. **MessageRouter** - для runtime обработки сообщений
|
|
168
|
+
2. **RPCRegistry** - для code generation (discovery)
|
|
169
|
+
|
|
170
|
+
### Type Hints
|
|
171
|
+
|
|
172
|
+
Обязательно указывайте типы:
|
|
173
|
+
- `params: YourParamsModel` - Pydantic модель параметров
|
|
174
|
+
- `-> YourResultModel` - Pydantic модель результата
|
|
175
|
+
|
|
176
|
+
Discovery извлекает эти типы для генерации клиентов.
|
|
177
|
+
|
|
178
|
+
## Архитектура Генераторов
|
|
179
|
+
|
|
180
|
+
### Thin Wrapper Pattern
|
|
181
|
+
|
|
182
|
+
Генерируется 2 слоя:
|
|
183
|
+
|
|
184
|
+
1. **Base RPC Client** (`rpc_client.py/ts/go`)
|
|
185
|
+
- Управляет WebSocket соединением
|
|
186
|
+
- Реализует correlation ID pattern
|
|
187
|
+
- Отправляет запросы на канал `rpc.requests`
|
|
188
|
+
- Получает ответы на канал `user#{user_id}`
|
|
189
|
+
- Мэтчит ответы по correlation_id
|
|
190
|
+
|
|
191
|
+
2. **Typed API Client** (`client.py/ts/go`)
|
|
192
|
+
- Тонкая обёртка над base client
|
|
193
|
+
- Type-safe методы для каждого RPC endpoint
|
|
194
|
+
- Автоматическая сериализация/десериализация
|
|
195
|
+
- IDE autocomplete, type checking
|
|
196
|
+
|
|
197
|
+
### Correlation ID Pattern
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
Client Centrifugo Server
|
|
201
|
+
| | |
|
|
202
|
+
|-- publish('rpc.requests') ------->| |
|
|
203
|
+
| { | |
|
|
204
|
+
| method: 'tasks.get_stats', | |
|
|
205
|
+
| params: {...}, | |
|
|
206
|
+
| correlation_id: 'uuid-123', | |
|
|
207
|
+
| reply_to: 'user#123' | |
|
|
208
|
+
| } | |
|
|
209
|
+
| |-- subscribe to channel -->|
|
|
210
|
+
| | |
|
|
211
|
+
| |<-- publish to user#123 ---|
|
|
212
|
+
|<-- receive on user#123 -----------| { |
|
|
213
|
+
| { | correlation_id: ... |
|
|
214
|
+
| correlation_id: 'uuid-123', | result: {...} |
|
|
215
|
+
| result: {...} | } |
|
|
216
|
+
| } | |
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Структура Сгенерированных Файлов
|
|
220
|
+
|
|
221
|
+
### Python Client
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
clients/python/
|
|
225
|
+
├── __init__.py # Exports: CentrifugoRPCClient, APIClient, models
|
|
226
|
+
├── models.py # Pydantic models
|
|
227
|
+
├── rpc_client.py # CentrifugoRPCClient (base)
|
|
228
|
+
├── client.py # APIClient (thin wrapper)
|
|
229
|
+
├── requirements.txt # Dependencies: centrifuge, pydantic
|
|
230
|
+
└── README.md # Usage documentation
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### TypeScript Client
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
clients/typescript/
|
|
237
|
+
├── index.ts # Exports: CentrifugoRPCClient, APIClient, types
|
|
238
|
+
├── types.ts # TypeScript interfaces
|
|
239
|
+
├── rpc-client.ts # CentrifugoRPCClient (base)
|
|
240
|
+
├── client.ts # APIClient (thin wrapper)
|
|
241
|
+
├── package.json # Dependencies: centrifuge
|
|
242
|
+
├── tsconfig.json # TypeScript config
|
|
243
|
+
└── README.md # Usage documentation
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Go Client
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
clients/go/
|
|
250
|
+
├── types.go # Go structs
|
|
251
|
+
├── rpc_client.go # CentrifugoRPCClient (base)
|
|
252
|
+
├── client.go # APIClient (thin wrapper)
|
|
253
|
+
├── go.mod # Dependencies: centrifuge-go
|
|
254
|
+
└── README.md # Usage documentation
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Management Command
|
|
258
|
+
|
|
259
|
+
### Опции
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
python manage.py generate_centrifugo_clients \
|
|
263
|
+
--output ./clients \ # Директория для клиентов (обязательно)
|
|
264
|
+
--python \ # Генерировать Python клиент
|
|
265
|
+
--typescript \ # Генерировать TypeScript клиент
|
|
266
|
+
--go \ # Генерировать Go клиент
|
|
267
|
+
--all \ # Генерировать все клиенты
|
|
268
|
+
--router-path myapp.router \ # Кастомный router (опционально)
|
|
269
|
+
--verbose # Verbose вывод
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Примеры
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
# Все языки с verbose
|
|
276
|
+
python manage.py generate_centrifugo_clients -o ./sdk --all --verbose
|
|
277
|
+
|
|
278
|
+
# Только Python и TypeScript
|
|
279
|
+
python manage.py generate_centrifugo_clients -o ./sdk --python --typescript
|
|
280
|
+
|
|
281
|
+
# Кастомный router
|
|
282
|
+
python manage.py generate_centrifugo_clients \
|
|
283
|
+
-o ./sdk \
|
|
284
|
+
--all \
|
|
285
|
+
--router-path myapp.custom_router.router
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Вывод
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
Centrifugo Client Code Generation
|
|
292
|
+
============================================================
|
|
293
|
+
Using global MessageRouter
|
|
294
|
+
|
|
295
|
+
Discovering RPC methods...
|
|
296
|
+
Found 3 RPC methods
|
|
297
|
+
- tasks.get_stats: TaskStatsParams -> TaskStats
|
|
298
|
+
- users.get_profile: UserProfileParams -> UserProfile
|
|
299
|
+
- notifications.send: NotificationParams -> NotificationResult
|
|
300
|
+
|
|
301
|
+
Output directory: ./clients
|
|
302
|
+
|
|
303
|
+
Generating Python client...
|
|
304
|
+
✓ Generated at: ./clients/python
|
|
305
|
+
|
|
306
|
+
Generating TypeScript client...
|
|
307
|
+
✓ Generated at: ./clients/typescript
|
|
308
|
+
|
|
309
|
+
Generating Go client...
|
|
310
|
+
✓ Generated at: ./clients/go
|
|
311
|
+
|
|
312
|
+
============================================================
|
|
313
|
+
Successfully generated 3 client(s): Python, TypeScript, Go
|
|
314
|
+
|
|
315
|
+
Next steps:
|
|
316
|
+
cd ./clients/python && pip install -r requirements.txt
|
|
317
|
+
cd ./clients/typescript && npm install
|
|
318
|
+
cd ./clients/go && go mod tidy
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Advanced Usage
|
|
322
|
+
|
|
323
|
+
### Кастомный Router
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
# myapp/routers.py
|
|
327
|
+
from django_cfg.apps.centrifugo.router import MessageRouter
|
|
328
|
+
|
|
329
|
+
custom_router = MessageRouter()
|
|
330
|
+
|
|
331
|
+
@custom_router.register("custom.method")
|
|
332
|
+
async def custom_handler(conn, params):
|
|
333
|
+
return {"result": "custom"}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Затем:
|
|
337
|
+
```bash
|
|
338
|
+
python manage.py generate_centrifugo_clients \
|
|
339
|
+
--output ./clients \
|
|
340
|
+
--all \
|
|
341
|
+
--router-path myapp.routers.custom_router
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Optional Fields
|
|
345
|
+
|
|
346
|
+
Pydantic опциональные поля → nullable в клиентах:
|
|
347
|
+
|
|
348
|
+
```python
|
|
349
|
+
class Params(BaseModel):
|
|
350
|
+
required: str
|
|
351
|
+
optional: Optional[str] = None
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
TypeScript:
|
|
355
|
+
```typescript
|
|
356
|
+
interface Params {
|
|
357
|
+
required: string;
|
|
358
|
+
optional?: string | null;
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Go:
|
|
363
|
+
```go
|
|
364
|
+
type Params struct {
|
|
365
|
+
Required string `json:"required"`
|
|
366
|
+
Optional *string `json:"optional"`
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Nested Models
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
class Address(BaseModel):
|
|
374
|
+
city: str
|
|
375
|
+
country: str
|
|
376
|
+
|
|
377
|
+
class User(BaseModel):
|
|
378
|
+
name: str
|
|
379
|
+
address: Address
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Автоматически генерируются все nested модели.
|
|
383
|
+
|
|
384
|
+
### List/Dict Fields
|
|
385
|
+
|
|
386
|
+
```python
|
|
387
|
+
class Data(BaseModel):
|
|
388
|
+
tags: List[str]
|
|
389
|
+
metadata: Dict[str, Any]
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
TypeScript:
|
|
393
|
+
```typescript
|
|
394
|
+
interface Data {
|
|
395
|
+
tags: string[];
|
|
396
|
+
metadata: { [key: string]: any };
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Go:
|
|
401
|
+
```go
|
|
402
|
+
type Data struct {
|
|
403
|
+
Tags []string `json:"tags"`
|
|
404
|
+
Metadata map[string]interface{} `json:"metadata"`
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## Type Conversion
|
|
409
|
+
|
|
410
|
+
### Python → TypeScript
|
|
411
|
+
|
|
412
|
+
| Python | TypeScript |
|
|
413
|
+
|-----------------------|-------------------------|
|
|
414
|
+
| `str` | `string` |
|
|
415
|
+
| `int`, `float` | `number` |
|
|
416
|
+
| `bool` | `boolean` |
|
|
417
|
+
| `List[T]` | `T[]` |
|
|
418
|
+
| `Dict[str, T]` | `{ [key: string]: T }` |
|
|
419
|
+
| `Optional[T]` | `T \| null` |
|
|
420
|
+
| `datetime` | `string` (ISO 8601) |
|
|
421
|
+
| `BaseModel` | `interface` |
|
|
422
|
+
|
|
423
|
+
### Python → Go
|
|
424
|
+
|
|
425
|
+
| Python | Go |
|
|
426
|
+
|-----------------------|-------------------------|
|
|
427
|
+
| `str` | `string` |
|
|
428
|
+
| `int` | `int64` |
|
|
429
|
+
| `float` | `float64` |
|
|
430
|
+
| `bool` | `bool` |
|
|
431
|
+
| `List[T]` | `[]T` |
|
|
432
|
+
| `Dict[str, T]` | `map[string]T` |
|
|
433
|
+
| `Optional[T]` | `*T` |
|
|
434
|
+
| `datetime` | `time.Time` |
|
|
435
|
+
| `BaseModel` | `struct` |
|
|
436
|
+
|
|
437
|
+
## Best Practices
|
|
438
|
+
|
|
439
|
+
### 1. Используйте Field с description
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
class Params(BaseModel):
|
|
443
|
+
user_id: str = Field(..., description="User ID to fetch")
|
|
444
|
+
limit: int = Field(10, description="Maximum results")
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Генерирует комментарии в клиентах:
|
|
448
|
+
```typescript
|
|
449
|
+
interface Params {
|
|
450
|
+
/** User ID to fetch */
|
|
451
|
+
user_id: string;
|
|
452
|
+
/** Maximum results */
|
|
453
|
+
limit?: number;
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### 2. Добавляйте Docstrings
|
|
458
|
+
|
|
459
|
+
```python
|
|
460
|
+
@websocket_rpc("users.get")
|
|
461
|
+
async def get_user(conn, params: GetUserParams) -> User:
|
|
462
|
+
"""
|
|
463
|
+
Get user by ID.
|
|
464
|
+
|
|
465
|
+
Retrieves full user profile including permissions and settings.
|
|
466
|
+
"""
|
|
467
|
+
...
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Docstring попадает в README клиентов.
|
|
471
|
+
|
|
472
|
+
### 3. Версионирование API
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
@websocket_rpc("users.v2.get")
|
|
476
|
+
async def get_user_v2(conn, params: GetUserParamsV2) -> UserV2:
|
|
477
|
+
"""Get user (API v2)."""
|
|
478
|
+
...
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### 4. Namespace Methods
|
|
482
|
+
|
|
483
|
+
```python
|
|
484
|
+
@websocket_rpc("tasks.list")
|
|
485
|
+
@websocket_rpc("tasks.create")
|
|
486
|
+
@websocket_rpc("tasks.update")
|
|
487
|
+
@websocket_rpc("tasks.delete")
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Генерирует:
|
|
491
|
+
```python
|
|
492
|
+
api.tasks_list(...)
|
|
493
|
+
api.tasks_create(...)
|
|
494
|
+
api.tasks_update(...)
|
|
495
|
+
api.tasks_delete(...)
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### 5. Error Handling
|
|
499
|
+
|
|
500
|
+
```python
|
|
501
|
+
class ErrorResult(BaseModel):
|
|
502
|
+
error: str
|
|
503
|
+
code: str
|
|
504
|
+
|
|
505
|
+
@websocket_rpc("tasks.get")
|
|
506
|
+
async def get_task(conn, params: GetTaskParams) -> TaskResult:
|
|
507
|
+
if not task_exists(params.task_id):
|
|
508
|
+
raise ValueError("Task not found")
|
|
509
|
+
return TaskResult(...)
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Клиент получит exception:
|
|
513
|
+
```python
|
|
514
|
+
try:
|
|
515
|
+
result = await api.tasks_get(params)
|
|
516
|
+
except Exception as e:
|
|
517
|
+
print(f"Error: {e}")
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
## Troubleshooting
|
|
521
|
+
|
|
522
|
+
### No RPC methods found
|
|
523
|
+
|
|
524
|
+
Проблема: `Found 0 RPC methods`
|
|
525
|
+
|
|
526
|
+
Решение:
|
|
527
|
+
1. Проверьте что handlers импортированы в `AppConfig.ready()`
|
|
528
|
+
2. Проверьте что используется `@websocket_rpc` декоратор
|
|
529
|
+
3. Проверьте type hints на параметрах и return
|
|
530
|
+
|
|
531
|
+
### Type conversion errors
|
|
532
|
+
|
|
533
|
+
Проблема: Неправильная конвертация типов
|
|
534
|
+
|
|
535
|
+
Решение:
|
|
536
|
+
- Используйте только Pydantic BaseModel для params/result
|
|
537
|
+
- Избегайте Union types (кроме Optional)
|
|
538
|
+
- Используйте стандартные типы (str, int, float, bool, List, Dict)
|
|
539
|
+
|
|
540
|
+
### Import errors in generated code
|
|
541
|
+
|
|
542
|
+
Проблема: Generated code не импортирует правильно
|
|
543
|
+
|
|
544
|
+
Решение:
|
|
545
|
+
- Убедитесь что все Pydantic модели имеют уникальные имена
|
|
546
|
+
- Проверьте что nested модели тоже BaseModel
|
|
547
|
+
- Regenerate clients после изменения моделей
|
|
548
|
+
|
|
549
|
+
## Примеры
|
|
550
|
+
|
|
551
|
+
### Real-Time Notifications
|
|
552
|
+
|
|
553
|
+
```python
|
|
554
|
+
# Handler
|
|
555
|
+
@websocket_rpc("notifications.subscribe")
|
|
556
|
+
async def subscribe_notifications(conn, params: SubscribeParams) -> SubscriptionResult:
|
|
557
|
+
"""Subscribe to user notifications."""
|
|
558
|
+
# Subscribe logic
|
|
559
|
+
return SubscriptionResult(subscription_id="sub-123")
|
|
560
|
+
|
|
561
|
+
# Python client
|
|
562
|
+
result = await api.notifications_subscribe(
|
|
563
|
+
SubscribeParams(user_id="user-123", topics=["orders", "messages"])
|
|
564
|
+
)
|
|
565
|
+
print(f"Subscribed: {result.subscription_id}")
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Batch Operations
|
|
569
|
+
|
|
570
|
+
```python
|
|
571
|
+
# Handler
|
|
572
|
+
class BatchTaskParams(BaseModel):
|
|
573
|
+
task_ids: List[str]
|
|
574
|
+
|
|
575
|
+
class BatchTaskResult(BaseModel):
|
|
576
|
+
results: List[TaskStatus]
|
|
577
|
+
|
|
578
|
+
@websocket_rpc("tasks.batch_status")
|
|
579
|
+
async def batch_task_status(conn, params: BatchTaskParams) -> BatchTaskResult:
|
|
580
|
+
"""Get status for multiple tasks."""
|
|
581
|
+
statuses = [get_task_status(tid) for tid in params.task_ids]
|
|
582
|
+
return BatchTaskResult(results=statuses)
|
|
583
|
+
|
|
584
|
+
# TypeScript client
|
|
585
|
+
const result = await api.tasksBatchStatus({
|
|
586
|
+
task_ids: ['task-1', 'task-2', 'task-3']
|
|
587
|
+
});
|
|
588
|
+
result.results.forEach(status => console.log(status));
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Streaming Data
|
|
592
|
+
|
|
593
|
+
```python
|
|
594
|
+
# Handler
|
|
595
|
+
@websocket_rpc("data.stream")
|
|
596
|
+
async def stream_data(conn, params: StreamParams) -> StreamResult:
|
|
597
|
+
"""Start data stream."""
|
|
598
|
+
# Initialize stream
|
|
599
|
+
return StreamResult(stream_id="stream-123", channel="data.stream.123")
|
|
600
|
+
|
|
601
|
+
# Go client
|
|
602
|
+
result, err := api.DataStream(ctx, StreamParams{
|
|
603
|
+
Source: "sensors",
|
|
604
|
+
Limit: 1000,
|
|
605
|
+
})
|
|
606
|
+
if err != nil {
|
|
607
|
+
log.Fatal(err)
|
|
608
|
+
}
|
|
609
|
+
fmt.Printf("Stream ID: %s, Channel: %s\n", result.StreamId, result.Channel)
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
## См. также
|
|
613
|
+
|
|
614
|
+
- [Centrifugo Documentation](https://centrifugal.dev/)
|
|
615
|
+
- [Pydantic Documentation](https://docs.pydantic.dev/)
|
|
616
|
+
- [django-ipc (original inspiration)](../../../django-ipc/)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Centrifugo WebSocket Client Code Generation.
|
|
3
|
+
|
|
4
|
+
Generates type-safe Python, TypeScript, and Go clients from Pydantic models.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .discovery import (
|
|
8
|
+
RPCMethodInfo,
|
|
9
|
+
discover_rpc_methods_from_router,
|
|
10
|
+
extract_all_models,
|
|
11
|
+
get_method_summary,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"RPCMethodInfo",
|
|
16
|
+
"discover_rpc_methods_from_router",
|
|
17
|
+
"extract_all_models",
|
|
18
|
+
"get_method_summary",
|
|
19
|
+
]
|