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
|
@@ -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,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Management command to generate .proto files from Django models.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python manage.py generate_protos [app_label ...]
|
|
6
|
+
python manage.py generate_protos crypto
|
|
7
|
+
python manage.py generate_protos crypto accounts
|
|
8
|
+
python manage.py generate_protos --all
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from django.core.management.base import CommandError
|
|
12
|
+
from django.apps import apps
|
|
13
|
+
|
|
14
|
+
from django_cfg.management.utils import AdminCommand
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Command(AdminCommand):
|
|
18
|
+
command_name = 'generate_protos'
|
|
19
|
+
help = "Generate .proto files from Django models"
|
|
20
|
+
|
|
21
|
+
def add_arguments(self, parser):
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"apps",
|
|
24
|
+
nargs="*",
|
|
25
|
+
type=str,
|
|
26
|
+
help="App labels to generate protos for",
|
|
27
|
+
)
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"--all",
|
|
30
|
+
action="store_true",
|
|
31
|
+
help="Generate protos for all enabled apps from GRPC config",
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"--output-dir",
|
|
35
|
+
type=str,
|
|
36
|
+
default=None,
|
|
37
|
+
help="Custom output directory (overrides config)",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def handle(self, *args, **options):
|
|
41
|
+
from django_cfg.apps.integrations.grpc.utils.proto_gen import generate_proto_for_app
|
|
42
|
+
from django_cfg.apps.integrations.grpc.services.config_helper import get_grpc_config
|
|
43
|
+
|
|
44
|
+
# Get gRPC config
|
|
45
|
+
grpc_config = get_grpc_config()
|
|
46
|
+
if not grpc_config or not grpc_config.enabled:
|
|
47
|
+
raise CommandError("gRPC is not enabled in configuration")
|
|
48
|
+
|
|
49
|
+
# Determine which apps to generate
|
|
50
|
+
app_labels = options["apps"]
|
|
51
|
+
|
|
52
|
+
if options["all"]:
|
|
53
|
+
# Use enabled_apps from config
|
|
54
|
+
app_labels = grpc_config.enabled_apps
|
|
55
|
+
if not app_labels:
|
|
56
|
+
raise CommandError("No enabled_apps configured in GRPCConfig")
|
|
57
|
+
self.stdout.write(
|
|
58
|
+
self.style.SUCCESS(
|
|
59
|
+
f"Generating protos for all enabled apps: {', '.join(app_labels)}"
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
elif not app_labels:
|
|
63
|
+
# No apps specified - show help
|
|
64
|
+
raise CommandError(
|
|
65
|
+
"Please specify app labels or use --all flag\n"
|
|
66
|
+
"Examples:\n"
|
|
67
|
+
" python manage.py generate_protos crypto\n"
|
|
68
|
+
" python manage.py generate_protos crypto accounts\n"
|
|
69
|
+
" python manage.py generate_protos --all"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Validate apps exist
|
|
73
|
+
for app_label in app_labels:
|
|
74
|
+
try:
|
|
75
|
+
apps.get_app_config(app_label)
|
|
76
|
+
except LookupError:
|
|
77
|
+
raise CommandError(f"App '{app_label}' not found")
|
|
78
|
+
|
|
79
|
+
# Get output directory from options or config
|
|
80
|
+
output_dir = None
|
|
81
|
+
if options["output_dir"]:
|
|
82
|
+
from pathlib import Path
|
|
83
|
+
output_dir = Path(options["output_dir"])
|
|
84
|
+
self.stdout.write(
|
|
85
|
+
self.style.WARNING(f"Using custom output directory: {output_dir}")
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Generate protos
|
|
89
|
+
total_generated = 0
|
|
90
|
+
for app_label in app_labels:
|
|
91
|
+
self.stdout.write(f"\nš¦ Generating proto for app: {app_label}")
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
count = generate_proto_for_app(app_label, output_dir=output_dir)
|
|
95
|
+
if count > 0:
|
|
96
|
+
total_generated += count
|
|
97
|
+
self.stdout.write(
|
|
98
|
+
self.style.SUCCESS(f" ā
Generated {app_label}.proto")
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
self.stdout.write(
|
|
102
|
+
self.style.WARNING(f" ā ļø No models found in {app_label}")
|
|
103
|
+
)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
self.stdout.write(
|
|
106
|
+
self.style.ERROR(f" ā Failed: {e}")
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Summary
|
|
110
|
+
self.stdout.write("\n" + "=" * 70)
|
|
111
|
+
if total_generated > 0:
|
|
112
|
+
# Show output location
|
|
113
|
+
if output_dir:
|
|
114
|
+
output_location = output_dir
|
|
115
|
+
elif grpc_config.proto and grpc_config.proto.output_dir:
|
|
116
|
+
output_location = grpc_config.proto.output_dir
|
|
117
|
+
else:
|
|
118
|
+
output_location = "media/protos"
|
|
119
|
+
|
|
120
|
+
self.stdout.write(
|
|
121
|
+
self.style.SUCCESS(
|
|
122
|
+
f"š Generated {total_generated} proto file(s)\n"
|
|
123
|
+
f"š Output directory: {output_location}"
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
else:
|
|
127
|
+
self.stdout.write(
|
|
128
|
+
self.style.WARNING("ā ļø No proto files generated")
|
|
129
|
+
)
|
|
130
|
+
self.stdout.write("=" * 70)
|