django-cfg 1.5.14__py3-none-any.whl → 1.5.29__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/business/accounts/serializers/profile.py +42 -0
- django_cfg/apps/business/support/serializers.py +3 -2
- django_cfg/apps/integrations/centrifugo/__init__.py +2 -0
- django_cfg/apps/integrations/centrifugo/apps.py +2 -1
- django_cfg/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +151 -12
- django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +2 -2
- django_cfg/apps/integrations/centrifugo/services/__init__.py +6 -0
- django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
- django_cfg/apps/integrations/centrifugo/services/client/client.py +1 -1
- django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -0
- django_cfg/apps/integrations/centrifugo/services/logging.py +47 -0
- django_cfg/apps/integrations/centrifugo/services/publisher.py +371 -0
- django_cfg/apps/integrations/centrifugo/services/token_generator.py +122 -0
- django_cfg/apps/integrations/centrifugo/urls.py +8 -0
- django_cfg/apps/integrations/centrifugo/views/__init__.py +2 -0
- django_cfg/apps/integrations/centrifugo/views/admin_api.py +29 -32
- django_cfg/apps/integrations/centrifugo/views/testing_api.py +31 -116
- django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
- django_cfg/apps/integrations/centrifugo/views/wrapper.py +259 -0
- django_cfg/apps/integrations/grpc/auth/api_key_auth.py +11 -10
- django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +56 -1
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +315 -26
- django_cfg/apps/integrations/grpc/management/proto/__init__.py +3 -0
- django_cfg/apps/integrations/grpc/management/proto/compiler.py +194 -0
- django_cfg/apps/integrations/grpc/managers/grpc_request_log.py +84 -0
- django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +126 -3
- django_cfg/apps/integrations/grpc/models/grpc_api_key.py +7 -1
- django_cfg/apps/integrations/grpc/models/grpc_server_status.py +22 -3
- django_cfg/apps/integrations/grpc/services/__init__.py +102 -17
- django_cfg/apps/integrations/grpc/services/centrifugo/__init__.py +29 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/bridge.py +469 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/config.py +167 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/demo.py +626 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/test_publish.py +229 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/transformers.py +89 -0
- django_cfg/apps/integrations/grpc/services/client/__init__.py +26 -0
- django_cfg/apps/integrations/grpc/services/commands/IMPLEMENTATION.md +456 -0
- django_cfg/apps/integrations/grpc/services/commands/README.md +252 -0
- django_cfg/apps/integrations/grpc/services/commands/__init__.py +93 -0
- django_cfg/apps/integrations/grpc/services/commands/base.py +243 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/__init__.py +22 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/base_client.py +228 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/client.py +272 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/config.py +177 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/start.py +125 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/stop.py +101 -0
- django_cfg/apps/integrations/grpc/services/commands/registry.py +170 -0
- django_cfg/apps/integrations/grpc/services/discovery/__init__.py +39 -0
- django_cfg/apps/integrations/grpc/services/{discovery.py → discovery/discovery.py} +67 -54
- django_cfg/apps/integrations/grpc/services/{service_registry.py → discovery/registry.py} +215 -5
- django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/__init__.py +3 -1
- django_cfg/apps/integrations/grpc/services/interceptors/centrifugo.py +541 -0
- django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/metrics.py +3 -3
- django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/request_logger.py +10 -13
- django_cfg/apps/integrations/grpc/services/management/__init__.py +37 -0
- django_cfg/apps/integrations/grpc/services/monitoring/__init__.py +38 -0
- django_cfg/apps/integrations/grpc/services/{monitoring_service.py → monitoring/monitoring.py} +2 -2
- django_cfg/apps/integrations/grpc/services/{testing_service.py → monitoring/testing.py} +5 -5
- django_cfg/apps/integrations/grpc/services/rendering/__init__.py +27 -0
- django_cfg/apps/integrations/grpc/services/{chart_generator.py → rendering/charts.py} +1 -1
- django_cfg/apps/integrations/grpc/services/routing/__init__.py +59 -0
- django_cfg/apps/integrations/grpc/services/routing/config.py +76 -0
- django_cfg/apps/integrations/grpc/services/routing/router.py +430 -0
- django_cfg/apps/integrations/grpc/services/streaming/__init__.py +117 -0
- django_cfg/apps/integrations/grpc/services/streaming/config.py +451 -0
- django_cfg/apps/integrations/grpc/services/streaming/service.py +651 -0
- django_cfg/apps/integrations/grpc/services/streaming/types.py +367 -0
- django_cfg/apps/integrations/grpc/utils/SERVER_LOGGING.md +164 -0
- django_cfg/apps/integrations/grpc/utils/__init__.py +58 -1
- django_cfg/apps/integrations/grpc/utils/converters.py +565 -0
- django_cfg/apps/integrations/grpc/utils/handlers.py +242 -0
- django_cfg/apps/integrations/grpc/utils/proto_gen.py +1 -1
- django_cfg/apps/integrations/grpc/utils/streaming_logger.py +261 -13
- django_cfg/apps/integrations/grpc/views/charts.py +1 -1
- django_cfg/apps/integrations/grpc/views/config.py +1 -1
- django_cfg/apps/system/dashboard/serializers/config.py +95 -9
- django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
- django_cfg/apps/system/frontend/views.py +87 -6
- django_cfg/core/base/config_model.py +11 -0
- django_cfg/core/builders/middleware_builder.py +5 -0
- django_cfg/core/builders/security_builder.py +1 -0
- django_cfg/core/generation/integration_generators/api.py +2 -0
- django_cfg/management/commands/pool_status.py +153 -0
- django_cfg/middleware/pool_cleanup.py +261 -0
- django_cfg/models/api/grpc/config.py +2 -2
- django_cfg/models/infrastructure/database/config.py +16 -0
- django_cfg/models/infrastructure/database/converters.py +2 -0
- django_cfg/modules/django_admin/utils/html/composition.py +57 -13
- django_cfg/modules/django_admin/utils/html_builder.py +1 -0
- django_cfg/modules/django_client/core/generator/typescript/generator.py +26 -0
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +7 -1
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +5 -0
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +11 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +1 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +29 -1
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +4 -0
- django_cfg/modules/django_client/core/groups/manager.py +25 -18
- django_cfg/modules/django_client/core/ir/schema.py +15 -1
- django_cfg/modules/django_client/core/parser/base.py +12 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +9 -5
- django_cfg/modules/django_logging/django_logger.py +58 -19
- django_cfg/pyproject.toml +3 -3
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/index.html +0 -39
- django_cfg/utils/pool_monitor.py +320 -0
- django_cfg/utils/smart_defaults.py +233 -7
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/METADATA +75 -5
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/RECORD +118 -74
- /django_cfg/apps/integrations/grpc/services/{grpc_client.py → client/client.py} +0 -0
- /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/errors.py +0 -0
- /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/logging.py +0 -0
- /django_cfg/apps/integrations/grpc/services/{config_helper.py → management/config_helper.py} +0 -0
- /django_cfg/apps/integrations/grpc/services/{proto_files_manager.py → management/proto_manager.py} +0 -0
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/licenses/LICENSE +0 -0
|
@@ -207,6 +207,48 @@ class GRPCRequestLogManager(models.Manager):
|
|
|
207
207
|
]
|
|
208
208
|
)
|
|
209
209
|
|
|
210
|
+
async def amark_success(
|
|
211
|
+
self,
|
|
212
|
+
log_instance,
|
|
213
|
+
duration_ms: int | None = None,
|
|
214
|
+
response_size: int | None = None,
|
|
215
|
+
response_data: dict | None = None,
|
|
216
|
+
):
|
|
217
|
+
"""
|
|
218
|
+
Mark request as successful (ASYNC - Django 5.2).
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
log_instance: GRPCRequestLog instance
|
|
222
|
+
duration_ms: Duration in milliseconds
|
|
223
|
+
response_size: Response size in bytes
|
|
224
|
+
response_data: Response data
|
|
225
|
+
"""
|
|
226
|
+
from ..models import GRPCRequestLog
|
|
227
|
+
|
|
228
|
+
log_instance.status = GRPCRequestLog.StatusChoices.SUCCESS
|
|
229
|
+
log_instance.grpc_status_code = "OK"
|
|
230
|
+
log_instance.completed_at = timezone.now()
|
|
231
|
+
|
|
232
|
+
if duration_ms is not None:
|
|
233
|
+
log_instance.duration_ms = duration_ms
|
|
234
|
+
|
|
235
|
+
if response_size is not None:
|
|
236
|
+
log_instance.response_size = response_size
|
|
237
|
+
|
|
238
|
+
if response_data is not None:
|
|
239
|
+
log_instance.response_data = response_data
|
|
240
|
+
|
|
241
|
+
await log_instance.asave(
|
|
242
|
+
update_fields=[
|
|
243
|
+
"status",
|
|
244
|
+
"grpc_status_code",
|
|
245
|
+
"completed_at",
|
|
246
|
+
"duration_ms",
|
|
247
|
+
"response_size",
|
|
248
|
+
"response_data",
|
|
249
|
+
]
|
|
250
|
+
)
|
|
251
|
+
|
|
210
252
|
def mark_error(
|
|
211
253
|
self,
|
|
212
254
|
log_instance,
|
|
@@ -249,6 +291,48 @@ class GRPCRequestLogManager(models.Manager):
|
|
|
249
291
|
]
|
|
250
292
|
)
|
|
251
293
|
|
|
294
|
+
async def amark_error(
|
|
295
|
+
self,
|
|
296
|
+
log_instance,
|
|
297
|
+
grpc_status_code: str,
|
|
298
|
+
error_message: str,
|
|
299
|
+
error_details: dict | None = None,
|
|
300
|
+
duration_ms: int | None = None,
|
|
301
|
+
):
|
|
302
|
+
"""
|
|
303
|
+
Mark request as failed (ASYNC - Django 5.2).
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
log_instance: GRPCRequestLog instance
|
|
307
|
+
grpc_status_code: gRPC status code
|
|
308
|
+
error_message: Error message
|
|
309
|
+
error_details: Additional error details
|
|
310
|
+
duration_ms: Duration in milliseconds
|
|
311
|
+
"""
|
|
312
|
+
from ..models import GRPCRequestLog
|
|
313
|
+
|
|
314
|
+
log_instance.status = GRPCRequestLog.StatusChoices.ERROR
|
|
315
|
+
log_instance.grpc_status_code = grpc_status_code
|
|
316
|
+
log_instance.error_message = error_message
|
|
317
|
+
log_instance.completed_at = timezone.now()
|
|
318
|
+
|
|
319
|
+
if error_details is not None:
|
|
320
|
+
log_instance.error_details = error_details
|
|
321
|
+
|
|
322
|
+
if duration_ms is not None:
|
|
323
|
+
log_instance.duration_ms = duration_ms
|
|
324
|
+
|
|
325
|
+
await log_instance.asave(
|
|
326
|
+
update_fields=[
|
|
327
|
+
"status",
|
|
328
|
+
"grpc_status_code",
|
|
329
|
+
"error_message",
|
|
330
|
+
"error_details",
|
|
331
|
+
"completed_at",
|
|
332
|
+
"duration_ms",
|
|
333
|
+
]
|
|
334
|
+
)
|
|
335
|
+
|
|
252
336
|
def mark_cancelled(
|
|
253
337
|
self,
|
|
254
338
|
log_instance,
|
|
@@ -32,7 +32,7 @@ class GRPCServerStatusManager(models.Manager):
|
|
|
32
32
|
enable_health_check: bool = True,
|
|
33
33
|
):
|
|
34
34
|
"""
|
|
35
|
-
Start tracking a new server instance.
|
|
35
|
+
Start tracking a new server instance (SYNC).
|
|
36
36
|
|
|
37
37
|
Args:
|
|
38
38
|
host: Server host address
|
|
@@ -88,9 +88,75 @@ class GRPCServerStatusManager(models.Manager):
|
|
|
88
88
|
|
|
89
89
|
return status
|
|
90
90
|
|
|
91
|
+
async def astart_server(
|
|
92
|
+
self,
|
|
93
|
+
host: str,
|
|
94
|
+
port: int,
|
|
95
|
+
pid: int = None,
|
|
96
|
+
max_workers: int = 10,
|
|
97
|
+
enable_reflection: bool = False,
|
|
98
|
+
enable_health_check: bool = True,
|
|
99
|
+
):
|
|
100
|
+
"""
|
|
101
|
+
Start tracking a new server instance (ASYNC - Django 5.2).
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
host: Server host address
|
|
105
|
+
port: Server port
|
|
106
|
+
pid: Process ID (defaults to current process)
|
|
107
|
+
max_workers: Maximum worker threads
|
|
108
|
+
enable_reflection: Whether reflection is enabled
|
|
109
|
+
enable_health_check: Whether health check is enabled
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
GRPCServerStatus instance
|
|
113
|
+
|
|
114
|
+
Example:
|
|
115
|
+
>>> status = await GRPCServerStatus.objects.astart_server(
|
|
116
|
+
... host="[::]",
|
|
117
|
+
... port=50051,
|
|
118
|
+
... pid=12345
|
|
119
|
+
... )
|
|
120
|
+
>>> status.is_running
|
|
121
|
+
True
|
|
122
|
+
|
|
123
|
+
Note:
|
|
124
|
+
External/internal server detection is automatic based on env_mode.
|
|
125
|
+
Production mode assumes external server (Docker), dev/test assumes local.
|
|
126
|
+
"""
|
|
127
|
+
if pid is None:
|
|
128
|
+
pid = os.getpid()
|
|
129
|
+
|
|
130
|
+
hostname = socket.gethostname()
|
|
131
|
+
address = f"{host}:{port}"
|
|
132
|
+
instance_id = f"{hostname}:{port}:{pid}"
|
|
133
|
+
|
|
134
|
+
# Mark any existing server at this address as stopped (async)
|
|
135
|
+
await self.astop_servers_at_address(address)
|
|
136
|
+
|
|
137
|
+
# Create or update server status (handles restart with same instance_id)
|
|
138
|
+
status, created = await self.aupdate_or_create(
|
|
139
|
+
instance_id=instance_id,
|
|
140
|
+
defaults={
|
|
141
|
+
"host": host,
|
|
142
|
+
"port": port,
|
|
143
|
+
"address": address,
|
|
144
|
+
"pid": pid,
|
|
145
|
+
"hostname": hostname,
|
|
146
|
+
"status": self.model.StatusChoices.STARTING,
|
|
147
|
+
"max_workers": max_workers,
|
|
148
|
+
"enable_reflection": enable_reflection,
|
|
149
|
+
"enable_health_check": enable_health_check,
|
|
150
|
+
"started_at": timezone.now(),
|
|
151
|
+
"last_heartbeat": timezone.now(),
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
return status
|
|
156
|
+
|
|
91
157
|
def get_current_server(self, address: str = None) -> Optional["GRPCServerStatus"]:
|
|
92
158
|
"""
|
|
93
|
-
Get the currently running server.
|
|
159
|
+
Get the currently running server (SYNC).
|
|
94
160
|
|
|
95
161
|
Args:
|
|
96
162
|
address: Optional address filter (host:port)
|
|
@@ -122,6 +188,40 @@ class GRPCServerStatusManager(models.Manager):
|
|
|
122
188
|
|
|
123
189
|
return None
|
|
124
190
|
|
|
191
|
+
async def aget_current_server(self, address: str = None) -> Optional["GRPCServerStatus"]:
|
|
192
|
+
"""
|
|
193
|
+
Get the currently running server (ASYNC - Django 5.2).
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
address: Optional address filter (host:port)
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
GRPCServerStatus instance or None
|
|
200
|
+
|
|
201
|
+
Example:
|
|
202
|
+
>>> server = await GRPCServerStatus.objects.aget_current_server()
|
|
203
|
+
>>> if server and server.is_running:
|
|
204
|
+
... print(f"Server running for {server.uptime_display}")
|
|
205
|
+
"""
|
|
206
|
+
queryset = self.filter(
|
|
207
|
+
status__in=[
|
|
208
|
+
self.model.StatusChoices.STARTING,
|
|
209
|
+
self.model.StatusChoices.RUNNING,
|
|
210
|
+
]
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if address:
|
|
214
|
+
queryset = queryset.filter(address=address)
|
|
215
|
+
|
|
216
|
+
# Get most recent (Django 5.2: afirst)
|
|
217
|
+
server = await queryset.order_by("-started_at").afirst()
|
|
218
|
+
|
|
219
|
+
# Verify it's actually running
|
|
220
|
+
if server and server.is_running:
|
|
221
|
+
return server
|
|
222
|
+
|
|
223
|
+
return None
|
|
224
|
+
|
|
125
225
|
def get_running_servers(self):
|
|
126
226
|
"""
|
|
127
227
|
Get all currently running servers.
|
|
@@ -183,7 +283,7 @@ class GRPCServerStatusManager(models.Manager):
|
|
|
183
283
|
|
|
184
284
|
def stop_servers_at_address(self, address: str):
|
|
185
285
|
"""
|
|
186
|
-
Stop all servers at a specific address.
|
|
286
|
+
Stop all servers at a specific address (SYNC).
|
|
187
287
|
|
|
188
288
|
Args:
|
|
189
289
|
address: Server address (host:port)
|
|
@@ -202,6 +302,29 @@ class GRPCServerStatusManager(models.Manager):
|
|
|
202
302
|
for server in servers:
|
|
203
303
|
server.mark_stopped("Replaced by new server instance")
|
|
204
304
|
|
|
305
|
+
async def astop_servers_at_address(self, address: str):
|
|
306
|
+
"""
|
|
307
|
+
Stop all servers at a specific address (ASYNC - Django 5.2).
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
address: Server address (host:port)
|
|
311
|
+
|
|
312
|
+
Example:
|
|
313
|
+
>>> await GRPCServerStatus.objects.astop_servers_at_address("[::]:50051")
|
|
314
|
+
"""
|
|
315
|
+
servers = []
|
|
316
|
+
async for server in self.filter(
|
|
317
|
+
address=address,
|
|
318
|
+
status__in=[
|
|
319
|
+
self.model.StatusChoices.STARTING,
|
|
320
|
+
self.model.StatusChoices.RUNNING,
|
|
321
|
+
],
|
|
322
|
+
):
|
|
323
|
+
servers.append(server)
|
|
324
|
+
|
|
325
|
+
for server in servers:
|
|
326
|
+
await server.amark_stopped("Replaced by new server instance")
|
|
327
|
+
|
|
205
328
|
def cleanup_stale_servers(self, hours: int = 24):
|
|
206
329
|
"""
|
|
207
330
|
Mark stale servers as stopped.
|
|
@@ -184,11 +184,17 @@ class GrpcApiKey(models.Model):
|
|
|
184
184
|
return f"{self.key[:4]}...{self.key[-4:]}"
|
|
185
185
|
|
|
186
186
|
def mark_used(self) -> None:
|
|
187
|
-
"""Mark this key as used (update last_used_at and increment counter)."""
|
|
187
|
+
"""Mark this key as used (update last_used_at and increment counter) (SYNC)."""
|
|
188
188
|
self.last_used_at = timezone.now()
|
|
189
189
|
self.request_count += 1
|
|
190
190
|
self.save(update_fields=["last_used_at", "request_count"])
|
|
191
191
|
|
|
192
|
+
async def amark_used(self) -> None:
|
|
193
|
+
"""Mark this key as used (update last_used_at and increment counter) (ASYNC - Django 5.2)."""
|
|
194
|
+
self.last_used_at = timezone.now()
|
|
195
|
+
self.request_count += 1
|
|
196
|
+
await self.asave(update_fields=["last_used_at", "request_count"])
|
|
197
|
+
|
|
192
198
|
def revoke(self) -> None:
|
|
193
199
|
"""Revoke this key (set is_active=False)."""
|
|
194
200
|
self.is_active = False
|
|
@@ -271,24 +271,43 @@ class GRPCServerStatus(models.Model):
|
|
|
271
271
|
return False
|
|
272
272
|
|
|
273
273
|
def mark_running(self):
|
|
274
|
-
"""Mark server as running."""
|
|
274
|
+
"""Mark server as running (SYNC)."""
|
|
275
275
|
self.status = self.StatusChoices.RUNNING
|
|
276
276
|
self.error_message = None
|
|
277
277
|
self.save(update_fields=["status", "error_message", "updated_at", "last_heartbeat"])
|
|
278
278
|
|
|
279
|
+
async def amark_running(self):
|
|
280
|
+
"""Mark server as running (ASYNC - Django 5.2)."""
|
|
281
|
+
self.status = self.StatusChoices.RUNNING
|
|
282
|
+
self.error_message = None
|
|
283
|
+
await self.asave(update_fields=["status", "error_message", "updated_at", "last_heartbeat"])
|
|
284
|
+
|
|
279
285
|
def mark_stopping(self):
|
|
280
|
-
"""Mark server as stopping."""
|
|
286
|
+
"""Mark server as stopping (SYNC)."""
|
|
281
287
|
self.status = self.StatusChoices.STOPPING
|
|
282
288
|
self.save(update_fields=["status", "updated_at", "last_heartbeat"])
|
|
283
289
|
|
|
290
|
+
async def amark_stopping(self):
|
|
291
|
+
"""Mark server as stopping (ASYNC - Django 5.2)."""
|
|
292
|
+
self.status = self.StatusChoices.STOPPING
|
|
293
|
+
await self.asave(update_fields=["status", "updated_at", "last_heartbeat"])
|
|
294
|
+
|
|
284
295
|
def mark_stopped(self, error_message: str = None):
|
|
285
|
-
"""Mark server as stopped."""
|
|
296
|
+
"""Mark server as stopped (SYNC)."""
|
|
286
297
|
self.status = self.StatusChoices.STOPPED
|
|
287
298
|
self.stopped_at = timezone.now()
|
|
288
299
|
if error_message:
|
|
289
300
|
self.error_message = error_message
|
|
290
301
|
self.save(update_fields=["status", "stopped_at", "error_message", "updated_at"])
|
|
291
302
|
|
|
303
|
+
async def amark_stopped(self, error_message: str = None):
|
|
304
|
+
"""Mark server as stopped (ASYNC - Django 5.2)."""
|
|
305
|
+
self.status = self.StatusChoices.STOPPED
|
|
306
|
+
self.stopped_at = timezone.now()
|
|
307
|
+
if error_message:
|
|
308
|
+
self.error_message = error_message
|
|
309
|
+
await self.asave(update_fields=["status", "stopped_at", "error_message", "updated_at"])
|
|
310
|
+
|
|
292
311
|
def mark_error(self, error_message: str):
|
|
293
312
|
"""Mark server as error."""
|
|
294
313
|
self.status = self.StatusChoices.ERROR
|
|
@@ -1,36 +1,117 @@
|
|
|
1
1
|
"""
|
|
2
2
|
gRPC services utilities.
|
|
3
3
|
|
|
4
|
-
Provides
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
Provides organized gRPC service components:
|
|
5
|
+
- **streaming**: Universal bidirectional streaming (NEW)
|
|
6
|
+
- **routing**: Cross-process command routing (NEW)
|
|
7
|
+
- **client**: gRPC client utilities
|
|
8
|
+
- **discovery**: Service discovery and registry
|
|
9
|
+
- **management**: Proto files and config management
|
|
10
|
+
- **monitoring**: Service monitoring and testing
|
|
11
|
+
- **rendering**: Content generation (charts, etc.)
|
|
12
|
+
- **base**: Base service classes
|
|
13
|
+
|
|
14
|
+
**Quick Imports**:
|
|
15
|
+
```python
|
|
16
|
+
# New universal components
|
|
17
|
+
from django_cfg.apps.integrations.grpc.services.streaming import (
|
|
18
|
+
BidirectionalStreamingService,
|
|
19
|
+
ConfigPresets,
|
|
20
|
+
)
|
|
7
21
|
|
|
8
|
-
from .
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
get_grpc_auth_config,
|
|
12
|
-
get_grpc_config,
|
|
13
|
-
get_grpc_config_or_default,
|
|
14
|
-
get_grpc_server_config,
|
|
15
|
-
is_grpc_enabled,
|
|
22
|
+
from django_cfg.apps.integrations.grpc.services.routing import (
|
|
23
|
+
CrossProcessCommandRouter,
|
|
24
|
+
CrossProcessConfig,
|
|
16
25
|
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
from .
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
|
|
27
|
+
# Existing services
|
|
28
|
+
from django_cfg.apps.integrations.grpc.services import (
|
|
29
|
+
BaseService,
|
|
30
|
+
ServiceDiscovery,
|
|
31
|
+
)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Created: 2025-11-07
|
|
35
|
+
Status: %%PRODUCTION%%
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
# Lazy imports to avoid Django initialization on import
|
|
39
|
+
# Import only when actually accessed via __getattr__
|
|
40
|
+
|
|
41
|
+
def __getattr__(name):
|
|
42
|
+
"""Lazy import to avoid Django setup on package import."""
|
|
43
|
+
|
|
44
|
+
# Base service classes
|
|
45
|
+
if name in ('AuthRequiredService', 'BaseService', 'ReadOnlyService'):
|
|
46
|
+
from .base import AuthRequiredService, BaseService, ReadOnlyService
|
|
47
|
+
return locals()[name]
|
|
48
|
+
|
|
49
|
+
# Management utilities
|
|
50
|
+
if name in ('get_enabled_apps', 'get_grpc_auth_config', 'get_grpc_config',
|
|
51
|
+
'get_grpc_config_or_default', 'get_grpc_server_config', 'is_grpc_enabled'):
|
|
52
|
+
from .management.config_helper import (
|
|
53
|
+
get_enabled_apps,
|
|
54
|
+
get_grpc_auth_config,
|
|
55
|
+
get_grpc_config,
|
|
56
|
+
get_grpc_config_or_default,
|
|
57
|
+
get_grpc_server_config,
|
|
58
|
+
is_grpc_enabled,
|
|
59
|
+
)
|
|
60
|
+
return locals()[name]
|
|
61
|
+
|
|
62
|
+
if name == 'ProtoFilesManager':
|
|
63
|
+
from .management.proto_manager import ProtoFilesManager
|
|
64
|
+
return ProtoFilesManager
|
|
65
|
+
|
|
66
|
+
# Discovery
|
|
67
|
+
if name == 'ServiceDiscovery':
|
|
68
|
+
from .discovery.discovery import ServiceDiscovery
|
|
69
|
+
return ServiceDiscovery
|
|
70
|
+
|
|
71
|
+
if name == 'discover_and_register_services':
|
|
72
|
+
from .discovery.discovery import discover_and_register_services
|
|
73
|
+
return discover_and_register_services
|
|
74
|
+
|
|
75
|
+
if name == 'ServiceRegistryManager':
|
|
76
|
+
from .discovery.registry import ServiceRegistryManager
|
|
77
|
+
return ServiceRegistryManager
|
|
78
|
+
|
|
79
|
+
# Client
|
|
80
|
+
if name == 'DynamicGRPCClient':
|
|
81
|
+
from .client.client import DynamicGRPCClient
|
|
82
|
+
return DynamicGRPCClient
|
|
83
|
+
|
|
84
|
+
# Monitoring
|
|
85
|
+
if name == 'MonitoringService':
|
|
86
|
+
from .monitoring.monitoring import MonitoringService
|
|
87
|
+
return MonitoringService
|
|
88
|
+
|
|
89
|
+
if name == 'TestingService':
|
|
90
|
+
from .monitoring.testing import TestingService
|
|
91
|
+
return TestingService
|
|
92
|
+
|
|
93
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
94
|
+
|
|
23
95
|
|
|
24
96
|
__all__ = [
|
|
97
|
+
# Base classes
|
|
25
98
|
"BaseService",
|
|
26
99
|
"ReadOnlyService",
|
|
27
100
|
"AuthRequiredService",
|
|
101
|
+
|
|
102
|
+
# Discovery
|
|
28
103
|
"ServiceDiscovery",
|
|
29
104
|
"discover_and_register_services",
|
|
30
105
|
"ServiceRegistryManager",
|
|
106
|
+
|
|
107
|
+
# Monitoring
|
|
31
108
|
"MonitoringService",
|
|
32
109
|
"TestingService",
|
|
110
|
+
|
|
111
|
+
# Client
|
|
33
112
|
"DynamicGRPCClient",
|
|
113
|
+
|
|
114
|
+
# Management
|
|
34
115
|
"ProtoFilesManager",
|
|
35
116
|
"get_grpc_config",
|
|
36
117
|
"get_grpc_config_or_default",
|
|
@@ -38,4 +119,8 @@ __all__ = [
|
|
|
38
119
|
"get_grpc_server_config",
|
|
39
120
|
"get_grpc_auth_config",
|
|
40
121
|
"get_enabled_apps",
|
|
122
|
+
|
|
123
|
+
# New components available via subpackages:
|
|
124
|
+
# - streaming: BidirectionalStreamingService, ConfigPresets, etc.
|
|
125
|
+
# - routing: CrossProcessCommandRouter, CrossProcessConfig
|
|
41
126
|
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gRPC → Centrifugo Integration.
|
|
3
|
+
|
|
4
|
+
Mixin and configuration for bridging gRPC streaming events to Centrifugo WebSocket.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .bridge import CentrifugoBridgeMixin
|
|
8
|
+
from .config import ChannelConfig, CentrifugoChannels
|
|
9
|
+
from .demo import (
|
|
10
|
+
DemoChannels,
|
|
11
|
+
DemoBridgeService,
|
|
12
|
+
test_complete_integration,
|
|
13
|
+
test_demo_service,
|
|
14
|
+
send_demo_event,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
# Core components
|
|
19
|
+
"CentrifugoBridgeMixin",
|
|
20
|
+
"ChannelConfig",
|
|
21
|
+
"CentrifugoChannels",
|
|
22
|
+
|
|
23
|
+
# Demo/testing
|
|
24
|
+
"DemoChannels",
|
|
25
|
+
"DemoBridgeService",
|
|
26
|
+
"test_complete_integration",
|
|
27
|
+
"test_demo_service",
|
|
28
|
+
"send_demo_event",
|
|
29
|
+
]
|