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,378 @@
1
+ """
2
+ Streaming Logger Utilities for gRPC Services.
3
+
4
+ Provides reusable logger configuration for gRPC streaming services.
5
+ Follows django-cfg logging patterns for consistency.
6
+ """
7
+
8
+ import logging
9
+ import os
10
+ import sys
11
+ from pathlib import Path
12
+ from typing import Optional
13
+ from django.utils import timezone
14
+
15
+ # Rich for beautiful console output
16
+ from rich.console import Console
17
+ from rich.panel import Panel
18
+ from rich.table import Table
19
+ from rich.text import Text
20
+
21
+
22
+ class AutoTracebackHandler(logging.Handler):
23
+ """
24
+ Custom handler that automatically adds exception info to ERROR and CRITICAL logs.
25
+
26
+ This ensures full tracebacks are always logged for errors, even if exc_info=True
27
+ is not explicitly specified.
28
+ """
29
+
30
+ def __init__(self, base_handler: logging.Handler):
31
+ super().__init__()
32
+ self.base_handler = base_handler
33
+ self.setLevel(base_handler.level)
34
+ self.setFormatter(base_handler.formatter)
35
+
36
+ def emit(self, record: logging.LogRecord):
37
+ """Emit log record, automatically adding exc_info for errors."""
38
+ # If ERROR or CRITICAL and no exc_info yet, add current exception if any
39
+ if record.levelno >= logging.ERROR and not record.exc_info:
40
+ # Check if we're in exception context
41
+ exc_info = sys.exc_info()
42
+ if exc_info[0] is not None:
43
+ record.exc_info = exc_info
44
+
45
+ # Delegate to base handler
46
+ self.base_handler.emit(record)
47
+
48
+
49
+ def setup_streaming_logger(
50
+ name: str = "grpc_streaming",
51
+ logs_dir: Optional[Path] = None,
52
+ level: int = logging.DEBUG,
53
+ console_level: int = logging.INFO
54
+ ) -> logging.Logger:
55
+ """
56
+ Setup dedicated logger for gRPC streaming with file and console handlers.
57
+
58
+ Follows django-cfg logging pattern:
59
+ - Uses os.getcwd() / 'logs' / 'grpc_streaming' for log directory
60
+ - Time-based log file names (streaming_YYYYMMDD_HHMMSS.log)
61
+ - Detailed file logging (DEBUG level by default)
62
+ - Concise console logging (INFO level by default)
63
+
64
+ Args:
65
+ name: Logger name (default: "grpc_streaming")
66
+ logs_dir: Directory for log files (default: <cwd>/logs/grpc_streaming)
67
+ level: File logging level (default: DEBUG)
68
+ console_level: Console logging level (default: INFO)
69
+
70
+ Returns:
71
+ Configured logger instance
72
+
73
+ Example:
74
+ ```python
75
+ from django_cfg.apps.integrations.grpc.utils import setup_streaming_logger
76
+
77
+ # Basic usage
78
+ logger = setup_streaming_logger()
79
+
80
+ # Custom configuration
81
+ logger = setup_streaming_logger(
82
+ name="my_streaming_service",
83
+ logs_dir=Path("/var/log/grpc"),
84
+ level=logging.INFO
85
+ )
86
+
87
+ logger.info("Service started")
88
+ logger.debug("Detailed debug info")
89
+ ```
90
+
91
+ Features:
92
+ - Automatic log directory creation
93
+ - Time-based log file names
94
+ - No duplicate logs (propagate=False)
95
+ - UTF-8 encoding
96
+ - Reusable across all django-cfg gRPC projects
97
+ """
98
+ # Create logger
99
+ streaming_logger = logging.getLogger(name)
100
+ streaming_logger.setLevel(level)
101
+
102
+ # Avoid duplicate handlers if logger already configured
103
+ if streaming_logger.handlers:
104
+ return streaming_logger
105
+
106
+ # Determine logs directory using django-cfg pattern
107
+ if logs_dir is None:
108
+ # Pattern from django_cfg.modules.django_logging:
109
+ # current_dir = Path(os.getcwd())
110
+ # logs_dir = current_dir / 'logs' / 'grpc_streaming'
111
+ current_dir = Path(os.getcwd())
112
+ logs_dir = current_dir / 'logs' / 'grpc_streaming'
113
+
114
+ # Create logs directory
115
+ logs_dir.mkdir(parents=True, exist_ok=True)
116
+
117
+ # Create log filename with timestamp
118
+ log_filename = f'streaming_{timezone.now().strftime("%Y%m%d_%H%M%S")}.log'
119
+ log_file_path = logs_dir / log_filename
120
+
121
+ # File handler - detailed logs with auto-traceback
122
+ base_file_handler = logging.FileHandler(
123
+ log_file_path,
124
+ encoding='utf-8'
125
+ )
126
+ base_file_handler.setLevel(level)
127
+ file_formatter = logging.Formatter(
128
+ '%(asctime)s | %(levelname)-8s | %(message)s',
129
+ datefmt='%H:%M:%S'
130
+ )
131
+ base_file_handler.setFormatter(file_formatter)
132
+
133
+ # Wrap with auto-traceback handler for automatic exc_info on errors
134
+ file_handler = AutoTracebackHandler(base_file_handler)
135
+ streaming_logger.addHandler(file_handler)
136
+
137
+ # Console handler - important messages only (also with auto-traceback)
138
+ base_console_handler = logging.StreamHandler()
139
+ base_console_handler.setLevel(console_level)
140
+ console_formatter = logging.Formatter('%(levelname)s: %(message)s')
141
+ base_console_handler.setFormatter(console_formatter)
142
+
143
+ # Wrap console handler with auto-traceback too
144
+ console_handler = AutoTracebackHandler(base_console_handler)
145
+ streaming_logger.addHandler(console_handler)
146
+
147
+ # Prevent propagation to avoid duplicate logs
148
+ streaming_logger.propagate = False
149
+
150
+ # Log initialization
151
+ streaming_logger.info("=" * 80)
152
+ streaming_logger.info(f"🌊 {name.title()} Logger Initialized")
153
+ streaming_logger.info(f"📁 Log file: {log_file_path}")
154
+ streaming_logger.info("=" * 80)
155
+
156
+ return streaming_logger
157
+
158
+
159
+ def get_streaming_logger(name: str = "grpc_streaming") -> logging.Logger:
160
+ """
161
+ Get existing streaming logger or create new one.
162
+
163
+ Args:
164
+ name: Logger name (default: "grpc_streaming")
165
+
166
+ Returns:
167
+ Logger instance
168
+
169
+ Example:
170
+ ```python
171
+ from django_cfg.apps.integrations.grpc.utils import get_streaming_logger
172
+
173
+ logger = get_streaming_logger()
174
+ logger.info("Using existing logger")
175
+ ```
176
+ """
177
+ logger = logging.getLogger(name)
178
+
179
+ # If not configured yet, set it up
180
+ if not logger.handlers:
181
+ return setup_streaming_logger(name)
182
+
183
+ return logger
184
+
185
+
186
+ def log_server_start(
187
+ logger: logging.Logger,
188
+ server_type: str = "Server",
189
+ mode: str = "Development",
190
+ hotreload_enabled: bool = False,
191
+ use_rich: bool = True,
192
+ **extra_info
193
+ ):
194
+ """
195
+ Log server startup with timestamp and configuration using Rich panels.
196
+
197
+ Args:
198
+ logger: Logger instance to use
199
+ server_type: Type of server (e.g., "gRPC Server", "WebSocket Server")
200
+ mode: Running mode (Development/Production)
201
+ hotreload_enabled: Whether hot-reload is enabled
202
+ use_rich: Use Rich for beautiful output (default: True)
203
+ **extra_info: Additional key-value pairs to log
204
+
205
+ Example:
206
+ ```python
207
+ from django_cfg.apps.integrations.grpc.utils import log_server_start
208
+ from datetime import datetime
209
+
210
+ start_time = log_server_start(
211
+ logger,
212
+ server_type="gRPC Server",
213
+ mode="Development",
214
+ hotreload_enabled=True,
215
+ host="0.0.0.0",
216
+ port=50051
217
+ )
218
+ ```
219
+
220
+ Returns:
221
+ datetime object of start time for later use in log_server_shutdown
222
+ """
223
+ from datetime import datetime
224
+
225
+ start_time = datetime.now()
226
+
227
+ if use_rich:
228
+ # Create Rich table for server info
229
+ console = Console()
230
+
231
+ table = Table(show_header=False, box=None, padding=(0, 1))
232
+ table.add_column("Key", style="cyan")
233
+ table.add_column("Value", style="white")
234
+
235
+ table.add_row("⏰ Started at", start_time.strftime('%Y-%m-%d %H:%M:%S'))
236
+ table.add_row("Mode", f"[{'red' if mode == 'Production' else 'green'}]{mode}[/]")
237
+ table.add_row("Hotreload", f"[{'yellow' if hotreload_enabled else 'dim'}]{'Enabled ⚡' if hotreload_enabled else 'Disabled'}[/]")
238
+
239
+ # Add extra info
240
+ for key, value in extra_info.items():
241
+ key_display = key.replace('_', ' ').title()
242
+ table.add_row(key_display, str(value))
243
+
244
+ # Create panel
245
+ panel = Panel(
246
+ table,
247
+ title=f"[bold green]🚀 {server_type} Starting[/bold green]",
248
+ border_style="green",
249
+ padding=(1, 2)
250
+ )
251
+
252
+ console.print(panel)
253
+
254
+ if hotreload_enabled:
255
+ console.print(
256
+ "[yellow]⚠️ Hotreload active - connections may be dropped on code changes[/yellow]",
257
+ style="bold"
258
+ )
259
+ else:
260
+ # Fallback to simple logging
261
+ logger.info("=" * 80)
262
+ logger.info(f"🚀 {server_type} Starting")
263
+ logger.info(f" ⏰ Started at: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
264
+ logger.info(f" Mode: {mode}")
265
+ logger.info(f" Hotreload: {'Enabled' if hotreload_enabled else 'Disabled'}")
266
+
267
+ for key, value in extra_info.items():
268
+ key_display = key.replace('_', ' ').title()
269
+ logger.info(f" {key_display}: {value}")
270
+
271
+ if hotreload_enabled:
272
+ logger.warning(
273
+ "⚠️ Hotreload active - connections may be dropped on code changes"
274
+ )
275
+
276
+ logger.info("=" * 80)
277
+
278
+ return start_time
279
+
280
+
281
+ def log_server_shutdown(
282
+ logger: logging.Logger,
283
+ start_time,
284
+ server_type: str = "Server",
285
+ reason: str = None,
286
+ use_rich: bool = True,
287
+ **extra_info
288
+ ):
289
+ """
290
+ Log server shutdown with uptime calculation using Rich panels.
291
+
292
+ Args:
293
+ logger: Logger instance to use
294
+ start_time: datetime object from log_server_start()
295
+ server_type: Type of server (e.g., "gRPC Server", "WebSocket Server")
296
+ reason: Shutdown reason (e.g., "Keyboard interrupt", "Hotreload")
297
+ use_rich: Use Rich for beautiful output (default: True)
298
+ **extra_info: Additional key-value pairs to log
299
+
300
+ Example:
301
+ ```python
302
+ from django_cfg.apps.integrations.grpc.utils import log_server_shutdown
303
+
304
+ log_server_shutdown(
305
+ logger,
306
+ start_time,
307
+ server_type="gRPC Server",
308
+ reason="Hotreload triggered",
309
+ active_connections=5
310
+ )
311
+ ```
312
+ """
313
+ from datetime import datetime
314
+
315
+ end_time = datetime.now()
316
+ uptime = end_time - start_time
317
+ uptime_seconds = int(uptime.total_seconds())
318
+
319
+ # Format uptime
320
+ hours = uptime_seconds // 3600
321
+ minutes = (uptime_seconds % 3600) // 60
322
+ seconds = uptime_seconds % 60
323
+ uptime_str = f"{hours}h {minutes}m {seconds}s"
324
+
325
+ if use_rich:
326
+ # Create Rich table for shutdown info
327
+ console = Console()
328
+
329
+ table = Table(show_header=False, box=None, padding=(0, 1))
330
+ table.add_column("Key", style="cyan")
331
+ table.add_column("Value", style="white")
332
+
333
+ if reason:
334
+ table.add_row("📋 Reason", reason)
335
+
336
+ table.add_row("⏱️ Uptime", f"[bold]{uptime_str}[/bold]")
337
+ table.add_row("🕐 Stopped at", end_time.strftime('%Y-%m-%d %H:%M:%S'))
338
+
339
+ # Add extra info
340
+ for key, value in extra_info.items():
341
+ key_display = key.replace('_', ' ').title()
342
+ table.add_row(key_display, str(value))
343
+
344
+ # Create panel
345
+ panel = Panel(
346
+ table,
347
+ title=f"[bold red]🧹 Shutting down {server_type}[/bold red]",
348
+ border_style="red",
349
+ padding=(1, 2)
350
+ )
351
+
352
+ console.print(panel)
353
+ console.print("[green]✅ Server shutdown complete[/green]", style="bold")
354
+ else:
355
+ # Fallback to simple logging
356
+ logger.info("=" * 80)
357
+ logger.info(f"🧹 Shutting down {server_type}...")
358
+
359
+ if reason:
360
+ logger.info(f" 📋 Reason: {reason}")
361
+
362
+ logger.info(f" ⏱️ Uptime: {uptime_str}")
363
+ logger.info(f" 🕐 Stopped at: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
364
+
365
+ for key, value in extra_info.items():
366
+ key_display = key.replace('_', ' ').title()
367
+ logger.info(f" {key_display}: {value}")
368
+
369
+ logger.info("✅ Server shutdown complete")
370
+ logger.info("=" * 80)
371
+
372
+
373
+ __all__ = [
374
+ "setup_streaming_logger",
375
+ "get_streaming_logger",
376
+ "log_server_start",
377
+ "log_server_shutdown",
378
+ ]
@@ -2,8 +2,10 @@
2
2
  Views for gRPC monitoring API.
3
3
  """
4
4
 
5
+ from .api_keys import GRPCApiKeyViewSet
5
6
  from .config import GRPCConfigViewSet
6
7
  from .monitoring import GRPCMonitorViewSet
8
+ from .proto_files import GRPCProtoFilesViewSet
7
9
  from .services import GRPCServiceViewSet
8
10
  from .testing import GRPCTestingViewSet
9
11
 
@@ -12,4 +14,6 @@ __all__ = [
12
14
  "GRPCConfigViewSet",
13
15
  "GRPCServiceViewSet",
14
16
  "GRPCTestingViewSet",
17
+ "GRPCApiKeyViewSet",
18
+ "GRPCProtoFilesViewSet",
15
19
  ]
@@ -0,0 +1,255 @@
1
+ """
2
+ gRPC API Keys ViewSet.
3
+
4
+ Provides REST API endpoints for viewing API keys.
5
+ Create/Update/Delete operations handled through Django Admin.
6
+ """
7
+
8
+ from django.db.models import Count, Q, Sum
9
+ from django.utils import timezone
10
+ from django_cfg.mixins import AdminAPIMixin
11
+ from django_cfg.modules.django_logging import get_logger
12
+ from drf_spectacular.types import OpenApiTypes
13
+ from drf_spectacular.utils import OpenApiParameter, extend_schema
14
+ from rest_framework import status, viewsets
15
+ from rest_framework.decorators import action
16
+ from rest_framework.response import Response
17
+
18
+ from ..models import GrpcApiKey
19
+ from ..serializers.api_keys import (
20
+ ApiKeyListSerializer,
21
+ ApiKeySerializer,
22
+ ApiKeyStatsSerializer,
23
+ )
24
+
25
+ logger = get_logger("grpc.api_keys")
26
+
27
+
28
+ class GRPCApiKeyViewSet(AdminAPIMixin, viewsets.GenericViewSet):
29
+ """
30
+ ViewSet for gRPC API Keys (Read-Only).
31
+
32
+ Provides listing and statistics for API keys.
33
+ Create/Update/Delete operations are handled through Django Admin.
34
+
35
+ Requires admin authentication (JWT, Session, or Basic Auth).
36
+ """
37
+
38
+ queryset = GrpcApiKey.objects.none()
39
+ serializer_class = ApiKeySerializer
40
+
41
+ @extend_schema(
42
+ tags=["gRPC API Keys"],
43
+ summary="List API keys",
44
+ description="Returns a list of all API keys with their details. Uses standard DRF pagination.",
45
+ parameters=[
46
+ OpenApiParameter(
47
+ name="is_active",
48
+ type=OpenApiTypes.BOOL,
49
+ location=OpenApiParameter.QUERY,
50
+ description="Filter by active status",
51
+ required=False,
52
+ ),
53
+ OpenApiParameter(
54
+ name="key_type",
55
+ type=OpenApiTypes.STR,
56
+ location=OpenApiParameter.QUERY,
57
+ description="Filter by key type (service, cli, webhook, internal, development)",
58
+ required=False,
59
+ ),
60
+ OpenApiParameter(
61
+ name="user_id",
62
+ type=OpenApiTypes.INT,
63
+ location=OpenApiParameter.QUERY,
64
+ description="Filter by user ID",
65
+ required=False,
66
+ ),
67
+ ],
68
+ responses={
69
+ 200: ApiKeySerializer(many=True),
70
+ 400: {"description": "Invalid parameters"},
71
+ },
72
+ )
73
+ def list(self, request):
74
+ """List all API keys."""
75
+ try:
76
+ # Build queryset with filters
77
+ queryset = GrpcApiKey.objects.select_related("user", "created_by").all()
78
+
79
+ # Apply filters
80
+ is_active = request.GET.get("is_active")
81
+ if is_active is not None:
82
+ is_active_bool = is_active.lower() in ("true", "1", "yes")
83
+ queryset = queryset.filter(is_active=is_active_bool)
84
+
85
+ key_type = request.GET.get("key_type")
86
+ if key_type:
87
+ queryset = queryset.filter(key_type=key_type)
88
+
89
+ user_id = request.GET.get("user_id")
90
+ if user_id:
91
+ queryset = queryset.filter(user_id=user_id)
92
+
93
+ # Order by most recently created
94
+ queryset = queryset.order_by("-created_at")
95
+
96
+ # Paginate
97
+ page = self.paginate_queryset(queryset)
98
+ if page is not None:
99
+ # Serialize paginated data
100
+ results = []
101
+ for key in page:
102
+ results.append({
103
+ "id": key.id,
104
+ "name": key.name,
105
+ "key_type": key.key_type,
106
+ "masked_key": key.masked_key,
107
+ "is_active": key.is_active,
108
+ "is_valid": key.is_valid,
109
+ "user_id": key.user.id,
110
+ "username": key.user.username,
111
+ "user_email": key.user.email,
112
+ "request_count": key.request_count,
113
+ "last_used_at": key.last_used_at,
114
+ "expires_at": key.expires_at,
115
+ "created_at": key.created_at,
116
+ "created_by": key.created_by.username if key.created_by else None,
117
+ })
118
+ return self.get_paginated_response(results)
119
+
120
+ # No pagination fallback
121
+ results = []
122
+ for key in queryset[:100]:
123
+ results.append({
124
+ "id": key.id,
125
+ "name": key.name,
126
+ "key_type": key.key_type,
127
+ "masked_key": key.masked_key,
128
+ "is_active": key.is_active,
129
+ "is_valid": key.is_valid,
130
+ "user_id": key.user.id,
131
+ "username": key.user.username,
132
+ "user_email": key.user.email,
133
+ "request_count": key.request_count,
134
+ "last_used_at": key.last_used_at,
135
+ "expires_at": key.expires_at,
136
+ "created_at": key.created_at,
137
+ "created_by": key.created_by.username if key.created_by else None,
138
+ })
139
+
140
+ return Response({"results": results, "count": len(results)})
141
+
142
+ except ValueError as e:
143
+ logger.warning(f"API keys list validation error: {e}")
144
+ return Response(
145
+ {"error": str(e)}, status=status.HTTP_400_BAD_REQUEST
146
+ )
147
+ except Exception as e:
148
+ logger.error(f"API keys list error: {e}", exc_info=True)
149
+ return Response(
150
+ {"error": "Internal server error"},
151
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
152
+ )
153
+
154
+ @extend_schema(
155
+ tags=["gRPC API Keys"],
156
+ summary="Get API key details",
157
+ description="Returns detailed information about a specific API key.",
158
+ responses={
159
+ 200: ApiKeySerializer,
160
+ 404: {"description": "API key not found"},
161
+ },
162
+ )
163
+ def retrieve(self, request, pk=None):
164
+ """Get details of a specific API key."""
165
+ try:
166
+ key = GrpcApiKey.objects.select_related("user", "created_by").get(pk=pk)
167
+
168
+ data = {
169
+ "id": key.id,
170
+ "name": key.name,
171
+ "key_type": key.key_type,
172
+ "masked_key": key.masked_key,
173
+ "is_active": key.is_active,
174
+ "is_valid": key.is_valid,
175
+ "user_id": key.user.id,
176
+ "username": key.user.username,
177
+ "user_email": key.user.email,
178
+ "request_count": key.request_count,
179
+ "last_used_at": key.last_used_at,
180
+ "expires_at": key.expires_at,
181
+ "created_at": key.created_at,
182
+ "created_by": key.created_by.username if key.created_by else None,
183
+ }
184
+
185
+ serializer = ApiKeySerializer(data=data)
186
+ serializer.is_valid(raise_exception=True)
187
+ return Response(serializer.data)
188
+
189
+ except GrpcApiKey.DoesNotExist:
190
+ return Response(
191
+ {"error": "API key not found"},
192
+ status=status.HTTP_404_NOT_FOUND,
193
+ )
194
+ except Exception as e:
195
+ logger.error(f"API key retrieve error: {e}", exc_info=True)
196
+ return Response(
197
+ {"error": "Internal server error"},
198
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
199
+ )
200
+
201
+ @extend_schema(
202
+ tags=["gRPC API Keys"],
203
+ summary="Get API keys statistics",
204
+ description="Returns overall statistics about API keys usage.",
205
+ responses={
206
+ 200: ApiKeyStatsSerializer,
207
+ },
208
+ )
209
+ @action(detail=False, methods=["get"], url_path="stats")
210
+ def stats(self, request):
211
+ """Get API keys statistics."""
212
+ try:
213
+ # Total keys
214
+ total_keys = GrpcApiKey.objects.count()
215
+
216
+ # Active keys
217
+ active_keys = GrpcApiKey.objects.filter(is_active=True).count()
218
+
219
+ # Expired keys
220
+ expired_keys = GrpcApiKey.objects.filter(
221
+ Q(is_active=True) & Q(expires_at__lt=timezone.now())
222
+ ).count()
223
+
224
+ # Total requests across all keys
225
+ total_requests = GrpcApiKey.objects.aggregate(
226
+ total=Sum("request_count")
227
+ )["total"] or 0
228
+
229
+ # Keys by type
230
+ keys_by_type_qs = GrpcApiKey.objects.values("key_type").annotate(
231
+ count=Count("id")
232
+ )
233
+ keys_by_type = {item["key_type"]: item["count"] for item in keys_by_type_qs}
234
+
235
+ stats_data = {
236
+ "total_keys": total_keys,
237
+ "active_keys": active_keys,
238
+ "expired_keys": expired_keys,
239
+ "total_requests": total_requests,
240
+ "keys_by_type": keys_by_type,
241
+ }
242
+
243
+ serializer = ApiKeyStatsSerializer(data=stats_data)
244
+ serializer.is_valid(raise_exception=True)
245
+ return Response(serializer.data)
246
+
247
+ except Exception as e:
248
+ logger.error(f"API keys stats error: {e}", exc_info=True)
249
+ return Response(
250
+ {"error": "Internal server error"},
251
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR,
252
+ )
253
+
254
+
255
+ __all__ = ["GRPCApiKeyViewSet"]