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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/integrations/centrifugo/__init__.py +2 -0
- django_cfg/apps/integrations/centrifugo/services/client/client.py +1 -1
- django_cfg/apps/integrations/centrifugo/services/logging.py +90 -14
- django_cfg/apps/integrations/centrifugo/views/admin_api.py +29 -32
- django_cfg/apps/integrations/centrifugo/views/testing_api.py +47 -43
- django_cfg/apps/integrations/centrifugo/views/wrapper.py +41 -29
- django_cfg/apps/integrations/grpc/auth/api_key_auth.py +11 -10
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +1 -1
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +22 -36
- django_cfg/apps/integrations/grpc/managers/grpc_request_log.py +84 -0
- django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +126 -3
- django_cfg/apps/integrations/grpc/models/grpc_api_key.py +7 -1
- django_cfg/apps/integrations/grpc/models/grpc_server_status.py +22 -3
- django_cfg/apps/integrations/grpc/services/__init__.py +102 -17
- django_cfg/apps/integrations/grpc/services/centrifugo/bridge.py +469 -0
- django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/demo.py +1 -1
- django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/test_publish.py +4 -4
- django_cfg/apps/integrations/grpc/services/client/__init__.py +26 -0
- django_cfg/apps/integrations/grpc/services/commands/IMPLEMENTATION.md +456 -0
- django_cfg/apps/integrations/grpc/services/commands/README.md +252 -0
- django_cfg/apps/integrations/grpc/services/commands/__init__.py +93 -0
- django_cfg/apps/integrations/grpc/services/commands/base.py +243 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/__init__.py +22 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/base_client.py +228 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/client.py +272 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/config.py +177 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/start.py +125 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/stop.py +101 -0
- django_cfg/apps/integrations/grpc/services/commands/registry.py +170 -0
- django_cfg/apps/integrations/grpc/services/discovery/__init__.py +39 -0
- django_cfg/apps/integrations/grpc/services/{discovery.py → discovery/discovery.py} +62 -55
- django_cfg/apps/integrations/grpc/services/{service_registry.py → discovery/registry.py} +216 -5
- django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/metrics.py +3 -3
- django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/request_logger.py +10 -13
- django_cfg/apps/integrations/grpc/services/management/__init__.py +37 -0
- django_cfg/apps/integrations/grpc/services/monitoring/__init__.py +38 -0
- django_cfg/apps/integrations/grpc/services/{monitoring_service.py → monitoring/monitoring.py} +2 -2
- django_cfg/apps/integrations/grpc/services/{testing_service.py → monitoring/testing.py} +5 -5
- django_cfg/apps/integrations/grpc/services/rendering/__init__.py +27 -0
- django_cfg/apps/integrations/grpc/services/{chart_generator.py → rendering/charts.py} +1 -1
- django_cfg/apps/integrations/grpc/services/routing/__init__.py +59 -0
- django_cfg/apps/integrations/grpc/services/routing/config.py +76 -0
- django_cfg/apps/integrations/grpc/services/routing/router.py +430 -0
- django_cfg/apps/integrations/grpc/services/streaming/__init__.py +117 -0
- django_cfg/apps/integrations/grpc/services/streaming/config.py +451 -0
- django_cfg/apps/integrations/grpc/services/streaming/service.py +651 -0
- django_cfg/apps/integrations/grpc/services/streaming/types.py +367 -0
- django_cfg/apps/integrations/grpc/utils/__init__.py +58 -1
- django_cfg/apps/integrations/grpc/utils/converters.py +565 -0
- django_cfg/apps/integrations/grpc/utils/handlers.py +242 -0
- django_cfg/apps/integrations/grpc/utils/proto_gen.py +1 -1
- django_cfg/apps/integrations/grpc/utils/streaming_logger.py +55 -8
- django_cfg/apps/integrations/grpc/views/charts.py +1 -1
- django_cfg/apps/integrations/grpc/views/config.py +1 -1
- django_cfg/core/base/config_model.py +11 -0
- django_cfg/core/builders/middleware_builder.py +5 -0
- django_cfg/management/commands/pool_status.py +153 -0
- django_cfg/middleware/pool_cleanup.py +261 -0
- django_cfg/models/api/grpc/config.py +2 -2
- django_cfg/models/infrastructure/database/config.py +16 -0
- django_cfg/models/infrastructure/database/converters.py +2 -0
- django_cfg/modules/django_admin/utils/html/composition.py +57 -13
- django_cfg/modules/django_admin/utils/html_builder.py +1 -0
- django_cfg/modules/django_client/core/generator/typescript/files_generator.py +12 -0
- django_cfg/modules/django_client/core/generator/typescript/generator.py +8 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +22 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/validation-events.ts.jinja +133 -0
- django_cfg/modules/django_client/core/groups/manager.py +25 -18
- django_cfg/modules/django_client/management/commands/generate_client.py +9 -5
- django_cfg/modules/django_client/urls.py +38 -5
- django_cfg/modules/django_logging/django_logger.py +58 -19
- django_cfg/modules/django_twilio/email_otp.py +3 -1
- django_cfg/modules/django_twilio/sms.py +3 -1
- django_cfg/modules/django_twilio/unified.py +6 -2
- django_cfg/modules/django_twilio/whatsapp.py +3 -1
- django_cfg/pyproject.toml +3 -3
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/index.html +17 -57
- django_cfg/utils/pool_monitor.py +320 -0
- django_cfg/utils/smart_defaults.py +233 -7
- {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/METADATA +75 -5
- {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/RECORD +97 -68
- django_cfg/apps/integrations/grpc/centrifugo/bridge.py +0 -277
- /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/__init__.py +0 -0
- /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/config.py +0 -0
- /django_cfg/apps/integrations/grpc/{centrifugo → services/centrifugo}/transformers.py +0 -0
- /django_cfg/apps/integrations/grpc/services/{grpc_client.py → client/client.py} +0 -0
- /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/__init__.py +0 -0
- /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/centrifugo.py +0 -0
- /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/errors.py +0 -0
- /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/logging.py +0 -0
- /django_cfg/apps/integrations/grpc/services/{config_helper.py → management/config_helper.py} +0 -0
- /django_cfg/apps/integrations/grpc/services/{proto_files_manager.py → management/proto_manager.py} +0 -0
- {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.20.dist-info → django_cfg-1.5.31.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"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/django_cfg/apps/integrations/grpc/services/{config_helper.py → management/config_helper.py}
RENAMED
|
File without changes
|
/django_cfg/apps/integrations/grpc/services/{proto_files_manager.py → management/proto_manager.py}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|