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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/business/accounts/serializers/profile.py +42 -0
- django_cfg/apps/business/support/serializers.py +3 -2
- django_cfg/apps/integrations/centrifugo/__init__.py +2 -0
- django_cfg/apps/integrations/centrifugo/apps.py +2 -1
- django_cfg/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +151 -12
- django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +2 -2
- django_cfg/apps/integrations/centrifugo/services/__init__.py +6 -0
- django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
- django_cfg/apps/integrations/centrifugo/services/client/client.py +1 -1
- django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -0
- django_cfg/apps/integrations/centrifugo/services/logging.py +47 -0
- django_cfg/apps/integrations/centrifugo/services/publisher.py +371 -0
- django_cfg/apps/integrations/centrifugo/services/token_generator.py +122 -0
- django_cfg/apps/integrations/centrifugo/urls.py +8 -0
- django_cfg/apps/integrations/centrifugo/views/__init__.py +2 -0
- django_cfg/apps/integrations/centrifugo/views/admin_api.py +29 -32
- django_cfg/apps/integrations/centrifugo/views/testing_api.py +31 -116
- django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
- django_cfg/apps/integrations/centrifugo/views/wrapper.py +259 -0
- django_cfg/apps/integrations/grpc/auth/api_key_auth.py +11 -10
- django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +56 -1
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +315 -26
- django_cfg/apps/integrations/grpc/management/proto/__init__.py +3 -0
- django_cfg/apps/integrations/grpc/management/proto/compiler.py +194 -0
- 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/__init__.py +29 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/bridge.py +469 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/config.py +167 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/demo.py +626 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/test_publish.py +229 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/transformers.py +89 -0
- 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} +67 -54
- django_cfg/apps/integrations/grpc/services/{service_registry.py → discovery/registry.py} +215 -5
- django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/__init__.py +3 -1
- django_cfg/apps/integrations/grpc/services/interceptors/centrifugo.py +541 -0
- 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/SERVER_LOGGING.md +164 -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 +261 -13
- django_cfg/apps/integrations/grpc/views/charts.py +1 -1
- django_cfg/apps/integrations/grpc/views/config.py +1 -1
- django_cfg/apps/system/dashboard/serializers/config.py +95 -9
- django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
- django_cfg/apps/system/frontend/views.py +87 -6
- django_cfg/core/base/config_model.py +11 -0
- django_cfg/core/builders/middleware_builder.py +5 -0
- django_cfg/core/builders/security_builder.py +1 -0
- django_cfg/core/generation/integration_generators/api.py +2 -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/generator.py +26 -0
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +7 -1
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +5 -0
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +11 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +1 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +29 -1
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +4 -0
- django_cfg/modules/django_client/core/groups/manager.py +25 -18
- django_cfg/modules/django_client/core/ir/schema.py +15 -1
- django_cfg/modules/django_client/core/parser/base.py +12 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +9 -5
- django_cfg/modules/django_logging/django_logger.py +58 -19
- django_cfg/pyproject.toml +3 -3
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/index.html +0 -39
- django_cfg/utils/pool_monitor.py +320 -0
- django_cfg/utils/smart_defaults.py +233 -7
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/METADATA +75 -5
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/RECORD +118 -74
- /django_cfg/apps/integrations/grpc/services/{grpc_client.py → client/client.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.14.dist-info → django_cfg-1.5.29.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Protocol type definitions for type-safe gRPC bidirectional streaming.
|
|
3
|
+
|
|
4
|
+
This module defines Protocol types used for generic, reusable gRPC streaming components.
|
|
5
|
+
All protocols are fully typed and support generic message/command types.
|
|
6
|
+
|
|
7
|
+
**Design Goals**:
|
|
8
|
+
- 100% type-safe callbacks
|
|
9
|
+
- Zero runtime overhead (protocols are compile-time only)
|
|
10
|
+
- Compatible with any protobuf message types
|
|
11
|
+
- Enables IDE autocomplete and mypy validation
|
|
12
|
+
|
|
13
|
+
**Usage Example**:
|
|
14
|
+
```python
|
|
15
|
+
# Define your processor
|
|
16
|
+
async def process_signal_message(
|
|
17
|
+
client_id: str,
|
|
18
|
+
message: SignalCommand,
|
|
19
|
+
output_queue: asyncio.Queue[SignalMessage]
|
|
20
|
+
) -> None:
|
|
21
|
+
# Your logic here
|
|
22
|
+
await output_queue.put(response)
|
|
23
|
+
|
|
24
|
+
# Type checker validates signature matches MessageProcessor protocol
|
|
25
|
+
service = BidirectionalStreamingService(
|
|
26
|
+
message_processor=process_signal_message, # ✅ Type-safe
|
|
27
|
+
...
|
|
28
|
+
)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Created: 2025-11-07
|
|
32
|
+
Status: %%PRODUCTION%%
|
|
33
|
+
Phase: Phase 1 - Universal Components
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from typing import Protocol, TypeVar, Any
|
|
37
|
+
import asyncio
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ============================================================================
|
|
41
|
+
# Generic Type Variables
|
|
42
|
+
# ============================================================================
|
|
43
|
+
|
|
44
|
+
TMessage = TypeVar('TMessage', contravariant=True)
|
|
45
|
+
"""
|
|
46
|
+
Generic type for incoming gRPC messages (commands from client).
|
|
47
|
+
|
|
48
|
+
**Contravariant**: Allows passing more specific message types where generic is expected.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
- Protocol expects: Message (base)
|
|
52
|
+
- Can pass: SignalCommand (subclass)
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
TCommand = TypeVar('TCommand', covariant=True)
|
|
56
|
+
"""
|
|
57
|
+
Generic type for outgoing gRPC commands (responses to client).
|
|
58
|
+
|
|
59
|
+
**Covariant**: Allows returning more general command types where specific is expected.
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
- Protocol returns: BotResponse (specific)
|
|
63
|
+
- Can be treated as: Message (base)
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ============================================================================
|
|
68
|
+
# Core Processing Protocols
|
|
69
|
+
# ============================================================================
|
|
70
|
+
|
|
71
|
+
class MessageProcessor(Protocol[TMessage, TCommand]):
|
|
72
|
+
"""
|
|
73
|
+
Protocol for processing incoming gRPC messages and generating responses.
|
|
74
|
+
|
|
75
|
+
This is the core business logic handler that processes each message from the client
|
|
76
|
+
and optionally enqueues response commands.
|
|
77
|
+
|
|
78
|
+
**Type Parameters**:
|
|
79
|
+
TMessage: Type of incoming messages (e.g., SignalCommand, BotCommand)
|
|
80
|
+
TCommand: Type of outgoing commands (e.g., SignalMessage, BotResponse)
|
|
81
|
+
|
|
82
|
+
**Signature**:
|
|
83
|
+
async def(client_id: str, message: TMessage, output_queue: Queue[TCommand]) -> None
|
|
84
|
+
|
|
85
|
+
**Parameters**:
|
|
86
|
+
client_id: Unique identifier for the connected client
|
|
87
|
+
message: Incoming message from client (generic type TMessage)
|
|
88
|
+
output_queue: Queue to enqueue response commands (generic type TCommand)
|
|
89
|
+
|
|
90
|
+
**Example Implementation**:
|
|
91
|
+
```python
|
|
92
|
+
async def process_bot_command(
|
|
93
|
+
client_id: str,
|
|
94
|
+
message: BotCommand,
|
|
95
|
+
output_queue: asyncio.Queue[BotResponse]
|
|
96
|
+
) -> None:
|
|
97
|
+
logger.info(f"Processing command for {client_id}")
|
|
98
|
+
|
|
99
|
+
if message.command_type == CommandType.START:
|
|
100
|
+
response = BotResponse(status="started")
|
|
101
|
+
await output_queue.put(response)
|
|
102
|
+
```
|
|
103
|
+
"""
|
|
104
|
+
async def __call__(
|
|
105
|
+
self,
|
|
106
|
+
client_id: str,
|
|
107
|
+
message: TMessage,
|
|
108
|
+
output_queue: asyncio.Queue[TCommand]
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Process incoming message and optionally enqueue responses."""
|
|
111
|
+
...
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class ClientIdExtractor(Protocol[TMessage]):
|
|
115
|
+
"""
|
|
116
|
+
Protocol for extracting client ID from incoming gRPC messages.
|
|
117
|
+
|
|
118
|
+
Different services may store client IDs in different message fields.
|
|
119
|
+
This protocol allows type-safe client ID extraction.
|
|
120
|
+
|
|
121
|
+
**Type Parameters**:
|
|
122
|
+
TMessage: Type of incoming messages
|
|
123
|
+
|
|
124
|
+
**Signature**:
|
|
125
|
+
def(message: TMessage) -> str
|
|
126
|
+
|
|
127
|
+
**Example Implementation**:
|
|
128
|
+
```python
|
|
129
|
+
def extract_bot_client_id(message: BotCommand) -> str:
|
|
130
|
+
return str(message.bot_id)
|
|
131
|
+
|
|
132
|
+
def extract_signal_client_id(message: SignalCommand) -> str:
|
|
133
|
+
return message.client_id
|
|
134
|
+
```
|
|
135
|
+
"""
|
|
136
|
+
def __call__(self, message: TMessage) -> str:
|
|
137
|
+
"""Extract client ID from message."""
|
|
138
|
+
...
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class PingMessageCreator(Protocol[TCommand]):
|
|
142
|
+
"""
|
|
143
|
+
Protocol for creating ping/keepalive messages.
|
|
144
|
+
|
|
145
|
+
Bidirectional streams need periodic ping messages to keep connections alive.
|
|
146
|
+
This protocol allows type-safe ping message creation.
|
|
147
|
+
|
|
148
|
+
**Type Parameters**:
|
|
149
|
+
TCommand: Type of outgoing commands
|
|
150
|
+
|
|
151
|
+
**Signature**:
|
|
152
|
+
def() -> TCommand
|
|
153
|
+
|
|
154
|
+
**Example Implementation**:
|
|
155
|
+
```python
|
|
156
|
+
def create_bot_ping() -> BotResponse:
|
|
157
|
+
return BotResponse(
|
|
158
|
+
message_type=MessageType.PING,
|
|
159
|
+
timestamp=int(time.time())
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def create_signal_ping() -> SignalMessage:
|
|
163
|
+
return SignalMessage(is_ping=True)
|
|
164
|
+
```
|
|
165
|
+
"""
|
|
166
|
+
def __call__(self) -> TCommand:
|
|
167
|
+
"""Create a ping message."""
|
|
168
|
+
...
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# ============================================================================
|
|
172
|
+
# Connection Management Protocols
|
|
173
|
+
# ============================================================================
|
|
174
|
+
|
|
175
|
+
class ConnectionCallback(Protocol):
|
|
176
|
+
"""
|
|
177
|
+
Protocol for connection lifecycle callbacks.
|
|
178
|
+
|
|
179
|
+
**Signature**:
|
|
180
|
+
async def(client_id: str) -> None
|
|
181
|
+
|
|
182
|
+
**Use Cases**:
|
|
183
|
+
- on_connect: Initialize resources, log connection
|
|
184
|
+
- on_disconnect: Cleanup resources, update database
|
|
185
|
+
- on_error: Handle connection errors
|
|
186
|
+
|
|
187
|
+
**Example Implementation**:
|
|
188
|
+
```python
|
|
189
|
+
async def on_client_connected(client_id: str) -> None:
|
|
190
|
+
logger.info(f"Client {client_id} connected")
|
|
191
|
+
await db.mark_client_active(client_id)
|
|
192
|
+
|
|
193
|
+
async def on_client_disconnected(client_id: str) -> None:
|
|
194
|
+
logger.info(f"Client {client_id} disconnected")
|
|
195
|
+
await db.mark_client_inactive(client_id)
|
|
196
|
+
```
|
|
197
|
+
"""
|
|
198
|
+
async def __call__(self, client_id: str) -> None:
|
|
199
|
+
"""Handle connection lifecycle event."""
|
|
200
|
+
...
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class ErrorHandler(Protocol):
|
|
204
|
+
"""
|
|
205
|
+
Protocol for handling errors during streaming.
|
|
206
|
+
|
|
207
|
+
**Signature**:
|
|
208
|
+
async def(client_id: str, error: Exception) -> None
|
|
209
|
+
|
|
210
|
+
**Example Implementation**:
|
|
211
|
+
```python
|
|
212
|
+
async def handle_stream_error(client_id: str, error: Exception) -> None:
|
|
213
|
+
logger.error(f"Error for {client_id}: {error}")
|
|
214
|
+
|
|
215
|
+
if isinstance(error, asyncio.CancelledError):
|
|
216
|
+
# Normal cancellation, just log
|
|
217
|
+
pass
|
|
218
|
+
elif isinstance(error, grpc.RpcError):
|
|
219
|
+
# gRPC-specific error handling
|
|
220
|
+
await notify_admin(client_id, error)
|
|
221
|
+
else:
|
|
222
|
+
# Unexpected error, escalate
|
|
223
|
+
raise
|
|
224
|
+
```
|
|
225
|
+
"""
|
|
226
|
+
async def __call__(self, client_id: str, error: Exception) -> None:
|
|
227
|
+
"""Handle streaming error."""
|
|
228
|
+
...
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# ============================================================================
|
|
232
|
+
# Type Aliases for Common Patterns
|
|
233
|
+
# ============================================================================
|
|
234
|
+
|
|
235
|
+
MessageProcessorType = MessageProcessor[Any, Any]
|
|
236
|
+
"""Type alias for MessageProcessor without generic constraints."""
|
|
237
|
+
|
|
238
|
+
ClientIdExtractorType = ClientIdExtractor[Any]
|
|
239
|
+
"""Type alias for ClientIdExtractor without generic constraints."""
|
|
240
|
+
|
|
241
|
+
PingMessageCreatorType = PingMessageCreator[Any]
|
|
242
|
+
"""Type alias for PingMessageCreator without generic constraints."""
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# ============================================================================
|
|
246
|
+
# Type Guards and Validation
|
|
247
|
+
# ============================================================================
|
|
248
|
+
|
|
249
|
+
def is_valid_message_processor(func: Any) -> bool:
|
|
250
|
+
"""
|
|
251
|
+
Runtime check if function matches MessageProcessor protocol.
|
|
252
|
+
|
|
253
|
+
**Parameters**:
|
|
254
|
+
func: Function to validate
|
|
255
|
+
|
|
256
|
+
**Returns**:
|
|
257
|
+
True if function signature matches protocol
|
|
258
|
+
|
|
259
|
+
**Example**:
|
|
260
|
+
```python
|
|
261
|
+
async def my_processor(client_id: str, msg: Command, queue: Queue) -> None:
|
|
262
|
+
pass
|
|
263
|
+
|
|
264
|
+
assert is_valid_message_processor(my_processor) # ✅
|
|
265
|
+
```
|
|
266
|
+
"""
|
|
267
|
+
if not callable(func):
|
|
268
|
+
return False
|
|
269
|
+
|
|
270
|
+
import inspect
|
|
271
|
+
sig = inspect.signature(func)
|
|
272
|
+
params = list(sig.parameters.values())
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
len(params) == 3 and
|
|
276
|
+
params[0].annotation in (str, inspect.Parameter.empty) and
|
|
277
|
+
inspect.iscoroutinefunction(func)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def is_valid_client_id_extractor(func: Any) -> bool:
|
|
282
|
+
"""
|
|
283
|
+
Runtime check if function matches ClientIdExtractor protocol.
|
|
284
|
+
|
|
285
|
+
**Parameters**:
|
|
286
|
+
func: Function to validate
|
|
287
|
+
|
|
288
|
+
**Returns**:
|
|
289
|
+
True if function signature matches protocol
|
|
290
|
+
|
|
291
|
+
**Example**:
|
|
292
|
+
```python
|
|
293
|
+
def extract_id(msg: Command) -> str:
|
|
294
|
+
return msg.client_id
|
|
295
|
+
|
|
296
|
+
assert is_valid_client_id_extractor(extract_id) # ✅
|
|
297
|
+
```
|
|
298
|
+
"""
|
|
299
|
+
if not callable(func):
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
import inspect
|
|
303
|
+
sig = inspect.signature(func)
|
|
304
|
+
params = list(sig.parameters.values())
|
|
305
|
+
|
|
306
|
+
return (
|
|
307
|
+
len(params) == 1 and
|
|
308
|
+
sig.return_annotation in (str, inspect.Parameter.empty)
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def is_valid_ping_creator(func: Any) -> bool:
|
|
313
|
+
"""
|
|
314
|
+
Runtime check if function matches PingMessageCreator protocol.
|
|
315
|
+
|
|
316
|
+
**Parameters**:
|
|
317
|
+
func: Function to validate
|
|
318
|
+
|
|
319
|
+
**Returns**:
|
|
320
|
+
True if function signature matches protocol
|
|
321
|
+
|
|
322
|
+
**Example**:
|
|
323
|
+
```python
|
|
324
|
+
def create_ping() -> Message:
|
|
325
|
+
return Message(is_ping=True)
|
|
326
|
+
|
|
327
|
+
assert is_valid_ping_creator(create_ping) # ✅
|
|
328
|
+
```
|
|
329
|
+
"""
|
|
330
|
+
if not callable(func):
|
|
331
|
+
return False
|
|
332
|
+
|
|
333
|
+
import inspect
|
|
334
|
+
sig = inspect.signature(func)
|
|
335
|
+
params = list(sig.parameters.values())
|
|
336
|
+
|
|
337
|
+
return len(params) == 0
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
# ============================================================================
|
|
341
|
+
# Exports
|
|
342
|
+
# ============================================================================
|
|
343
|
+
|
|
344
|
+
__all__ = [
|
|
345
|
+
# Type variables
|
|
346
|
+
'TMessage',
|
|
347
|
+
'TCommand',
|
|
348
|
+
|
|
349
|
+
# Core protocols
|
|
350
|
+
'MessageProcessor',
|
|
351
|
+
'ClientIdExtractor',
|
|
352
|
+
'PingMessageCreator',
|
|
353
|
+
|
|
354
|
+
# Connection protocols
|
|
355
|
+
'ConnectionCallback',
|
|
356
|
+
'ErrorHandler',
|
|
357
|
+
|
|
358
|
+
# Type aliases
|
|
359
|
+
'MessageProcessorType',
|
|
360
|
+
'ClientIdExtractorType',
|
|
361
|
+
'PingMessageCreatorType',
|
|
362
|
+
|
|
363
|
+
# Validation
|
|
364
|
+
'is_valid_message_processor',
|
|
365
|
+
'is_valid_client_id_extractor',
|
|
366
|
+
'is_valid_ping_creator',
|
|
367
|
+
]
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Server Lifecycle Logging Utilities
|
|
2
|
+
|
|
3
|
+
Reusable functions for logging server startup and shutdown with timestamps and uptime tracking.
|
|
4
|
+
|
|
5
|
+
**Uses Rich** for beautiful output with panels, tables, and colors! 🎨
|
|
6
|
+
|
|
7
|
+
## 📦 Functions
|
|
8
|
+
|
|
9
|
+
### `log_server_start()`
|
|
10
|
+
|
|
11
|
+
Logs server startup with timestamp and configuration.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from django_cfg.apps.integrations.grpc.utils import log_server_start
|
|
15
|
+
|
|
16
|
+
start_time = log_server_start(
|
|
17
|
+
logger,
|
|
18
|
+
server_type="gRPC Server", # Server type
|
|
19
|
+
mode="Development", # Production/Development
|
|
20
|
+
hotreload_enabled=True, # Is hotreload enabled?
|
|
21
|
+
host="0.0.0.0", # Additional parameters
|
|
22
|
+
port=50051
|
|
23
|
+
)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Output (Rich Panel):**
|
|
27
|
+
```
|
|
28
|
+
╭────────────── 🚀 gRPC Server Starting ───────────────╮
|
|
29
|
+
│ │
|
|
30
|
+
│ ⏰ Started at 2025-11-05 14:30:15 │
|
|
31
|
+
│ Mode Development │
|
|
32
|
+
│ Hotreload Enabled ⚡ │
|
|
33
|
+
│ Host 0.0.0.0 │
|
|
34
|
+
│ Port 50051 │
|
|
35
|
+
│ │
|
|
36
|
+
╰──────────────────────────────────────────────────────╯
|
|
37
|
+
⚠️ Hotreload active - connections may be dropped on code changes
|
|
38
|
+
```
|
|
39
|
+
(with green border and colors!)
|
|
40
|
+
|
|
41
|
+
**Returns:** `datetime` object of start time (for use in `log_server_shutdown`)
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### `log_server_shutdown()`
|
|
46
|
+
|
|
47
|
+
Logs server shutdown with uptime calculation.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from django_cfg.apps.integrations.grpc.utils import log_server_shutdown
|
|
51
|
+
|
|
52
|
+
log_server_shutdown(
|
|
53
|
+
logger,
|
|
54
|
+
start_time, # datetime from log_server_start()
|
|
55
|
+
server_type="gRPC Server",
|
|
56
|
+
reason="Hotreload triggered", # Shutdown reason
|
|
57
|
+
active_connections=5 # Additional parameters
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Output (Rich Panel):**
|
|
62
|
+
```
|
|
63
|
+
╭───────────── 🧹 Shutting down gRPC Server ──────────────╮
|
|
64
|
+
│ │
|
|
65
|
+
│ 📋 Reason Hotreload triggered │
|
|
66
|
+
│ ⏱️ Uptime 0h 2m 35s │
|
|
67
|
+
│ 🕐 Stopped at 2025-11-05 14:32:50 │
|
|
68
|
+
│ Active Connections 5 │
|
|
69
|
+
│ │
|
|
70
|
+
╰──────────────────────────────────────────────────────────╯
|
|
71
|
+
✅ Server shutdown complete
|
|
72
|
+
```
|
|
73
|
+
(with red border and colors!)
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 🎯 Complete Usage Example
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from django_cfg.apps.integrations.grpc.utils import (
|
|
81
|
+
setup_streaming_logger,
|
|
82
|
+
log_server_start,
|
|
83
|
+
log_server_shutdown,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Create logger
|
|
87
|
+
logger = setup_streaming_logger('my_server')
|
|
88
|
+
|
|
89
|
+
# Log server start
|
|
90
|
+
start_time = log_server_start(
|
|
91
|
+
logger,
|
|
92
|
+
server_type="WebSocket Server",
|
|
93
|
+
mode="Production",
|
|
94
|
+
hotreload_enabled=False,
|
|
95
|
+
host="0.0.0.0",
|
|
96
|
+
port=8000
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
# Server work...
|
|
101
|
+
await server.serve_forever()
|
|
102
|
+
shutdown_reason = "Normal termination"
|
|
103
|
+
except KeyboardInterrupt:
|
|
104
|
+
shutdown_reason = "Keyboard interrupt"
|
|
105
|
+
finally:
|
|
106
|
+
# Log server shutdown
|
|
107
|
+
log_server_shutdown(
|
|
108
|
+
logger,
|
|
109
|
+
start_time,
|
|
110
|
+
server_type="WebSocket Server",
|
|
111
|
+
reason=shutdown_reason,
|
|
112
|
+
total_connections=1250
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 📊 Used in:
|
|
119
|
+
|
|
120
|
+
- ✅ `rungrpc` - gRPC server command
|
|
121
|
+
- ✅ Can be used for any servers (WebSocket, HTTP, etc.)
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 🔧 Parameters
|
|
126
|
+
|
|
127
|
+
### `log_server_start()`
|
|
128
|
+
|
|
129
|
+
| Parameter | Type | Required | Description |
|
|
130
|
+
|----------|-----|----------|-------------|
|
|
131
|
+
| `logger` | `logging.Logger` | ✅ Yes | Logger instance |
|
|
132
|
+
| `server_type` | `str` | ❌ No | Server type (default: "Server") |
|
|
133
|
+
| `mode` | `str` | ❌ No | Running mode (default: "Development") |
|
|
134
|
+
| `hotreload_enabled` | `bool` | ❌ No | Is hotreload enabled (default: False) |
|
|
135
|
+
| `use_rich` | `bool` | ❌ No | Use Rich for output (default: True) |
|
|
136
|
+
| `**extra_info` | `dict` | ❌ No | Additional key-value pairs to log |
|
|
137
|
+
|
|
138
|
+
### `log_server_shutdown()`
|
|
139
|
+
|
|
140
|
+
| Parameter | Type | Required | Description |
|
|
141
|
+
|----------|-----|----------|-------------|
|
|
142
|
+
| `logger` | `logging.Logger` | ✅ Yes | Logger instance |
|
|
143
|
+
| `start_time` | `datetime` | ✅ Yes | Start time from `log_server_start()` |
|
|
144
|
+
| `server_type` | `str` | ❌ No | Server type (default: "Server") |
|
|
145
|
+
| `reason` | `str` | ❌ No | Shutdown reason |
|
|
146
|
+
| `use_rich` | `bool` | ❌ No | Use Rich for output (default: True) |
|
|
147
|
+
| `**extra_info` | `dict` | ❌ No | Additional key-value pairs to log |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 💡 Benefits
|
|
152
|
+
|
|
153
|
+
1. ✅ **DRY** - single code for all servers
|
|
154
|
+
2. ✅ **Consistency** - uniform log format
|
|
155
|
+
3. ✅ **Rich UI** - beautiful panels, tables, colors 🎨
|
|
156
|
+
4. ✅ **Uptime tracking** - automatic time calculation
|
|
157
|
+
5. ✅ **Flexible** - can add arbitrary parameters via `**extra_info`
|
|
158
|
+
6. ✅ **Hotreload aware** - special warning for hotreload mode
|
|
159
|
+
7. ✅ **Fallback** - works without Rich (if `use_rich=False`)
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
**Author:** django-cfg team
|
|
164
|
+
**Date:** 2025-11-05
|
|
@@ -2,8 +2,65 @@
|
|
|
2
2
|
Utilities for gRPC Integration.
|
|
3
3
|
|
|
4
4
|
Reusable utilities for gRPC services in django-cfg.
|
|
5
|
+
|
|
6
|
+
**Available Modules**:
|
|
7
|
+
- streaming_logger: Rich logging for gRPC streams
|
|
8
|
+
- converters: Protobuf ↔ Python conversions (Pydantic configured)
|
|
9
|
+
- handlers: gRPC handler factory utilities
|
|
10
|
+
|
|
11
|
+
**Quick Imports**:
|
|
12
|
+
```python
|
|
13
|
+
from django_cfg.apps.integrations.grpc.utils import (
|
|
14
|
+
# Logging
|
|
15
|
+
setup_streaming_logger,
|
|
16
|
+
get_streaming_logger,
|
|
17
|
+
|
|
18
|
+
# Converters
|
|
19
|
+
ProtobufConverterMixin,
|
|
20
|
+
ConverterConfig,
|
|
21
|
+
|
|
22
|
+
# Handlers
|
|
23
|
+
create_grpc_handler,
|
|
24
|
+
)
|
|
25
|
+
```
|
|
5
26
|
"""
|
|
6
27
|
|
|
7
28
|
from .streaming_logger import setup_streaming_logger, get_streaming_logger
|
|
29
|
+
from .converters import (
|
|
30
|
+
ConverterConfig,
|
|
31
|
+
ProtobufConverterMixin,
|
|
32
|
+
datetime_to_timestamp,
|
|
33
|
+
timestamp_to_datetime,
|
|
34
|
+
dict_to_struct,
|
|
35
|
+
struct_to_dict,
|
|
36
|
+
)
|
|
37
|
+
from .handlers import (
|
|
38
|
+
create_grpc_handler,
|
|
39
|
+
create_multiple_grpc_handlers,
|
|
40
|
+
validate_grpc_handler,
|
|
41
|
+
validate_grpc_handlers,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
# Logging
|
|
46
|
+
"setup_streaming_logger",
|
|
47
|
+
"get_streaming_logger",
|
|
48
|
+
|
|
49
|
+
# Converters - Config
|
|
50
|
+
"ConverterConfig",
|
|
51
|
+
|
|
52
|
+
# Converters - Mixin
|
|
53
|
+
"ProtobufConverterMixin",
|
|
54
|
+
|
|
55
|
+
# Converters - Standalone functions
|
|
56
|
+
"datetime_to_timestamp",
|
|
57
|
+
"timestamp_to_datetime",
|
|
58
|
+
"dict_to_struct",
|
|
59
|
+
"struct_to_dict",
|
|
8
60
|
|
|
9
|
-
|
|
61
|
+
# Handlers
|
|
62
|
+
"create_grpc_handler",
|
|
63
|
+
"create_multiple_grpc_handlers",
|
|
64
|
+
"validate_grpc_handler",
|
|
65
|
+
"validate_grpc_handlers",
|
|
66
|
+
]
|