django-cfg 1.5.8__py3-none-any.whl → 1.5.14__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/api/commands/serializers.py +152 -0
- django_cfg/apps/api/commands/views.py +32 -0
- django_cfg/apps/business/accounts/management/commands/otp_test.py +5 -2
- django_cfg/apps/business/agents/management/commands/create_agent.py +5 -194
- django_cfg/apps/business/agents/management/commands/load_agent_templates.py +205 -0
- django_cfg/apps/business/agents/management/commands/orchestrator_status.py +4 -2
- django_cfg/apps/business/knowbase/management/commands/knowbase_stats.py +4 -2
- django_cfg/apps/business/knowbase/management/commands/setup_knowbase.py +4 -2
- django_cfg/apps/business/newsletter/management/commands/test_newsletter.py +5 -2
- django_cfg/apps/business/payments/management/commands/check_payment_status.py +4 -2
- django_cfg/apps/business/payments/management/commands/create_payment.py +4 -2
- django_cfg/apps/business/payments/management/commands/sync_currencies.py +4 -2
- django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +5 -5
- django_cfg/apps/integrations/centrifugo/serializers/__init__.py +2 -1
- django_cfg/apps/integrations/centrifugo/serializers/publishes.py +22 -2
- django_cfg/apps/integrations/centrifugo/views/monitoring.py +25 -40
- django_cfg/apps/integrations/grpc/admin/__init__.py +7 -1
- django_cfg/apps/integrations/grpc/admin/config.py +113 -9
- django_cfg/apps/integrations/grpc/admin/grpc_api_key.py +129 -0
- django_cfg/apps/integrations/grpc/admin/grpc_request_log.py +72 -63
- django_cfg/apps/integrations/grpc/admin/grpc_server_status.py +236 -0
- django_cfg/apps/integrations/grpc/auth/__init__.py +11 -3
- django_cfg/apps/integrations/grpc/auth/api_key_auth.py +320 -0
- django_cfg/apps/integrations/grpc/interceptors/logging.py +17 -20
- django_cfg/apps/integrations/grpc/interceptors/metrics.py +15 -14
- django_cfg/apps/integrations/grpc/interceptors/request_logger.py +79 -59
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +130 -0
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +171 -96
- django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
- django_cfg/apps/integrations/grpc/managers/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/managers/grpc_api_key.py +192 -0
- django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +19 -11
- django_cfg/apps/integrations/grpc/migrations/0005_grpcapikey.py +143 -0
- django_cfg/apps/integrations/grpc/migrations/0006_grpcrequestlog_api_key_and_more.py +34 -0
- django_cfg/apps/integrations/grpc/models/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/models/grpc_api_key.py +198 -0
- django_cfg/apps/integrations/grpc/models/grpc_request_log.py +11 -0
- django_cfg/apps/integrations/grpc/models/grpc_server_status.py +39 -4
- django_cfg/apps/integrations/grpc/serializers/__init__.py +22 -6
- django_cfg/apps/integrations/grpc/serializers/api_keys.py +63 -0
- django_cfg/apps/integrations/grpc/serializers/charts.py +118 -120
- django_cfg/apps/integrations/grpc/serializers/config.py +65 -51
- django_cfg/apps/integrations/grpc/serializers/health.py +7 -7
- django_cfg/apps/integrations/grpc/serializers/proto_files.py +74 -0
- django_cfg/apps/integrations/grpc/serializers/requests.py +13 -7
- django_cfg/apps/integrations/grpc/serializers/service_registry.py +181 -112
- django_cfg/apps/integrations/grpc/serializers/services.py +14 -32
- django_cfg/apps/integrations/grpc/serializers/stats.py +50 -12
- django_cfg/apps/integrations/grpc/serializers/testing.py +66 -58
- django_cfg/apps/integrations/grpc/services/__init__.py +2 -0
- django_cfg/apps/integrations/grpc/services/monitoring_service.py +149 -43
- django_cfg/apps/integrations/grpc/services/proto_files_manager.py +268 -0
- django_cfg/apps/integrations/grpc/services/service_registry.py +48 -46
- django_cfg/apps/integrations/grpc/services/testing_service.py +10 -15
- django_cfg/apps/integrations/grpc/urls.py +8 -0
- django_cfg/apps/integrations/grpc/utils/__init__.py +4 -13
- django_cfg/apps/integrations/grpc/utils/integration_test.py +334 -0
- django_cfg/apps/integrations/grpc/utils/proto_gen.py +48 -8
- django_cfg/apps/integrations/grpc/utils/streaming_logger.py +177 -0
- django_cfg/apps/integrations/grpc/views/__init__.py +4 -0
- django_cfg/apps/integrations/grpc/views/api_keys.py +255 -0
- django_cfg/apps/integrations/grpc/views/charts.py +21 -14
- django_cfg/apps/integrations/grpc/views/config.py +8 -6
- django_cfg/apps/integrations/grpc/views/monitoring.py +51 -79
- django_cfg/apps/integrations/grpc/views/proto_files.py +214 -0
- django_cfg/apps/integrations/grpc/views/services.py +30 -21
- django_cfg/apps/integrations/grpc/views/testing.py +45 -43
- django_cfg/apps/integrations/rq/views/jobs.py +19 -9
- django_cfg/apps/integrations/rq/views/schedule.py +7 -3
- django_cfg/apps/system/dashboard/serializers/commands.py +25 -1
- django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
- django_cfg/apps/system/maintenance/management/commands/maintenance.py +5 -2
- django_cfg/apps/system/maintenance/management/commands/process_scheduled_maintenance.py +4 -2
- django_cfg/apps/system/maintenance/management/commands/sync_cloudflare.py +5 -2
- django_cfg/config.py +33 -0
- django_cfg/core/generation/integration_generators/grpc_generator.py +30 -32
- django_cfg/management/commands/check_endpoints.py +2 -2
- django_cfg/management/commands/check_settings.py +3 -10
- django_cfg/management/commands/clear_constance.py +3 -10
- django_cfg/management/commands/create_token.py +4 -11
- django_cfg/management/commands/list_urls.py +4 -10
- django_cfg/management/commands/migrate_all.py +18 -12
- django_cfg/management/commands/migrator.py +4 -11
- django_cfg/management/commands/script.py +4 -10
- django_cfg/management/commands/show_config.py +8 -16
- django_cfg/management/commands/show_urls.py +5 -11
- django_cfg/management/commands/superuser.py +4 -11
- django_cfg/management/commands/tree.py +5 -10
- django_cfg/management/utils/README.md +402 -0
- django_cfg/management/utils/__init__.py +29 -0
- django_cfg/management/utils/mixins.py +176 -0
- django_cfg/middleware/pagination.py +53 -54
- django_cfg/models/api/grpc/__init__.py +15 -21
- django_cfg/models/api/grpc/config.py +155 -73
- django_cfg/models/ngrok/config.py +7 -6
- django_cfg/modules/django_client/core/generator/python/files_generator.py +5 -13
- django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +16 -4
- django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -3
- django_cfg/modules/django_client/core/generator/typescript/files_generator.py +6 -5
- django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +12 -8
- django_cfg/modules/django_client/core/parser/base.py +114 -30
- django_cfg/modules/django_client/management/commands/generate_client.py +5 -2
- django_cfg/modules/django_client/management/commands/validate_openapi.py +5 -2
- django_cfg/modules/django_email/management/commands/test_email.py +4 -10
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +16 -13
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +4 -11
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +4 -11
- django_cfg/modules/django_unfold/navigation.py +6 -18
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/modules.py +1 -4
- django_cfg/requirements.txt +52 -0
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/METADATA +1 -1
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/RECORD +118 -97
- django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.14.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Django management command to run gRPC server.
|
|
2
|
+
Django management command to run async gRPC server.
|
|
3
3
|
|
|
4
4
|
Usage:
|
|
5
5
|
python manage.py rungrpc
|
|
6
6
|
python manage.py rungrpc --host 0.0.0.0 --port 50051
|
|
7
|
-
python manage.py rungrpc --workers 20
|
|
8
7
|
"""
|
|
9
8
|
|
|
10
9
|
from __future__ import annotations
|
|
11
10
|
|
|
12
|
-
import
|
|
11
|
+
import asyncio
|
|
13
12
|
import signal
|
|
14
13
|
import sys
|
|
15
|
-
from concurrent import futures
|
|
16
14
|
|
|
17
15
|
from django.conf import settings
|
|
18
16
|
from django.core.management.base import BaseCommand
|
|
19
17
|
|
|
18
|
+
from django_cfg.modules.django_logging import get_logger
|
|
19
|
+
|
|
20
20
|
# Check dependencies before importing grpc
|
|
21
21
|
from django_cfg.apps.integrations.grpc._cfg import check_grpc_dependencies
|
|
22
22
|
|
|
@@ -28,24 +28,36 @@ except Exception as e:
|
|
|
28
28
|
|
|
29
29
|
# Now safe to import grpc
|
|
30
30
|
import grpc
|
|
31
|
-
|
|
32
|
-
logger = logging.getLogger(__name__)
|
|
31
|
+
import grpc.aio
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
class Command(BaseCommand):
|
|
36
35
|
"""
|
|
37
|
-
Run gRPC server with auto-discovered services.
|
|
36
|
+
Run async gRPC server with auto-discovered services.
|
|
38
37
|
|
|
39
38
|
Features:
|
|
39
|
+
- Async server with grpc.aio
|
|
40
40
|
- Auto-discovers and registers services
|
|
41
|
-
- Configurable host, port
|
|
41
|
+
- Configurable host, port
|
|
42
42
|
- Health check support
|
|
43
43
|
- Reflection support
|
|
44
44
|
- Graceful shutdown
|
|
45
45
|
- Signal handling
|
|
46
46
|
"""
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
# Web execution metadata
|
|
49
|
+
web_executable = False
|
|
50
|
+
requires_input = False
|
|
51
|
+
is_destructive = False
|
|
52
|
+
|
|
53
|
+
help = "Run async gRPC server"
|
|
54
|
+
|
|
55
|
+
def __init__(self, *args, **kwargs):
|
|
56
|
+
"""Initialize with self.logger and async server reference."""
|
|
57
|
+
super().__init__(*args, **kwargs)
|
|
58
|
+
self.logger = get_logger('rungrpc')
|
|
59
|
+
self.server = None
|
|
60
|
+
self.shutdown_event = None
|
|
49
61
|
|
|
50
62
|
def add_arguments(self, parser):
|
|
51
63
|
"""Add command arguments."""
|
|
@@ -61,12 +73,6 @@ class Command(BaseCommand):
|
|
|
61
73
|
default=None,
|
|
62
74
|
help="Server port (default: from settings or 50051)",
|
|
63
75
|
)
|
|
64
|
-
parser.add_argument(
|
|
65
|
-
"--workers",
|
|
66
|
-
type=int,
|
|
67
|
-
default=None,
|
|
68
|
-
help="Max worker threads (default: from settings or 10)",
|
|
69
|
-
)
|
|
70
76
|
parser.add_argument(
|
|
71
77
|
"--no-reflection",
|
|
72
78
|
action="store_true",
|
|
@@ -77,53 +83,98 @@ class Command(BaseCommand):
|
|
|
77
83
|
action="store_true",
|
|
78
84
|
help="Disable health check service",
|
|
79
85
|
)
|
|
86
|
+
parser.add_argument(
|
|
87
|
+
"--asyncio-debug",
|
|
88
|
+
action="store_true",
|
|
89
|
+
help="Enable asyncio debug mode",
|
|
90
|
+
)
|
|
80
91
|
|
|
81
92
|
def handle(self, *args, **options):
|
|
82
|
-
"""Run gRPC server."""
|
|
93
|
+
"""Run async gRPC server."""
|
|
94
|
+
# Enable asyncio debug if requested
|
|
95
|
+
if options.get("asyncio_debug"):
|
|
96
|
+
asyncio.get_event_loop().set_debug(True)
|
|
97
|
+
self.logger.info("Asyncio debug mode enabled")
|
|
98
|
+
|
|
99
|
+
# Run async main
|
|
100
|
+
asyncio.run(self._async_main(*args, **options))
|
|
101
|
+
|
|
102
|
+
async def _async_main(self, *args, **options):
|
|
103
|
+
"""Main async server loop."""
|
|
83
104
|
# Import models here to avoid AppRegistryNotReady
|
|
84
105
|
from django_cfg.apps.integrations.grpc.models import GRPCServerStatus
|
|
106
|
+
from django_cfg.apps.integrations.grpc.services.config_helper import (
|
|
107
|
+
get_grpc_server_config,
|
|
108
|
+
)
|
|
85
109
|
|
|
86
110
|
# Get configuration
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
111
|
+
grpc_server_config_obj = get_grpc_server_config()
|
|
112
|
+
|
|
113
|
+
# Fallback to settings if not configured via django-cfg
|
|
114
|
+
if not grpc_server_config_obj:
|
|
115
|
+
grpc_server_config = getattr(settings, "GRPC_SERVER", {})
|
|
116
|
+
host = options["host"] or grpc_server_config.get("host", "[::]")
|
|
117
|
+
port = options["port"] or grpc_server_config.get("port", 50051)
|
|
118
|
+
max_concurrent_streams = grpc_server_config.get("max_concurrent_streams", None)
|
|
119
|
+
enable_reflection = not options["no_reflection"] and grpc_server_config.get(
|
|
120
|
+
"enable_reflection", False
|
|
121
|
+
)
|
|
122
|
+
enable_health_check = not options["no_health_check"] and grpc_server_config.get(
|
|
123
|
+
"enable_health_check", True
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
# Use django-cfg config
|
|
127
|
+
host = options["host"] or grpc_server_config_obj.host
|
|
128
|
+
port = options["port"] or grpc_server_config_obj.port
|
|
129
|
+
max_concurrent_streams = grpc_server_config_obj.max_concurrent_streams
|
|
130
|
+
enable_reflection = (
|
|
131
|
+
not options["no_reflection"] and grpc_server_config_obj.enable_reflection
|
|
132
|
+
)
|
|
133
|
+
enable_health_check = (
|
|
134
|
+
not options["no_health_check"]
|
|
135
|
+
and grpc_server_config_obj.enable_health_check
|
|
136
|
+
)
|
|
137
|
+
grpc_server_config = {
|
|
138
|
+
"host": grpc_server_config_obj.host,
|
|
139
|
+
"port": grpc_server_config_obj.port,
|
|
140
|
+
"max_concurrent_streams": grpc_server_config_obj.max_concurrent_streams,
|
|
141
|
+
"enable_reflection": grpc_server_config_obj.enable_reflection,
|
|
142
|
+
"enable_health_check": grpc_server_config_obj.enable_health_check,
|
|
143
|
+
"compression": grpc_server_config_obj.compression,
|
|
144
|
+
"max_send_message_length": grpc_server_config_obj.max_send_message_length,
|
|
145
|
+
"max_receive_message_length": grpc_server_config_obj.max_receive_message_length,
|
|
146
|
+
"keepalive_time_ms": grpc_server_config_obj.keepalive_time_ms,
|
|
147
|
+
"keepalive_timeout_ms": grpc_server_config_obj.keepalive_timeout_ms,
|
|
148
|
+
}
|
|
101
149
|
|
|
102
150
|
# gRPC options
|
|
103
151
|
grpc_options = self._build_grpc_options(grpc_server_config)
|
|
104
152
|
|
|
105
|
-
#
|
|
106
|
-
|
|
107
|
-
|
|
153
|
+
# Add max_concurrent_streams if specified
|
|
154
|
+
if max_concurrent_streams:
|
|
155
|
+
grpc_options.append(("grpc.max_concurrent_streams", max_concurrent_streams))
|
|
156
|
+
|
|
157
|
+
# Create async server
|
|
158
|
+
self.server = grpc.aio.server(
|
|
108
159
|
options=grpc_options,
|
|
109
|
-
interceptors=self.
|
|
160
|
+
interceptors=await self._build_interceptors_async(),
|
|
110
161
|
)
|
|
111
162
|
|
|
112
163
|
# Discover and register services FIRST
|
|
113
|
-
service_count = self.
|
|
164
|
+
service_count = await self._register_services_async(self.server)
|
|
114
165
|
|
|
115
166
|
# Add health check with registered services
|
|
116
167
|
health_servicer = None
|
|
117
168
|
if enable_health_check:
|
|
118
|
-
health_servicer = self.
|
|
169
|
+
health_servicer = await self._add_health_check_async(self.server)
|
|
119
170
|
|
|
120
171
|
# Add reflection
|
|
121
172
|
if enable_reflection:
|
|
122
|
-
self.
|
|
173
|
+
await self._add_reflection_async(self.server)
|
|
123
174
|
|
|
124
175
|
# Bind server
|
|
125
176
|
address = f"{host}:{port}"
|
|
126
|
-
server.add_insecure_port(address)
|
|
177
|
+
self.server.add_insecure_port(address)
|
|
127
178
|
|
|
128
179
|
# Track server status in database
|
|
129
180
|
server_status = None
|
|
@@ -131,35 +182,41 @@ class Command(BaseCommand):
|
|
|
131
182
|
import os
|
|
132
183
|
from django_cfg.apps.integrations.grpc.services import ServiceDiscovery
|
|
133
184
|
|
|
134
|
-
# Get registered services metadata
|
|
185
|
+
# Get registered services metadata (run in thread to avoid blocking)
|
|
135
186
|
discovery = ServiceDiscovery()
|
|
136
|
-
services_metadata =
|
|
187
|
+
services_metadata = await asyncio.to_thread(
|
|
188
|
+
discovery.get_registered_services
|
|
189
|
+
)
|
|
137
190
|
|
|
138
|
-
server_status =
|
|
191
|
+
server_status = await asyncio.to_thread(
|
|
192
|
+
GRPCServerStatus.objects.start_server,
|
|
139
193
|
host=host,
|
|
140
194
|
port=port,
|
|
141
195
|
pid=os.getpid(),
|
|
142
|
-
max_workers=
|
|
196
|
+
max_workers=0, # Async server - no workers
|
|
143
197
|
enable_reflection=enable_reflection,
|
|
144
198
|
enable_health_check=enable_health_check,
|
|
145
199
|
)
|
|
146
200
|
|
|
147
201
|
# Store registered services in database
|
|
148
202
|
server_status.registered_services = services_metadata
|
|
149
|
-
|
|
203
|
+
await asyncio.to_thread(
|
|
204
|
+
server_status.save,
|
|
205
|
+
update_fields=["registered_services"]
|
|
206
|
+
)
|
|
150
207
|
|
|
151
208
|
except Exception as e:
|
|
152
|
-
logger.warning(f"Could not start server status tracking: {e}")
|
|
209
|
+
self.logger.warning(f"Could not start server status tracking: {e}")
|
|
153
210
|
|
|
154
211
|
# Start server
|
|
155
|
-
server.start()
|
|
212
|
+
await self.server.start()
|
|
156
213
|
|
|
157
214
|
# Mark server as running
|
|
158
215
|
if server_status:
|
|
159
216
|
try:
|
|
160
|
-
server_status.mark_running
|
|
217
|
+
await asyncio.to_thread(server_status.mark_running)
|
|
161
218
|
except Exception as e:
|
|
162
|
-
logger.warning(f"Could not mark server as running: {e}")
|
|
219
|
+
self.logger.warning(f"Could not mark server as running: {e}")
|
|
163
220
|
|
|
164
221
|
# Display gRPC-specific startup info
|
|
165
222
|
try:
|
|
@@ -168,32 +225,35 @@ class Command(BaseCommand):
|
|
|
168
225
|
|
|
169
226
|
# Get registered service names
|
|
170
227
|
discovery = ServiceDiscovery()
|
|
171
|
-
services_metadata =
|
|
228
|
+
services_metadata = await asyncio.to_thread(
|
|
229
|
+
discovery.get_registered_services
|
|
230
|
+
)
|
|
172
231
|
service_names = [s.get('name', 'Unknown') for s in services_metadata]
|
|
173
232
|
|
|
174
233
|
# Display startup info
|
|
175
234
|
grpc_display = GRPCDisplayManager()
|
|
176
|
-
|
|
235
|
+
await asyncio.to_thread(
|
|
236
|
+
grpc_display.display_grpc_startup,
|
|
177
237
|
host=host,
|
|
178
238
|
port=port,
|
|
179
|
-
max_workers=
|
|
239
|
+
max_workers=0, # Async server
|
|
180
240
|
enable_reflection=enable_reflection,
|
|
181
241
|
enable_health_check=enable_health_check,
|
|
182
242
|
registered_services=service_count,
|
|
183
243
|
service_names=service_names,
|
|
184
244
|
)
|
|
185
245
|
except Exception as e:
|
|
186
|
-
logger.warning(f"Could not display gRPC startup info: {e}")
|
|
246
|
+
self.logger.warning(f"Could not display gRPC startup info: {e}")
|
|
187
247
|
|
|
188
248
|
# Setup signal handlers for graceful shutdown
|
|
189
|
-
self.
|
|
249
|
+
self._setup_signal_handlers_async(self.server, server_status)
|
|
190
250
|
|
|
191
251
|
# Keep server running
|
|
192
|
-
self.stdout.write(self.style.SUCCESS("\n✅ gRPC server is running..."))
|
|
252
|
+
self.stdout.write(self.style.SUCCESS("\n✅ Async gRPC server is running..."))
|
|
193
253
|
self.stdout.write("Press CTRL+C to stop\n")
|
|
194
254
|
|
|
195
255
|
try:
|
|
196
|
-
server.wait_for_termination()
|
|
256
|
+
await self.server.wait_for_termination()
|
|
197
257
|
except KeyboardInterrupt:
|
|
198
258
|
# Signal handler will take care of graceful shutdown
|
|
199
259
|
pass
|
|
@@ -244,12 +304,12 @@ class Command(BaseCommand):
|
|
|
244
304
|
|
|
245
305
|
return options
|
|
246
306
|
|
|
247
|
-
def
|
|
307
|
+
async def _build_interceptors_async(self) -> list:
|
|
248
308
|
"""
|
|
249
|
-
Build server interceptors from configuration.
|
|
309
|
+
Build async server interceptors from configuration.
|
|
250
310
|
|
|
251
311
|
Returns:
|
|
252
|
-
List of interceptor instances
|
|
312
|
+
List of async interceptor instances
|
|
253
313
|
"""
|
|
254
314
|
grpc_framework_config = getattr(settings, "GRPC_FRAMEWORK", {})
|
|
255
315
|
interceptor_paths = grpc_framework_config.get("SERVER_INTERCEPTORS", [])
|
|
@@ -269,22 +329,22 @@ class Command(BaseCommand):
|
|
|
269
329
|
interceptor = interceptor_class()
|
|
270
330
|
interceptors.append(interceptor)
|
|
271
331
|
|
|
272
|
-
logger.debug(f"Loaded interceptor: {class_name}")
|
|
332
|
+
self.logger.debug(f"Loaded async interceptor: {class_name}")
|
|
273
333
|
|
|
274
334
|
except Exception as e:
|
|
275
|
-
logger.error(f"Failed to load interceptor {interceptor_path}: {e}")
|
|
335
|
+
self.logger.error(f"Failed to load async interceptor {interceptor_path}: {e}")
|
|
276
336
|
|
|
277
337
|
return interceptors
|
|
278
338
|
|
|
279
|
-
def
|
|
339
|
+
async def _add_health_check_async(self, server):
|
|
280
340
|
"""
|
|
281
|
-
Add health check service
|
|
341
|
+
Add health check service to async server.
|
|
282
342
|
|
|
283
343
|
Args:
|
|
284
|
-
server: gRPC server instance
|
|
344
|
+
server: Async gRPC server instance
|
|
285
345
|
|
|
286
346
|
Returns:
|
|
287
|
-
health_servicer: Health servicer instance
|
|
347
|
+
health_servicer: Health servicer instance or None
|
|
288
348
|
"""
|
|
289
349
|
try:
|
|
290
350
|
from grpc_health.v1 import health, health_pb2, health_pb2_grpc
|
|
@@ -294,14 +354,13 @@ class Command(BaseCommand):
|
|
|
294
354
|
|
|
295
355
|
# Set overall server status
|
|
296
356
|
health_servicer.set("", health_pb2.HealthCheckResponse.SERVING)
|
|
297
|
-
logger.info("Overall server health: SERVING")
|
|
357
|
+
self.logger.info("Overall server health: SERVING")
|
|
298
358
|
|
|
299
|
-
# Get registered service names
|
|
359
|
+
# Get registered service names from async server
|
|
300
360
|
service_names = []
|
|
301
361
|
if hasattr(server, '_state') and hasattr(server._state, 'generic_handlers'):
|
|
302
362
|
for handler in server._state.generic_handlers:
|
|
303
363
|
if hasattr(handler, 'service_name'):
|
|
304
|
-
# service_name() returns a callable or list
|
|
305
364
|
names = handler.service_name()
|
|
306
365
|
if callable(names):
|
|
307
366
|
names = names()
|
|
@@ -316,44 +375,42 @@ class Command(BaseCommand):
|
|
|
316
375
|
service_name,
|
|
317
376
|
health_pb2.HealthCheckResponse.SERVING
|
|
318
377
|
)
|
|
319
|
-
logger.info(f"Service '{service_name}' health: SERVING")
|
|
378
|
+
self.logger.info(f"Service '{service_name}' health: SERVING")
|
|
320
379
|
|
|
321
|
-
# Register health service
|
|
380
|
+
# Register health service to async server
|
|
322
381
|
health_pb2_grpc.add_HealthServicer_to_server(health_servicer, server)
|
|
323
382
|
|
|
324
|
-
logger.info(
|
|
383
|
+
self.logger.info(
|
|
325
384
|
f"✅ Health check enabled for {len(service_names)} service(s)"
|
|
326
385
|
)
|
|
327
386
|
|
|
328
|
-
# Return servicer for dynamic health updates
|
|
329
387
|
return health_servicer
|
|
330
388
|
|
|
331
389
|
except ImportError:
|
|
332
|
-
logger.warning(
|
|
390
|
+
self.logger.warning(
|
|
333
391
|
"grpcio-health-checking not installed. "
|
|
334
392
|
"Install with: pip install 'django-cfg[grpc]'"
|
|
335
393
|
)
|
|
336
394
|
return None
|
|
337
395
|
except Exception as e:
|
|
338
|
-
logger.error(f"Failed to add health check service: {e}")
|
|
396
|
+
self.logger.error(f"Failed to add health check service: {e}")
|
|
339
397
|
return None
|
|
340
398
|
|
|
341
|
-
def
|
|
399
|
+
async def _add_reflection_async(self, server):
|
|
342
400
|
"""
|
|
343
|
-
Add reflection service to server.
|
|
401
|
+
Add reflection service to async server.
|
|
344
402
|
|
|
345
403
|
Args:
|
|
346
|
-
server: gRPC server instance
|
|
404
|
+
server: Async gRPC server instance
|
|
347
405
|
"""
|
|
348
406
|
try:
|
|
349
407
|
from grpc_reflection.v1alpha import reflection
|
|
350
408
|
|
|
351
|
-
# Get service names from
|
|
409
|
+
# Get service names from async server
|
|
352
410
|
service_names = []
|
|
353
411
|
if hasattr(server, '_state') and hasattr(server._state, 'generic_handlers'):
|
|
354
412
|
for handler in server._state.generic_handlers:
|
|
355
413
|
if hasattr(handler, 'service_name'):
|
|
356
|
-
# service_name() returns a callable or list
|
|
357
414
|
names = handler.service_name()
|
|
358
415
|
if callable(names):
|
|
359
416
|
names = names()
|
|
@@ -365,25 +422,25 @@ class Command(BaseCommand):
|
|
|
365
422
|
# Add grpc.reflection.v1alpha.ServerReflection service itself
|
|
366
423
|
service_names.append('grpc.reflection.v1alpha.ServerReflection')
|
|
367
424
|
|
|
368
|
-
# Add reflection
|
|
425
|
+
# Add reflection to async server
|
|
369
426
|
reflection.enable_server_reflection(service_names, server)
|
|
370
427
|
|
|
371
|
-
logger.info(f"Server reflection enabled for {len(service_names)} service(s)")
|
|
428
|
+
self.logger.info(f"Server reflection enabled for {len(service_names)} service(s)")
|
|
372
429
|
|
|
373
430
|
except ImportError:
|
|
374
|
-
logger.warning(
|
|
431
|
+
self.logger.warning(
|
|
375
432
|
"grpcio-reflection not installed. "
|
|
376
433
|
"Install with: pip install grpcio-reflection"
|
|
377
434
|
)
|
|
378
435
|
except Exception as e:
|
|
379
|
-
logger.error(f"Failed to enable server reflection: {e}")
|
|
436
|
+
self.logger.error(f"Failed to enable server reflection: {e}")
|
|
380
437
|
|
|
381
|
-
def
|
|
438
|
+
async def _register_services_async(self, server) -> int:
|
|
382
439
|
"""
|
|
383
|
-
Discover and register services to server.
|
|
440
|
+
Discover and register services to async server.
|
|
384
441
|
|
|
385
442
|
Args:
|
|
386
|
-
server: gRPC server instance
|
|
443
|
+
server: Async gRPC server instance
|
|
387
444
|
|
|
388
445
|
Returns:
|
|
389
446
|
Number of services registered
|
|
@@ -391,22 +448,26 @@ class Command(BaseCommand):
|
|
|
391
448
|
try:
|
|
392
449
|
from django_cfg.apps.integrations.grpc.services.discovery import discover_and_register_services
|
|
393
450
|
|
|
394
|
-
|
|
451
|
+
# Service registration is sync, run in thread
|
|
452
|
+
count = await asyncio.to_thread(
|
|
453
|
+
discover_and_register_services,
|
|
454
|
+
server
|
|
455
|
+
)
|
|
395
456
|
return count
|
|
396
457
|
|
|
397
458
|
except Exception as e:
|
|
398
|
-
logger.error(f"Failed to register services: {e}", exc_info=True)
|
|
459
|
+
self.logger.error(f"Failed to register services: {e}", exc_info=True)
|
|
399
460
|
self.stdout.write(
|
|
400
461
|
self.style.ERROR(f"Error registering services: {e}")
|
|
401
462
|
)
|
|
402
463
|
return 0
|
|
403
464
|
|
|
404
|
-
def
|
|
465
|
+
def _setup_signal_handlers_async(self, server, server_status=None):
|
|
405
466
|
"""
|
|
406
|
-
Setup signal handlers for graceful shutdown.
|
|
467
|
+
Setup signal handlers for graceful async server shutdown.
|
|
407
468
|
|
|
408
469
|
Args:
|
|
409
|
-
server: gRPC server instance
|
|
470
|
+
server: Async gRPC server instance
|
|
410
471
|
server_status: GRPCServerStatus instance (optional)
|
|
411
472
|
"""
|
|
412
473
|
# Flag to prevent multiple shutdown attempts
|
|
@@ -423,19 +484,33 @@ class Command(BaseCommand):
|
|
|
423
484
|
# Mark server as stopping
|
|
424
485
|
if server_status:
|
|
425
486
|
try:
|
|
426
|
-
|
|
487
|
+
import django
|
|
488
|
+
if django.VERSION >= (3, 0):
|
|
489
|
+
from asgiref.sync import sync_to_async
|
|
490
|
+
# Run in sync context
|
|
491
|
+
try:
|
|
492
|
+
server_status.mark_stopping()
|
|
493
|
+
except:
|
|
494
|
+
pass
|
|
427
495
|
except Exception as e:
|
|
428
|
-
logger.warning(f"Could not mark server as stopping: {e}")
|
|
496
|
+
self.logger.warning(f"Could not mark server as stopping: {e}")
|
|
429
497
|
|
|
430
|
-
# Stop server
|
|
431
|
-
|
|
498
|
+
# Stop async server
|
|
499
|
+
try:
|
|
500
|
+
# Create task to stop server
|
|
501
|
+
loop = asyncio.get_event_loop()
|
|
502
|
+
loop.create_task(server.stop(grace=5))
|
|
503
|
+
except Exception as e:
|
|
504
|
+
self.logger.error(f"Error stopping server: {e}")
|
|
432
505
|
|
|
433
|
-
# Mark server as stopped
|
|
506
|
+
# Mark server as stopped (async-safe)
|
|
434
507
|
if server_status:
|
|
435
508
|
try:
|
|
436
|
-
|
|
509
|
+
from asgiref.sync import sync_to_async
|
|
510
|
+
# Wrap sync DB operation in sync_to_async
|
|
511
|
+
asyncio.create_task(sync_to_async(server_status.mark_stopped)())
|
|
437
512
|
except Exception as e:
|
|
438
|
-
logger.warning(f"Could not mark server as stopped: {e}")
|
|
513
|
+
self.logger.warning(f"Could not mark server as stopped: {e}")
|
|
439
514
|
|
|
440
515
|
self.stdout.write(self.style.SUCCESS("✅ Server stopped"))
|
|
441
516
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django management command for gRPC integration testing.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python manage.py test_grpc_integration
|
|
6
|
+
python manage.py test_grpc_integration --app crypto
|
|
7
|
+
python manage.py test_grpc_integration --quiet
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
from django_cfg.management.utils import SafeCommand
|
|
13
|
+
|
|
14
|
+
from django_cfg.apps.integrations.grpc.utils import GRPCIntegrationTest
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Command(SafeCommand):
|
|
18
|
+
command_name = 'test_grpc_integration'
|
|
19
|
+
help = "Run comprehensive gRPC integration test with API keys"
|
|
20
|
+
|
|
21
|
+
def add_arguments(self, parser):
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"--app",
|
|
24
|
+
type=str,
|
|
25
|
+
default="crypto",
|
|
26
|
+
help="Django app label to test (default: crypto)",
|
|
27
|
+
)
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"--quiet",
|
|
30
|
+
"-q",
|
|
31
|
+
action="store_true",
|
|
32
|
+
help="Suppress verbose output",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def handle(self, *args, **options):
|
|
36
|
+
app_label = options["app"]
|
|
37
|
+
quiet = options["quiet"]
|
|
38
|
+
|
|
39
|
+
if not quiet:
|
|
40
|
+
self.stdout.write(
|
|
41
|
+
self.style.SUCCESS(f"Starting gRPC integration test for app: {app_label}")
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Создаем и запускаем тест
|
|
45
|
+
test = GRPCIntegrationTest(app_label=app_label, quiet=quiet)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
success = test.run()
|
|
49
|
+
|
|
50
|
+
if success:
|
|
51
|
+
self.stdout.write(
|
|
52
|
+
self.style.SUCCESS("\n✅ Integration test completed successfully!")
|
|
53
|
+
)
|
|
54
|
+
sys.exit(0)
|
|
55
|
+
else:
|
|
56
|
+
self.stdout.write(
|
|
57
|
+
self.style.ERROR("\n❌ Integration test failed!")
|
|
58
|
+
)
|
|
59
|
+
sys.exit(1)
|
|
60
|
+
|
|
61
|
+
except KeyboardInterrupt:
|
|
62
|
+
self.stdout.write(
|
|
63
|
+
self.style.WARNING("\n⚠️ Test interrupted by user")
|
|
64
|
+
)
|
|
65
|
+
test.step6_cleanup()
|
|
66
|
+
sys.exit(1)
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
self.stdout.write(
|
|
70
|
+
self.style.ERROR(f"\n❌ Critical error: {e}")
|
|
71
|
+
)
|
|
72
|
+
import traceback
|
|
73
|
+
traceback.print_exc()
|
|
74
|
+
test.step6_cleanup()
|
|
75
|
+
sys.exit(1)
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
Managers for gRPC app models.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from .grpc_api_key import GrpcApiKeyManager
|
|
5
6
|
from .grpc_request_log import GRPCRequestLogManager, GRPCRequestLogQuerySet
|
|
6
7
|
from .grpc_server_status import GRPCServerStatusManager
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
10
|
+
"GrpcApiKeyManager",
|
|
9
11
|
"GRPCRequestLogManager",
|
|
10
12
|
"GRPCRequestLogQuerySet",
|
|
11
13
|
"GRPCServerStatusManager",
|