django-cfg 1.5.14__py3-none-any.whl → 1.5.20__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/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/direct_client.py +282 -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/testing_api.py +0 -79
- django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
- django_cfg/apps/integrations/centrifugo/views/wrapper.py +257 -0
- django_cfg/apps/integrations/grpc/centrifugo/__init__.py +29 -0
- django_cfg/apps/integrations/grpc/centrifugo/bridge.py +277 -0
- django_cfg/apps/integrations/grpc/centrifugo/config.py +167 -0
- django_cfg/apps/integrations/grpc/centrifugo/demo.py +626 -0
- django_cfg/apps/integrations/grpc/centrifugo/test_publish.py +229 -0
- django_cfg/apps/integrations/grpc/centrifugo/transformers.py +89 -0
- django_cfg/apps/integrations/grpc/interceptors/__init__.py +3 -1
- django_cfg/apps/integrations/grpc/interceptors/centrifugo.py +541 -0
- django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +55 -0
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +311 -7
- 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/services/discovery.py +7 -1
- django_cfg/apps/integrations/grpc/utils/SERVER_LOGGING.md +164 -0
- django_cfg/apps/integrations/grpc/utils/streaming_logger.py +206 -5
- 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/builders/security_builder.py +1 -0
- django_cfg/core/generation/integration_generators/api.py +2 -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/ir/schema.py +15 -1
- django_cfg/modules/django_client/core/parser/base.py +12 -0
- django_cfg/pyproject.toml +1 -1
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/METADATA +1 -1
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/RECORD +53 -37
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test script to verify Centrifugo event publishing.
|
|
3
|
+
|
|
4
|
+
Run this to test that events are being published to Centrifugo channels.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
# From Django shell:
|
|
8
|
+
>>> from django_cfg.apps.integrations.grpc.centrifugo.test_publish import run_test
|
|
9
|
+
>>> await run_test()
|
|
10
|
+
|
|
11
|
+
# Or from async context:
|
|
12
|
+
>>> import asyncio
|
|
13
|
+
>>> from django_cfg.apps.integrations.grpc.centrifugo.test_publish import run_test
|
|
14
|
+
>>> asyncio.run(run_test())
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def run_test(verbose: bool = True):
|
|
24
|
+
"""
|
|
25
|
+
Run complete Centrifugo integration test.
|
|
26
|
+
|
|
27
|
+
Tests:
|
|
28
|
+
1. Centrifugo client initialization
|
|
29
|
+
2. Interceptor simulation (RPC metadata)
|
|
30
|
+
3. Mixin demo (message data)
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
verbose: Show detailed output
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
dict: Test results with success/failure counts
|
|
37
|
+
"""
|
|
38
|
+
if verbose:
|
|
39
|
+
# Set logging to DEBUG to see all details
|
|
40
|
+
logging.basicConfig(
|
|
41
|
+
level=logging.DEBUG,
|
|
42
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
logger.info("=" * 70)
|
|
46
|
+
logger.info("🧪 CENTRIFUGO INTEGRATION TEST")
|
|
47
|
+
logger.info("=" * 70)
|
|
48
|
+
|
|
49
|
+
results = {
|
|
50
|
+
'client_init': False,
|
|
51
|
+
'interceptor_test': 0,
|
|
52
|
+
'mixin_test': {},
|
|
53
|
+
'total_published': 0,
|
|
54
|
+
'errors': [],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Test 1: Client initialization
|
|
58
|
+
logger.info("\n📍 Step 1: Testing Centrifugo client initialization")
|
|
59
|
+
logger.info("-" * 70)
|
|
60
|
+
try:
|
|
61
|
+
from django_cfg.apps.integrations.centrifugo import get_centrifugo_client
|
|
62
|
+
|
|
63
|
+
client = get_centrifugo_client()
|
|
64
|
+
logger.info(f"✅ Client initialized: {client.wrapper_url}")
|
|
65
|
+
results['client_init'] = True
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.error(f"❌ Client initialization failed: {e}")
|
|
68
|
+
results['errors'].append(f"Client init: {str(e)}")
|
|
69
|
+
return results
|
|
70
|
+
|
|
71
|
+
# Test 2: Test interceptor simulation (raw publish)
|
|
72
|
+
logger.info("\n📍 Step 2: Testing Interceptor-style publishing (RPC metadata)")
|
|
73
|
+
logger.info("-" * 70)
|
|
74
|
+
try:
|
|
75
|
+
from django_cfg.apps.integrations.grpc.centrifugo.demo import send_demo_event
|
|
76
|
+
|
|
77
|
+
# Send 3 test events
|
|
78
|
+
for i in range(1, 4):
|
|
79
|
+
success = await send_demo_event(
|
|
80
|
+
channel=f"grpc#demo#TestMethod#meta",
|
|
81
|
+
metadata={
|
|
82
|
+
'test_number': i,
|
|
83
|
+
'test_type': 'interceptor_simulation',
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
if success:
|
|
87
|
+
results['interceptor_test'] += 1
|
|
88
|
+
results['total_published'] += 1
|
|
89
|
+
logger.info(f" ✅ Event {i}/3 published to grpc#demo#TestMethod#meta")
|
|
90
|
+
else:
|
|
91
|
+
logger.warning(f" ⚠️ Event {i}/3 failed")
|
|
92
|
+
|
|
93
|
+
await asyncio.sleep(0.5)
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.error(f"❌ Interceptor test failed: {e}")
|
|
97
|
+
results['errors'].append(f"Interceptor: {str(e)}")
|
|
98
|
+
|
|
99
|
+
# Test 3: Test mixin (DemoBridgeService)
|
|
100
|
+
logger.info("\n📍 Step 3: Testing Mixin-style publishing (message data)")
|
|
101
|
+
logger.info("-" * 70)
|
|
102
|
+
try:
|
|
103
|
+
from django_cfg.apps.integrations.grpc.centrifugo.demo import test_demo_service
|
|
104
|
+
|
|
105
|
+
stats = await test_demo_service(service_id='test-integration', count=3)
|
|
106
|
+
results['mixin_test'] = stats
|
|
107
|
+
results['total_published'] += sum(stats.values())
|
|
108
|
+
|
|
109
|
+
logger.info(f" 📊 Mixin test results: {stats}")
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"❌ Mixin test failed: {e}")
|
|
113
|
+
results['errors'].append(f"Mixin: {str(e)}")
|
|
114
|
+
|
|
115
|
+
# Summary
|
|
116
|
+
logger.info("\n" + "=" * 70)
|
|
117
|
+
logger.info("📊 TEST SUMMARY")
|
|
118
|
+
logger.info("=" * 70)
|
|
119
|
+
logger.info(f"Client initialized: {'✅ Yes' if results['client_init'] else '❌ No'}")
|
|
120
|
+
logger.info(f"Interceptor events: {results['interceptor_test']}/3")
|
|
121
|
+
logger.info(f"Mixin events: {results['mixin_test']}")
|
|
122
|
+
logger.info(f"Total published: {results['total_published']}")
|
|
123
|
+
|
|
124
|
+
if results['errors']:
|
|
125
|
+
logger.error(f"\n❌ Errors encountered:")
|
|
126
|
+
for error in results['errors']:
|
|
127
|
+
logger.error(f" - {error}")
|
|
128
|
+
else:
|
|
129
|
+
logger.info(f"\n✅ All tests passed successfully!")
|
|
130
|
+
|
|
131
|
+
logger.info("=" * 70)
|
|
132
|
+
|
|
133
|
+
return results
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def check_centrifugo_config():
|
|
137
|
+
"""
|
|
138
|
+
Check Centrifugo configuration.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
dict: Configuration details
|
|
142
|
+
"""
|
|
143
|
+
logger.info("🔍 Checking Centrifugo configuration...")
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
from django_cfg.apps.integrations.centrifugo.services.config_helper import get_centrifugo_config
|
|
147
|
+
|
|
148
|
+
config = get_centrifugo_config()
|
|
149
|
+
|
|
150
|
+
if not config:
|
|
151
|
+
logger.error("❌ Centrifugo not configured in django-cfg")
|
|
152
|
+
return {
|
|
153
|
+
'enabled': False,
|
|
154
|
+
'error': 'Not configured',
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
logger.info(f"✅ Centrifugo enabled: {config.enabled}")
|
|
158
|
+
logger.info(f" Wrapper URL: {config.wrapper_url}")
|
|
159
|
+
logger.info(f" Log all calls: {config.log_all_calls}")
|
|
160
|
+
logger.info(f" Log only ACK: {config.log_only_with_ack}")
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
'enabled': config.enabled,
|
|
164
|
+
'wrapper_url': config.wrapper_url,
|
|
165
|
+
'log_all_calls': config.log_all_calls,
|
|
166
|
+
'log_only_with_ack': config.log_only_with_ack,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.error(f"❌ Failed to get config: {e}")
|
|
171
|
+
return {
|
|
172
|
+
'enabled': False,
|
|
173
|
+
'error': str(e),
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
async def test_simple_publish():
|
|
178
|
+
"""
|
|
179
|
+
Simple test - publish one event and check logs.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
bool: True if successful
|
|
183
|
+
"""
|
|
184
|
+
logger.info("🧪 Simple publish test...")
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
from django_cfg.apps.integrations.centrifugo import get_centrifugo_client
|
|
188
|
+
from datetime import datetime, timezone as tz
|
|
189
|
+
|
|
190
|
+
client = get_centrifugo_client()
|
|
191
|
+
|
|
192
|
+
test_data = {
|
|
193
|
+
'message': 'Hello from test_publish.py',
|
|
194
|
+
'timestamp': datetime.now(tz.utc).isoformat(),
|
|
195
|
+
'test': True,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
logger.info(f"📤 Publishing to channel: test#demo#simple")
|
|
199
|
+
|
|
200
|
+
result = await client.publish(
|
|
201
|
+
channel='test#demo#simple',
|
|
202
|
+
data=test_data
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
logger.info(f"✅ Publish result: {result}")
|
|
206
|
+
logger.info(f" Message ID: {result.message_id}")
|
|
207
|
+
logger.info(f" Published: {result.published}")
|
|
208
|
+
|
|
209
|
+
return result.published
|
|
210
|
+
|
|
211
|
+
except Exception as e:
|
|
212
|
+
logger.error(f"❌ Publish failed: {e}", exc_info=True)
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
if __name__ == "__main__":
|
|
217
|
+
# For standalone execution
|
|
218
|
+
print("Running Centrifugo integration test...")
|
|
219
|
+
print("=" * 70)
|
|
220
|
+
results = asyncio.run(run_test(verbose=True))
|
|
221
|
+
print("\nTest completed.")
|
|
222
|
+
print(f"Total events published: {results['total_published']}")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
__all__ = [
|
|
226
|
+
'run_test',
|
|
227
|
+
'check_centrifugo_config',
|
|
228
|
+
'test_simple_publish',
|
|
229
|
+
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Protobuf to JSON Transformers.
|
|
3
|
+
|
|
4
|
+
Utilities for transforming protobuf messages to JSON-serializable dictionaries.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
from google.protobuf.message import Message
|
|
9
|
+
from google.protobuf.json_format import MessageToDict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def transform_protobuf_to_dict(message: Message) -> Dict[str, Any]:
|
|
13
|
+
"""
|
|
14
|
+
Transform protobuf message to JSON-serializable dict.
|
|
15
|
+
|
|
16
|
+
Uses google.protobuf.json_format.MessageToDict with sensible defaults.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
message: Protobuf message instance
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
JSON-serializable dictionary
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
```python
|
|
26
|
+
from .generated import bot_streaming_service_pb2
|
|
27
|
+
|
|
28
|
+
heartbeat = bot_streaming_service_pb2.HeartbeatUpdate(
|
|
29
|
+
cpu_usage=45.2,
|
|
30
|
+
memory_usage=60.1
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
data = transform_protobuf_to_dict(heartbeat)
|
|
34
|
+
# {'cpu_usage': 45.2, 'memory_usage': 60.1, ...}
|
|
35
|
+
```
|
|
36
|
+
"""
|
|
37
|
+
return MessageToDict(
|
|
38
|
+
message,
|
|
39
|
+
preserving_proto_field_name=True,
|
|
40
|
+
use_integers_for_enums=False, # Use string names for enums
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def transform_with_enum_mapping(
|
|
45
|
+
message: Message,
|
|
46
|
+
enum_mappings: Dict[str, Dict[int, str]]
|
|
47
|
+
) -> Dict[str, Any]:
|
|
48
|
+
"""
|
|
49
|
+
Transform protobuf with custom enum value mappings.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
message: Protobuf message
|
|
53
|
+
enum_mappings: {field_name: {enum_value: string_name}}
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Dictionary with custom enum names
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
```python
|
|
60
|
+
transform_with_enum_mapping(
|
|
61
|
+
heartbeat,
|
|
62
|
+
enum_mappings={
|
|
63
|
+
'status': {
|
|
64
|
+
0: 'UNSPECIFIED',
|
|
65
|
+
1: 'STOPPED',
|
|
66
|
+
2: 'RUNNING',
|
|
67
|
+
3: 'PAUSED',
|
|
68
|
+
4: 'ERROR',
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
"""
|
|
74
|
+
data = MessageToDict(message, preserving_proto_field_name=True)
|
|
75
|
+
|
|
76
|
+
# Apply custom enum mappings
|
|
77
|
+
for field_name, mapping in enum_mappings.items():
|
|
78
|
+
if field_name in data:
|
|
79
|
+
enum_value = data[field_name]
|
|
80
|
+
if isinstance(enum_value, int):
|
|
81
|
+
data[field_name] = mapping.get(enum_value, str(enum_value))
|
|
82
|
+
|
|
83
|
+
return data
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
__all__ = [
|
|
87
|
+
"transform_protobuf_to_dict",
|
|
88
|
+
"transform_with_enum_mapping",
|
|
89
|
+
]
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
"""
|
|
2
|
-
gRPC interceptors for logging, metrics, and
|
|
2
|
+
gRPC interceptors for logging, metrics, error handling, and Centrifugo publishing.
|
|
3
3
|
|
|
4
4
|
Provides production-ready interceptors for gRPC services.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from .centrifugo import CentrifugoInterceptor
|
|
7
8
|
from .errors import ErrorHandlingInterceptor
|
|
8
9
|
from .logging import LoggingInterceptor
|
|
9
10
|
from .metrics import MetricsInterceptor, get_metrics, reset_metrics
|
|
10
11
|
from .request_logger import RequestLoggerInterceptor
|
|
11
12
|
|
|
12
13
|
__all__ = [
|
|
14
|
+
"CentrifugoInterceptor",
|
|
13
15
|
"LoggingInterceptor",
|
|
14
16
|
"MetricsInterceptor",
|
|
15
17
|
"ErrorHandlingInterceptor",
|