django-cfg 1.5.20__py3-none-any.whl → 1.5.31__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 (98) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/integrations/centrifugo/__init__.py +2 -0
  3. django_cfg/apps/integrations/centrifugo/services/client/client.py +1 -1
  4. django_cfg/apps/integrations/centrifugo/services/logging.py +90 -14
  5. django_cfg/apps/integrations/centrifugo/views/admin_api.py +29 -32
  6. django_cfg/apps/integrations/centrifugo/views/testing_api.py +47 -43
  7. django_cfg/apps/integrations/centrifugo/views/wrapper.py +41 -29
  8. django_cfg/apps/integrations/grpc/auth/api_key_auth.py +11 -10
  9. django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +1 -1
  10. django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +22 -36
  11. django_cfg/apps/integrations/grpc/managers/grpc_request_log.py +84 -0
  12. django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +126 -3
  13. django_cfg/apps/integrations/grpc/models/grpc_api_key.py +7 -1
  14. django_cfg/apps/integrations/grpc/models/grpc_server_status.py +22 -3
  15. django_cfg/apps/integrations/grpc/services/__init__.py +102 -17
  16. django_cfg/apps/integrations/grpc/services/centrifugo/bridge.py +469 -0
  17. django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/demo.py +1 -1
  18. django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/test_publish.py +4 -4
  19. django_cfg/apps/integrations/grpc/services/client/__init__.py +26 -0
  20. django_cfg/apps/integrations/grpc/services/commands/IMPLEMENTATION.md +456 -0
  21. django_cfg/apps/integrations/grpc/services/commands/README.md +252 -0
  22. django_cfg/apps/integrations/grpc/services/commands/__init__.py +93 -0
  23. django_cfg/apps/integrations/grpc/services/commands/base.py +243 -0
  24. django_cfg/apps/integrations/grpc/services/commands/examples/__init__.py +22 -0
  25. django_cfg/apps/integrations/grpc/services/commands/examples/base_client.py +228 -0
  26. django_cfg/apps/integrations/grpc/services/commands/examples/client.py +272 -0
  27. django_cfg/apps/integrations/grpc/services/commands/examples/config.py +177 -0
  28. django_cfg/apps/integrations/grpc/services/commands/examples/start.py +125 -0
  29. django_cfg/apps/integrations/grpc/services/commands/examples/stop.py +101 -0
  30. django_cfg/apps/integrations/grpc/services/commands/registry.py +170 -0
  31. django_cfg/apps/integrations/grpc/services/discovery/__init__.py +39 -0
  32. django_cfg/apps/integrations/grpc/services/{discovery.py → discovery/discovery.py} +62 -55
  33. django_cfg/apps/integrations/grpc/services/{service_registry.py → discovery/registry.py} +216 -5
  34. django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/metrics.py +3 -3
  35. django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/request_logger.py +10 -13
  36. django_cfg/apps/integrations/grpc/services/management/__init__.py +37 -0
  37. django_cfg/apps/integrations/grpc/services/monitoring/__init__.py +38 -0
  38. django_cfg/apps/integrations/grpc/services/{monitoring_service.py → monitoring/monitoring.py} +2 -2
  39. django_cfg/apps/integrations/grpc/services/{testing_service.py → monitoring/testing.py} +5 -5
  40. django_cfg/apps/integrations/grpc/services/rendering/__init__.py +27 -0
  41. django_cfg/apps/integrations/grpc/services/{chart_generator.py → rendering/charts.py} +1 -1
  42. django_cfg/apps/integrations/grpc/services/routing/__init__.py +59 -0
  43. django_cfg/apps/integrations/grpc/services/routing/config.py +76 -0
  44. django_cfg/apps/integrations/grpc/services/routing/router.py +430 -0
  45. django_cfg/apps/integrations/grpc/services/streaming/__init__.py +117 -0
  46. django_cfg/apps/integrations/grpc/services/streaming/config.py +451 -0
  47. django_cfg/apps/integrations/grpc/services/streaming/service.py +651 -0
  48. django_cfg/apps/integrations/grpc/services/streaming/types.py +367 -0
  49. django_cfg/apps/integrations/grpc/utils/__init__.py +58 -1
  50. django_cfg/apps/integrations/grpc/utils/converters.py +565 -0
  51. django_cfg/apps/integrations/grpc/utils/handlers.py +242 -0
  52. django_cfg/apps/integrations/grpc/utils/proto_gen.py +1 -1
  53. django_cfg/apps/integrations/grpc/utils/streaming_logger.py +55 -8
  54. django_cfg/apps/integrations/grpc/views/charts.py +1 -1
  55. django_cfg/apps/integrations/grpc/views/config.py +1 -1
  56. django_cfg/core/base/config_model.py +11 -0
  57. django_cfg/core/builders/middleware_builder.py +5 -0
  58. django_cfg/management/commands/pool_status.py +153 -0
  59. django_cfg/middleware/pool_cleanup.py +261 -0
  60. django_cfg/models/api/grpc/config.py +2 -2
  61. django_cfg/models/infrastructure/database/config.py +16 -0
  62. django_cfg/models/infrastructure/database/converters.py +2 -0
  63. django_cfg/modules/django_admin/utils/html/composition.py +57 -13
  64. django_cfg/modules/django_admin/utils/html_builder.py +1 -0
  65. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +12 -0
  66. django_cfg/modules/django_client/core/generator/typescript/generator.py +8 -0
  67. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +22 -0
  68. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +4 -0
  69. django_cfg/modules/django_client/core/generator/typescript/templates/utils/validation-events.ts.jinja +133 -0
  70. django_cfg/modules/django_client/core/groups/manager.py +25 -18
  71. django_cfg/modules/django_client/management/commands/generate_client.py +9 -5
  72. django_cfg/modules/django_client/urls.py +38 -5
  73. django_cfg/modules/django_logging/django_logger.py +58 -19
  74. django_cfg/modules/django_twilio/email_otp.py +3 -1
  75. django_cfg/modules/django_twilio/sms.py +3 -1
  76. django_cfg/modules/django_twilio/unified.py +6 -2
  77. django_cfg/modules/django_twilio/whatsapp.py +3 -1
  78. django_cfg/pyproject.toml +3 -3
  79. django_cfg/static/frontend/admin.zip +0 -0
  80. django_cfg/templates/admin/index.html +17 -57
  81. django_cfg/utils/pool_monitor.py +320 -0
  82. django_cfg/utils/smart_defaults.py +233 -7
  83. {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/METADATA +75 -5
  84. {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/RECORD +97 -68
  85. django_cfg/apps/integrations/grpc/centrifugo/bridge.py +0 -277
  86. /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/__init__.py +0 -0
  87. /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/config.py +0 -0
  88. /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/transformers.py +0 -0
  89. /django_cfg/apps/integrations/grpc/services/{grpc_client.py → client/client.py} +0 -0
  90. /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/__init__.py +0 -0
  91. /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/centrifugo.py +0 -0
  92. /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/errors.py +0 -0
  93. /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/logging.py +0 -0
  94. /django_cfg/apps/integrations/grpc/services/{config_helper.py → management/config_helper.py} +0 -0
  95. /django_cfg/apps/integrations/grpc/services/{proto_files_manager.py → management/proto_manager.py} +0 -0
  96. {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/WHEEL +0 -0
  97. {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/entry_points.txt +0 -0
  98. {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.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
@@ -19,6 +19,39 @@ from rich.table import Table
19
19
  from rich.text import Text
20
20
 
21
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
+
54
+
22
55
  class AutoTracebackHandler(logging.Handler):
23
56
  """
24
57
  Custom handler that automatically adds exception info to ERROR and CRITICAL logs.
@@ -49,8 +82,8 @@ class AutoTracebackHandler(logging.Handler):
49
82
  def setup_streaming_logger(
50
83
  name: str = "grpc_streaming",
51
84
  logs_dir: Optional[Path] = None,
52
- level: int = logging.DEBUG,
53
- console_level: int = logging.INFO
85
+ level: Optional[int] = None,
86
+ console_level: Optional[int] = None
54
87
  ) -> logging.Logger:
55
88
  """
56
89
  Setup dedicated logger for gRPC streaming with file and console handlers.
@@ -58,14 +91,15 @@ def setup_streaming_logger(
58
91
  Follows django-cfg logging pattern:
59
92
  - Uses os.getcwd() / 'logs' / 'grpc_streaming' for log directory
60
93
  - 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)
94
+ - Auto-detects debug mode for appropriate logging levels
95
+ - In dev/debug: files=DEBUG+, console=DEBUG+
96
+ - In production: files=INFO+, console=WARNING+
63
97
 
64
98
  Args:
65
99
  name: Logger name (default: "grpc_streaming")
66
100
  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)
101
+ level: File logging level (default: auto-detect from debug mode)
102
+ console_level: Console logging level (default: auto-detect from debug mode)
69
103
 
70
104
  Returns:
71
105
  Configured logger instance
@@ -74,14 +108,14 @@ def setup_streaming_logger(
74
108
  ```python
75
109
  from django_cfg.apps.integrations.grpc.utils import setup_streaming_logger
76
110
 
77
- # Basic usage
111
+ # Basic usage (auto-detects debug mode)
78
112
  logger = setup_streaming_logger()
79
113
 
80
114
  # Custom configuration
81
115
  logger = setup_streaming_logger(
82
116
  name="my_streaming_service",
83
117
  logs_dir=Path("/var/log/grpc"),
84
- level=logging.INFO
118
+ level=logging.INFO # Override auto-detection
85
119
  )
86
120
 
87
121
  logger.info("Service started")
@@ -93,8 +127,21 @@ def setup_streaming_logger(
93
127
  - Time-based log file names
94
128
  - No duplicate logs (propagate=False)
95
129
  - UTF-8 encoding
130
+ - Debug mode auto-detection (cached for performance)
96
131
  - Reusable across all django-cfg gRPC projects
97
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
+
98
145
  # Create logger
99
146
  streaming_logger = logging.getLogger(name)
100
147
  streaming_logger.setLevel(level)
@@ -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
 
@@ -221,6 +221,17 @@ class DjangoConfig(BaseModel):
221
221
  description="Database connections",
222
222
  )
223
223
 
224
+ enable_pool_cleanup: bool = Field(
225
+ default=False,
226
+ description=(
227
+ "Enable explicit connection pool cleanup middleware. "
228
+ "Django already closes connections automatically, but this middleware "
229
+ "adds explicit guarantees and rollback on errors. "
230
+ "Enable only if you experience connection leaks. "
231
+ "Note: Not needed with ATOMIC_REQUESTS=True (default)."
232
+ ),
233
+ )
234
+
224
235
  # === Cache Configuration ===
225
236
  # Redis URL - used for automatic cache configuration if cache_default is not set
226
237
  redis_url: Optional[str] = Field(
@@ -66,6 +66,11 @@ class MiddlewareBuilder:
66
66
  # Add custom user middleware
67
67
  middleware.extend(self.config.custom_middleware)
68
68
 
69
+ # Add connection pool cleanup middleware LAST (if enabled)
70
+ # This ensures connections are returned to pool after ALL other middleware
71
+ if self.config.enable_pool_cleanup:
72
+ middleware.append('django_cfg.middleware.pool_cleanup.ConnectionPoolCleanupMiddleware')
73
+
69
74
  return middleware
70
75
 
71
76
  def _get_feature_middleware(self) -> List[str]:
@@ -0,0 +1,153 @@
1
+ """
2
+ Django management command to display connection pool status.
3
+
4
+ Usage:
5
+ python manage.py pool_status
6
+ python manage.py pool_status --database=secondary
7
+ python manage.py pool_status --json
8
+ """
9
+
10
+ import json
11
+
12
+ from django.core.management.base import BaseCommand
13
+
14
+ from django_cfg.utils.pool_monitor import PoolMonitor
15
+
16
+
17
+ class Command(BaseCommand):
18
+ """Display database connection pool status."""
19
+
20
+ help = 'Display database connection pool status and health information'
21
+
22
+ def add_arguments(self, parser):
23
+ """Add command arguments."""
24
+ parser.add_argument(
25
+ '--database',
26
+ default='default',
27
+ help='Database alias to check (default: default)',
28
+ )
29
+ parser.add_argument(
30
+ '--json',
31
+ action='store_true',
32
+ help='Output in JSON format',
33
+ )
34
+
35
+ def handle(self, *args, **options):
36
+ """Execute command."""
37
+ database_alias = options['database']
38
+ json_output = options['json']
39
+
40
+ monitor = PoolMonitor(database_alias=database_alias)
41
+
42
+ if json_output:
43
+ self._handle_json_output(monitor)
44
+ else:
45
+ self._handle_pretty_output(monitor)
46
+
47
+ def _handle_json_output(self, monitor: PoolMonitor):
48
+ """Output pool status as JSON."""
49
+ info = monitor.get_pool_info_dict()
50
+ self.stdout.write(json.dumps(info, indent=2))
51
+
52
+ def _handle_pretty_output(self, monitor: PoolMonitor):
53
+ """Output pool status in pretty formatted text."""
54
+ stats = monitor.get_pool_stats()
55
+
56
+ if not stats:
57
+ self.stdout.write(self.style.WARNING('\n⚠️ Connection Pooling: Not Configured'))
58
+ self.stdout.write('')
59
+ self.stdout.write('Database is not using connection pooling.')
60
+ self.stdout.write('Consider enabling pooling for production environments.')
61
+ self.stdout.write('')
62
+ return
63
+
64
+ health = monitor.check_pool_health()
65
+
66
+ # Header
67
+ self.stdout.write('')
68
+ self.stdout.write(self.style.SUCCESS('=' * 70))
69
+ self.stdout.write(self.style.SUCCESS(' DATABASE CONNECTION POOL STATUS'))
70
+ self.stdout.write(self.style.SUCCESS('=' * 70))
71
+ self.stdout.write('')
72
+
73
+ # Deployment Information
74
+ mode = 'ASGI' if stats['is_asgi'] else 'WSGI'
75
+ mode_icon = '🚀' if stats['is_asgi'] else '🐍'
76
+
77
+ self.stdout.write(self.style.HTTP_INFO('Deployment Information:'))
78
+ self.stdout.write(f" Mode: {mode_icon} {mode}")
79
+ self.stdout.write(f" Environment: {stats['environment'].title()}")
80
+ self.stdout.write(f" Database: {monitor.database_alias}")
81
+ self.stdout.write(f" Backend: {stats['backend']}")
82
+ self.stdout.write('')
83
+
84
+ # Pool Configuration
85
+ self.stdout.write(self.style.HTTP_INFO('Pool Configuration:'))
86
+ self.stdout.write(f" Min Size: {stats['pool_min_size']:3d} connections")
87
+ self.stdout.write(f" Max Size: {stats['pool_max_size']:3d} connections")
88
+ self.stdout.write(f" Timeout: {stats['pool_timeout']:3d} seconds")
89
+ self.stdout.write(f" Max Lifetime: {stats['max_lifetime']:4d} seconds ({stats['max_lifetime'] // 60} min)")
90
+ self.stdout.write(f" Max Idle: {stats['max_idle']:4d} seconds ({stats['max_idle'] // 60} min)")
91
+ self.stdout.write('')
92
+
93
+ # Current Status (if available)
94
+ if stats['pool_size'] is not None:
95
+ self.stdout.write(self.style.HTTP_INFO('Current Status:'))
96
+ self.stdout.write(f" Current Size: {stats['pool_size']:3d} connections")
97
+ if stats['pool_available'] is not None:
98
+ self.stdout.write(f" Available: {stats['pool_available']:3d} connections")
99
+ self.stdout.write(f" Capacity: {health['capacity_percent']:.1f}% used")
100
+ self.stdout.write('')
101
+ else:
102
+ self.stdout.write(self.style.WARNING('Current Status: Not available (pool not active yet)'))
103
+ self.stdout.write('')
104
+
105
+ # Health Check
106
+ status_styles = {
107
+ 'healthy': self.style.SUCCESS,
108
+ 'warning': self.style.WARNING,
109
+ 'critical': self.style.ERROR,
110
+ 'unavailable': self.style.NOTICE,
111
+ }
112
+ status_icons = {
113
+ 'healthy': '✅',
114
+ 'warning': '⚠️',
115
+ 'critical': '🔴',
116
+ 'unavailable': '⚪',
117
+ }
118
+
119
+ status_style = status_styles.get(health['status'], self.style.NOTICE)
120
+ status_icon = status_icons.get(health['status'], '❓')
121
+ status_text = health['status'].upper()
122
+
123
+ self.stdout.write(self.style.HTTP_INFO('Health Check:'))
124
+ self.stdout.write(f" Status: {status_icon} " + status_style(status_text))
125
+
126
+ if health['issues']:
127
+ self.stdout.write('')
128
+ self.stdout.write(self.style.WARNING(' Issues Detected:'))
129
+ for issue in health['issues']:
130
+ self.stdout.write(self.style.WARNING(f" • {issue}"))
131
+
132
+ if health['recommendations']:
133
+ self.stdout.write('')
134
+ self.stdout.write(self.style.NOTICE(' Recommendations:'))
135
+ for rec in health['recommendations']:
136
+ self.stdout.write(f" • {rec}")
137
+
138
+ # Footer
139
+ self.stdout.write('')
140
+ self.stdout.write(self.style.SUCCESS('=' * 70))
141
+ self.stdout.write('')
142
+
143
+ # Overall summary
144
+ if health['healthy']:
145
+ self.stdout.write(self.style.SUCCESS('✅ Pool is healthy and operating normally'))
146
+ elif health['status'] == 'warning':
147
+ self.stdout.write(self.style.WARNING('⚠️ Pool is functional but requires attention'))
148
+ elif health['status'] == 'critical':
149
+ self.stdout.write(self.style.ERROR('🔴 Pool is in critical state - immediate action required'))
150
+ else:
151
+ self.stdout.write(self.style.NOTICE('ℹ️ Pool status check completed'))
152
+
153
+ self.stdout.write('')