django-cfg 1.5.20__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 (88) 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 +47 -0
  5. django_cfg/apps/integrations/centrifugo/views/admin_api.py +29 -32
  6. django_cfg/apps/integrations/centrifugo/views/testing_api.py +31 -37
  7. django_cfg/apps/integrations/centrifugo/views/wrapper.py +25 -23
  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 +21 -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} +215 -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/groups/manager.py +25 -18
  66. django_cfg/modules/django_client/management/commands/generate_client.py +9 -5
  67. django_cfg/modules/django_logging/django_logger.py +58 -19
  68. django_cfg/pyproject.toml +3 -3
  69. django_cfg/static/frontend/admin.zip +0 -0
  70. django_cfg/templates/admin/index.html +0 -39
  71. django_cfg/utils/pool_monitor.py +320 -0
  72. django_cfg/utils/smart_defaults.py +233 -7
  73. {django_cfg-1.5.20.dist-info → django_cfg-1.5.29.dist-info}/METADATA +75 -5
  74. {django_cfg-1.5.20.dist-info → django_cfg-1.5.29.dist-info}/RECORD +87 -59
  75. django_cfg/apps/integrations/grpc/centrifugo/bridge.py +0 -277
  76. /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/__init__.py +0 -0
  77. /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/config.py +0 -0
  78. /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/transformers.py +0 -0
  79. /django_cfg/apps/integrations/grpc/services/{grpc_client.py → client/client.py} +0 -0
  80. /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/__init__.py +0 -0
  81. /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/centrifugo.py +0 -0
  82. /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/errors.py +0 -0
  83. /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/logging.py +0 -0
  84. /django_cfg/apps/integrations/grpc/services/{config_helper.py → management/config_helper.py} +0 -0
  85. /django_cfg/apps/integrations/grpc/services/{proto_files_manager.py → management/proto_manager.py} +0 -0
  86. {django_cfg-1.5.20.dist-info → django_cfg-1.5.29.dist-info}/WHEEL +0 -0
  87. {django_cfg-1.5.20.dist-info → django_cfg-1.5.29.dist-info}/entry_points.txt +0 -0
  88. {django_cfg-1.5.20.dist-info → django_cfg-1.5.29.dist-info}/licenses/LICENSE +0 -0
@@ -1,277 +0,0 @@
1
- """
2
- Centrifugo Bridge Mixin for gRPC Services.
3
-
4
- Universal mixin that enables automatic publishing of gRPC stream events
5
- to Centrifugo WebSocket channels using Pydantic configuration.
6
- """
7
-
8
- import logging
9
- import time
10
- from datetime import datetime, timezone as tz
11
- from typing import Dict, Optional, Any, TYPE_CHECKING
12
-
13
- from .config import CentrifugoChannels, ChannelConfig
14
- from .transformers import transform_protobuf_to_dict
15
-
16
- if TYPE_CHECKING:
17
- from django_cfg.apps.integrations.centrifugo import CentrifugoClient
18
-
19
- logger = logging.getLogger(__name__)
20
-
21
-
22
- class CentrifugoBridgeMixin:
23
- """
24
- Universal mixin for publishing gRPC stream events to Centrifugo.
25
-
26
- Uses Pydantic models for type-safe, validated configuration.
27
-
28
- Features:
29
- - Type-safe Pydantic configuration
30
- - Automatic event publishing to WebSocket channels
31
- - Built-in protobuf → JSON transformation
32
- - Graceful degradation if Centrifugo unavailable
33
- - Custom transform functions support
34
- - Template-based channel naming
35
- - Per-channel rate limiting
36
- - Critical event bypassing
37
-
38
- Usage:
39
- ```python
40
- from django_cfg.apps.integrations.grpc.mixins import (
41
- CentrifugoBridgeMixin,
42
- CentrifugoChannels,
43
- ChannelConfig,
44
- )
45
-
46
- class BotChannels(CentrifugoChannels):
47
- heartbeat: ChannelConfig = ChannelConfig(
48
- template='bot#{bot_id}#heartbeat',
49
- rate_limit=0.1
50
- )
51
- status: ChannelConfig = ChannelConfig(
52
- template='bot#{bot_id}#status',
53
- critical=True
54
- )
55
-
56
- class BotStreamingService(
57
- bot_streaming_service_pb2_grpc.BotStreamingServiceServicer,
58
- CentrifugoBridgeMixin
59
- ):
60
- centrifugo_channels = BotChannels()
61
-
62
- async def ConnectBot(self, request_iterator, context):
63
- async for message in request_iterator:
64
- # Your business logic
65
- await self._handle_message(bot_id, message)
66
-
67
- # Auto-publish to Centrifugo (1 line!)
68
- await self._notify_centrifugo(message, bot_id=bot_id)
69
- ```
70
- """
71
-
72
- # Class-level Pydantic config (optional, can be set in __init__)
73
- centrifugo_channels: Optional[CentrifugoChannels] = None
74
-
75
- def __init__(self):
76
- """Initialize Centrifugo bridge from Pydantic configuration."""
77
- super().__init__()
78
-
79
- # Instance attributes
80
- self._centrifugo_enabled: bool = False
81
- self._centrifugo_graceful: bool = True
82
- self._centrifugo_client: Optional['CentrifugoClient'] = None
83
- self._centrifugo_mappings: Dict[str, Dict[str, Any]] = {}
84
- self._centrifugo_last_publish: Dict[str, float] = {}
85
-
86
- # Auto-setup if config exists
87
- if self.centrifugo_channels:
88
- self._setup_from_pydantic_config(self.centrifugo_channels)
89
-
90
- def _setup_from_pydantic_config(self, config: CentrifugoChannels):
91
- """
92
- Setup Centrifugo bridge from Pydantic configuration.
93
-
94
- Args:
95
- config: CentrifugoChannels instance with channel mappings
96
- """
97
- self._centrifugo_enabled = config.enabled
98
- self._centrifugo_graceful = config.graceful_degradation
99
-
100
- # Extract channel mappings
101
- for field_name, channel_config in config.get_channel_mappings().items():
102
- if channel_config.enabled:
103
- self._centrifugo_mappings[field_name] = {
104
- 'template': channel_config.template,
105
- 'rate_limit': channel_config.rate_limit or config.default_rate_limit,
106
- 'critical': channel_config.critical,
107
- 'transform': channel_config.transform,
108
- 'metadata': channel_config.metadata,
109
- }
110
-
111
- # Initialize client if enabled
112
- if self._centrifugo_enabled and self._centrifugo_mappings:
113
- self._initialize_centrifugo_client()
114
-
115
- def _initialize_centrifugo_client(self):
116
- """Lazy initialize Centrifugo client."""
117
- try:
118
- from django_cfg.apps.integrations.centrifugo import get_centrifugo_client
119
- self._centrifugo_client = get_centrifugo_client()
120
- logger.info(
121
- f"✅ Centrifugo bridge enabled with {len(self._centrifugo_mappings)} channels"
122
- )
123
- except Exception as e:
124
- logger.warning(f"⚠️ Centrifugo client not available: {e}")
125
- if not self._centrifugo_graceful:
126
- raise
127
- self._centrifugo_enabled = False
128
-
129
- async def _notify_centrifugo(
130
- self,
131
- message: Any, # Protobuf message
132
- **context: Any # Template variables for channel rendering
133
- ) -> bool:
134
- """
135
- Publish protobuf message to Centrifugo based on configured mappings.
136
-
137
- Automatically detects which field is set in the message and publishes
138
- to the corresponding channel.
139
-
140
- Args:
141
- message: Protobuf message (e.g., BotMessage with heartbeat/status/etc.)
142
- **context: Template variables for channel name rendering
143
- Example: bot_id='123', user_id='456'
144
-
145
- Returns:
146
- True if published successfully, False otherwise
147
-
148
- Example:
149
- ```python
150
- # message = BotMessage with heartbeat field set
151
- await self._notify_centrifugo(message, bot_id='bot-123')
152
- # → Publishes to channel: bot#bot-123#heartbeat
153
- ```
154
- """
155
- if not self._centrifugo_enabled or not self._centrifugo_client:
156
- return False
157
-
158
- # Check each mapped field
159
- for field_name, mapping in self._centrifugo_mappings.items():
160
- if message.HasField(field_name):
161
- return await self._publish_field(
162
- field_name,
163
- message,
164
- mapping,
165
- context
166
- )
167
-
168
- return False
169
-
170
- async def _publish_field(
171
- self,
172
- field_name: str,
173
- message: Any,
174
- mapping: Dict[str, Any],
175
- context: dict
176
- ) -> bool:
177
- """
178
- Publish specific message field to Centrifugo.
179
-
180
- Args:
181
- field_name: Name of the protobuf field
182
- message: Full protobuf message
183
- mapping: Channel mapping configuration
184
- context: Template variables
185
-
186
- Returns:
187
- True if published successfully
188
- """
189
- try:
190
- # Render channel from template
191
- channel = mapping['template'].format(**context)
192
-
193
- # Rate limiting check (unless critical)
194
- if not mapping['critical'] and mapping['rate_limit']:
195
- now = time.time()
196
- last = self._centrifugo_last_publish.get(channel, 0)
197
- if now - last < mapping['rate_limit']:
198
- logger.debug(f"⏱️ Rate limit: skipping {field_name} for {channel}")
199
- return False
200
- self._centrifugo_last_publish[channel] = now
201
-
202
- # Get field value
203
- field_value = getattr(message, field_name)
204
-
205
- # Transform to dict
206
- data = self._transform_field(field_name, field_value, mapping, context)
207
-
208
- # Publish to Centrifugo
209
- await self._centrifugo_client.publish(
210
- channel=channel,
211
- data=data
212
- )
213
-
214
- logger.debug(f"✅ Published {field_name} to {channel}")
215
- return True
216
-
217
- except KeyError as e:
218
- logger.error(
219
- f"❌ Missing template variable in channel: {e}. "
220
- f"Template: {mapping['template']}, Context: {context}"
221
- )
222
- return False
223
-
224
- except Exception as e:
225
- logger.error(
226
- f"❌ Failed to publish {field_name} to Centrifugo: {e}",
227
- exc_info=True
228
- )
229
- if not self._centrifugo_graceful:
230
- raise
231
- return False
232
-
233
- def _transform_field(
234
- self,
235
- field_name: str,
236
- field_value: Any,
237
- mapping: Dict[str, Any],
238
- context: dict
239
- ) -> dict:
240
- """
241
- Transform protobuf field to JSON-serializable dict.
242
-
243
- Args:
244
- field_name: Field name
245
- field_value: Protobuf message field value
246
- mapping: Channel mapping with optional transform function
247
- context: Template context variables
248
-
249
- Returns:
250
- JSON-serializable dictionary
251
- """
252
- # Use custom transform if provided
253
- if mapping['transform']:
254
- data = mapping['transform'](field_name, field_value)
255
- else:
256
- # Default protobuf → dict transform
257
- data = transform_protobuf_to_dict(field_value)
258
-
259
- # Add metadata
260
- data['type'] = field_name
261
- data['timestamp'] = datetime.now(tz.utc).isoformat()
262
-
263
- # Merge channel metadata
264
- if mapping['metadata']:
265
- for key, value in mapping['metadata'].items():
266
- if key not in data:
267
- data[key] = value
268
-
269
- # Add context variables (bot_id, user_id, etc.)
270
- for key, value in context.items():
271
- if key not in data:
272
- data[key] = value
273
-
274
- return data
275
-
276
-
277
- __all__ = ["CentrifugoBridgeMixin"]