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,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Centrifugo Log Model.
|
|
3
|
+
|
|
4
|
+
Django model for tracking Centrifugo publish operations.
|
|
5
|
+
Mirrors django-ipc RPCLog patterns for easy migration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from django.conf import settings
|
|
9
|
+
from django.db import models
|
|
10
|
+
from django.utils import timezone
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CentrifugoLog(models.Model):
|
|
14
|
+
"""
|
|
15
|
+
Log of Centrifugo publish operations.
|
|
16
|
+
|
|
17
|
+
Tracks all publish calls with ACK tracking, mirroring RPCLog functionality.
|
|
18
|
+
Provides observability for debugging and monitoring.
|
|
19
|
+
|
|
20
|
+
Fields mirror RPCLog for migration compatibility:
|
|
21
|
+
- correlation_id → message_id
|
|
22
|
+
- method → channel
|
|
23
|
+
- params → data
|
|
24
|
+
- status → status
|
|
25
|
+
- duration_ms → duration_ms
|
|
26
|
+
- is_event → is_notification
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
>>> log = CentrifugoLog.objects.create(
|
|
30
|
+
... message_id="abc123",
|
|
31
|
+
... channel="user#456",
|
|
32
|
+
... data={"title": "Hello", "message": "World"},
|
|
33
|
+
... wait_for_ack=True
|
|
34
|
+
... )
|
|
35
|
+
>>> log.mark_success(acks_received=1, duration_ms=125)
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
# Custom manager
|
|
39
|
+
from ..managers.centrifugo_log import CentrifugoLogManager
|
|
40
|
+
|
|
41
|
+
objects: CentrifugoLogManager = CentrifugoLogManager()
|
|
42
|
+
|
|
43
|
+
class StatusChoices(models.TextChoices):
|
|
44
|
+
"""Status of publish operation."""
|
|
45
|
+
|
|
46
|
+
PENDING = "pending", "Pending"
|
|
47
|
+
SUCCESS = "success", "Success"
|
|
48
|
+
FAILED = "failed", "Failed"
|
|
49
|
+
TIMEOUT = "timeout", "Timeout"
|
|
50
|
+
PARTIAL = "partial", "Partial Delivery" # Some ACKs received, not all
|
|
51
|
+
|
|
52
|
+
# Identity
|
|
53
|
+
message_id = models.CharField(
|
|
54
|
+
max_length=100,
|
|
55
|
+
unique=True,
|
|
56
|
+
db_index=True,
|
|
57
|
+
help_text="Unique message identifier (UUID)",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Publish details
|
|
61
|
+
channel = models.CharField(
|
|
62
|
+
max_length=200,
|
|
63
|
+
db_index=True,
|
|
64
|
+
help_text="Centrifugo channel (e.g., user#123, broadcast)",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
data = models.JSONField(
|
|
68
|
+
help_text="Published data (JSON payload)",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# ACK tracking
|
|
72
|
+
wait_for_ack = models.BooleanField(
|
|
73
|
+
default=False,
|
|
74
|
+
db_index=True,
|
|
75
|
+
help_text="Whether this publish waited for ACK",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
ack_timeout = models.IntegerField(
|
|
79
|
+
null=True,
|
|
80
|
+
blank=True,
|
|
81
|
+
help_text="ACK timeout in seconds",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
acks_received = models.IntegerField(
|
|
85
|
+
default=0,
|
|
86
|
+
help_text="Number of ACKs received",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
acks_expected = models.IntegerField(
|
|
90
|
+
null=True,
|
|
91
|
+
blank=True,
|
|
92
|
+
help_text="Number of ACKs expected (if known)",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Status tracking
|
|
96
|
+
status = models.CharField(
|
|
97
|
+
max_length=20,
|
|
98
|
+
choices=StatusChoices.choices,
|
|
99
|
+
default=StatusChoices.PENDING,
|
|
100
|
+
db_index=True,
|
|
101
|
+
help_text="Current status of publish operation",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
error_code = models.CharField(
|
|
105
|
+
max_length=100,
|
|
106
|
+
null=True,
|
|
107
|
+
blank=True,
|
|
108
|
+
help_text="Error code if failed",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
error_message = models.TextField(
|
|
112
|
+
null=True,
|
|
113
|
+
blank=True,
|
|
114
|
+
help_text="Error message if failed",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Performance
|
|
118
|
+
duration_ms = models.IntegerField(
|
|
119
|
+
null=True,
|
|
120
|
+
blank=True,
|
|
121
|
+
help_text="Total duration in milliseconds",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Metadata
|
|
125
|
+
is_notification = models.BooleanField(
|
|
126
|
+
default=True,
|
|
127
|
+
db_index=True,
|
|
128
|
+
help_text="Whether this is a notification (vs other pub type)",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
user = models.ForeignKey(
|
|
132
|
+
settings.AUTH_USER_MODEL,
|
|
133
|
+
on_delete=models.SET_NULL,
|
|
134
|
+
null=True,
|
|
135
|
+
blank=True,
|
|
136
|
+
related_name="centrifugo_logs",
|
|
137
|
+
help_text="User who triggered the publish (if applicable)",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
caller_ip = models.GenericIPAddressField(
|
|
141
|
+
null=True,
|
|
142
|
+
blank=True,
|
|
143
|
+
help_text="IP address of caller",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
user_agent = models.TextField(
|
|
147
|
+
null=True,
|
|
148
|
+
blank=True,
|
|
149
|
+
help_text="User agent of caller",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Timestamps
|
|
153
|
+
created_at = models.DateTimeField(
|
|
154
|
+
auto_now_add=True,
|
|
155
|
+
db_index=True,
|
|
156
|
+
help_text="When publish was initiated",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
completed_at = models.DateTimeField(
|
|
160
|
+
null=True,
|
|
161
|
+
blank=True,
|
|
162
|
+
db_index=True,
|
|
163
|
+
help_text="When publish completed (success/failure/timeout)",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
class Meta:
|
|
167
|
+
db_table = "django_cfg_centrifugo_log"
|
|
168
|
+
ordering = ["-created_at"]
|
|
169
|
+
indexes = [
|
|
170
|
+
models.Index(fields=["channel", "-created_at"]),
|
|
171
|
+
models.Index(fields=["status", "-created_at"]),
|
|
172
|
+
models.Index(fields=["wait_for_ack", "status"]),
|
|
173
|
+
models.Index(fields=["user", "-created_at"]),
|
|
174
|
+
]
|
|
175
|
+
verbose_name = "Centrifugo Log"
|
|
176
|
+
verbose_name_plural = "Centrifugo Logs"
|
|
177
|
+
|
|
178
|
+
def __str__(self) -> str:
|
|
179
|
+
"""String representation."""
|
|
180
|
+
return f"{self.channel} ({self.message_id[:8]}...) - {self.status}"
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def is_completed(self) -> bool:
|
|
184
|
+
"""Check if publish is completed (any terminal status)."""
|
|
185
|
+
return self.status in [
|
|
186
|
+
self.StatusChoices.SUCCESS,
|
|
187
|
+
self.StatusChoices.FAILED,
|
|
188
|
+
self.StatusChoices.TIMEOUT,
|
|
189
|
+
self.StatusChoices.PARTIAL,
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def is_successful(self) -> bool:
|
|
194
|
+
"""Check if publish was successful."""
|
|
195
|
+
return self.status == self.StatusChoices.SUCCESS
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def delivery_rate(self) -> float | None:
|
|
199
|
+
"""
|
|
200
|
+
Calculate delivery rate (ACKs received / expected).
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Delivery rate (0.0 to 1.0) or None if unknown
|
|
204
|
+
"""
|
|
205
|
+
if self.acks_expected and self.acks_expected > 0:
|
|
206
|
+
return self.acks_received / self.acks_expected
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
__all__ = ["CentrifugoLog"]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Global registry for RPC handlers.
|
|
3
|
+
|
|
4
|
+
Stores metadata about registered handlers for code generation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Dict, List, Any, Callable, Optional, Type
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class RegisteredHandler:
|
|
17
|
+
"""
|
|
18
|
+
Metadata about registered RPC handler.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
name: RPC method name (e.g., "tasks.get_stats")
|
|
22
|
+
handler: Handler function
|
|
23
|
+
param_type: Pydantic model for parameters
|
|
24
|
+
return_type: Pydantic model for return value
|
|
25
|
+
docstring: Handler documentation
|
|
26
|
+
"""
|
|
27
|
+
name: str
|
|
28
|
+
handler: Callable
|
|
29
|
+
param_type: Optional[Type[BaseModel]]
|
|
30
|
+
return_type: Optional[Type[BaseModel]]
|
|
31
|
+
docstring: Optional[str]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RPCRegistry:
|
|
35
|
+
"""
|
|
36
|
+
Global registry for RPC handlers.
|
|
37
|
+
|
|
38
|
+
Used by @websocket_rpc decorator to register handlers
|
|
39
|
+
and by codegen to discover available methods.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self):
|
|
43
|
+
self._handlers: Dict[str, RegisteredHandler] = {}
|
|
44
|
+
|
|
45
|
+
def register(
|
|
46
|
+
self,
|
|
47
|
+
name: str,
|
|
48
|
+
handler: Callable,
|
|
49
|
+
param_type: Optional[Type[BaseModel]] = None,
|
|
50
|
+
return_type: Optional[Type[BaseModel]] = None,
|
|
51
|
+
docstring: Optional[str] = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Register RPC handler.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
name: RPC method name (e.g., "tasks.get_stats")
|
|
58
|
+
handler: Handler function
|
|
59
|
+
param_type: Pydantic model for parameters
|
|
60
|
+
return_type: Pydantic model for return value
|
|
61
|
+
docstring: Handler documentation
|
|
62
|
+
"""
|
|
63
|
+
if name in self._handlers:
|
|
64
|
+
logger.warning(f"Handler '{name}' already registered, overwriting")
|
|
65
|
+
|
|
66
|
+
self._handlers[name] = RegisteredHandler(
|
|
67
|
+
name=name,
|
|
68
|
+
handler=handler,
|
|
69
|
+
param_type=param_type,
|
|
70
|
+
return_type=return_type,
|
|
71
|
+
docstring=docstring,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
logger.debug(f"Registered RPC handler: {name}")
|
|
75
|
+
|
|
76
|
+
def get_handler(self, name: str) -> Optional[RegisteredHandler]:
|
|
77
|
+
"""Get handler by name."""
|
|
78
|
+
return self._handlers.get(name)
|
|
79
|
+
|
|
80
|
+
def get_all_handlers(self) -> List[RegisteredHandler]:
|
|
81
|
+
"""Get all registered handlers."""
|
|
82
|
+
return list(self._handlers.values())
|
|
83
|
+
|
|
84
|
+
def list_methods(self) -> List[str]:
|
|
85
|
+
"""List all registered method names."""
|
|
86
|
+
return list(self._handlers.keys())
|
|
87
|
+
|
|
88
|
+
def clear(self) -> None:
|
|
89
|
+
"""Clear all registered handlers (for testing)."""
|
|
90
|
+
self._handlers.clear()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Global registry instance
|
|
94
|
+
_global_registry = RPCRegistry()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_global_registry() -> RPCRegistry:
|
|
98
|
+
"""Get global RPC registry instance."""
|
|
99
|
+
return _global_registry
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
__all__ = [
|
|
103
|
+
"RegisteredHandler",
|
|
104
|
+
"RPCRegistry",
|
|
105
|
+
"get_global_registry",
|
|
106
|
+
]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MessageRouter for Centrifugo RPC handlers.
|
|
3
|
+
|
|
4
|
+
Compatible with django-ipc's MessageRouter interface for code generation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Dict, Callable, Any
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MessageRouter:
|
|
14
|
+
"""
|
|
15
|
+
Message router for Centrifugo RPC handlers.
|
|
16
|
+
|
|
17
|
+
Provides django-ipc compatible interface for handler registration
|
|
18
|
+
and discovery by codegen system.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
"""Initialize message router."""
|
|
23
|
+
self._handlers: Dict[str, Callable] = {}
|
|
24
|
+
|
|
25
|
+
def register(self, method_name: str):
|
|
26
|
+
"""
|
|
27
|
+
Decorator to register RPC handler.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
method_name: RPC method name (e.g., "tasks.get_stats")
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Decorator function
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> router = MessageRouter()
|
|
37
|
+
>>>
|
|
38
|
+
>>> @router.register("tasks.get_stats")
|
|
39
|
+
>>> async def handle_get_stats(conn, params: TaskStatsParams) -> TaskStatsResult:
|
|
40
|
+
... return TaskStatsResult(total=100, completed=50)
|
|
41
|
+
"""
|
|
42
|
+
def decorator(handler_func: Callable) -> Callable:
|
|
43
|
+
if method_name in self._handlers:
|
|
44
|
+
logger.warning(f"Handler '{method_name}' already registered, overwriting")
|
|
45
|
+
|
|
46
|
+
self._handlers[method_name] = handler_func
|
|
47
|
+
logger.debug(f"Registered handler: {method_name}")
|
|
48
|
+
|
|
49
|
+
return handler_func
|
|
50
|
+
|
|
51
|
+
return decorator
|
|
52
|
+
|
|
53
|
+
def get_handler(self, method_name: str) -> Callable:
|
|
54
|
+
"""
|
|
55
|
+
Get handler by method name.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
method_name: RPC method name
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Handler function
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
KeyError: If handler not found
|
|
65
|
+
"""
|
|
66
|
+
return self._handlers[method_name]
|
|
67
|
+
|
|
68
|
+
def has_handler(self, method_name: str) -> bool:
|
|
69
|
+
"""
|
|
70
|
+
Check if handler exists.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
method_name: RPC method name
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
True if handler registered
|
|
77
|
+
"""
|
|
78
|
+
return method_name in self._handlers
|
|
79
|
+
|
|
80
|
+
def list_methods(self) -> list[str]:
|
|
81
|
+
"""
|
|
82
|
+
List all registered method names.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
List of method names
|
|
86
|
+
"""
|
|
87
|
+
return list(self._handlers.keys())
|
|
88
|
+
|
|
89
|
+
async def handle_message(self, method_name: str, params: Any, conn: Any = None) -> Any:
|
|
90
|
+
"""
|
|
91
|
+
Handle RPC message.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
method_name: RPC method name
|
|
95
|
+
params: Method parameters
|
|
96
|
+
conn: Connection object (optional)
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Handler result
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
KeyError: If handler not found
|
|
103
|
+
"""
|
|
104
|
+
handler = self.get_handler(method_name)
|
|
105
|
+
|
|
106
|
+
# Call handler with or without connection
|
|
107
|
+
if conn is not None:
|
|
108
|
+
return await handler(conn, params)
|
|
109
|
+
else:
|
|
110
|
+
return await handler(params)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# Global router instance
|
|
114
|
+
_global_router = MessageRouter()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def get_global_router() -> MessageRouter:
|
|
118
|
+
"""Get global MessageRouter instance."""
|
|
119
|
+
return _global_router
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
__all__ = [
|
|
123
|
+
"MessageRouter",
|
|
124
|
+
"get_global_router",
|
|
125
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Serializers for Centrifugo module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .admin_api import (
|
|
6
|
+
CentrifugoChannelsRequest,
|
|
7
|
+
CentrifugoChannelsResponse,
|
|
8
|
+
CentrifugoHistoryRequest,
|
|
9
|
+
CentrifugoHistoryResponse,
|
|
10
|
+
CentrifugoInfoRequest,
|
|
11
|
+
CentrifugoInfoResponse,
|
|
12
|
+
CentrifugoPresenceRequest,
|
|
13
|
+
CentrifugoPresenceResponse,
|
|
14
|
+
CentrifugoPresenceStatsRequest,
|
|
15
|
+
CentrifugoPresenceStatsResponse,
|
|
16
|
+
)
|
|
17
|
+
from .channels import ChannelListSerializer, ChannelStatsSerializer
|
|
18
|
+
from .health import HealthCheckSerializer
|
|
19
|
+
from .publishes import RecentPublishesSerializer
|
|
20
|
+
from .stats import OverviewStatsSerializer
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
# Monitoring API (Django logs)
|
|
24
|
+
"HealthCheckSerializer",
|
|
25
|
+
"OverviewStatsSerializer",
|
|
26
|
+
"RecentPublishesSerializer",
|
|
27
|
+
"ChannelStatsSerializer",
|
|
28
|
+
"ChannelListSerializer",
|
|
29
|
+
# Admin API (Centrifugo server)
|
|
30
|
+
"CentrifugoInfoRequest",
|
|
31
|
+
"CentrifugoInfoResponse",
|
|
32
|
+
"CentrifugoChannelsRequest",
|
|
33
|
+
"CentrifugoChannelsResponse",
|
|
34
|
+
"CentrifugoPresenceRequest",
|
|
35
|
+
"CentrifugoPresenceResponse",
|
|
36
|
+
"CentrifugoPresenceStatsRequest",
|
|
37
|
+
"CentrifugoPresenceStatsResponse",
|
|
38
|
+
"CentrifugoHistoryRequest",
|
|
39
|
+
"CentrifugoHistoryResponse",
|
|
40
|
+
]
|