django-cfg 1.5.14__py3-none-any.whl → 1.5.29__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 (118) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/business/accounts/serializers/profile.py +42 -0
  3. django_cfg/apps/business/support/serializers.py +3 -2
  4. django_cfg/apps/integrations/centrifugo/__init__.py +2 -0
  5. django_cfg/apps/integrations/centrifugo/apps.py +2 -1
  6. django_cfg/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +151 -12
  7. django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +2 -2
  8. django_cfg/apps/integrations/centrifugo/services/__init__.py +6 -0
  9. django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
  10. django_cfg/apps/integrations/centrifugo/services/client/client.py +1 -1
  11. django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -0
  12. django_cfg/apps/integrations/centrifugo/services/logging.py +47 -0
  13. django_cfg/apps/integrations/centrifugo/services/publisher.py +371 -0
  14. django_cfg/apps/integrations/centrifugo/services/token_generator.py +122 -0
  15. django_cfg/apps/integrations/centrifugo/urls.py +8 -0
  16. django_cfg/apps/integrations/centrifugo/views/__init__.py +2 -0
  17. django_cfg/apps/integrations/centrifugo/views/admin_api.py +29 -32
  18. django_cfg/apps/integrations/centrifugo/views/testing_api.py +31 -116
  19. django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
  20. django_cfg/apps/integrations/centrifugo/views/wrapper.py +259 -0
  21. django_cfg/apps/integrations/grpc/auth/api_key_auth.py +11 -10
  22. django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
  23. django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +56 -1
  24. django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +315 -26
  25. django_cfg/apps/integrations/grpc/management/proto/__init__.py +3 -0
  26. django_cfg/apps/integrations/grpc/management/proto/compiler.py +194 -0
  27. django_cfg/apps/integrations/grpc/managers/grpc_request_log.py +84 -0
  28. django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +126 -3
  29. django_cfg/apps/integrations/grpc/models/grpc_api_key.py +7 -1
  30. django_cfg/apps/integrations/grpc/models/grpc_server_status.py +22 -3
  31. django_cfg/apps/integrations/grpc/services/__init__.py +102 -17
  32. django_cfg/apps/integrations/grpc/services/centrifugo/__init__.py +29 -0
  33. django_cfg/apps/integrations/grpc/services/centrifugo/bridge.py +469 -0
  34. django_cfg/apps/integrations/grpc/services/centrifugo/config.py +167 -0
  35. django_cfg/apps/integrations/grpc/services/centrifugo/demo.py +626 -0
  36. django_cfg/apps/integrations/grpc/services/centrifugo/test_publish.py +229 -0
  37. django_cfg/apps/integrations/grpc/services/centrifugo/transformers.py +89 -0
  38. django_cfg/apps/integrations/grpc/services/client/__init__.py +26 -0
  39. django_cfg/apps/integrations/grpc/services/commands/IMPLEMENTATION.md +456 -0
  40. django_cfg/apps/integrations/grpc/services/commands/README.md +252 -0
  41. django_cfg/apps/integrations/grpc/services/commands/__init__.py +93 -0
  42. django_cfg/apps/integrations/grpc/services/commands/base.py +243 -0
  43. django_cfg/apps/integrations/grpc/services/commands/examples/__init__.py +22 -0
  44. django_cfg/apps/integrations/grpc/services/commands/examples/base_client.py +228 -0
  45. django_cfg/apps/integrations/grpc/services/commands/examples/client.py +272 -0
  46. django_cfg/apps/integrations/grpc/services/commands/examples/config.py +177 -0
  47. django_cfg/apps/integrations/grpc/services/commands/examples/start.py +125 -0
  48. django_cfg/apps/integrations/grpc/services/commands/examples/stop.py +101 -0
  49. django_cfg/apps/integrations/grpc/services/commands/registry.py +170 -0
  50. django_cfg/apps/integrations/grpc/services/discovery/__init__.py +39 -0
  51. django_cfg/apps/integrations/grpc/services/{discovery.py → discovery/discovery.py} +67 -54
  52. django_cfg/apps/integrations/grpc/services/{service_registry.py → discovery/registry.py} +215 -5
  53. django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/__init__.py +3 -1
  54. django_cfg/apps/integrations/grpc/services/interceptors/centrifugo.py +541 -0
  55. django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/metrics.py +3 -3
  56. django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/request_logger.py +10 -13
  57. django_cfg/apps/integrations/grpc/services/management/__init__.py +37 -0
  58. django_cfg/apps/integrations/grpc/services/monitoring/__init__.py +38 -0
  59. django_cfg/apps/integrations/grpc/services/{monitoring_service.py → monitoring/monitoring.py} +2 -2
  60. django_cfg/apps/integrations/grpc/services/{testing_service.py → monitoring/testing.py} +5 -5
  61. django_cfg/apps/integrations/grpc/services/rendering/__init__.py +27 -0
  62. django_cfg/apps/integrations/grpc/services/{chart_generator.py → rendering/charts.py} +1 -1
  63. django_cfg/apps/integrations/grpc/services/routing/__init__.py +59 -0
  64. django_cfg/apps/integrations/grpc/services/routing/config.py +76 -0
  65. django_cfg/apps/integrations/grpc/services/routing/router.py +430 -0
  66. django_cfg/apps/integrations/grpc/services/streaming/__init__.py +117 -0
  67. django_cfg/apps/integrations/grpc/services/streaming/config.py +451 -0
  68. django_cfg/apps/integrations/grpc/services/streaming/service.py +651 -0
  69. django_cfg/apps/integrations/grpc/services/streaming/types.py +367 -0
  70. django_cfg/apps/integrations/grpc/utils/SERVER_LOGGING.md +164 -0
  71. django_cfg/apps/integrations/grpc/utils/__init__.py +58 -1
  72. django_cfg/apps/integrations/grpc/utils/converters.py +565 -0
  73. django_cfg/apps/integrations/grpc/utils/handlers.py +242 -0
  74. django_cfg/apps/integrations/grpc/utils/proto_gen.py +1 -1
  75. django_cfg/apps/integrations/grpc/utils/streaming_logger.py +261 -13
  76. django_cfg/apps/integrations/grpc/views/charts.py +1 -1
  77. django_cfg/apps/integrations/grpc/views/config.py +1 -1
  78. django_cfg/apps/system/dashboard/serializers/config.py +95 -9
  79. django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
  80. django_cfg/apps/system/frontend/views.py +87 -6
  81. django_cfg/core/base/config_model.py +11 -0
  82. django_cfg/core/builders/middleware_builder.py +5 -0
  83. django_cfg/core/builders/security_builder.py +1 -0
  84. django_cfg/core/generation/integration_generators/api.py +2 -0
  85. django_cfg/management/commands/pool_status.py +153 -0
  86. django_cfg/middleware/pool_cleanup.py +261 -0
  87. django_cfg/models/api/grpc/config.py +2 -2
  88. django_cfg/models/infrastructure/database/config.py +16 -0
  89. django_cfg/models/infrastructure/database/converters.py +2 -0
  90. django_cfg/modules/django_admin/utils/html/composition.py +57 -13
  91. django_cfg/modules/django_admin/utils/html_builder.py +1 -0
  92. django_cfg/modules/django_client/core/generator/typescript/generator.py +26 -0
  93. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +7 -1
  94. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +5 -0
  95. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +11 -0
  96. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +1 -0
  97. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +29 -1
  98. django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +4 -0
  99. django_cfg/modules/django_client/core/groups/manager.py +25 -18
  100. django_cfg/modules/django_client/core/ir/schema.py +15 -1
  101. django_cfg/modules/django_client/core/parser/base.py +12 -0
  102. django_cfg/modules/django_client/management/commands/generate_client.py +9 -5
  103. django_cfg/modules/django_logging/django_logger.py +58 -19
  104. django_cfg/pyproject.toml +3 -3
  105. django_cfg/static/frontend/admin.zip +0 -0
  106. django_cfg/templates/admin/index.html +0 -39
  107. django_cfg/utils/pool_monitor.py +320 -0
  108. django_cfg/utils/smart_defaults.py +233 -7
  109. {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/METADATA +75 -5
  110. {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/RECORD +118 -74
  111. /django_cfg/apps/integrations/grpc/services/{grpc_client.py → client/client.py} +0 -0
  112. /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/errors.py +0 -0
  113. /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/logging.py +0 -0
  114. /django_cfg/apps/integrations/grpc/services/{config_helper.py → management/config_helper.py} +0 -0
  115. /django_cfg/apps/integrations/grpc/services/{proto_files_manager.py → management/proto_manager.py} +0 -0
  116. {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/WHEEL +0 -0
  117. {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/entry_points.txt +0 -0
  118. {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,242 @@
1
+ """
2
+ gRPC handler factory utilities.
3
+
4
+ This module provides utilities for creating and registering gRPC servicers
5
+ with proper error handling and logging.
6
+
7
+ **Purpose**:
8
+ Simplify the boilerplate code needed to register gRPC services with servers.
9
+
10
+ **Usage Example**:
11
+ ```python
12
+ from django_cfg.apps.integrations.grpc.utils.handlers import create_grpc_handler
13
+
14
+ # Your servicer class
15
+ class BotStreamingService(pb2_grpc.BotStreamingServiceServicer):
16
+ pass
17
+
18
+ # Create handler tuple
19
+ handlers = create_grpc_handler(
20
+ servicer_class=BotStreamingService,
21
+ add_servicer_func=pb2_grpc.add_BotStreamingServiceServicer_to_server,
22
+ )
23
+
24
+ # Use with django-cfg
25
+ GRPC_HANDLERS = [handlers]
26
+ ```
27
+
28
+ Created: 2025-11-07
29
+ Status: %%PRODUCTION%%
30
+ Phase: Phase 1 - Universal Components
31
+ """
32
+
33
+ from typing import Callable, Tuple, Any, Optional, Type
34
+ import logging
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+
39
+ # ============================================================================
40
+ # Handler Factory
41
+ # ============================================================================
42
+
43
+ def create_grpc_handler(
44
+ servicer_class: Type,
45
+ add_servicer_func: Callable[[Any, Any], None],
46
+ servicer_kwargs: Optional[dict] = None,
47
+ ) -> Tuple[Callable[[Any, Any], None], Type, dict]:
48
+ """
49
+ Create gRPC handler tuple for django-cfg GRPC_HANDLERS.
50
+
51
+ This factory creates the standardized tuple format expected by django-cfg's
52
+ gRPC server initialization:
53
+ ```python
54
+ (add_servicer_function, servicer_class, init_kwargs)
55
+ ```
56
+
57
+ Args:
58
+ servicer_class: gRPC servicer class (e.g., MyStreamingService)
59
+ add_servicer_func: Generated add_*_to_server function from pb2_grpc
60
+ servicer_kwargs: Optional kwargs to pass to servicer constructor
61
+
62
+ Returns:
63
+ Tuple of (add_func, servicer_class, kwargs) for GRPC_HANDLERS
64
+
65
+ Example:
66
+ ```python
67
+ from .generated import bot_streaming_service_pb2_grpc
68
+ from .services import BotStreamingService
69
+
70
+ handlers = create_grpc_handler(
71
+ servicer_class=BotStreamingService,
72
+ add_servicer_func=bot_streaming_service_pb2_grpc.add_BotStreamingServiceServicer_to_server,
73
+ servicer_kwargs={'config': my_config},
74
+ )
75
+
76
+ # In settings.py
77
+ GRPC_HANDLERS = [handlers]
78
+ ```
79
+
80
+ **django-cfg Integration**:
81
+ The tuple is used by django-cfg like this:
82
+ ```python
83
+ for add_func, servicer_class, kwargs in GRPC_HANDLERS:
84
+ servicer = servicer_class(**kwargs)
85
+ add_func(servicer, server)
86
+ ```
87
+ """
88
+ kwargs = servicer_kwargs or {}
89
+
90
+ logger.debug(
91
+ f"Created gRPC handler: {servicer_class.__name__} "
92
+ f"with {len(kwargs)} init kwargs"
93
+ )
94
+
95
+ return (add_servicer_func, servicer_class, kwargs)
96
+
97
+
98
+ def create_multiple_grpc_handlers(
99
+ handlers_config: list[dict],
100
+ ) -> list[Tuple[Callable[[Any, Any], None], Type, dict]]:
101
+ """
102
+ Create multiple gRPC handlers from configuration list.
103
+
104
+ Convenience function for creating multiple handlers at once.
105
+
106
+ Args:
107
+ handlers_config: List of handler configurations, each with keys:
108
+ - servicer_class: Servicer class
109
+ - add_servicer_func: Add function
110
+ - servicer_kwargs: Optional init kwargs
111
+
112
+ Returns:
113
+ List of handler tuples for GRPC_HANDLERS
114
+
115
+ Example:
116
+ ```python
117
+ handlers = create_multiple_grpc_handlers([
118
+ {
119
+ 'servicer_class': BotStreamingService,
120
+ 'add_servicer_func': pb2_grpc.add_BotStreamingServiceServicer_to_server,
121
+ },
122
+ {
123
+ 'servicer_class': SignalStreamingService,
124
+ 'add_servicer_func': signal_pb2_grpc.add_SignalStreamingServiceServicer_to_server,
125
+ 'servicer_kwargs': {'timeout': 30},
126
+ },
127
+ ])
128
+
129
+ # In settings.py
130
+ GRPC_HANDLERS = handlers
131
+ ```
132
+ """
133
+ handlers = []
134
+
135
+ for config in handlers_config:
136
+ handler = create_grpc_handler(
137
+ servicer_class=config['servicer_class'],
138
+ add_servicer_func=config['add_servicer_func'],
139
+ servicer_kwargs=config.get('servicer_kwargs'),
140
+ )
141
+ handlers.append(handler)
142
+
143
+ logger.info(f"Created {len(handlers)} gRPC handlers")
144
+
145
+ return handlers
146
+
147
+
148
+ # ============================================================================
149
+ # Validation Utilities
150
+ # ============================================================================
151
+
152
+ def validate_grpc_handler(
153
+ handler: Tuple[Callable, Type, dict],
154
+ ) -> bool:
155
+ """
156
+ Validate that handler tuple has correct structure.
157
+
158
+ Args:
159
+ handler: Handler tuple to validate
160
+
161
+ Returns:
162
+ True if valid, False otherwise
163
+
164
+ Example:
165
+ ```python
166
+ handler = create_grpc_handler(MyService, add_func)
167
+
168
+ if validate_grpc_handler(handler):
169
+ GRPC_HANDLERS.append(handler)
170
+ ```
171
+ """
172
+ if not isinstance(handler, tuple):
173
+ logger.error(f"Handler must be tuple, got {type(handler)}")
174
+ return False
175
+
176
+ if len(handler) != 3:
177
+ logger.error(f"Handler tuple must have 3 elements, got {len(handler)}")
178
+ return False
179
+
180
+ add_func, servicer_class, kwargs = handler
181
+
182
+ if not callable(add_func):
183
+ logger.error(f"First element (add_func) must be callable, got {type(add_func)}")
184
+ return False
185
+
186
+ if not isinstance(servicer_class, type):
187
+ logger.error(f"Second element (servicer_class) must be class, got {type(servicer_class)}")
188
+ return False
189
+
190
+ if not isinstance(kwargs, dict):
191
+ logger.error(f"Third element (kwargs) must be dict, got {type(kwargs)}")
192
+ return False
193
+
194
+ return True
195
+
196
+
197
+ def validate_grpc_handlers(
198
+ handlers: list,
199
+ ) -> Tuple[bool, list[str]]:
200
+ """
201
+ Validate list of handlers.
202
+
203
+ Args:
204
+ handlers: List of handler tuples
205
+
206
+ Returns:
207
+ Tuple of (all_valid: bool, errors: list[str])
208
+
209
+ Example:
210
+ ```python
211
+ valid, errors = validate_grpc_handlers(GRPC_HANDLERS)
212
+
213
+ if not valid:
214
+ for error in errors:
215
+ logger.error(error)
216
+ raise ValueError("Invalid gRPC handlers")
217
+ ```
218
+ """
219
+ errors = []
220
+
221
+ if not isinstance(handlers, list):
222
+ errors.append(f"GRPC_HANDLERS must be list, got {type(handlers)}")
223
+ return False, errors
224
+
225
+ for i, handler in enumerate(handlers):
226
+ if not validate_grpc_handler(handler):
227
+ errors.append(f"Handler #{i} is invalid")
228
+
229
+ all_valid = len(errors) == 0
230
+ return all_valid, errors
231
+
232
+
233
+ # ============================================================================
234
+ # Exports
235
+ # ============================================================================
236
+
237
+ __all__ = [
238
+ 'create_grpc_handler',
239
+ 'create_multiple_grpc_handlers',
240
+ 'validate_grpc_handler',
241
+ 'validate_grpc_handlers',
242
+ ]
@@ -394,7 +394,7 @@ def generate_proto_for_app(app_label: str, output_dir: Optional[Path] = None) ->
394
394
  ```
395
395
  """
396
396
  # Get gRPC config from django-cfg (Pydantic)
397
- from ..services.config_helper import get_grpc_config
397
+ from ..services.management.config_helper import get_grpc_config
398
398
 
399
399
  grpc_config = get_grpc_config()
400
400
  proto_config = grpc_config.proto if grpc_config else None
@@ -12,6 +12,45 @@ from pathlib import Path
12
12
  from typing import Optional
13
13
  from django.utils import timezone
14
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
+ # ========================================================================
23
+ # Module-level debug mode caching (performance optimization)
24
+ # ========================================================================
25
+
26
+ _debug_mode: Optional[bool] = None # Cached debug mode to avoid repeated config loads
27
+
28
+
29
+ def _get_debug_mode() -> bool:
30
+ """
31
+ Get debug mode from config (cached at module level).
32
+
33
+ Loads config only once and caches the result to avoid repeated config loads.
34
+ This is a performance optimization - config loading can be expensive.
35
+
36
+ Returns:
37
+ True if debug mode is enabled, False otherwise
38
+ """
39
+ global _debug_mode
40
+
41
+ if _debug_mode is not None:
42
+ return _debug_mode
43
+
44
+ # Load config once and cache
45
+ try:
46
+ from django_cfg.core.state import get_current_config
47
+ config = get_current_config()
48
+ _debug_mode = config.debug if config and hasattr(config, 'debug') else False
49
+ except Exception:
50
+ _debug_mode = os.getenv('DEBUG', 'false').lower() in ('true', '1', 'yes')
51
+
52
+ return _debug_mode
53
+
15
54
 
16
55
  class AutoTracebackHandler(logging.Handler):
17
56
  """
@@ -43,8 +82,8 @@ class AutoTracebackHandler(logging.Handler):
43
82
  def setup_streaming_logger(
44
83
  name: str = "grpc_streaming",
45
84
  logs_dir: Optional[Path] = None,
46
- level: int = logging.DEBUG,
47
- console_level: int = logging.INFO
85
+ level: Optional[int] = None,
86
+ console_level: Optional[int] = None
48
87
  ) -> logging.Logger:
49
88
  """
50
89
  Setup dedicated logger for gRPC streaming with file and console handlers.
@@ -52,14 +91,15 @@ def setup_streaming_logger(
52
91
  Follows django-cfg logging pattern:
53
92
  - Uses os.getcwd() / 'logs' / 'grpc_streaming' for log directory
54
93
  - Time-based log file names (streaming_YYYYMMDD_HHMMSS.log)
55
- - Detailed file logging (DEBUG level by default)
56
- - Concise console logging (INFO level by default)
94
+ - Auto-detects debug mode for appropriate logging levels
95
+ - In dev/debug: files=DEBUG+, console=DEBUG+
96
+ - In production: files=INFO+, console=WARNING+
57
97
 
58
98
  Args:
59
99
  name: Logger name (default: "grpc_streaming")
60
100
  logs_dir: Directory for log files (default: <cwd>/logs/grpc_streaming)
61
- level: File logging level (default: DEBUG)
62
- console_level: Console logging level (default: INFO)
101
+ level: File logging level (default: auto-detect from debug mode)
102
+ console_level: Console logging level (default: auto-detect from debug mode)
63
103
 
64
104
  Returns:
65
105
  Configured logger instance
@@ -68,14 +108,14 @@ def setup_streaming_logger(
68
108
  ```python
69
109
  from django_cfg.apps.integrations.grpc.utils import setup_streaming_logger
70
110
 
71
- # Basic usage
111
+ # Basic usage (auto-detects debug mode)
72
112
  logger = setup_streaming_logger()
73
113
 
74
114
  # Custom configuration
75
115
  logger = setup_streaming_logger(
76
116
  name="my_streaming_service",
77
117
  logs_dir=Path("/var/log/grpc"),
78
- level=logging.INFO
118
+ level=logging.INFO # Override auto-detection
79
119
  )
80
120
 
81
121
  logger.info("Service started")
@@ -87,8 +127,21 @@ def setup_streaming_logger(
87
127
  - Time-based log file names
88
128
  - No duplicate logs (propagate=False)
89
129
  - UTF-8 encoding
130
+ - Debug mode auto-detection (cached for performance)
90
131
  - Reusable across all django-cfg gRPC projects
91
132
  """
133
+ # Auto-detect debug mode (cached - loaded once)
134
+ debug = _get_debug_mode()
135
+
136
+ # Auto-determine logging levels based on debug mode if not explicitly provided
137
+ if level is None:
138
+ # File handlers: DEBUG in dev, INFO in production
139
+ level = logging.DEBUG if debug else logging.INFO
140
+
141
+ if console_level is None:
142
+ # Console: DEBUG in dev (full visibility), WARNING in production (reduce noise)
143
+ console_level = logging.DEBUG if debug else logging.WARNING
144
+
92
145
  # Create logger
93
146
  streaming_logger = logging.getLogger(name)
94
147
  streaming_logger.setLevel(level)
@@ -128,11 +181,14 @@ def setup_streaming_logger(
128
181
  file_handler = AutoTracebackHandler(base_file_handler)
129
182
  streaming_logger.addHandler(file_handler)
130
183
 
131
- # Console handler - important messages only
132
- console_handler = logging.StreamHandler()
133
- console_handler.setLevel(console_level)
184
+ # Console handler - important messages only (also with auto-traceback)
185
+ base_console_handler = logging.StreamHandler()
186
+ base_console_handler.setLevel(console_level)
134
187
  console_formatter = logging.Formatter('%(levelname)s: %(message)s')
135
- console_handler.setFormatter(console_formatter)
188
+ base_console_handler.setFormatter(console_formatter)
189
+
190
+ # Wrap console handler with auto-traceback too
191
+ console_handler = AutoTracebackHandler(base_console_handler)
136
192
  streaming_logger.addHandler(console_handler)
137
193
 
138
194
  # Prevent propagation to avoid duplicate logs
@@ -174,4 +230,196 @@ def get_streaming_logger(name: str = "grpc_streaming") -> logging.Logger:
174
230
  return logger
175
231
 
176
232
 
177
- __all__ = ["setup_streaming_logger", "get_streaming_logger"]
233
+ def log_server_start(
234
+ logger: logging.Logger,
235
+ server_type: str = "Server",
236
+ mode: str = "Development",
237
+ hotreload_enabled: bool = False,
238
+ use_rich: bool = True,
239
+ **extra_info
240
+ ):
241
+ """
242
+ Log server startup with timestamp and configuration using Rich panels.
243
+
244
+ Args:
245
+ logger: Logger instance to use
246
+ server_type: Type of server (e.g., "gRPC Server", "WebSocket Server")
247
+ mode: Running mode (Development/Production)
248
+ hotreload_enabled: Whether hot-reload is enabled
249
+ use_rich: Use Rich for beautiful output (default: True)
250
+ **extra_info: Additional key-value pairs to log
251
+
252
+ Example:
253
+ ```python
254
+ from django_cfg.apps.integrations.grpc.utils import log_server_start
255
+ from datetime import datetime
256
+
257
+ start_time = log_server_start(
258
+ logger,
259
+ server_type="gRPC Server",
260
+ mode="Development",
261
+ hotreload_enabled=True,
262
+ host="0.0.0.0",
263
+ port=50051
264
+ )
265
+ ```
266
+
267
+ Returns:
268
+ datetime object of start time for later use in log_server_shutdown
269
+ """
270
+ from datetime import datetime
271
+
272
+ start_time = datetime.now()
273
+
274
+ if use_rich:
275
+ # Create Rich table for server info
276
+ console = Console()
277
+
278
+ table = Table(show_header=False, box=None, padding=(0, 1))
279
+ table.add_column("Key", style="cyan")
280
+ table.add_column("Value", style="white")
281
+
282
+ table.add_row("⏰ Started at", start_time.strftime('%Y-%m-%d %H:%M:%S'))
283
+ table.add_row("Mode", f"[{'red' if mode == 'Production' else 'green'}]{mode}[/]")
284
+ table.add_row("Hotreload", f"[{'yellow' if hotreload_enabled else 'dim'}]{'Enabled ⚡' if hotreload_enabled else 'Disabled'}[/]")
285
+
286
+ # Add extra info
287
+ for key, value in extra_info.items():
288
+ key_display = key.replace('_', ' ').title()
289
+ table.add_row(key_display, str(value))
290
+
291
+ # Create panel
292
+ panel = Panel(
293
+ table,
294
+ title=f"[bold green]🚀 {server_type} Starting[/bold green]",
295
+ border_style="green",
296
+ padding=(1, 2)
297
+ )
298
+
299
+ console.print(panel)
300
+
301
+ if hotreload_enabled:
302
+ console.print(
303
+ "[yellow]⚠️ Hotreload active - connections may be dropped on code changes[/yellow]",
304
+ style="bold"
305
+ )
306
+ else:
307
+ # Fallback to simple logging
308
+ logger.info("=" * 80)
309
+ logger.info(f"🚀 {server_type} Starting")
310
+ logger.info(f" ⏰ Started at: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
311
+ logger.info(f" Mode: {mode}")
312
+ logger.info(f" Hotreload: {'Enabled' if hotreload_enabled else 'Disabled'}")
313
+
314
+ for key, value in extra_info.items():
315
+ key_display = key.replace('_', ' ').title()
316
+ logger.info(f" {key_display}: {value}")
317
+
318
+ if hotreload_enabled:
319
+ logger.warning(
320
+ "⚠️ Hotreload active - connections may be dropped on code changes"
321
+ )
322
+
323
+ logger.info("=" * 80)
324
+
325
+ return start_time
326
+
327
+
328
+ def log_server_shutdown(
329
+ logger: logging.Logger,
330
+ start_time,
331
+ server_type: str = "Server",
332
+ reason: str = None,
333
+ use_rich: bool = True,
334
+ **extra_info
335
+ ):
336
+ """
337
+ Log server shutdown with uptime calculation using Rich panels.
338
+
339
+ Args:
340
+ logger: Logger instance to use
341
+ start_time: datetime object from log_server_start()
342
+ server_type: Type of server (e.g., "gRPC Server", "WebSocket Server")
343
+ reason: Shutdown reason (e.g., "Keyboard interrupt", "Hotreload")
344
+ use_rich: Use Rich for beautiful output (default: True)
345
+ **extra_info: Additional key-value pairs to log
346
+
347
+ Example:
348
+ ```python
349
+ from django_cfg.apps.integrations.grpc.utils import log_server_shutdown
350
+
351
+ log_server_shutdown(
352
+ logger,
353
+ start_time,
354
+ server_type="gRPC Server",
355
+ reason="Hotreload triggered",
356
+ active_connections=5
357
+ )
358
+ ```
359
+ """
360
+ from datetime import datetime
361
+
362
+ end_time = datetime.now()
363
+ uptime = end_time - start_time
364
+ uptime_seconds = int(uptime.total_seconds())
365
+
366
+ # Format uptime
367
+ hours = uptime_seconds // 3600
368
+ minutes = (uptime_seconds % 3600) // 60
369
+ seconds = uptime_seconds % 60
370
+ uptime_str = f"{hours}h {minutes}m {seconds}s"
371
+
372
+ if use_rich:
373
+ # Create Rich table for shutdown info
374
+ console = Console()
375
+
376
+ table = Table(show_header=False, box=None, padding=(0, 1))
377
+ table.add_column("Key", style="cyan")
378
+ table.add_column("Value", style="white")
379
+
380
+ if reason:
381
+ table.add_row("📋 Reason", reason)
382
+
383
+ table.add_row("⏱️ Uptime", f"[bold]{uptime_str}[/bold]")
384
+ table.add_row("🕐 Stopped at", end_time.strftime('%Y-%m-%d %H:%M:%S'))
385
+
386
+ # Add extra info
387
+ for key, value in extra_info.items():
388
+ key_display = key.replace('_', ' ').title()
389
+ table.add_row(key_display, str(value))
390
+
391
+ # Create panel
392
+ panel = Panel(
393
+ table,
394
+ title=f"[bold red]🧹 Shutting down {server_type}[/bold red]",
395
+ border_style="red",
396
+ padding=(1, 2)
397
+ )
398
+
399
+ console.print(panel)
400
+ console.print("[green]✅ Server shutdown complete[/green]", style="bold")
401
+ else:
402
+ # Fallback to simple logging
403
+ logger.info("=" * 80)
404
+ logger.info(f"🧹 Shutting down {server_type}...")
405
+
406
+ if reason:
407
+ logger.info(f" 📋 Reason: {reason}")
408
+
409
+ logger.info(f" ⏱️ Uptime: {uptime_str}")
410
+ logger.info(f" 🕐 Stopped at: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
411
+
412
+ for key, value in extra_info.items():
413
+ key_display = key.replace('_', ' ').title()
414
+ logger.info(f" {key_display}: {value}")
415
+
416
+ logger.info("✅ Server shutdown complete")
417
+ logger.info("=" * 80)
418
+
419
+
420
+ __all__ = [
421
+ "setup_streaming_logger",
422
+ "get_streaming_logger",
423
+ "log_server_start",
424
+ "log_server_shutdown",
425
+ ]
@@ -22,7 +22,7 @@ from ..serializers.charts import (
22
22
  ServerUptimeChartSerializer,
23
23
  ServiceActivityChartSerializer,
24
24
  )
25
- from ..services.chart_generator import ChartGeneratorService
25
+ from ..services.rendering.charts import ChartGeneratorService
26
26
 
27
27
  logger = get_logger("grpc.charts")
28
28
 
@@ -16,7 +16,7 @@ from rest_framework.response import Response
16
16
  from ..models import GRPCRequestLog, GRPCServerStatus
17
17
  from ..serializers.config import GRPCConfigSerializer, GRPCServerInfoSerializer
18
18
  from ..services import ServiceDiscovery
19
- from ..services.config_helper import get_grpc_config, get_grpc_server_config
19
+ from ..services.management.config_helper import get_grpc_config, get_grpc_server_config
20
20
 
21
21
  logger = get_logger("grpc.config")
22
22