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,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Universal bidirectional streaming components for gRPC.
|
|
3
|
+
|
|
4
|
+
This package provides generic, type-safe components for implementing
|
|
5
|
+
bidirectional gRPC streaming services.
|
|
6
|
+
|
|
7
|
+
**Components**:
|
|
8
|
+
- types: Protocol definitions for type-safe callbacks
|
|
9
|
+
- config: Pydantic v2 configuration models
|
|
10
|
+
- service: BidirectionalStreamingService implementation
|
|
11
|
+
|
|
12
|
+
**Usage Example**:
|
|
13
|
+
```python
|
|
14
|
+
from django_cfg.apps.integrations.grpc.services.streaming import (
|
|
15
|
+
BidirectionalStreamingService,
|
|
16
|
+
BidirectionalStreamingConfig,
|
|
17
|
+
ConfigPresets,
|
|
18
|
+
MessageProcessor,
|
|
19
|
+
ClientIdExtractor,
|
|
20
|
+
PingMessageCreator,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Use preset config
|
|
24
|
+
service = BidirectionalStreamingService(
|
|
25
|
+
config=ConfigPresets.PRODUCTION,
|
|
26
|
+
message_processor=my_processor,
|
|
27
|
+
client_id_extractor=extract_id,
|
|
28
|
+
ping_message_creator=create_ping,
|
|
29
|
+
)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Created: 2025-11-07
|
|
33
|
+
Status: %%PRODUCTION%%
|
|
34
|
+
Phase: Phase 1 - Universal Components
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Type definitions
|
|
38
|
+
from .types import (
|
|
39
|
+
# Type variables
|
|
40
|
+
TMessage,
|
|
41
|
+
TCommand,
|
|
42
|
+
|
|
43
|
+
# Core protocols
|
|
44
|
+
MessageProcessor,
|
|
45
|
+
ClientIdExtractor,
|
|
46
|
+
PingMessageCreator,
|
|
47
|
+
|
|
48
|
+
# Connection protocols
|
|
49
|
+
ConnectionCallback,
|
|
50
|
+
ErrorHandler,
|
|
51
|
+
|
|
52
|
+
# Type aliases
|
|
53
|
+
MessageProcessorType,
|
|
54
|
+
ClientIdExtractorType,
|
|
55
|
+
PingMessageCreatorType,
|
|
56
|
+
|
|
57
|
+
# Validation
|
|
58
|
+
is_valid_message_processor,
|
|
59
|
+
is_valid_client_id_extractor,
|
|
60
|
+
is_valid_ping_creator,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Configuration
|
|
64
|
+
from .config import (
|
|
65
|
+
# Enums
|
|
66
|
+
StreamingMode,
|
|
67
|
+
PingStrategy,
|
|
68
|
+
|
|
69
|
+
# Models
|
|
70
|
+
BidirectionalStreamingConfig,
|
|
71
|
+
|
|
72
|
+
# Presets
|
|
73
|
+
ConfigPresets,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Service - lazy import to avoid grpc dependency
|
|
77
|
+
def __getattr__(name):
|
|
78
|
+
"""Lazy import BidirectionalStreamingService to avoid grpc dependency."""
|
|
79
|
+
if name == 'BidirectionalStreamingService':
|
|
80
|
+
from .service import BidirectionalStreamingService
|
|
81
|
+
return BidirectionalStreamingService
|
|
82
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
__all__ = [
|
|
86
|
+
# Type variables
|
|
87
|
+
'TMessage',
|
|
88
|
+
'TCommand',
|
|
89
|
+
|
|
90
|
+
# Protocols
|
|
91
|
+
'MessageProcessor',
|
|
92
|
+
'ClientIdExtractor',
|
|
93
|
+
'PingMessageCreator',
|
|
94
|
+
'ConnectionCallback',
|
|
95
|
+
'ErrorHandler',
|
|
96
|
+
|
|
97
|
+
# Type aliases
|
|
98
|
+
'MessageProcessorType',
|
|
99
|
+
'ClientIdExtractorType',
|
|
100
|
+
'PingMessageCreatorType',
|
|
101
|
+
|
|
102
|
+
# Validation functions
|
|
103
|
+
'is_valid_message_processor',
|
|
104
|
+
'is_valid_client_id_extractor',
|
|
105
|
+
'is_valid_ping_creator',
|
|
106
|
+
|
|
107
|
+
# Enums
|
|
108
|
+
'StreamingMode',
|
|
109
|
+
'PingStrategy',
|
|
110
|
+
|
|
111
|
+
# Configuration
|
|
112
|
+
'BidirectionalStreamingConfig',
|
|
113
|
+
'ConfigPresets',
|
|
114
|
+
|
|
115
|
+
# Service
|
|
116
|
+
'BidirectionalStreamingService',
|
|
117
|
+
]
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pydantic v2 configuration models for gRPC bidirectional streaming.
|
|
3
|
+
|
|
4
|
+
This module provides type-safe, validated configuration for bidirectional streaming services.
|
|
5
|
+
All models are frozen and use strict validation to prevent runtime errors.
|
|
6
|
+
|
|
7
|
+
**Design Principles**:
|
|
8
|
+
- 100% Pydantic v2 (no raw dicts)
|
|
9
|
+
- Frozen models (immutable)
|
|
10
|
+
- Strict validation with Field constraints
|
|
11
|
+
- extra='forbid' to catch typos
|
|
12
|
+
- Comprehensive documentation
|
|
13
|
+
|
|
14
|
+
**Usage Example**:
|
|
15
|
+
```python
|
|
16
|
+
config = BidirectionalStreamingConfig(
|
|
17
|
+
ping_interval=5.0,
|
|
18
|
+
ping_timeout=30.0,
|
|
19
|
+
enable_sleep_zero=True,
|
|
20
|
+
max_queue_size=1000
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
service = BidirectionalStreamingService(
|
|
24
|
+
config=config,
|
|
25
|
+
message_processor=process_messages,
|
|
26
|
+
...
|
|
27
|
+
)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Created: 2025-11-07
|
|
31
|
+
Status: %%PRODUCTION%%
|
|
32
|
+
Phase: Phase 1 - Universal Components
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from enum import Enum
|
|
36
|
+
from typing import Optional, Any
|
|
37
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ============================================================================
|
|
41
|
+
# Enumerations
|
|
42
|
+
# ============================================================================
|
|
43
|
+
|
|
44
|
+
class StreamingMode(str, Enum):
|
|
45
|
+
"""
|
|
46
|
+
Streaming iteration modes for handling bidirectional streams.
|
|
47
|
+
|
|
48
|
+
**Modes**:
|
|
49
|
+
ASYNC_FOR: Use `async for message in stream` (simpler, automatic iteration)
|
|
50
|
+
ANEXT: Use `await anext(stream)` (manual control, better error handling)
|
|
51
|
+
|
|
52
|
+
**Comparison**:
|
|
53
|
+
|
|
54
|
+
| Feature | ASYNC_FOR | ANEXT |
|
|
55
|
+
|-----------------------|-----------|-------|
|
|
56
|
+
| Simplicity | ✅ Simple | ⚠️ Manual |
|
|
57
|
+
| Error Control | ⚠️ Limited | ✅ Full |
|
|
58
|
+
| Timeout Control | ⚠️ Limited | ✅ Full |
|
|
59
|
+
| Early Exit | ⚠️ Limited | ✅ Easy |
|
|
60
|
+
|
|
61
|
+
**Usage**:
|
|
62
|
+
```python
|
|
63
|
+
# ASYNC_FOR mode
|
|
64
|
+
config = BidirectionalStreamingConfig(streaming_mode=StreamingMode.ASYNC_FOR)
|
|
65
|
+
# Implementation: async for message in stream: ...
|
|
66
|
+
|
|
67
|
+
# ANEXT mode
|
|
68
|
+
config = BidirectionalStreamingConfig(streaming_mode=StreamingMode.ANEXT)
|
|
69
|
+
# Implementation: message = await anext(stream)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Recommendation**:
|
|
73
|
+
- Use ASYNC_FOR for simple services
|
|
74
|
+
- Use ANEXT for services requiring fine-grained control
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
ASYNC_FOR = "async_for"
|
|
78
|
+
"""Use async for iteration (automatic, simpler)"""
|
|
79
|
+
|
|
80
|
+
ANEXT = "anext"
|
|
81
|
+
"""Use anext() calls (manual, more control)"""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class PingStrategy(str, Enum):
|
|
85
|
+
"""
|
|
86
|
+
Strategies for sending ping/keepalive messages.
|
|
87
|
+
|
|
88
|
+
**Strategies**:
|
|
89
|
+
INTERVAL: Send ping every N seconds (time-based)
|
|
90
|
+
ON_IDLE: Send ping only when no messages sent recently (activity-based)
|
|
91
|
+
DISABLED: Don't send automatic pings (manual control)
|
|
92
|
+
|
|
93
|
+
**Comparison**:
|
|
94
|
+
|
|
95
|
+
| Strategy | Network Usage | Responsiveness | Use Case |
|
|
96
|
+
|-----------|---------------|----------------|----------|
|
|
97
|
+
| INTERVAL | ⚠️ Higher | ✅ Predictable | Critical services |
|
|
98
|
+
| ON_IDLE | ✅ Lower | ⚠️ Variable | Normal services |
|
|
99
|
+
| DISABLED | ✅ Minimal | ❌ Manual | Testing/debug |
|
|
100
|
+
|
|
101
|
+
**Usage**:
|
|
102
|
+
```python
|
|
103
|
+
# Send ping every 5 seconds
|
|
104
|
+
config = BidirectionalStreamingConfig(
|
|
105
|
+
ping_strategy=PingStrategy.INTERVAL,
|
|
106
|
+
ping_interval=5.0
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Send ping only after 10 seconds of inactivity
|
|
110
|
+
config = BidirectionalStreamingConfig(
|
|
111
|
+
ping_strategy=PingStrategy.ON_IDLE,
|
|
112
|
+
ping_interval=10.0
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# No automatic pings
|
|
116
|
+
config = BidirectionalStreamingConfig(
|
|
117
|
+
ping_strategy=PingStrategy.DISABLED
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
INTERVAL = "interval"
|
|
123
|
+
"""Send ping every N seconds regardless of activity"""
|
|
124
|
+
|
|
125
|
+
ON_IDLE = "on_idle"
|
|
126
|
+
"""Send ping only after N seconds of inactivity"""
|
|
127
|
+
|
|
128
|
+
DISABLED = "disabled"
|
|
129
|
+
"""Don't send automatic pings"""
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ============================================================================
|
|
133
|
+
# Configuration Models
|
|
134
|
+
# ============================================================================
|
|
135
|
+
|
|
136
|
+
class BidirectionalStreamingConfig(BaseModel):
|
|
137
|
+
"""
|
|
138
|
+
Configuration for bidirectional gRPC streaming services.
|
|
139
|
+
|
|
140
|
+
This model provides type-safe configuration with validation for all streaming parameters.
|
|
141
|
+
All fields have sensible defaults based on production experience.
|
|
142
|
+
|
|
143
|
+
**Core Parameters**:
|
|
144
|
+
streaming_mode: How to iterate over input stream (ASYNC_FOR vs ANEXT)
|
|
145
|
+
ping_strategy: When to send keepalive pings (INTERVAL vs ON_IDLE)
|
|
146
|
+
ping_interval: Seconds between pings (must be > 0)
|
|
147
|
+
ping_timeout: Max seconds to wait for ping response (must be >= ping_interval)
|
|
148
|
+
|
|
149
|
+
**Queue Parameters**:
|
|
150
|
+
max_queue_size: Max items in output queue before blocking (must be > 0)
|
|
151
|
+
queue_timeout: Max seconds to wait when enqueuing to full queue
|
|
152
|
+
|
|
153
|
+
**Advanced Parameters**:
|
|
154
|
+
enable_sleep_zero: Enable `await asyncio.sleep(0)` for event loop yielding
|
|
155
|
+
enable_centrifugo: Auto-publish to Centrifugo WebSocket channels
|
|
156
|
+
enable_logging: Enable structured logging for stream events
|
|
157
|
+
|
|
158
|
+
**Validation Rules**:
|
|
159
|
+
- ping_timeout >= ping_interval (can't timeout before ping)
|
|
160
|
+
- ping_interval > 0 (must be positive)
|
|
161
|
+
- max_queue_size > 0 (must have capacity)
|
|
162
|
+
- All timeouts must be positive
|
|
163
|
+
|
|
164
|
+
**Example - Production Config**:
|
|
165
|
+
```python
|
|
166
|
+
config = BidirectionalStreamingConfig(
|
|
167
|
+
streaming_mode=StreamingMode.ANEXT,
|
|
168
|
+
ping_strategy=PingStrategy.INTERVAL,
|
|
169
|
+
ping_interval=5.0,
|
|
170
|
+
ping_timeout=30.0,
|
|
171
|
+
max_queue_size=1000,
|
|
172
|
+
enable_sleep_zero=True,
|
|
173
|
+
enable_centrifugo=True,
|
|
174
|
+
enable_logging=True
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Example - Development Config**:
|
|
179
|
+
```python
|
|
180
|
+
config = BidirectionalStreamingConfig(
|
|
181
|
+
streaming_mode=StreamingMode.ASYNC_FOR,
|
|
182
|
+
ping_strategy=PingStrategy.ON_IDLE,
|
|
183
|
+
ping_interval=10.0,
|
|
184
|
+
enable_centrifugo=False,
|
|
185
|
+
enable_logging=True
|
|
186
|
+
)
|
|
187
|
+
```
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
# ------------------------------------------------------------------------
|
|
191
|
+
# Streaming Parameters
|
|
192
|
+
# ------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
streaming_mode: StreamingMode = Field(
|
|
195
|
+
default=StreamingMode.ANEXT,
|
|
196
|
+
description="How to iterate over input stream (ASYNC_FOR vs ANEXT)",
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# ------------------------------------------------------------------------
|
|
200
|
+
# Ping/Keepalive Parameters
|
|
201
|
+
# ------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
ping_strategy: PingStrategy = Field(
|
|
204
|
+
default=PingStrategy.INTERVAL,
|
|
205
|
+
description="When to send keepalive pings (INTERVAL vs ON_IDLE vs DISABLED)",
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
ping_interval: float = Field(
|
|
209
|
+
default=5.0,
|
|
210
|
+
gt=0.0,
|
|
211
|
+
le=300.0, # Max 5 minutes
|
|
212
|
+
description="Seconds between pings (must be > 0, max 300)",
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
ping_timeout: Optional[float] = Field(
|
|
216
|
+
default=None,
|
|
217
|
+
gt=0.0,
|
|
218
|
+
le=600.0, # Max 10 minutes
|
|
219
|
+
description="Max seconds to wait for ping response (None = 6x ping_interval)",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# ------------------------------------------------------------------------
|
|
223
|
+
# Queue Parameters
|
|
224
|
+
# ------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
max_queue_size: int = Field(
|
|
227
|
+
default=1000,
|
|
228
|
+
gt=0,
|
|
229
|
+
le=100000, # Max 100k items
|
|
230
|
+
description="Max items in output queue before blocking (must be > 0)",
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
queue_timeout: Optional[float] = Field(
|
|
234
|
+
default=10.0,
|
|
235
|
+
gt=0.0,
|
|
236
|
+
le=60.0, # Max 1 minute
|
|
237
|
+
description="Max seconds to wait when enqueuing to full queue (None = no timeout)",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# ------------------------------------------------------------------------
|
|
241
|
+
# Advanced Parameters
|
|
242
|
+
# ------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
enable_sleep_zero: bool = Field(
|
|
245
|
+
default=True,
|
|
246
|
+
description="Enable `await asyncio.sleep(0)` for event loop yielding (CRITICAL for responsiveness)",
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
enable_centrifugo: bool = Field(
|
|
250
|
+
default=True,
|
|
251
|
+
description="Auto-publish messages to Centrifugo WebSocket channels",
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
enable_logging: bool = Field(
|
|
255
|
+
default=True,
|
|
256
|
+
description="Enable structured logging for stream events",
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
logger_name: Optional[str] = Field(
|
|
260
|
+
default=None,
|
|
261
|
+
min_length=1,
|
|
262
|
+
max_length=100,
|
|
263
|
+
description="Logger name for auto-created logger (None = 'grpc_streaming')",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# ------------------------------------------------------------------------
|
|
267
|
+
# Connection Management
|
|
268
|
+
# ------------------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
connection_timeout: Optional[float] = Field(
|
|
271
|
+
default=None,
|
|
272
|
+
gt=0.0,
|
|
273
|
+
le=3600.0, # Max 1 hour
|
|
274
|
+
description="Max seconds for entire connection (None = unlimited)",
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
max_consecutive_errors: int = Field(
|
|
278
|
+
default=3,
|
|
279
|
+
ge=0,
|
|
280
|
+
le=100,
|
|
281
|
+
description="Max consecutive errors before disconnecting (0 = unlimited)",
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# ------------------------------------------------------------------------
|
|
285
|
+
# Validators
|
|
286
|
+
# ------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
@field_validator('ping_timeout')
|
|
289
|
+
@classmethod
|
|
290
|
+
def set_default_ping_timeout(cls, v: Optional[float], info) -> float:
|
|
291
|
+
"""
|
|
292
|
+
Set ping_timeout to 6x ping_interval if not provided.
|
|
293
|
+
|
|
294
|
+
This ensures reasonable timeout with safety margin.
|
|
295
|
+
"""
|
|
296
|
+
if v is None:
|
|
297
|
+
ping_interval = info.data.get('ping_interval', 5.0)
|
|
298
|
+
return ping_interval * 6.0
|
|
299
|
+
return v
|
|
300
|
+
|
|
301
|
+
@model_validator(mode='after')
|
|
302
|
+
def validate_timeout_relationship(self) -> 'BidirectionalStreamingConfig':
|
|
303
|
+
"""
|
|
304
|
+
Ensure ping_timeout >= ping_interval.
|
|
305
|
+
|
|
306
|
+
Can't timeout before next ping is due.
|
|
307
|
+
"""
|
|
308
|
+
# ping_timeout is set by field_validator, so it should never be None here
|
|
309
|
+
if self.ping_timeout is not None and self.ping_timeout < self.ping_interval:
|
|
310
|
+
raise ValueError(
|
|
311
|
+
f"ping_timeout ({self.ping_timeout}s) must be >= ping_interval ({self.ping_interval}s)"
|
|
312
|
+
)
|
|
313
|
+
return self
|
|
314
|
+
|
|
315
|
+
@model_validator(mode='after')
|
|
316
|
+
def validate_ping_strategy_requirements(self) -> 'BidirectionalStreamingConfig':
|
|
317
|
+
"""
|
|
318
|
+
Ensure ping_interval is meaningful when ping_strategy is not DISABLED.
|
|
319
|
+
"""
|
|
320
|
+
if self.ping_strategy != PingStrategy.DISABLED:
|
|
321
|
+
if self.ping_interval > 60.0:
|
|
322
|
+
raise ValueError(
|
|
323
|
+
f"ping_interval ({self.ping_interval}s) should be <= 60s for {self.ping_strategy.value} strategy"
|
|
324
|
+
)
|
|
325
|
+
return self
|
|
326
|
+
|
|
327
|
+
# ------------------------------------------------------------------------
|
|
328
|
+
# Model Configuration
|
|
329
|
+
# ------------------------------------------------------------------------
|
|
330
|
+
|
|
331
|
+
model_config = {
|
|
332
|
+
'extra': 'forbid', # Reject unknown fields (catch typos)
|
|
333
|
+
'frozen': True, # Immutable (thread-safe)
|
|
334
|
+
'validate_assignment': True, # Validate on attribute assignment
|
|
335
|
+
'str_strip_whitespace': True, # Strip strings
|
|
336
|
+
'use_enum_values': False, # Keep enum objects (not values)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
# ------------------------------------------------------------------------
|
|
340
|
+
# Computed Properties
|
|
341
|
+
# ------------------------------------------------------------------------
|
|
342
|
+
|
|
343
|
+
def is_ping_enabled(self) -> bool:
|
|
344
|
+
"""Check if ping is enabled."""
|
|
345
|
+
return self.ping_strategy != PingStrategy.DISABLED
|
|
346
|
+
|
|
347
|
+
def should_yield_event_loop(self) -> bool:
|
|
348
|
+
"""Check if should call await asyncio.sleep(0)."""
|
|
349
|
+
return self.enable_sleep_zero
|
|
350
|
+
|
|
351
|
+
def get_effective_ping_timeout(self) -> float:
|
|
352
|
+
"""Get ping_timeout with fallback to 6x ping_interval."""
|
|
353
|
+
return self.ping_timeout if self.ping_timeout is not None else self.ping_interval * 6.0
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
# ============================================================================
|
|
357
|
+
# Preset Configurations
|
|
358
|
+
# ============================================================================
|
|
359
|
+
|
|
360
|
+
class ConfigPresets:
|
|
361
|
+
"""
|
|
362
|
+
Predefined configurations for common use cases.
|
|
363
|
+
|
|
364
|
+
**Available Presets**:
|
|
365
|
+
- PRODUCTION: High-reliability config for production
|
|
366
|
+
- DEVELOPMENT: Relaxed config for development
|
|
367
|
+
- TESTING: Minimal config for unit tests
|
|
368
|
+
- HIGH_THROUGHPUT: Optimized for high message volume
|
|
369
|
+
- LOW_LATENCY: Optimized for responsiveness
|
|
370
|
+
"""
|
|
371
|
+
|
|
372
|
+
PRODUCTION = BidirectionalStreamingConfig(
|
|
373
|
+
streaming_mode=StreamingMode.ANEXT,
|
|
374
|
+
ping_strategy=PingStrategy.INTERVAL,
|
|
375
|
+
ping_interval=5.0,
|
|
376
|
+
ping_timeout=30.0,
|
|
377
|
+
max_queue_size=1000,
|
|
378
|
+
enable_sleep_zero=True,
|
|
379
|
+
enable_centrifugo=True,
|
|
380
|
+
enable_logging=True,
|
|
381
|
+
max_consecutive_errors=3,
|
|
382
|
+
)
|
|
383
|
+
"""Production config: 5s pings, 30s timeout, full logging."""
|
|
384
|
+
|
|
385
|
+
DEVELOPMENT = BidirectionalStreamingConfig(
|
|
386
|
+
streaming_mode=StreamingMode.ASYNC_FOR,
|
|
387
|
+
ping_strategy=PingStrategy.ON_IDLE,
|
|
388
|
+
ping_interval=10.0,
|
|
389
|
+
ping_timeout=60.0,
|
|
390
|
+
max_queue_size=100,
|
|
391
|
+
enable_sleep_zero=True,
|
|
392
|
+
enable_centrifugo=False,
|
|
393
|
+
enable_logging=True,
|
|
394
|
+
max_consecutive_errors=10,
|
|
395
|
+
)
|
|
396
|
+
"""Development config: Relaxed timeouts, no Centrifugo."""
|
|
397
|
+
|
|
398
|
+
TESTING = BidirectionalStreamingConfig(
|
|
399
|
+
streaming_mode=StreamingMode.ASYNC_FOR,
|
|
400
|
+
ping_strategy=PingStrategy.DISABLED,
|
|
401
|
+
ping_interval=1.0,
|
|
402
|
+
max_queue_size=10,
|
|
403
|
+
enable_sleep_zero=False,
|
|
404
|
+
enable_centrifugo=False,
|
|
405
|
+
enable_logging=False,
|
|
406
|
+
max_consecutive_errors=0,
|
|
407
|
+
)
|
|
408
|
+
"""Testing config: No pings, minimal queues, no logging."""
|
|
409
|
+
|
|
410
|
+
HIGH_THROUGHPUT = BidirectionalStreamingConfig(
|
|
411
|
+
streaming_mode=StreamingMode.ASYNC_FOR,
|
|
412
|
+
ping_strategy=PingStrategy.ON_IDLE,
|
|
413
|
+
ping_interval=30.0,
|
|
414
|
+
ping_timeout=180.0,
|
|
415
|
+
max_queue_size=10000,
|
|
416
|
+
enable_sleep_zero=True,
|
|
417
|
+
enable_centrifugo=True,
|
|
418
|
+
enable_logging=False, # Reduce overhead
|
|
419
|
+
max_consecutive_errors=10,
|
|
420
|
+
)
|
|
421
|
+
"""High throughput: Large queues, infrequent pings, no logging."""
|
|
422
|
+
|
|
423
|
+
LOW_LATENCY = BidirectionalStreamingConfig(
|
|
424
|
+
streaming_mode=StreamingMode.ANEXT,
|
|
425
|
+
ping_strategy=PingStrategy.INTERVAL,
|
|
426
|
+
ping_interval=1.0,
|
|
427
|
+
ping_timeout=5.0,
|
|
428
|
+
max_queue_size=100,
|
|
429
|
+
enable_sleep_zero=True,
|
|
430
|
+
enable_centrifugo=True,
|
|
431
|
+
enable_logging=True,
|
|
432
|
+
max_consecutive_errors=3,
|
|
433
|
+
)
|
|
434
|
+
"""Low latency: Frequent pings, small queues, immediate responsiveness."""
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# ============================================================================
|
|
438
|
+
# Exports
|
|
439
|
+
# ============================================================================
|
|
440
|
+
|
|
441
|
+
__all__ = [
|
|
442
|
+
# Enums
|
|
443
|
+
'StreamingMode',
|
|
444
|
+
'PingStrategy',
|
|
445
|
+
|
|
446
|
+
# Models
|
|
447
|
+
'BidirectionalStreamingConfig',
|
|
448
|
+
|
|
449
|
+
# Presets
|
|
450
|
+
'ConfigPresets',
|
|
451
|
+
]
|