django-cfg 1.5.8__py3-none-any.whl → 1.5.20__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/accounts/serializers/profile.py +42 -0
- 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/business/support/serializers.py +3 -2
- 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 +6 -6
- 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/services/__init__.py +6 -0
- django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
- django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -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/monitoring.py +25 -40
- django_cfg/apps/integrations/centrifugo/views/testing_api.py +0 -79
- django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
- django_cfg/apps/integrations/centrifugo/views/wrapper.py +257 -0
- 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/centrifugo/__init__.py +29 -0
- django_cfg/apps/integrations/grpc/centrifugo/bridge.py +277 -0
- django_cfg/apps/integrations/grpc/centrifugo/config.py +167 -0
- django_cfg/apps/integrations/grpc/centrifugo/demo.py +626 -0
- django_cfg/apps/integrations/grpc/centrifugo/test_publish.py +229 -0
- django_cfg/apps/integrations/grpc/centrifugo/transformers.py +89 -0
- django_cfg/apps/integrations/grpc/interceptors/__init__.py +3 -1
- django_cfg/apps/integrations/grpc/interceptors/centrifugo.py +541 -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/compile_proto.py +105 -0
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +185 -0
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +474 -95
- django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
- 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/__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/discovery.py +7 -1
- 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/SERVER_LOGGING.md +164 -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 +378 -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/serializers/config.py +95 -9
- django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
- django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
- django_cfg/apps/system/frontend/views.py +87 -6
- 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/builders/security_builder.py +1 -0
- django_cfg/core/generation/integration_generators/api.py +2 -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/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/generator/typescript/templates/main_index.ts.jinja +12 -8
- django_cfg/modules/django_client/core/ir/schema.py +15 -1
- django_cfg/modules/django_client/core/parser/base.py +126 -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.20.dist-info}/METADATA +1 -1
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/RECORD +158 -121
- django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,6 +12,7 @@ from collections import defaultdict
|
|
|
12
12
|
from typing import Callable
|
|
13
13
|
|
|
14
14
|
import grpc
|
|
15
|
+
import grpc.aio
|
|
15
16
|
|
|
16
17
|
logger = logging.getLogger(__name__)
|
|
17
18
|
|
|
@@ -135,9 +136,9 @@ def reset_metrics():
|
|
|
135
136
|
_metrics.reset()
|
|
136
137
|
|
|
137
138
|
|
|
138
|
-
class MetricsInterceptor(grpc.ServerInterceptor):
|
|
139
|
+
class MetricsInterceptor(grpc.aio.ServerInterceptor):
|
|
139
140
|
"""
|
|
140
|
-
gRPC interceptor for metrics collection.
|
|
141
|
+
gRPC interceptor for metrics collection (async).
|
|
141
142
|
|
|
142
143
|
Features:
|
|
143
144
|
- Tracks request counts
|
|
@@ -170,13 +171,13 @@ class MetricsInterceptor(grpc.ServerInterceptor):
|
|
|
170
171
|
"""Initialize metrics interceptor."""
|
|
171
172
|
self.collector = _metrics
|
|
172
173
|
|
|
173
|
-
def intercept_service(
|
|
174
|
+
async def intercept_service(
|
|
174
175
|
self,
|
|
175
176
|
continuation: Callable,
|
|
176
177
|
handler_call_details: grpc.HandlerCallDetails,
|
|
177
178
|
) -> grpc.RpcMethodHandler:
|
|
178
179
|
"""
|
|
179
|
-
Intercept gRPC service call for metrics collection.
|
|
180
|
+
Intercept gRPC service call for metrics collection (async).
|
|
180
181
|
|
|
181
182
|
Args:
|
|
182
183
|
continuation: Function to invoke the next interceptor or handler
|
|
@@ -190,8 +191,8 @@ class MetricsInterceptor(grpc.ServerInterceptor):
|
|
|
190
191
|
# Record request
|
|
191
192
|
self.collector.record_request(method_name)
|
|
192
193
|
|
|
193
|
-
# Get handler and wrap it
|
|
194
|
-
handler = continuation(handler_call_details)
|
|
194
|
+
# Get handler and wrap it (await for async)
|
|
195
|
+
handler = await continuation(handler_call_details)
|
|
195
196
|
|
|
196
197
|
if handler is None:
|
|
197
198
|
return None
|
|
@@ -215,10 +216,10 @@ class MetricsInterceptor(grpc.ServerInterceptor):
|
|
|
215
216
|
Wrapped RPC method handler
|
|
216
217
|
"""
|
|
217
218
|
def wrap_unary_unary(behavior):
|
|
218
|
-
def wrapper(request, context):
|
|
219
|
+
async def wrapper(request, context):
|
|
219
220
|
start_time = time.time()
|
|
220
221
|
try:
|
|
221
|
-
response = behavior(request, context)
|
|
222
|
+
response = await behavior(request, context)
|
|
222
223
|
duration_ms = (time.time() - start_time) * 1000
|
|
223
224
|
self.collector.record_response_time(method_name, duration_ms)
|
|
224
225
|
return response
|
|
@@ -230,10 +231,10 @@ class MetricsInterceptor(grpc.ServerInterceptor):
|
|
|
230
231
|
return wrapper
|
|
231
232
|
|
|
232
233
|
def wrap_unary_stream(behavior):
|
|
233
|
-
def wrapper(request, context):
|
|
234
|
+
async def wrapper(request, context):
|
|
234
235
|
start_time = time.time()
|
|
235
236
|
try:
|
|
236
|
-
for response in behavior(request, context):
|
|
237
|
+
async for response in behavior(request, context):
|
|
237
238
|
yield response
|
|
238
239
|
duration_ms = (time.time() - start_time) * 1000
|
|
239
240
|
self.collector.record_response_time(method_name, duration_ms)
|
|
@@ -245,10 +246,10 @@ class MetricsInterceptor(grpc.ServerInterceptor):
|
|
|
245
246
|
return wrapper
|
|
246
247
|
|
|
247
248
|
def wrap_stream_unary(behavior):
|
|
248
|
-
def wrapper(request_iterator, context):
|
|
249
|
+
async def wrapper(request_iterator, context):
|
|
249
250
|
start_time = time.time()
|
|
250
251
|
try:
|
|
251
|
-
response = behavior(request_iterator, context)
|
|
252
|
+
response = await behavior(request_iterator, context)
|
|
252
253
|
duration_ms = (time.time() - start_time) * 1000
|
|
253
254
|
self.collector.record_response_time(method_name, duration_ms)
|
|
254
255
|
return response
|
|
@@ -260,10 +261,10 @@ class MetricsInterceptor(grpc.ServerInterceptor):
|
|
|
260
261
|
return wrapper
|
|
261
262
|
|
|
262
263
|
def wrap_stream_stream(behavior):
|
|
263
|
-
def wrapper(request_iterator, context):
|
|
264
|
+
async def wrapper(request_iterator, context):
|
|
264
265
|
start_time = time.time()
|
|
265
266
|
try:
|
|
266
|
-
for response in behavior(request_iterator, context):
|
|
267
|
+
async for response in behavior(request_iterator, context):
|
|
267
268
|
yield response
|
|
268
269
|
duration_ms = (time.time() - start_time) * 1000
|
|
269
270
|
self.collector.record_response_time(method_name, duration_ms)
|
|
@@ -6,19 +6,21 @@ Automatically logs all gRPC requests to the database for monitoring.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import asyncio
|
|
9
10
|
import logging
|
|
10
11
|
import time
|
|
11
12
|
import uuid
|
|
12
13
|
from typing import Callable
|
|
13
14
|
|
|
14
15
|
import grpc
|
|
16
|
+
import grpc.aio
|
|
15
17
|
|
|
16
18
|
logger = logging.getLogger(__name__)
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
21
|
+
class RequestLoggerInterceptor(grpc.aio.ServerInterceptor):
|
|
20
22
|
"""
|
|
21
|
-
gRPC interceptor for request logging to database.
|
|
23
|
+
gRPC interceptor for request logging to database (async).
|
|
22
24
|
|
|
23
25
|
Features:
|
|
24
26
|
- Logs all requests to GRPCRequestLog model
|
|
@@ -53,13 +55,13 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
53
55
|
self.log_request_data = log_request_data
|
|
54
56
|
self.log_response_data = log_response_data
|
|
55
57
|
|
|
56
|
-
def intercept_service(
|
|
58
|
+
async def intercept_service(
|
|
57
59
|
self,
|
|
58
60
|
continuation: Callable,
|
|
59
61
|
handler_call_details: grpc.HandlerCallDetails,
|
|
60
62
|
) -> grpc.RpcMethodHandler:
|
|
61
63
|
"""
|
|
62
|
-
Intercept gRPC service call for logging.
|
|
64
|
+
Intercept gRPC service call for logging (async).
|
|
63
65
|
|
|
64
66
|
Args:
|
|
65
67
|
continuation: Function to invoke the next interceptor or handler
|
|
@@ -80,8 +82,8 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
80
82
|
peer = metadata_dict.get("peer", "unknown")
|
|
81
83
|
user_agent = metadata_dict.get("user-agent", None)
|
|
82
84
|
|
|
83
|
-
# Get handler and wrap it
|
|
84
|
-
handler = continuation(handler_call_details)
|
|
85
|
+
# Get handler and wrap it (await for async)
|
|
86
|
+
handler = await continuation(handler_call_details)
|
|
85
87
|
|
|
86
88
|
if handler is None:
|
|
87
89
|
logger.warning(f"[gRPC Logger] No handler found for {full_method}")
|
|
@@ -124,11 +126,11 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
124
126
|
Wrapped RPC method handler
|
|
125
127
|
"""
|
|
126
128
|
def wrap_unary_unary(behavior):
|
|
127
|
-
def wrapper(request, context):
|
|
129
|
+
async def wrapper(request, context):
|
|
128
130
|
start_time = time.time()
|
|
129
131
|
|
|
130
|
-
# Create log entry
|
|
131
|
-
log_entry = self.
|
|
132
|
+
# Create log entry (async)
|
|
133
|
+
log_entry = await self._create_log_entry_async(
|
|
132
134
|
request_id=request_id,
|
|
133
135
|
service_name=service_name,
|
|
134
136
|
method_name=method_name,
|
|
@@ -140,11 +142,11 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
140
142
|
)
|
|
141
143
|
|
|
142
144
|
try:
|
|
143
|
-
response = behavior(request, context)
|
|
145
|
+
response = await behavior(request, context)
|
|
144
146
|
duration_ms = int((time.time() - start_time) * 1000)
|
|
145
147
|
|
|
146
|
-
# Mark as successful
|
|
147
|
-
self.
|
|
148
|
+
# Mark as successful (async)
|
|
149
|
+
await self._mark_success_async(
|
|
148
150
|
log_entry,
|
|
149
151
|
duration_ms=duration_ms,
|
|
150
152
|
response=response if self.log_response_data else None,
|
|
@@ -154,8 +156,8 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
154
156
|
except Exception as e:
|
|
155
157
|
duration_ms = int((time.time() - start_time) * 1000)
|
|
156
158
|
|
|
157
|
-
# Mark as error
|
|
158
|
-
self.
|
|
159
|
+
# Mark as error (async)
|
|
160
|
+
await self._mark_error_async(
|
|
159
161
|
log_entry,
|
|
160
162
|
error=e,
|
|
161
163
|
context=context,
|
|
@@ -167,11 +169,11 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
167
169
|
return wrapper
|
|
168
170
|
|
|
169
171
|
def wrap_unary_stream(behavior):
|
|
170
|
-
def wrapper(request, context):
|
|
172
|
+
async def wrapper(request, context):
|
|
171
173
|
start_time = time.time()
|
|
172
174
|
|
|
173
|
-
# Create log entry
|
|
174
|
-
log_entry = self.
|
|
175
|
+
# Create log entry (async)
|
|
176
|
+
log_entry = await self._create_log_entry_async(
|
|
175
177
|
request_id=request_id,
|
|
176
178
|
service_name=service_name,
|
|
177
179
|
method_name=method_name,
|
|
@@ -184,14 +186,14 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
184
186
|
|
|
185
187
|
try:
|
|
186
188
|
response_count = 0
|
|
187
|
-
for response in behavior(request, context):
|
|
189
|
+
async for response in behavior(request, context):
|
|
188
190
|
response_count += 1
|
|
189
191
|
yield response
|
|
190
192
|
|
|
191
193
|
duration_ms = int((time.time() - start_time) * 1000)
|
|
192
194
|
|
|
193
|
-
# Mark as successful
|
|
194
|
-
self.
|
|
195
|
+
# Mark as successful (async)
|
|
196
|
+
await self._mark_success_async(
|
|
195
197
|
log_entry,
|
|
196
198
|
duration_ms=duration_ms,
|
|
197
199
|
response_data={"message_count": response_count} if not self.log_response_data else None,
|
|
@@ -200,8 +202,8 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
200
202
|
except Exception as e:
|
|
201
203
|
duration_ms = int((time.time() - start_time) * 1000)
|
|
202
204
|
|
|
203
|
-
# Mark as error
|
|
204
|
-
self.
|
|
205
|
+
# Mark as error (async)
|
|
206
|
+
await self._mark_error_async(
|
|
205
207
|
log_entry,
|
|
206
208
|
error=e,
|
|
207
209
|
context=context,
|
|
@@ -213,11 +215,11 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
213
215
|
return wrapper
|
|
214
216
|
|
|
215
217
|
def wrap_stream_unary(behavior):
|
|
216
|
-
def wrapper(request_iterator, context):
|
|
218
|
+
async def wrapper(request_iterator, context):
|
|
217
219
|
start_time = time.time()
|
|
218
220
|
|
|
219
|
-
# Create log entry
|
|
220
|
-
log_entry = self.
|
|
221
|
+
# Create log entry (async)
|
|
222
|
+
log_entry = await self._create_log_entry_async(
|
|
221
223
|
request_id=request_id,
|
|
222
224
|
service_name=service_name,
|
|
223
225
|
method_name=method_name,
|
|
@@ -228,19 +230,23 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
228
230
|
)
|
|
229
231
|
|
|
230
232
|
try:
|
|
231
|
-
# Count requests
|
|
233
|
+
# Count requests (async for)
|
|
232
234
|
requests = []
|
|
233
235
|
request_count = 0
|
|
234
|
-
for req in request_iterator:
|
|
236
|
+
async for req in request_iterator:
|
|
235
237
|
request_count += 1
|
|
236
238
|
requests.append(req)
|
|
237
239
|
|
|
238
|
-
# Process
|
|
239
|
-
|
|
240
|
+
# Process (create async generator)
|
|
241
|
+
async def async_iter():
|
|
242
|
+
for r in requests:
|
|
243
|
+
yield r
|
|
244
|
+
|
|
245
|
+
response = await behavior(async_iter(), context)
|
|
240
246
|
duration_ms = int((time.time() - start_time) * 1000)
|
|
241
247
|
|
|
242
|
-
# Mark as successful
|
|
243
|
-
self.
|
|
248
|
+
# Mark as successful (async)
|
|
249
|
+
await self._mark_success_async(
|
|
244
250
|
log_entry,
|
|
245
251
|
duration_ms=duration_ms,
|
|
246
252
|
request_data={"message_count": request_count} if not self.log_request_data else None,
|
|
@@ -251,8 +257,8 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
251
257
|
except Exception as e:
|
|
252
258
|
duration_ms = int((time.time() - start_time) * 1000)
|
|
253
259
|
|
|
254
|
-
# Mark as error
|
|
255
|
-
self.
|
|
260
|
+
# Mark as error (async)
|
|
261
|
+
await self._mark_error_async(
|
|
256
262
|
log_entry,
|
|
257
263
|
error=e,
|
|
258
264
|
context=context,
|
|
@@ -264,11 +270,11 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
264
270
|
return wrapper
|
|
265
271
|
|
|
266
272
|
def wrap_stream_stream(behavior):
|
|
267
|
-
def wrapper(request_iterator, context):
|
|
273
|
+
async def wrapper(request_iterator, context):
|
|
268
274
|
start_time = time.time()
|
|
269
275
|
|
|
270
|
-
# Create log entry
|
|
271
|
-
log_entry = self.
|
|
276
|
+
# Create log entry (async)
|
|
277
|
+
log_entry = await self._create_log_entry_async(
|
|
272
278
|
request_id=request_id,
|
|
273
279
|
service_name=service_name,
|
|
274
280
|
method_name=method_name,
|
|
@@ -279,23 +285,27 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
279
285
|
)
|
|
280
286
|
|
|
281
287
|
try:
|
|
282
|
-
# Count requests
|
|
288
|
+
# Count requests (async for)
|
|
283
289
|
requests = []
|
|
284
290
|
request_count = 0
|
|
285
|
-
for req in request_iterator:
|
|
291
|
+
async for req in request_iterator:
|
|
286
292
|
request_count += 1
|
|
287
293
|
requests.append(req)
|
|
288
294
|
|
|
289
|
-
# Process and count responses
|
|
295
|
+
# Process and count responses (async for)
|
|
296
|
+
async def async_iter():
|
|
297
|
+
for r in requests:
|
|
298
|
+
yield r
|
|
299
|
+
|
|
290
300
|
response_count = 0
|
|
291
|
-
for response in behavior(
|
|
301
|
+
async for response in behavior(async_iter(), context):
|
|
292
302
|
response_count += 1
|
|
293
303
|
yield response
|
|
294
304
|
|
|
295
305
|
duration_ms = int((time.time() - start_time) * 1000)
|
|
296
306
|
|
|
297
|
-
# Mark as successful
|
|
298
|
-
self.
|
|
307
|
+
# Mark as successful (async)
|
|
308
|
+
await self._mark_success_async(
|
|
299
309
|
log_entry,
|
|
300
310
|
duration_ms=duration_ms,
|
|
301
311
|
response_data={"request_count": request_count, "response_count": response_count} if not self.log_response_data else None,
|
|
@@ -304,8 +314,8 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
304
314
|
except Exception as e:
|
|
305
315
|
duration_ms = int((time.time() - start_time) * 1000)
|
|
306
316
|
|
|
307
|
-
# Mark as error
|
|
308
|
-
self.
|
|
317
|
+
# Mark as error (async)
|
|
318
|
+
await self._mark_error_async(
|
|
309
319
|
log_entry,
|
|
310
320
|
error=e,
|
|
311
321
|
context=context,
|
|
@@ -344,7 +354,7 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
344
354
|
else:
|
|
345
355
|
return handler
|
|
346
356
|
|
|
347
|
-
def
|
|
357
|
+
async def _create_log_entry_async(
|
|
348
358
|
self,
|
|
349
359
|
request_id: str,
|
|
350
360
|
service_name: str,
|
|
@@ -352,27 +362,33 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
352
362
|
full_method: str,
|
|
353
363
|
peer: str,
|
|
354
364
|
user_agent: str,
|
|
355
|
-
context: grpc.ServicerContext,
|
|
365
|
+
context: grpc.aio.ServicerContext,
|
|
356
366
|
request=None,
|
|
357
367
|
):
|
|
358
|
-
"""Create initial log entry in database."""
|
|
368
|
+
"""Create initial log entry in database (async)."""
|
|
359
369
|
try:
|
|
360
370
|
from ..models import GRPCRequestLog
|
|
371
|
+
from ..auth import get_current_grpc_user, get_current_grpc_api_key
|
|
361
372
|
|
|
362
|
-
# Get user from
|
|
363
|
-
user =
|
|
373
|
+
# Get user and api_key from contextvars (set by ApiKeyAuthInterceptor)
|
|
374
|
+
user = get_current_grpc_user()
|
|
375
|
+
api_key = get_current_grpc_api_key()
|
|
364
376
|
is_authenticated = user is not None
|
|
365
377
|
|
|
378
|
+
logger.info(f"[RequestLogger] Got contextvar api_key = {api_key} (user={user}, authenticated={is_authenticated})")
|
|
379
|
+
|
|
366
380
|
# Extract client IP from peer
|
|
367
381
|
client_ip = self._extract_ip_from_peer(peer)
|
|
368
382
|
|
|
369
|
-
# Create log entry
|
|
370
|
-
log_entry =
|
|
383
|
+
# Create log entry (wrap Django ORM in asyncio.to_thread)
|
|
384
|
+
log_entry = await asyncio.to_thread(
|
|
385
|
+
GRPCRequestLog.objects.create,
|
|
371
386
|
request_id=request_id,
|
|
372
387
|
service_name=service_name,
|
|
373
388
|
method_name=method_name,
|
|
374
389
|
full_method=full_method,
|
|
375
390
|
user=user if is_authenticated else None,
|
|
391
|
+
api_key=api_key,
|
|
376
392
|
is_authenticated=is_authenticated,
|
|
377
393
|
client_ip=client_ip,
|
|
378
394
|
user_agent=user_agent,
|
|
@@ -387,7 +403,7 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
387
403
|
logger.error(f"Failed to create log entry: {e}", exc_info=True)
|
|
388
404
|
return None
|
|
389
405
|
|
|
390
|
-
def
|
|
406
|
+
async def _mark_success_async(
|
|
391
407
|
self,
|
|
392
408
|
log_entry,
|
|
393
409
|
duration_ms: int,
|
|
@@ -395,7 +411,7 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
395
411
|
request_data: dict = None,
|
|
396
412
|
response_data: dict = None,
|
|
397
413
|
):
|
|
398
|
-
"""Mark log entry as successful."""
|
|
414
|
+
"""Mark log entry as successful (async)."""
|
|
399
415
|
if log_entry is None:
|
|
400
416
|
return
|
|
401
417
|
|
|
@@ -406,7 +422,9 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
406
422
|
if response:
|
|
407
423
|
response_data = self._serialize_message(response)
|
|
408
424
|
|
|
409
|
-
|
|
425
|
+
# Wrap Django ORM in asyncio.to_thread
|
|
426
|
+
await asyncio.to_thread(
|
|
427
|
+
GRPCRequestLog.objects.mark_success,
|
|
410
428
|
log_entry,
|
|
411
429
|
duration_ms=duration_ms,
|
|
412
430
|
response_data=response_data,
|
|
@@ -415,14 +433,14 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
415
433
|
except Exception as e:
|
|
416
434
|
logger.error(f"Failed to mark success: {e}", exc_info=True)
|
|
417
435
|
|
|
418
|
-
def
|
|
436
|
+
async def _mark_error_async(
|
|
419
437
|
self,
|
|
420
438
|
log_entry,
|
|
421
439
|
error: Exception,
|
|
422
|
-
context: grpc.ServicerContext,
|
|
440
|
+
context: grpc.aio.ServicerContext,
|
|
423
441
|
duration_ms: int,
|
|
424
442
|
):
|
|
425
|
-
"""Mark log entry as error."""
|
|
443
|
+
"""Mark log entry as error (async)."""
|
|
426
444
|
if log_entry is None:
|
|
427
445
|
return
|
|
428
446
|
|
|
@@ -432,7 +450,9 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
432
450
|
# Get gRPC status code
|
|
433
451
|
grpc_code = self._get_grpc_code(error, context)
|
|
434
452
|
|
|
435
|
-
|
|
453
|
+
# Wrap Django ORM in asyncio.to_thread
|
|
454
|
+
await asyncio.to_thread(
|
|
455
|
+
GRPCRequestLog.objects.mark_error,
|
|
436
456
|
log_entry,
|
|
437
457
|
grpc_status_code=grpc_code,
|
|
438
458
|
error_message=str(error),
|
|
@@ -485,7 +505,7 @@ class RequestLoggerInterceptor(grpc.ServerInterceptor):
|
|
|
485
505
|
pass
|
|
486
506
|
return None
|
|
487
507
|
|
|
488
|
-
def _get_grpc_code(self, error: Exception, context: grpc.ServicerContext) -> str:
|
|
508
|
+
def _get_grpc_code(self, error: Exception, context: grpc.aio.ServicerContext) -> str:
|
|
489
509
|
"""Get gRPC status code from error."""
|
|
490
510
|
try:
|
|
491
511
|
# Check if error is a gRPC error
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django management command to compile .proto files to Python.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
# Compile single proto file
|
|
6
|
+
python manage.py compile_proto path/to/file.proto
|
|
7
|
+
|
|
8
|
+
# Compile with custom output directory
|
|
9
|
+
python manage.py compile_proto path/to/file.proto --output-dir generated/
|
|
10
|
+
|
|
11
|
+
# Auto-fix imports (change 'import X' to 'from . import X')
|
|
12
|
+
python manage.py compile_proto path/to/file.proto --fix-imports
|
|
13
|
+
|
|
14
|
+
# Compile all proto files in a directory
|
|
15
|
+
python manage.py compile_proto path/to/protos/ --recursive
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from django.core.management.base import BaseCommand, CommandError
|
|
22
|
+
|
|
23
|
+
from django_cfg.apps.integrations.grpc.management.proto.compiler import ProtoCompiler
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Command(BaseCommand):
|
|
29
|
+
help = "Compile .proto files to Python using grpc_tools.protoc"
|
|
30
|
+
|
|
31
|
+
def add_arguments(self, parser):
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"proto_path",
|
|
34
|
+
type=str,
|
|
35
|
+
help="Path to .proto file or directory containing .proto files",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--output-dir",
|
|
39
|
+
type=str,
|
|
40
|
+
default=None,
|
|
41
|
+
help="Output directory for generated files (default: same as proto file)",
|
|
42
|
+
)
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"--proto-path",
|
|
45
|
+
type=str,
|
|
46
|
+
default=None,
|
|
47
|
+
help="Additional proto import path (passed to protoc -I flag)",
|
|
48
|
+
)
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"--fix-imports",
|
|
51
|
+
action="store_true",
|
|
52
|
+
default=True,
|
|
53
|
+
help="Fix imports in generated _grpc.py files (default: True)",
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"--no-fix-imports",
|
|
57
|
+
action="store_false",
|
|
58
|
+
dest="fix_imports",
|
|
59
|
+
help="Disable import fixing",
|
|
60
|
+
)
|
|
61
|
+
parser.add_argument(
|
|
62
|
+
"--recursive",
|
|
63
|
+
action="store_true",
|
|
64
|
+
help="Recursively compile all .proto files in directory",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def handle(self, *args, **options):
|
|
68
|
+
proto_path = Path(options["proto_path"])
|
|
69
|
+
output_dir = Path(options["output_dir"]) if options["output_dir"] else None
|
|
70
|
+
proto_import_path = Path(options["proto_path"]) if options.get("proto_path") else None
|
|
71
|
+
fix_imports = options["fix_imports"]
|
|
72
|
+
recursive = options["recursive"]
|
|
73
|
+
|
|
74
|
+
if not proto_path.exists():
|
|
75
|
+
raise CommandError(f"Path does not exist: {proto_path}")
|
|
76
|
+
|
|
77
|
+
# Create compiler
|
|
78
|
+
compiler = ProtoCompiler(
|
|
79
|
+
output_dir=output_dir,
|
|
80
|
+
proto_import_path=proto_import_path,
|
|
81
|
+
fix_imports=fix_imports,
|
|
82
|
+
verbose=True,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
self.stdout.write("")
|
|
86
|
+
|
|
87
|
+
# Compile proto file(s)
|
|
88
|
+
if proto_path.is_file():
|
|
89
|
+
success = compiler.compile_file(proto_path)
|
|
90
|
+
if not success:
|
|
91
|
+
raise CommandError(f"Failed to compile {proto_path}")
|
|
92
|
+
else:
|
|
93
|
+
success_count, failure_count = compiler.compile_directory(
|
|
94
|
+
proto_path,
|
|
95
|
+
recursive=recursive,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if failure_count > 0:
|
|
99
|
+
raise CommandError(
|
|
100
|
+
f"Failed to compile {failure_count} proto file(s) "
|
|
101
|
+
f"({success_count} succeeded)"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
self.stdout.write("")
|
|
105
|
+
self.stdout.write(self.style.SUCCESS("🎉 Done! All proto files compiled successfully."))
|