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
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Proto Files Manager Service.
|
|
3
|
+
|
|
4
|
+
Handles proto file operations: scanning, reading, generating, archiving.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import io
|
|
8
|
+
import zipfile
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from django.conf import settings
|
|
13
|
+
from django_cfg.modules.django_logging import get_logger
|
|
14
|
+
|
|
15
|
+
from .config_helper import get_grpc_config
|
|
16
|
+
|
|
17
|
+
logger = get_logger("grpc.proto_files_manager")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ProtoFilesManager:
|
|
21
|
+
"""
|
|
22
|
+
Service for managing proto files.
|
|
23
|
+
|
|
24
|
+
Handles:
|
|
25
|
+
- Scanning proto directory
|
|
26
|
+
- Reading proto files
|
|
27
|
+
- Generating proto files from Django models
|
|
28
|
+
- Creating zip archives
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
"""Initialize proto files manager."""
|
|
33
|
+
self.grpc_config = get_grpc_config()
|
|
34
|
+
|
|
35
|
+
def get_proto_dir(self) -> Path:
|
|
36
|
+
"""
|
|
37
|
+
Get proto files directory path.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Path to proto files directory
|
|
41
|
+
"""
|
|
42
|
+
if self.grpc_config and self.grpc_config.proto and self.grpc_config.proto.output_dir:
|
|
43
|
+
proto_dir = Path(settings.BASE_DIR) / self.grpc_config.proto.output_dir
|
|
44
|
+
else:
|
|
45
|
+
proto_dir = Path(settings.MEDIA_ROOT) / "protos"
|
|
46
|
+
|
|
47
|
+
return proto_dir
|
|
48
|
+
|
|
49
|
+
def scan_proto_files(self, request=None) -> List[Dict]:
|
|
50
|
+
"""
|
|
51
|
+
Scan proto directory and return list of proto files with metadata.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
request: Optional Django request object for building download URLs
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
List of dicts with proto file metadata:
|
|
58
|
+
{
|
|
59
|
+
"app_label": str,
|
|
60
|
+
"filename": str,
|
|
61
|
+
"size_bytes": int,
|
|
62
|
+
"package": str,
|
|
63
|
+
"messages_count": int,
|
|
64
|
+
"services_count": int,
|
|
65
|
+
"created_at": float,
|
|
66
|
+
"modified_at": float,
|
|
67
|
+
"download_url": str (if request provided),
|
|
68
|
+
}
|
|
69
|
+
"""
|
|
70
|
+
proto_dir = self.get_proto_dir()
|
|
71
|
+
|
|
72
|
+
if not proto_dir.exists():
|
|
73
|
+
logger.warning(f"Proto directory does not exist: {proto_dir}")
|
|
74
|
+
return []
|
|
75
|
+
|
|
76
|
+
proto_files = []
|
|
77
|
+
for proto_file in proto_dir.glob("*.proto"):
|
|
78
|
+
try:
|
|
79
|
+
metadata = self._parse_proto_file(proto_file)
|
|
80
|
+
if metadata:
|
|
81
|
+
# Add download URL if request provided
|
|
82
|
+
if request:
|
|
83
|
+
from django.urls import reverse
|
|
84
|
+
from django_cfg.core.state import get_current_config
|
|
85
|
+
|
|
86
|
+
app_label = metadata['app_label']
|
|
87
|
+
config = get_current_config()
|
|
88
|
+
|
|
89
|
+
# Use api_url from config (respects HTTPS behind reverse proxy)
|
|
90
|
+
# Falls back to request.build_absolute_uri if config not available
|
|
91
|
+
if config and hasattr(config, 'api_url'):
|
|
92
|
+
path = reverse('django_cfg_grpc:proto-files-detail', kwargs={'pk': app_label})
|
|
93
|
+
download_url = f"{config.api_url}{path}"
|
|
94
|
+
else:
|
|
95
|
+
download_url = request.build_absolute_uri(
|
|
96
|
+
reverse('django_cfg_grpc:proto-files-detail', kwargs={'pk': app_label})
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
metadata['download_url'] = download_url
|
|
100
|
+
|
|
101
|
+
proto_files.append(metadata)
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.error(f"Error reading proto file {proto_file}: {e}")
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
return proto_files
|
|
107
|
+
|
|
108
|
+
def _parse_proto_file(self, proto_file: Path) -> Optional[Dict]:
|
|
109
|
+
"""
|
|
110
|
+
Parse proto file and extract metadata.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
proto_file: Path to proto file
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Dict with proto file metadata or None if failed
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
stat = proto_file.stat()
|
|
120
|
+
content = proto_file.read_text()
|
|
121
|
+
|
|
122
|
+
# Parse proto file for metadata
|
|
123
|
+
package = ""
|
|
124
|
+
messages_count = 0
|
|
125
|
+
services_count = 0
|
|
126
|
+
|
|
127
|
+
for line in content.split("\n"):
|
|
128
|
+
line = line.strip()
|
|
129
|
+
if line.startswith("package "):
|
|
130
|
+
package = line.replace("package ", "").replace(";", "").strip()
|
|
131
|
+
elif line.startswith("message "):
|
|
132
|
+
messages_count += 1
|
|
133
|
+
elif line.startswith("service "):
|
|
134
|
+
services_count += 1
|
|
135
|
+
|
|
136
|
+
# Extract app_label from filename (crypto.proto -> crypto)
|
|
137
|
+
app_label = proto_file.stem
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
"app_label": app_label,
|
|
141
|
+
"filename": proto_file.name,
|
|
142
|
+
"size_bytes": stat.st_size,
|
|
143
|
+
"package": package,
|
|
144
|
+
"messages_count": messages_count,
|
|
145
|
+
"services_count": services_count,
|
|
146
|
+
"created_at": stat.st_ctime,
|
|
147
|
+
"modified_at": stat.st_mtime,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.error(f"Error parsing proto file {proto_file}: {e}")
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
def get_proto_file(self, app_label: str) -> Optional[Path]:
|
|
155
|
+
"""
|
|
156
|
+
Get path to proto file for specific app.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
app_label: App label (e.g., 'crypto')
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Path to proto file or None if not found
|
|
163
|
+
"""
|
|
164
|
+
proto_dir = self.get_proto_dir()
|
|
165
|
+
proto_file = proto_dir / f"{app_label}.proto"
|
|
166
|
+
|
|
167
|
+
if proto_file.exists():
|
|
168
|
+
return proto_file
|
|
169
|
+
|
|
170
|
+
logger.warning(f"Proto file not found for app '{app_label}'")
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
def create_zip_archive(self) -> Optional[bytes]:
|
|
174
|
+
"""
|
|
175
|
+
Create zip archive with all proto files.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Zip archive bytes or None if failed
|
|
179
|
+
"""
|
|
180
|
+
proto_dir = self.get_proto_dir()
|
|
181
|
+
|
|
182
|
+
if not proto_dir.exists():
|
|
183
|
+
logger.error(f"Proto directory does not exist: {proto_dir}")
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
# Create zip in memory
|
|
188
|
+
zip_buffer = io.BytesIO()
|
|
189
|
+
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
|
190
|
+
proto_files = list(proto_dir.glob("*.proto"))
|
|
191
|
+
|
|
192
|
+
if not proto_files:
|
|
193
|
+
logger.warning("No proto files found to archive")
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
for proto_file in proto_files:
|
|
197
|
+
zip_file.write(proto_file, arcname=proto_file.name)
|
|
198
|
+
logger.debug(f"Added to archive: {proto_file.name}")
|
|
199
|
+
|
|
200
|
+
zip_buffer.seek(0)
|
|
201
|
+
return zip_buffer.read()
|
|
202
|
+
|
|
203
|
+
except Exception as e:
|
|
204
|
+
logger.error(f"Error creating zip archive: {e}", exc_info=True)
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
def generate_protos(self, apps: Optional[List[str]] = None, force: bool = False) -> Dict:
|
|
208
|
+
"""
|
|
209
|
+
Generate proto files for specified apps.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
apps: List of app labels (uses enabled_apps from config if None)
|
|
213
|
+
force: Force regeneration even if proto file exists
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Dict with generation results:
|
|
217
|
+
{
|
|
218
|
+
"status": "success" | "failed",
|
|
219
|
+
"generated": List[str], # Successfully generated app labels
|
|
220
|
+
"generated_count": int,
|
|
221
|
+
"errors": List[Dict], # Errors for failed apps
|
|
222
|
+
}
|
|
223
|
+
"""
|
|
224
|
+
from ..utils.proto_gen import generate_proto_for_app
|
|
225
|
+
|
|
226
|
+
# Determine which apps to generate for
|
|
227
|
+
if not apps:
|
|
228
|
+
if self.grpc_config and self.grpc_config.enabled_apps:
|
|
229
|
+
apps = self.grpc_config.enabled_apps
|
|
230
|
+
else:
|
|
231
|
+
logger.error("No apps specified and no enabled_apps in config")
|
|
232
|
+
return {
|
|
233
|
+
"status": "failed",
|
|
234
|
+
"generated": [],
|
|
235
|
+
"generated_count": 0,
|
|
236
|
+
"errors": [{"app": "N/A", "error": "No apps specified"}],
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
generated = []
|
|
240
|
+
errors = []
|
|
241
|
+
|
|
242
|
+
for app_label in apps:
|
|
243
|
+
try:
|
|
244
|
+
logger.info(f"Generating proto for app: {app_label}")
|
|
245
|
+
count = generate_proto_for_app(app_label)
|
|
246
|
+
|
|
247
|
+
if count > 0:
|
|
248
|
+
generated.append(app_label)
|
|
249
|
+
logger.info(f"Successfully generated proto for {app_label}")
|
|
250
|
+
else:
|
|
251
|
+
error_msg = "No models found"
|
|
252
|
+
logger.warning(f"No models found in app {app_label}")
|
|
253
|
+
errors.append({"app": app_label, "error": error_msg})
|
|
254
|
+
|
|
255
|
+
except Exception as e:
|
|
256
|
+
error_msg = str(e)
|
|
257
|
+
logger.error(f"Proto generation error for {app_label}: {error_msg}", exc_info=True)
|
|
258
|
+
errors.append({"app": app_label, "error": error_msg})
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
"status": "success" if generated else "failed",
|
|
262
|
+
"generated": generated,
|
|
263
|
+
"generated_count": len(generated),
|
|
264
|
+
"errors": errors,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
__all__ = ["ProtoFilesManager"]
|
|
@@ -11,11 +11,6 @@ from django.db import models
|
|
|
11
11
|
from django.db.models import Avg, Count
|
|
12
12
|
|
|
13
13
|
from ..models import GRPCRequestLog, GRPCServerStatus
|
|
14
|
-
from ..serializers.service_registry import (
|
|
15
|
-
MethodStatsSerializer,
|
|
16
|
-
MethodSummarySerializer,
|
|
17
|
-
ServiceSummarySerializer,
|
|
18
|
-
)
|
|
19
14
|
from django_cfg.modules.django_logging import get_logger
|
|
20
15
|
|
|
21
16
|
logger = get_logger("grpc.service_registry")
|
|
@@ -52,7 +47,10 @@ class ServiceRegistryManager:
|
|
|
52
47
|
|
|
53
48
|
def get_all_services(self) -> List[Dict]:
|
|
54
49
|
"""
|
|
55
|
-
Get all registered services
|
|
50
|
+
Get all registered services.
|
|
51
|
+
|
|
52
|
+
Returns services from running server if available,
|
|
53
|
+
otherwise discovers services from filesystem.
|
|
56
54
|
|
|
57
55
|
Returns:
|
|
58
56
|
List of service metadata dictionaries
|
|
@@ -66,11 +64,16 @@ class ServiceRegistryManager:
|
|
|
66
64
|
'apps.CryptoService'
|
|
67
65
|
"""
|
|
68
66
|
current_server = self.get_current_server()
|
|
69
|
-
if not current_server:
|
|
70
|
-
logger.debug("No running gRPC server found")
|
|
71
|
-
return []
|
|
72
67
|
|
|
73
|
-
|
|
68
|
+
# If server is running, use its registered services
|
|
69
|
+
if current_server and current_server.registered_services:
|
|
70
|
+
return current_server.registered_services
|
|
71
|
+
|
|
72
|
+
# Otherwise, discover services from filesystem
|
|
73
|
+
logger.debug("Server not running - discovering services from filesystem")
|
|
74
|
+
from .discovery import ServiceDiscovery
|
|
75
|
+
discovery = ServiceDiscovery()
|
|
76
|
+
return discovery.get_registered_services()
|
|
74
77
|
|
|
75
78
|
def get_service_by_name(self, service_name: str) -> Optional[Dict]:
|
|
76
79
|
"""
|
|
@@ -183,23 +186,23 @@ class ServiceRegistryManager:
|
|
|
183
186
|
# Extract package name
|
|
184
187
|
package = service_name.split(".")[0] if "." in service_name else ""
|
|
185
188
|
|
|
186
|
-
#
|
|
187
|
-
service_summary =
|
|
188
|
-
name
|
|
189
|
-
full_name
|
|
190
|
-
package
|
|
191
|
-
methods_count
|
|
192
|
-
total_requests
|
|
193
|
-
success_rate
|
|
194
|
-
avg_duration_ms
|
|
195
|
-
last_activity_at
|
|
189
|
+
# Build dict directly
|
|
190
|
+
service_summary = {
|
|
191
|
+
"name": service_name,
|
|
192
|
+
"full_name": service.get("full_name", f"/{service_name}"),
|
|
193
|
+
"package": package,
|
|
194
|
+
"methods_count": len(service.get("methods", [])),
|
|
195
|
+
"total_requests": total,
|
|
196
|
+
"success_rate": round(success_rate, 2),
|
|
197
|
+
"avg_duration_ms": round(stats["avg_duration"] or 0, 2),
|
|
198
|
+
"last_activity_at": (
|
|
196
199
|
stats["last_activity"].isoformat()
|
|
197
200
|
if stats["last_activity"]
|
|
198
201
|
else None
|
|
199
202
|
),
|
|
200
|
-
|
|
203
|
+
}
|
|
201
204
|
|
|
202
|
-
services_with_stats.append(service_summary
|
|
205
|
+
services_with_stats.append(service_summary)
|
|
203
206
|
|
|
204
207
|
return services_with_stats
|
|
205
208
|
|
|
@@ -257,30 +260,29 @@ class ServiceRegistryManager:
|
|
|
257
260
|
successful = stats["successful"] or 0
|
|
258
261
|
success_rate = (successful / total * 100) if total > 0 else 0.0
|
|
259
262
|
|
|
260
|
-
#
|
|
261
|
-
method_stats =
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
methods_list.append(method_summary.model_dump())
|
|
263
|
+
# Build method stats dict
|
|
264
|
+
method_stats = {
|
|
265
|
+
"total_requests": total,
|
|
266
|
+
"successful": successful,
|
|
267
|
+
"errors": stats["errors"] or 0,
|
|
268
|
+
"success_rate": round(success_rate, 2),
|
|
269
|
+
"avg_duration_ms": round(stats["avg_duration"] or 0, 2),
|
|
270
|
+
"p50_duration_ms": p50,
|
|
271
|
+
"p95_duration_ms": p95,
|
|
272
|
+
"p99_duration_ms": p99,
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
# Build method summary dict
|
|
276
|
+
method_summary = {
|
|
277
|
+
"name": method_name,
|
|
278
|
+
"full_name": f"/{service_name}/{method_name}",
|
|
279
|
+
"service_name": service_name,
|
|
280
|
+
"request_type": "",
|
|
281
|
+
"response_type": "",
|
|
282
|
+
"stats": method_stats,
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
methods_list.append(method_summary)
|
|
284
286
|
|
|
285
287
|
return methods_list
|
|
286
288
|
|
|
@@ -11,11 +11,6 @@ from django.db.models import Count
|
|
|
11
11
|
from django_cfg.modules.django_logging import get_logger
|
|
12
12
|
|
|
13
13
|
from ..models import GRPCRequestLog
|
|
14
|
-
from ..serializers.testing import (
|
|
15
|
-
GRPCCallResponseSerializer,
|
|
16
|
-
GRPCExampleSerializer,
|
|
17
|
-
GRPCTestLogSerializer,
|
|
18
|
-
)
|
|
19
14
|
from ..testing import get_example
|
|
20
15
|
from .service_registry import ServiceRegistryManager
|
|
21
16
|
|
|
@@ -75,18 +70,18 @@ class TestingService:
|
|
|
75
70
|
# Get example from registry
|
|
76
71
|
example_data = get_example(service_name, method_name)
|
|
77
72
|
if example_data:
|
|
78
|
-
#
|
|
79
|
-
example =
|
|
80
|
-
service
|
|
81
|
-
method
|
|
82
|
-
description
|
|
73
|
+
# Build dict directly
|
|
74
|
+
example = {
|
|
75
|
+
"service": service_name,
|
|
76
|
+
"method": method_name,
|
|
77
|
+
"description": example_data.get(
|
|
83
78
|
"description", f"{method_name} method"
|
|
84
79
|
),
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
examples.append(example
|
|
80
|
+
"payload_example": example_data.get("request", {}),
|
|
81
|
+
"expected_response": example_data.get("response", {}),
|
|
82
|
+
"metadata_example": example_data.get("metadata", {}),
|
|
83
|
+
}
|
|
84
|
+
examples.append(example)
|
|
90
85
|
|
|
91
86
|
return examples
|
|
92
87
|
|
|
@@ -7,9 +7,11 @@ Public API endpoints for gRPC monitoring.
|
|
|
7
7
|
from django.urls import include, path
|
|
8
8
|
from rest_framework import routers
|
|
9
9
|
|
|
10
|
+
from .views.api_keys import GRPCApiKeyViewSet
|
|
10
11
|
from .views.charts import GRPCChartsViewSet
|
|
11
12
|
from .views.config import GRPCConfigViewSet
|
|
12
13
|
from .views.monitoring import GRPCMonitorViewSet
|
|
14
|
+
from .views.proto_files import GRPCProtoFilesViewSet
|
|
13
15
|
from .views.services import GRPCServiceViewSet
|
|
14
16
|
from .views.testing import GRPCTestingViewSet
|
|
15
17
|
|
|
@@ -33,6 +35,12 @@ router.register(r'test', GRPCTestingViewSet, basename='test')
|
|
|
33
35
|
# Charts endpoints (statistics visualization)
|
|
34
36
|
router.register(r'charts', GRPCChartsViewSet, basename='charts')
|
|
35
37
|
|
|
38
|
+
# API Keys endpoints (read-only)
|
|
39
|
+
router.register(r'api-keys', GRPCApiKeyViewSet, basename='api-keys')
|
|
40
|
+
|
|
41
|
+
# Proto Files endpoints (download proto files)
|
|
42
|
+
router.register(r'proto-files', GRPCProtoFilesViewSet, basename='proto-files')
|
|
43
|
+
|
|
36
44
|
urlpatterns = [
|
|
37
45
|
# Include router URLs
|
|
38
46
|
path('', include(router.urls)),
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
|
-
gRPC
|
|
2
|
+
Utilities for gRPC Integration.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Note:
|
|
7
|
-
For dependency checking, use `django_cfg.apps.integrations.grpc._cfg` instead.
|
|
8
|
-
This module focuses on user-facing utilities like proto generation.
|
|
4
|
+
Reusable utilities for gRPC services in django-cfg.
|
|
9
5
|
"""
|
|
10
6
|
|
|
11
|
-
from .
|
|
7
|
+
from .streaming_logger import setup_streaming_logger, get_streaming_logger
|
|
12
8
|
|
|
13
|
-
__all__ = [
|
|
14
|
-
# Proto generation
|
|
15
|
-
"ProtoFieldMapper",
|
|
16
|
-
"ProtoGenerator",
|
|
17
|
-
"generate_proto_for_app",
|
|
18
|
-
]
|
|
9
|
+
__all__ = ["setup_streaming_logger", "get_streaming_logger"]
|