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.

Files changed (159) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/commands/serializers.py +152 -0
  3. django_cfg/apps/api/commands/views.py +32 -0
  4. django_cfg/apps/business/accounts/management/commands/otp_test.py +5 -2
  5. django_cfg/apps/business/accounts/serializers/profile.py +42 -0
  6. django_cfg/apps/business/agents/management/commands/create_agent.py +5 -194
  7. django_cfg/apps/business/agents/management/commands/load_agent_templates.py +205 -0
  8. django_cfg/apps/business/agents/management/commands/orchestrator_status.py +4 -2
  9. django_cfg/apps/business/knowbase/management/commands/knowbase_stats.py +4 -2
  10. django_cfg/apps/business/knowbase/management/commands/setup_knowbase.py +4 -2
  11. django_cfg/apps/business/newsletter/management/commands/test_newsletter.py +5 -2
  12. django_cfg/apps/business/payments/management/commands/check_payment_status.py +4 -2
  13. django_cfg/apps/business/payments/management/commands/create_payment.py +4 -2
  14. django_cfg/apps/business/payments/management/commands/sync_currencies.py +4 -2
  15. django_cfg/apps/business/support/serializers.py +3 -2
  16. django_cfg/apps/integrations/centrifugo/apps.py +2 -1
  17. django_cfg/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +151 -12
  18. django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +6 -6
  19. django_cfg/apps/integrations/centrifugo/serializers/__init__.py +2 -1
  20. django_cfg/apps/integrations/centrifugo/serializers/publishes.py +22 -2
  21. django_cfg/apps/integrations/centrifugo/services/__init__.py +6 -0
  22. django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
  23. django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -0
  24. django_cfg/apps/integrations/centrifugo/services/publisher.py +371 -0
  25. django_cfg/apps/integrations/centrifugo/services/token_generator.py +122 -0
  26. django_cfg/apps/integrations/centrifugo/urls.py +8 -0
  27. django_cfg/apps/integrations/centrifugo/views/__init__.py +2 -0
  28. django_cfg/apps/integrations/centrifugo/views/monitoring.py +25 -40
  29. django_cfg/apps/integrations/centrifugo/views/testing_api.py +0 -79
  30. django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
  31. django_cfg/apps/integrations/centrifugo/views/wrapper.py +257 -0
  32. django_cfg/apps/integrations/grpc/admin/__init__.py +7 -1
  33. django_cfg/apps/integrations/grpc/admin/config.py +113 -9
  34. django_cfg/apps/integrations/grpc/admin/grpc_api_key.py +129 -0
  35. django_cfg/apps/integrations/grpc/admin/grpc_request_log.py +72 -63
  36. django_cfg/apps/integrations/grpc/admin/grpc_server_status.py +236 -0
  37. django_cfg/apps/integrations/grpc/auth/__init__.py +11 -3
  38. django_cfg/apps/integrations/grpc/auth/api_key_auth.py +320 -0
  39. django_cfg/apps/integrations/grpc/centrifugo/__init__.py +29 -0
  40. django_cfg/apps/integrations/grpc/centrifugo/bridge.py +277 -0
  41. django_cfg/apps/integrations/grpc/centrifugo/config.py +167 -0
  42. django_cfg/apps/integrations/grpc/centrifugo/demo.py +626 -0
  43. django_cfg/apps/integrations/grpc/centrifugo/test_publish.py +229 -0
  44. django_cfg/apps/integrations/grpc/centrifugo/transformers.py +89 -0
  45. django_cfg/apps/integrations/grpc/interceptors/__init__.py +3 -1
  46. django_cfg/apps/integrations/grpc/interceptors/centrifugo.py +541 -0
  47. django_cfg/apps/integrations/grpc/interceptors/logging.py +17 -20
  48. django_cfg/apps/integrations/grpc/interceptors/metrics.py +15 -14
  49. django_cfg/apps/integrations/grpc/interceptors/request_logger.py +79 -59
  50. django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
  51. django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +185 -0
  52. django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +474 -95
  53. django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
  54. django_cfg/apps/integrations/grpc/management/proto/__init__.py +3 -0
  55. django_cfg/apps/integrations/grpc/management/proto/compiler.py +194 -0
  56. django_cfg/apps/integrations/grpc/managers/__init__.py +2 -0
  57. django_cfg/apps/integrations/grpc/managers/grpc_api_key.py +192 -0
  58. django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +19 -11
  59. django_cfg/apps/integrations/grpc/migrations/0005_grpcapikey.py +143 -0
  60. django_cfg/apps/integrations/grpc/migrations/0006_grpcrequestlog_api_key_and_more.py +34 -0
  61. django_cfg/apps/integrations/grpc/models/__init__.py +2 -0
  62. django_cfg/apps/integrations/grpc/models/grpc_api_key.py +198 -0
  63. django_cfg/apps/integrations/grpc/models/grpc_request_log.py +11 -0
  64. django_cfg/apps/integrations/grpc/models/grpc_server_status.py +39 -4
  65. django_cfg/apps/integrations/grpc/serializers/__init__.py +22 -6
  66. django_cfg/apps/integrations/grpc/serializers/api_keys.py +63 -0
  67. django_cfg/apps/integrations/grpc/serializers/charts.py +118 -120
  68. django_cfg/apps/integrations/grpc/serializers/config.py +65 -51
  69. django_cfg/apps/integrations/grpc/serializers/health.py +7 -7
  70. django_cfg/apps/integrations/grpc/serializers/proto_files.py +74 -0
  71. django_cfg/apps/integrations/grpc/serializers/requests.py +13 -7
  72. django_cfg/apps/integrations/grpc/serializers/service_registry.py +181 -112
  73. django_cfg/apps/integrations/grpc/serializers/services.py +14 -32
  74. django_cfg/apps/integrations/grpc/serializers/stats.py +50 -12
  75. django_cfg/apps/integrations/grpc/serializers/testing.py +66 -58
  76. django_cfg/apps/integrations/grpc/services/__init__.py +2 -0
  77. django_cfg/apps/integrations/grpc/services/discovery.py +7 -1
  78. django_cfg/apps/integrations/grpc/services/monitoring_service.py +149 -43
  79. django_cfg/apps/integrations/grpc/services/proto_files_manager.py +268 -0
  80. django_cfg/apps/integrations/grpc/services/service_registry.py +48 -46
  81. django_cfg/apps/integrations/grpc/services/testing_service.py +10 -15
  82. django_cfg/apps/integrations/grpc/urls.py +8 -0
  83. django_cfg/apps/integrations/grpc/utils/SERVER_LOGGING.md +164 -0
  84. django_cfg/apps/integrations/grpc/utils/__init__.py +4 -13
  85. django_cfg/apps/integrations/grpc/utils/integration_test.py +334 -0
  86. django_cfg/apps/integrations/grpc/utils/proto_gen.py +48 -8
  87. django_cfg/apps/integrations/grpc/utils/streaming_logger.py +378 -0
  88. django_cfg/apps/integrations/grpc/views/__init__.py +4 -0
  89. django_cfg/apps/integrations/grpc/views/api_keys.py +255 -0
  90. django_cfg/apps/integrations/grpc/views/charts.py +21 -14
  91. django_cfg/apps/integrations/grpc/views/config.py +8 -6
  92. django_cfg/apps/integrations/grpc/views/monitoring.py +51 -79
  93. django_cfg/apps/integrations/grpc/views/proto_files.py +214 -0
  94. django_cfg/apps/integrations/grpc/views/services.py +30 -21
  95. django_cfg/apps/integrations/grpc/views/testing.py +45 -43
  96. django_cfg/apps/integrations/rq/views/jobs.py +19 -9
  97. django_cfg/apps/integrations/rq/views/schedule.py +7 -3
  98. django_cfg/apps/system/dashboard/serializers/commands.py +25 -1
  99. django_cfg/apps/system/dashboard/serializers/config.py +95 -9
  100. django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
  101. django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
  102. django_cfg/apps/system/frontend/views.py +87 -6
  103. django_cfg/apps/system/maintenance/management/commands/maintenance.py +5 -2
  104. django_cfg/apps/system/maintenance/management/commands/process_scheduled_maintenance.py +4 -2
  105. django_cfg/apps/system/maintenance/management/commands/sync_cloudflare.py +5 -2
  106. django_cfg/config.py +33 -0
  107. django_cfg/core/builders/security_builder.py +1 -0
  108. django_cfg/core/generation/integration_generators/api.py +2 -0
  109. django_cfg/core/generation/integration_generators/grpc_generator.py +30 -32
  110. django_cfg/management/commands/check_endpoints.py +2 -2
  111. django_cfg/management/commands/check_settings.py +3 -10
  112. django_cfg/management/commands/clear_constance.py +3 -10
  113. django_cfg/management/commands/create_token.py +4 -11
  114. django_cfg/management/commands/list_urls.py +4 -10
  115. django_cfg/management/commands/migrate_all.py +18 -12
  116. django_cfg/management/commands/migrator.py +4 -11
  117. django_cfg/management/commands/script.py +4 -10
  118. django_cfg/management/commands/show_config.py +8 -16
  119. django_cfg/management/commands/show_urls.py +5 -11
  120. django_cfg/management/commands/superuser.py +4 -11
  121. django_cfg/management/commands/tree.py +5 -10
  122. django_cfg/management/utils/README.md +402 -0
  123. django_cfg/management/utils/__init__.py +29 -0
  124. django_cfg/management/utils/mixins.py +176 -0
  125. django_cfg/middleware/pagination.py +53 -54
  126. django_cfg/models/api/grpc/__init__.py +15 -21
  127. django_cfg/models/api/grpc/config.py +155 -73
  128. django_cfg/models/ngrok/config.py +7 -6
  129. django_cfg/modules/django_client/core/generator/python/files_generator.py +5 -13
  130. django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +16 -4
  131. django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -3
  132. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +6 -5
  133. django_cfg/modules/django_client/core/generator/typescript/generator.py +26 -0
  134. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +7 -1
  135. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +5 -0
  136. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +11 -0
  137. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +1 -0
  138. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +29 -1
  139. django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +4 -0
  140. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +12 -8
  141. django_cfg/modules/django_client/core/ir/schema.py +15 -1
  142. django_cfg/modules/django_client/core/parser/base.py +126 -30
  143. django_cfg/modules/django_client/management/commands/generate_client.py +5 -2
  144. django_cfg/modules/django_client/management/commands/validate_openapi.py +5 -2
  145. django_cfg/modules/django_email/management/commands/test_email.py +4 -10
  146. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +16 -13
  147. django_cfg/modules/django_telegram/management/commands/test_telegram.py +4 -11
  148. django_cfg/modules/django_twilio/management/commands/test_twilio.py +4 -11
  149. django_cfg/modules/django_unfold/navigation.py +6 -18
  150. django_cfg/pyproject.toml +1 -1
  151. django_cfg/registry/modules.py +1 -4
  152. django_cfg/requirements.txt +52 -0
  153. django_cfg/static/frontend/admin.zip +0 -0
  154. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/METADATA +1 -1
  155. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/RECORD +158 -121
  156. django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
  157. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/WHEEL +0 -0
  158. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/entry_points.txt +0 -0
  159. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.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 from the running gRPC server.
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
- return current_server.registered_services or []
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
- # Validate and serialize data
187
- service_summary = ServiceSummarySerializer(
188
- name=service_name,
189
- full_name=service.get("full_name", f"/{service_name}"),
190
- package=package,
191
- methods_count=len(service.get("methods", [])),
192
- total_requests=total,
193
- success_rate=round(success_rate, 2),
194
- avg_duration_ms=round(stats["avg_duration"] or 0, 2),
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.model_dump())
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
- # Create stats object first
261
- method_stats = MethodStatsSerializer(
262
- service_name=service_name,
263
- method_name=method_name,
264
- total=total,
265
- successful=successful,
266
- errors=stats["errors"] or 0,
267
- avg_duration_ms=round(stats["avg_duration"] or 0, 2),
268
- p50_duration_ms=p50,
269
- p95_duration_ms=p95,
270
- p99_duration_ms=p99,
271
- )
272
-
273
- # Validate and serialize method with stats
274
- method_summary = MethodSummarySerializer(
275
- name=method_name,
276
- full_name=f"/{service_name}/{method_name}",
277
- service_name=service_name,
278
- request_type="",
279
- response_type="",
280
- stats=method_stats,
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
- # Validate and serialize
79
- example = GRPCExampleSerializer(
80
- service=service_name,
81
- method=method_name,
82
- description=example_data.get(
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
- request_example=example_data.get("request", {}),
86
- response_example=example_data.get("response", {}),
87
- notes=example_data.get("notes", ""),
88
- )
89
- examples.append(example.model_dump())
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)),
@@ -0,0 +1,164 @@
1
+ # Server Lifecycle Logging Utilities
2
+
3
+ Reusable functions for logging server startup and shutdown with timestamps and uptime tracking.
4
+
5
+ **Uses Rich** for beautiful output with panels, tables, and colors! 🎨
6
+
7
+ ## 📦 Functions
8
+
9
+ ### `log_server_start()`
10
+
11
+ Logs server startup with timestamp and configuration.
12
+
13
+ ```python
14
+ from django_cfg.apps.integrations.grpc.utils import log_server_start
15
+
16
+ start_time = log_server_start(
17
+ logger,
18
+ server_type="gRPC Server", # Server type
19
+ mode="Development", # Production/Development
20
+ hotreload_enabled=True, # Is hotreload enabled?
21
+ host="0.0.0.0", # Additional parameters
22
+ port=50051
23
+ )
24
+ ```
25
+
26
+ **Output (Rich Panel):**
27
+ ```
28
+ ╭────────────── 🚀 gRPC Server Starting ───────────────╮
29
+ │ │
30
+ │ ⏰ Started at 2025-11-05 14:30:15 │
31
+ │ Mode Development │
32
+ │ Hotreload Enabled ⚡ │
33
+ │ Host 0.0.0.0 │
34
+ │ Port 50051 │
35
+ │ │
36
+ ╰──────────────────────────────────────────────────────╯
37
+ ⚠️ Hotreload active - connections may be dropped on code changes
38
+ ```
39
+ (with green border and colors!)
40
+
41
+ **Returns:** `datetime` object of start time (for use in `log_server_shutdown`)
42
+
43
+ ---
44
+
45
+ ### `log_server_shutdown()`
46
+
47
+ Logs server shutdown with uptime calculation.
48
+
49
+ ```python
50
+ from django_cfg.apps.integrations.grpc.utils import log_server_shutdown
51
+
52
+ log_server_shutdown(
53
+ logger,
54
+ start_time, # datetime from log_server_start()
55
+ server_type="gRPC Server",
56
+ reason="Hotreload triggered", # Shutdown reason
57
+ active_connections=5 # Additional parameters
58
+ )
59
+ ```
60
+
61
+ **Output (Rich Panel):**
62
+ ```
63
+ ╭───────────── 🧹 Shutting down gRPC Server ──────────────╮
64
+ │ │
65
+ │ 📋 Reason Hotreload triggered │
66
+ │ ⏱️ Uptime 0h 2m 35s │
67
+ │ 🕐 Stopped at 2025-11-05 14:32:50 │
68
+ │ Active Connections 5 │
69
+ │ │
70
+ ╰──────────────────────────────────────────────────────────╯
71
+ ✅ Server shutdown complete
72
+ ```
73
+ (with red border and colors!)
74
+
75
+ ---
76
+
77
+ ## 🎯 Complete Usage Example
78
+
79
+ ```python
80
+ from django_cfg.apps.integrations.grpc.utils import (
81
+ setup_streaming_logger,
82
+ log_server_start,
83
+ log_server_shutdown,
84
+ )
85
+
86
+ # Create logger
87
+ logger = setup_streaming_logger('my_server')
88
+
89
+ # Log server start
90
+ start_time = log_server_start(
91
+ logger,
92
+ server_type="WebSocket Server",
93
+ mode="Production",
94
+ hotreload_enabled=False,
95
+ host="0.0.0.0",
96
+ port=8000
97
+ )
98
+
99
+ try:
100
+ # Server work...
101
+ await server.serve_forever()
102
+ shutdown_reason = "Normal termination"
103
+ except KeyboardInterrupt:
104
+ shutdown_reason = "Keyboard interrupt"
105
+ finally:
106
+ # Log server shutdown
107
+ log_server_shutdown(
108
+ logger,
109
+ start_time,
110
+ server_type="WebSocket Server",
111
+ reason=shutdown_reason,
112
+ total_connections=1250
113
+ )
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 📊 Used in:
119
+
120
+ - ✅ `rungrpc` - gRPC server command
121
+ - ✅ Can be used for any servers (WebSocket, HTTP, etc.)
122
+
123
+ ---
124
+
125
+ ## 🔧 Parameters
126
+
127
+ ### `log_server_start()`
128
+
129
+ | Parameter | Type | Required | Description |
130
+ |----------|-----|----------|-------------|
131
+ | `logger` | `logging.Logger` | ✅ Yes | Logger instance |
132
+ | `server_type` | `str` | ❌ No | Server type (default: "Server") |
133
+ | `mode` | `str` | ❌ No | Running mode (default: "Development") |
134
+ | `hotreload_enabled` | `bool` | ❌ No | Is hotreload enabled (default: False) |
135
+ | `use_rich` | `bool` | ❌ No | Use Rich for output (default: True) |
136
+ | `**extra_info` | `dict` | ❌ No | Additional key-value pairs to log |
137
+
138
+ ### `log_server_shutdown()`
139
+
140
+ | Parameter | Type | Required | Description |
141
+ |----------|-----|----------|-------------|
142
+ | `logger` | `logging.Logger` | ✅ Yes | Logger instance |
143
+ | `start_time` | `datetime` | ✅ Yes | Start time from `log_server_start()` |
144
+ | `server_type` | `str` | ❌ No | Server type (default: "Server") |
145
+ | `reason` | `str` | ❌ No | Shutdown reason |
146
+ | `use_rich` | `bool` | ❌ No | Use Rich for output (default: True) |
147
+ | `**extra_info` | `dict` | ❌ No | Additional key-value pairs to log |
148
+
149
+ ---
150
+
151
+ ## 💡 Benefits
152
+
153
+ 1. ✅ **DRY** - single code for all servers
154
+ 2. ✅ **Consistency** - uniform log format
155
+ 3. ✅ **Rich UI** - beautiful panels, tables, colors 🎨
156
+ 4. ✅ **Uptime tracking** - automatic time calculation
157
+ 5. ✅ **Flexible** - can add arbitrary parameters via `**extra_info`
158
+ 6. ✅ **Hotreload aware** - special warning for hotreload mode
159
+ 7. ✅ **Fallback** - works without Rich (if `use_rich=False`)
160
+
161
+ ---
162
+
163
+ **Author:** django-cfg team
164
+ **Date:** 2025-11-05
@@ -1,18 +1,9 @@
1
1
  """
2
- gRPC utilities.
2
+ Utilities for gRPC Integration.
3
3
 
4
- Provides proto generation and other helper utilities for gRPC integration.
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 .proto_gen import ProtoFieldMapper, ProtoGenerator, generate_proto_for_app
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"]