django-cfg 1.5.20__py3-none-any.whl → 1.5.31__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/integrations/centrifugo/__init__.py +2 -0
- django_cfg/apps/integrations/centrifugo/services/client/client.py +1 -1
- django_cfg/apps/integrations/centrifugo/services/logging.py +90 -14
- django_cfg/apps/integrations/centrifugo/views/admin_api.py +29 -32
- django_cfg/apps/integrations/centrifugo/views/testing_api.py +47 -43
- django_cfg/apps/integrations/centrifugo/views/wrapper.py +41 -29
- django_cfg/apps/integrations/grpc/auth/api_key_auth.py +11 -10
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +1 -1
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +22 -36
- 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/bridge.py +469 -0
- django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/demo.py +1 -1
- django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/test_publish.py +4 -4
- 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} +62 -55
- django_cfg/apps/integrations/grpc/services/{service_registry.py → discovery/registry.py} +216 -5
- 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/__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 +55 -8
- django_cfg/apps/integrations/grpc/views/charts.py +1 -1
- django_cfg/apps/integrations/grpc/views/config.py +1 -1
- django_cfg/core/base/config_model.py +11 -0
- django_cfg/core/builders/middleware_builder.py +5 -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/files_generator.py +12 -0
- django_cfg/modules/django_client/core/generator/typescript/generator.py +8 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +22 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/validation-events.ts.jinja +133 -0
- django_cfg/modules/django_client/core/groups/manager.py +25 -18
- django_cfg/modules/django_client/management/commands/generate_client.py +9 -5
- django_cfg/modules/django_client/urls.py +38 -5
- django_cfg/modules/django_logging/django_logger.py +58 -19
- django_cfg/modules/django_twilio/email_otp.py +3 -1
- django_cfg/modules/django_twilio/sms.py +3 -1
- django_cfg/modules/django_twilio/unified.py +6 -2
- django_cfg/modules/django_twilio/whatsapp.py +3 -1
- django_cfg/pyproject.toml +3 -3
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/index.html +17 -57
- django_cfg/utils/pool_monitor.py +320 -0
- django_cfg/utils/smart_defaults.py +233 -7
- {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/METADATA +75 -5
- {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/RECORD +97 -68
- django_cfg/apps/integrations/grpc/centrifugo/bridge.py +0 -277
- /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/__init__.py +0 -0
- /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/config.py +0 -0
- /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/transformers.py +0 -0
- /django_cfg/apps/integrations/grpc/services/{grpc_client.py → client/client.py} +0 -0
- /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/__init__.py +0 -0
- /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/centrifugo.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.20.dist-info → django_cfg-1.5.31.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/licenses/LICENSE +0 -0
|
@@ -237,7 +237,7 @@ class Command(BaseCommand):
|
|
|
237
237
|
|
|
238
238
|
# Import models here to avoid AppRegistryNotReady
|
|
239
239
|
from django_cfg.apps.integrations.grpc.models import GRPCServerStatus
|
|
240
|
-
from django_cfg.apps.integrations.grpc.services.config_helper import (
|
|
240
|
+
from django_cfg.apps.integrations.grpc.services.management.config_helper import (
|
|
241
241
|
get_grpc_server_config,
|
|
242
242
|
)
|
|
243
243
|
|
|
@@ -332,8 +332,7 @@ class Command(BaseCommand):
|
|
|
332
332
|
discovery.get_registered_services
|
|
333
333
|
)
|
|
334
334
|
|
|
335
|
-
server_status = await
|
|
336
|
-
GRPCServerStatus.objects.start_server,
|
|
335
|
+
server_status = await GRPCServerStatus.objects.astart_server(
|
|
337
336
|
host=host,
|
|
338
337
|
port=port,
|
|
339
338
|
pid=os.getpid(),
|
|
@@ -344,8 +343,7 @@ class Command(BaseCommand):
|
|
|
344
343
|
|
|
345
344
|
# Store registered services in database
|
|
346
345
|
server_status.registered_services = services_metadata
|
|
347
|
-
await
|
|
348
|
-
server_status.save,
|
|
346
|
+
await server_status.asave(
|
|
349
347
|
update_fields=["registered_services"]
|
|
350
348
|
)
|
|
351
349
|
|
|
@@ -361,7 +359,7 @@ class Command(BaseCommand):
|
|
|
361
359
|
# Mark server as running
|
|
362
360
|
if server_status:
|
|
363
361
|
try:
|
|
364
|
-
await
|
|
362
|
+
await server_status.amark_running()
|
|
365
363
|
except Exception as e:
|
|
366
364
|
self.logger.warning(f"Could not mark server as running: {e}")
|
|
367
365
|
|
|
@@ -425,7 +423,7 @@ class Command(BaseCommand):
|
|
|
425
423
|
if options.get("test"):
|
|
426
424
|
self.streaming_logger.info("🧪 Sending test Centrifugo event...")
|
|
427
425
|
try:
|
|
428
|
-
from django_cfg.apps.integrations.grpc.centrifugo.demo import send_demo_event
|
|
426
|
+
from django_cfg.apps.integrations.grpc.services.centrifugo.demo import send_demo_event
|
|
429
427
|
|
|
430
428
|
test_result = await send_demo_event(
|
|
431
429
|
channel="grpc#rungrpc#startup#test",
|
|
@@ -690,7 +688,6 @@ class Command(BaseCommand):
|
|
|
690
688
|
interval: Heartbeat interval in seconds (default: 30)
|
|
691
689
|
"""
|
|
692
690
|
from django_cfg.apps.integrations.grpc.models import GRPCServerStatus
|
|
693
|
-
from asgiref.sync import sync_to_async
|
|
694
691
|
|
|
695
692
|
try:
|
|
696
693
|
while True:
|
|
@@ -701,12 +698,10 @@ class Command(BaseCommand):
|
|
|
701
698
|
continue
|
|
702
699
|
|
|
703
700
|
try:
|
|
704
|
-
# Check if record still exists
|
|
705
|
-
record_exists = await
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
).exists
|
|
709
|
-
)()
|
|
701
|
+
# Check if record still exists (Django 5.2: Native async ORM)
|
|
702
|
+
record_exists = await GRPCServerStatus.objects.filter(
|
|
703
|
+
id=self.server_status.id
|
|
704
|
+
).aexists()
|
|
710
705
|
|
|
711
706
|
if not record_exists:
|
|
712
707
|
# Record was deleted - re-register server
|
|
@@ -722,21 +717,19 @@ class Command(BaseCommand):
|
|
|
722
717
|
discovery.get_registered_services
|
|
723
718
|
)
|
|
724
719
|
|
|
725
|
-
# Re-register server
|
|
726
|
-
new_server_status = await
|
|
727
|
-
GRPCServerStatus.objects.start_server,
|
|
720
|
+
# Re-register server (Django 5.2: Native async ORM)
|
|
721
|
+
new_server_status = await GRPCServerStatus.objects.astart_server(
|
|
728
722
|
**self.server_config
|
|
729
723
|
)
|
|
730
724
|
|
|
731
725
|
# Store registered services
|
|
732
726
|
new_server_status.registered_services = services_metadata
|
|
733
|
-
await
|
|
734
|
-
new_server_status.save,
|
|
727
|
+
await new_server_status.asave(
|
|
735
728
|
update_fields=["registered_services"]
|
|
736
729
|
)
|
|
737
730
|
|
|
738
|
-
# Mark as running
|
|
739
|
-
await
|
|
731
|
+
# Mark as running (Django 5.2: Native async ORM)
|
|
732
|
+
await new_server_status.amark_running()
|
|
740
733
|
|
|
741
734
|
# Update reference
|
|
742
735
|
self.server_status = new_server_status
|
|
@@ -745,8 +738,8 @@ class Command(BaseCommand):
|
|
|
745
738
|
f"✅ Successfully re-registered server (ID: {new_server_status.id})"
|
|
746
739
|
)
|
|
747
740
|
else:
|
|
748
|
-
# Record exists - just update heartbeat
|
|
749
|
-
await
|
|
741
|
+
# Record exists - just update heartbeat (Django 5.2: Native async ORM)
|
|
742
|
+
await self.server_status.amark_running()
|
|
750
743
|
self.logger.debug(f"Heartbeat updated (interval: {interval}s)")
|
|
751
744
|
|
|
752
745
|
except Exception as e:
|
|
@@ -785,17 +778,11 @@ class Command(BaseCommand):
|
|
|
785
778
|
|
|
786
779
|
self.stdout.write("\n🛑 Shutting down gracefully...")
|
|
787
780
|
|
|
788
|
-
# Mark server as stopping
|
|
781
|
+
# Mark server as stopping (sync context - signal handlers are sync)
|
|
789
782
|
if server_status:
|
|
790
783
|
try:
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
from asgiref.sync import sync_to_async
|
|
794
|
-
# Run in sync context
|
|
795
|
-
try:
|
|
796
|
-
server_status.mark_stopping()
|
|
797
|
-
except:
|
|
798
|
-
pass
|
|
784
|
+
# ✅ Use Django 5.2+ async ORM instead of sync_to_async
|
|
785
|
+
asyncio.create_task(server_status.amark_stopping())
|
|
799
786
|
except Exception as e:
|
|
800
787
|
self.logger.warning(f"Could not mark server as stopping: {e}")
|
|
801
788
|
|
|
@@ -807,12 +794,11 @@ class Command(BaseCommand):
|
|
|
807
794
|
except Exception as e:
|
|
808
795
|
self.logger.error(f"Error stopping server: {e}")
|
|
809
796
|
|
|
810
|
-
# Mark server as stopped (async-safe)
|
|
797
|
+
# Mark server as stopped (async-safe, Django 5.2: Native async ORM)
|
|
811
798
|
if server_status:
|
|
812
799
|
try:
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
asyncio.create_task(sync_to_async(server_status.mark_stopped)())
|
|
800
|
+
# Use native async method
|
|
801
|
+
asyncio.create_task(server_status.amark_stopped())
|
|
816
802
|
except Exception as e:
|
|
817
803
|
self.logger.warning(f"Could not mark server as stopped: {e}")
|
|
818
804
|
|
|
@@ -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
|
]
|