django-cfg 1.4.60__py3-none-any.whl → 1.4.62__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/ipc/admin.py +21 -1
- django_cfg/apps/ipc/apps.py +70 -0
- django_cfg/apps/ipc/migrations/0002_rpclog_is_event.py +23 -0
- django_cfg/apps/ipc/models.py +8 -0
- django_cfg/apps/ipc/services/rpc_log_consumer.py +330 -0
- django_cfg/pyproject.toml +1 -1
- {django_cfg-1.4.60.dist-info → django_cfg-1.4.62.dist-info}/METADATA +1 -1
- {django_cfg-1.4.60.dist-info → django_cfg-1.4.62.dist-info}/RECORD +12 -10
- {django_cfg-1.4.60.dist-info → django_cfg-1.4.62.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.60.dist-info → django_cfg-1.4.62.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.60.dist-info → django_cfg-1.4.62.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
django_cfg/apps/ipc/admin.py
CHANGED
|
@@ -30,6 +30,7 @@ rpclog_config = AdminConfig(
|
|
|
30
30
|
# List display
|
|
31
31
|
list_display=[
|
|
32
32
|
"method",
|
|
33
|
+
"type_badge", # NEW: Show RPC vs Event
|
|
33
34
|
"status",
|
|
34
35
|
"user",
|
|
35
36
|
"duration_ms",
|
|
@@ -73,9 +74,12 @@ rpclog_config = AdminConfig(
|
|
|
73
74
|
],
|
|
74
75
|
|
|
75
76
|
# Filters
|
|
76
|
-
list_filter=["status", "method", "created_at"],
|
|
77
|
+
list_filter=["status", "is_event", "method", "created_at"],
|
|
77
78
|
search_fields=["method", "correlation_id", "user__username", "user__email", "error_message"],
|
|
78
79
|
|
|
80
|
+
# Autocomplete for user field
|
|
81
|
+
autocomplete_fields=["user"],
|
|
82
|
+
|
|
79
83
|
# Readonly fields (custom methods below)
|
|
80
84
|
readonly_fields=[
|
|
81
85
|
"id",
|
|
@@ -108,6 +112,22 @@ class RPCLogAdmin(PydanticAdmin):
|
|
|
108
112
|
"""
|
|
109
113
|
config = rpclog_config
|
|
110
114
|
|
|
115
|
+
@computed_field("Type", ordering="is_event")
|
|
116
|
+
def type_badge(self, obj):
|
|
117
|
+
"""Display badge showing if this is an RPC call or server event."""
|
|
118
|
+
if obj.is_event:
|
|
119
|
+
return self.html.badge(
|
|
120
|
+
"EVENT",
|
|
121
|
+
variant="info",
|
|
122
|
+
icon=Icons.NOTIFICATION # or Icons.BROADCAST
|
|
123
|
+
)
|
|
124
|
+
else:
|
|
125
|
+
return self.html.badge(
|
|
126
|
+
"RPC",
|
|
127
|
+
variant="primary",
|
|
128
|
+
icon=Icons.API
|
|
129
|
+
)
|
|
130
|
+
|
|
111
131
|
@computed_field("Duration", ordering="duration_ms")
|
|
112
132
|
def duration_display(self, obj):
|
|
113
133
|
"""Display duration with color coding based on speed."""
|
django_cfg/apps/ipc/apps.py
CHANGED
|
@@ -4,7 +4,9 @@ Django app configuration for IPC/RPC module.
|
|
|
4
4
|
Provides RPC client and monitoring dashboard.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import asyncio
|
|
7
8
|
from django.apps import AppConfig
|
|
9
|
+
from django.conf import settings
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class IPCConfig(AppConfig):
|
|
@@ -15,6 +17,7 @@ class IPCConfig(AppConfig):
|
|
|
15
17
|
- RPC client for inter-service communication
|
|
16
18
|
- Monitoring dashboard with real-time stats
|
|
17
19
|
- DRF API endpoints for dashboard
|
|
20
|
+
- RPC log consumer (Redis Stream -> ORM)
|
|
18
21
|
"""
|
|
19
22
|
|
|
20
23
|
default_auto_field = 'django.db.models.BigAutoField'
|
|
@@ -26,3 +29,70 @@ class IPCConfig(AppConfig):
|
|
|
26
29
|
"""Initialize app when Django starts."""
|
|
27
30
|
# Import monitor to ensure Redis connection is initialized
|
|
28
31
|
from .services import monitor # noqa: F401
|
|
32
|
+
|
|
33
|
+
# Start RPC Log Consumer in background (if enabled)
|
|
34
|
+
self._start_rpc_log_consumer()
|
|
35
|
+
|
|
36
|
+
def _start_rpc_log_consumer(self):
|
|
37
|
+
"""Start RPC Log Consumer to capture logs from WebSocket server."""
|
|
38
|
+
# Check if RPC logging consumer is enabled
|
|
39
|
+
enable_consumer = getattr(settings, 'DJANGO_CFG_RPC_LOG_CONSUMER_ENABLED', True)
|
|
40
|
+
|
|
41
|
+
if not enable_consumer:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
# Check if we have RPC configuration
|
|
45
|
+
if not hasattr(settings, 'DJANGO_CFG_RPC'):
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
# Check if RPC is enabled (case-insensitive)
|
|
49
|
+
rpc_enabled = settings.DJANGO_CFG_RPC.get('ENABLED') or settings.DJANGO_CFG_RPC.get('enabled')
|
|
50
|
+
if not rpc_enabled:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
import threading
|
|
55
|
+
from .services.rpc_log_consumer import RPCLogConsumer
|
|
56
|
+
|
|
57
|
+
# Get Redis URL (case-insensitive)
|
|
58
|
+
redis_url = settings.DJANGO_CFG_RPC.get('REDIS_URL') or settings.DJANGO_CFG_RPC.get('redis_url', 'redis://localhost:6379/2')
|
|
59
|
+
|
|
60
|
+
consumer = RPCLogConsumer(
|
|
61
|
+
redis_url=redis_url,
|
|
62
|
+
stream_name="stream:rpc-logs",
|
|
63
|
+
consumer_group="django-rpc-loggers",
|
|
64
|
+
consumer_name="django-1",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Run consumer in background thread with its own event loop
|
|
68
|
+
def run_consumer():
|
|
69
|
+
"""Run consumer in background thread."""
|
|
70
|
+
try:
|
|
71
|
+
# Create new event loop for this thread
|
|
72
|
+
loop = asyncio.new_event_loop()
|
|
73
|
+
asyncio.set_event_loop(loop)
|
|
74
|
+
|
|
75
|
+
# Initialize and start consumer
|
|
76
|
+
loop.run_until_complete(consumer.initialize())
|
|
77
|
+
loop.run_until_complete(consumer.start())
|
|
78
|
+
|
|
79
|
+
# Keep running
|
|
80
|
+
loop.run_forever()
|
|
81
|
+
|
|
82
|
+
except Exception as e:
|
|
83
|
+
from django_cfg.modules.django_logging import get_logger
|
|
84
|
+
logger = get_logger("ipc.consumer")
|
|
85
|
+
logger.error(f"RPC Log Consumer error: {e}", exc_info=True)
|
|
86
|
+
|
|
87
|
+
# Start in daemon thread (won't block Django shutdown)
|
|
88
|
+
thread = threading.Thread(target=run_consumer, daemon=True, name="rpc-log-consumer")
|
|
89
|
+
thread.start()
|
|
90
|
+
|
|
91
|
+
from django_cfg.modules.django_logging import get_logger
|
|
92
|
+
logger = get_logger("ipc.apps")
|
|
93
|
+
logger.info("✅ RPC Log Consumer started in background thread")
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
from django_cfg.modules.django_logging import get_logger
|
|
97
|
+
logger = get_logger("ipc.apps")
|
|
98
|
+
logger.warning(f"Failed to start RPC Log Consumer: {e}")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Generated by Django 5.2.7 on 2025-10-24 07:41
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("django_cfg_ipc", "0001_initial"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name="rpclog",
|
|
15
|
+
name="is_event",
|
|
16
|
+
field=models.BooleanField(
|
|
17
|
+
db_index=True,
|
|
18
|
+
default=False,
|
|
19
|
+
help_text="True if this is a server-to-client event (not a request-response RPC call)",
|
|
20
|
+
verbose_name="Is Event",
|
|
21
|
+
),
|
|
22
|
+
),
|
|
23
|
+
]
|
django_cfg/apps/ipc/models.py
CHANGED
|
@@ -110,6 +110,14 @@ class RPCLog(models.Model):
|
|
|
110
110
|
help_text="Result from RPC call"
|
|
111
111
|
)
|
|
112
112
|
|
|
113
|
+
# Type: RPC call or server event
|
|
114
|
+
is_event = models.BooleanField(
|
|
115
|
+
default=False,
|
|
116
|
+
db_index=True,
|
|
117
|
+
verbose_name="Is Event",
|
|
118
|
+
help_text="True if this is a server-to-client event (not a request-response RPC call)"
|
|
119
|
+
)
|
|
120
|
+
|
|
113
121
|
# Status
|
|
114
122
|
status = models.CharField(
|
|
115
123
|
max_length=20,
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RPC Log Consumer - Redis Stream to Django ORM.
|
|
3
|
+
|
|
4
|
+
Consumes RPC events from Redis Stream (published by WebSocket server)
|
|
5
|
+
and saves them to Django database for monitoring and analytics.
|
|
6
|
+
|
|
7
|
+
Architecture:
|
|
8
|
+
- WebSocket Server -> Redis Stream (stream:rpc-logs)
|
|
9
|
+
- This Consumer <- Redis Stream -> Django ORM (RPCLog model)
|
|
10
|
+
|
|
11
|
+
This maintains service independence while enabling centralized logging.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import json
|
|
16
|
+
import time
|
|
17
|
+
from typing import Dict, Optional, Any
|
|
18
|
+
from django.contrib.auth import get_user_model
|
|
19
|
+
from django_cfg.modules.django_logging import get_logger
|
|
20
|
+
import redis.asyncio as redis
|
|
21
|
+
|
|
22
|
+
logger = get_logger("ipc.consumer")
|
|
23
|
+
User = get_user_model()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RPCLogConsumer:
|
|
27
|
+
"""
|
|
28
|
+
Consumes RPC events from Redis Stream and saves to Django ORM.
|
|
29
|
+
|
|
30
|
+
Features:
|
|
31
|
+
- Async consumer for high throughput
|
|
32
|
+
- Automatic reconnection on Redis errors
|
|
33
|
+
- Batched processing for performance
|
|
34
|
+
- Graceful shutdown
|
|
35
|
+
- Error tolerance (continues on DB errors)
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> consumer = RPCLogConsumer(
|
|
39
|
+
... redis_url="redis://localhost:6379/2",
|
|
40
|
+
... stream_name="stream:rpc-logs",
|
|
41
|
+
... )
|
|
42
|
+
>>> await consumer.start() # Runs in background
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
redis_url: str = "redis://localhost:6379/2",
|
|
48
|
+
stream_name: str = "stream:rpc-logs",
|
|
49
|
+
consumer_group: str = "django-rpc-loggers",
|
|
50
|
+
consumer_name: str = "django-1",
|
|
51
|
+
batch_size: int = 10,
|
|
52
|
+
block_ms: int = 1000,
|
|
53
|
+
):
|
|
54
|
+
"""
|
|
55
|
+
Initialize RPC Log Consumer.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
redis_url: Redis connection URL
|
|
59
|
+
stream_name: Redis Stream name to consume
|
|
60
|
+
consumer_group: Consumer group name
|
|
61
|
+
consumer_name: Unique consumer name
|
|
62
|
+
batch_size: Number of messages to process per batch
|
|
63
|
+
block_ms: Block time for XREADGROUP (milliseconds)
|
|
64
|
+
"""
|
|
65
|
+
self.redis_url = redis_url
|
|
66
|
+
self.stream_name = stream_name
|
|
67
|
+
self.consumer_group = consumer_group
|
|
68
|
+
self.consumer_name = consumer_name
|
|
69
|
+
self.batch_size = batch_size
|
|
70
|
+
self.block_ms = block_ms
|
|
71
|
+
|
|
72
|
+
self._redis: Optional[redis.Redis] = None
|
|
73
|
+
self._running = False
|
|
74
|
+
self._task: Optional[asyncio.Task] = None
|
|
75
|
+
|
|
76
|
+
# Pending requests (correlation_id -> log_entry)
|
|
77
|
+
self._pending_requests: Dict[str, Any] = {}
|
|
78
|
+
|
|
79
|
+
async def initialize(self) -> None:
|
|
80
|
+
"""Initialize Redis connection and consumer group."""
|
|
81
|
+
try:
|
|
82
|
+
self._redis = await redis.from_url(
|
|
83
|
+
self.redis_url,
|
|
84
|
+
decode_responses=True, # Decode Redis responses to strings
|
|
85
|
+
socket_keepalive=True,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Test connection
|
|
89
|
+
await self._redis.ping()
|
|
90
|
+
|
|
91
|
+
# Create consumer group if not exists
|
|
92
|
+
try:
|
|
93
|
+
await self._redis.xgroup_create(
|
|
94
|
+
name=self.stream_name,
|
|
95
|
+
groupname=self.consumer_group,
|
|
96
|
+
id="0", # Start from beginning
|
|
97
|
+
mkstream=True, # Create stream if not exists
|
|
98
|
+
)
|
|
99
|
+
logger.info(f"✅ Created consumer group: {self.consumer_group}")
|
|
100
|
+
except redis.ResponseError as e:
|
|
101
|
+
if "BUSYGROUP" in str(e):
|
|
102
|
+
logger.debug(f"Consumer group already exists: {self.consumer_group}")
|
|
103
|
+
else:
|
|
104
|
+
raise
|
|
105
|
+
|
|
106
|
+
logger.info(f"✅ RPC Log Consumer initialized: {self.stream_name}")
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error(f"❌ Failed to initialize RPC Log Consumer: {e}")
|
|
110
|
+
raise
|
|
111
|
+
|
|
112
|
+
async def start(self) -> None:
|
|
113
|
+
"""Start consuming RPC events from Redis Stream."""
|
|
114
|
+
if self._running:
|
|
115
|
+
logger.warning("RPC Log Consumer already running")
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
self._running = True
|
|
119
|
+
self._task = asyncio.create_task(self._consume_loop())
|
|
120
|
+
logger.info(f"🚀 RPC Log Consumer started: {self.consumer_name}")
|
|
121
|
+
|
|
122
|
+
async def stop(self) -> None:
|
|
123
|
+
"""Stop consuming and cleanup."""
|
|
124
|
+
if not self._running:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
logger.info("⏹️ Stopping RPC Log Consumer...")
|
|
128
|
+
self._running = False
|
|
129
|
+
|
|
130
|
+
if self._task:
|
|
131
|
+
self._task.cancel()
|
|
132
|
+
try:
|
|
133
|
+
await self._task
|
|
134
|
+
except asyncio.CancelledError:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
if self._redis:
|
|
138
|
+
await self._redis.close()
|
|
139
|
+
|
|
140
|
+
logger.info("✅ RPC Log Consumer stopped")
|
|
141
|
+
|
|
142
|
+
async def _consume_loop(self) -> None:
|
|
143
|
+
"""Main consume loop - reads from Redis Stream and processes events."""
|
|
144
|
+
last_id = ">" # Read only new messages
|
|
145
|
+
|
|
146
|
+
while self._running:
|
|
147
|
+
try:
|
|
148
|
+
# Read from stream
|
|
149
|
+
messages = await self._redis.xreadgroup(
|
|
150
|
+
groupname=self.consumer_group,
|
|
151
|
+
consumername=self.consumer_name,
|
|
152
|
+
streams={self.stream_name: last_id},
|
|
153
|
+
count=self.batch_size,
|
|
154
|
+
block=self.block_ms,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if not messages:
|
|
158
|
+
continue # No new messages, continue
|
|
159
|
+
|
|
160
|
+
# Process messages
|
|
161
|
+
for stream_name, stream_messages in messages:
|
|
162
|
+
for message_id, message_data in stream_messages:
|
|
163
|
+
try:
|
|
164
|
+
await self._process_event(message_id, message_data)
|
|
165
|
+
|
|
166
|
+
# Acknowledge message
|
|
167
|
+
await self._redis.xack(
|
|
168
|
+
self.stream_name,
|
|
169
|
+
self.consumer_group,
|
|
170
|
+
message_id,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.error(
|
|
175
|
+
f"Error processing RPC event {message_id}: {e}",
|
|
176
|
+
exc_info=True
|
|
177
|
+
)
|
|
178
|
+
# Continue processing other messages
|
|
179
|
+
|
|
180
|
+
except asyncio.CancelledError:
|
|
181
|
+
logger.info("RPC Log Consumer loop cancelled")
|
|
182
|
+
break
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logger.error(f"Error in RPC Log Consumer loop: {e}", exc_info=True)
|
|
185
|
+
# Wait before retrying
|
|
186
|
+
await asyncio.sleep(5)
|
|
187
|
+
|
|
188
|
+
async def _process_event(self, message_id: str, event_data: Dict[str, str]) -> None:
|
|
189
|
+
"""
|
|
190
|
+
Process single RPC event from Redis Stream.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
message_id: Redis message ID
|
|
194
|
+
event_data: Event data from stream
|
|
195
|
+
"""
|
|
196
|
+
event_type = event_data.get("event_type")
|
|
197
|
+
correlation_id = event_data.get("correlation_id")
|
|
198
|
+
|
|
199
|
+
if not correlation_id:
|
|
200
|
+
logger.warning(f"Event missing correlation_id: {message_id}")
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
if event_type == "request":
|
|
204
|
+
await self._handle_request(correlation_id, event_data)
|
|
205
|
+
elif event_type == "response":
|
|
206
|
+
await self._handle_response(correlation_id, event_data)
|
|
207
|
+
else:
|
|
208
|
+
logger.warning(f"Unknown event type: {event_type}")
|
|
209
|
+
|
|
210
|
+
async def _handle_request(self, correlation_id: str, event_data: Dict[str, str]) -> None:
|
|
211
|
+
"""
|
|
212
|
+
Handle RPC request event - create RPCLog entry.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
correlation_id: Correlation ID
|
|
216
|
+
event_data: Event data from stream
|
|
217
|
+
"""
|
|
218
|
+
try:
|
|
219
|
+
from ..models import RPCLog
|
|
220
|
+
|
|
221
|
+
method = event_data.get("method")
|
|
222
|
+
params_json = event_data.get("params", "{}")
|
|
223
|
+
user_id = event_data.get("user_id")
|
|
224
|
+
|
|
225
|
+
# Parse params
|
|
226
|
+
try:
|
|
227
|
+
params = json.loads(params_json)
|
|
228
|
+
except json.JSONDecodeError:
|
|
229
|
+
params = {}
|
|
230
|
+
|
|
231
|
+
# Get user if user_id provided
|
|
232
|
+
user = None
|
|
233
|
+
if user_id:
|
|
234
|
+
try:
|
|
235
|
+
user = await asyncio.to_thread(User.objects.get, pk=user_id)
|
|
236
|
+
except User.DoesNotExist:
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
# Create log entry in database (sync operation in thread)
|
|
240
|
+
log_entry = await asyncio.to_thread(
|
|
241
|
+
RPCLog.objects.create,
|
|
242
|
+
correlation_id=correlation_id,
|
|
243
|
+
method=method,
|
|
244
|
+
params=params,
|
|
245
|
+
user=user,
|
|
246
|
+
status=RPCLog.StatusChoices.PENDING,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Store in pending for later update
|
|
250
|
+
self._pending_requests[correlation_id] = log_entry
|
|
251
|
+
|
|
252
|
+
logger.debug(f"Created RPC log entry: {method} ({correlation_id})")
|
|
253
|
+
|
|
254
|
+
except Exception as e:
|
|
255
|
+
logger.error(f"Failed to handle RPC request: {e}", exc_info=True)
|
|
256
|
+
|
|
257
|
+
async def _handle_response(self, correlation_id: str, event_data: Dict[str, str]) -> None:
|
|
258
|
+
"""
|
|
259
|
+
Handle RPC response event - update RPCLog entry.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
correlation_id: Correlation ID
|
|
263
|
+
event_data: Event data from stream
|
|
264
|
+
"""
|
|
265
|
+
try:
|
|
266
|
+
from ..models import RPCLog
|
|
267
|
+
|
|
268
|
+
# Get log entry from pending or database
|
|
269
|
+
log_entry = self._pending_requests.pop(correlation_id, None)
|
|
270
|
+
|
|
271
|
+
if not log_entry:
|
|
272
|
+
# Try to find in database with retry (race condition with request handler)
|
|
273
|
+
max_retries = 3
|
|
274
|
+
retry_delay = 0.1 # 100ms
|
|
275
|
+
|
|
276
|
+
for attempt in range(max_retries):
|
|
277
|
+
try:
|
|
278
|
+
log_entry = await asyncio.to_thread(
|
|
279
|
+
RPCLog.objects.get,
|
|
280
|
+
correlation_id=correlation_id
|
|
281
|
+
)
|
|
282
|
+
break # Found it!
|
|
283
|
+
except RPCLog.DoesNotExist:
|
|
284
|
+
if attempt < max_retries - 1:
|
|
285
|
+
# Wait a bit and retry
|
|
286
|
+
await asyncio.sleep(retry_delay)
|
|
287
|
+
else:
|
|
288
|
+
# Give up after retries
|
|
289
|
+
logger.warning(f"RPCLog not found for correlation_id: {correlation_id} after {max_retries} retries")
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
# Parse response data
|
|
293
|
+
success = event_data.get("success") == "1"
|
|
294
|
+
duration_ms = int(event_data.get("duration_ms", 0))
|
|
295
|
+
|
|
296
|
+
if success:
|
|
297
|
+
result_json = event_data.get("result", "{}")
|
|
298
|
+
try:
|
|
299
|
+
result = json.loads(result_json)
|
|
300
|
+
except json.JSONDecodeError:
|
|
301
|
+
result = {}
|
|
302
|
+
|
|
303
|
+
# Mark as success (sync operation in thread)
|
|
304
|
+
await asyncio.to_thread(
|
|
305
|
+
log_entry.mark_success,
|
|
306
|
+
result,
|
|
307
|
+
duration_ms
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
logger.debug(f"Marked RPC log as success: {correlation_id}")
|
|
311
|
+
|
|
312
|
+
else:
|
|
313
|
+
error_code = event_data.get("error_code", "unknown")
|
|
314
|
+
error_message = event_data.get("error_message", "")
|
|
315
|
+
|
|
316
|
+
# Mark as failed (sync operation in thread)
|
|
317
|
+
await asyncio.to_thread(
|
|
318
|
+
log_entry.mark_failed,
|
|
319
|
+
error_code,
|
|
320
|
+
error_message,
|
|
321
|
+
duration_ms
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
logger.debug(f"Marked RPC log as failed: {correlation_id}")
|
|
325
|
+
|
|
326
|
+
except Exception as e:
|
|
327
|
+
logger.error(f"Failed to handle RPC response: {e}", exc_info=True)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
__all__ = ["RPCLogConsumer"]
|
django_cfg/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "django-cfg"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.62"
|
|
8
8
|
description = "Django AI framework with built-in agents, type-safe Pydantic v2 configuration, and 8 enterprise apps. Replace settings.py, validate at startup, 90% less code. Production-ready AI workflows for Django."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "ai-agents", "enterprise-django", "django-settings", "type-safe-config",]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-cfg
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.62
|
|
4
4
|
Summary: Django AI framework with built-in agents, type-safe Pydantic v2 configuration, and 8 enterprise apps. Replace settings.py, validate at startup, 90% less code. Production-ready AI workflows for Django.
|
|
5
5
|
Project-URL: Homepage, https://djangocfg.com
|
|
6
6
|
Project-URL: Documentation, https://djangocfg.com
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
django_cfg/README.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
django_cfg/__init__.py,sha256=
|
|
2
|
+
django_cfg/__init__.py,sha256=QGB9j2_6zmooWACx7m2Nj78mN7-GkzPrvS0aRLcjZTA,1620
|
|
3
3
|
django_cfg/apps.py,sha256=72m3uuvyqGiLx6gOfE-BD3P61jddCCERuBOYpxTX518,1605
|
|
4
4
|
django_cfg/config.py,sha256=y4Z3rnYsHBE0TehpwAIPaxr---mkvyKrZGGsNwYso74,1398
|
|
5
5
|
django_cfg/apps/__init__.py,sha256=JtDmEYt1OcleWM2ZaeX0LKDnRQzPOavfaXBWG4ECB5Q,26
|
|
@@ -123,18 +123,20 @@ django_cfg/apps/ipc/README.md,sha256=f_t_dK-W6GXMy7kxKpa_FidkZPII4M7eI3cmTFkJRMg
|
|
|
123
123
|
django_cfg/apps/ipc/RPC_LOGGING.md,sha256=NmYdCM0FggMdCbAzFH1dzKEsK97merVunN3Yk9nlO_E,7919
|
|
124
124
|
django_cfg/apps/ipc/TESTING.md,sha256=c-AdIfEZMbr7fvwKwfRWciZzS-m4TXnNcfcLJrt0fOQ,11544
|
|
125
125
|
django_cfg/apps/ipc/__init__.py,sha256=C86RhKR8MeKAyaJhegWjZCKohI5TlX5dIwhIHsmNEQk,1727
|
|
126
|
-
django_cfg/apps/ipc/admin.py,sha256
|
|
127
|
-
django_cfg/apps/ipc/apps.py,sha256=
|
|
128
|
-
django_cfg/apps/ipc/models.py,sha256=
|
|
126
|
+
django_cfg/apps/ipc/admin.py,sha256=-rVvznZpqDTeJp9qIMxPeVdV8FiFPa9aJCShRSmOPr4,6733
|
|
127
|
+
django_cfg/apps/ipc/apps.py,sha256=VIjSfRS6EiYHlSMDtT7UBFxrAeFShAA_jUG0-s6Yz2s,3516
|
|
128
|
+
django_cfg/apps/ipc/models.py,sha256=aCoGclqeu0CJBDobXz5Co6wvdTW2gM1xG4qOWkqsEls,6505
|
|
129
129
|
django_cfg/apps/ipc/urls.py,sha256=LuqwwAKqs-nHoXae3yJiUjVhK5T8DthrAt9LCAUAYNI,568
|
|
130
130
|
django_cfg/apps/ipc/urls_admin.py,sha256=YvixgQXTVFWojJeYmo0b7AF9X9g7Nmhwxv7Fm4ihTAg,399
|
|
131
131
|
django_cfg/apps/ipc/migrations/0001_initial.py,sha256=9fpp3bvYFZL48hRKeyxEchtnMSuQlbGIBeydZfsCMnQ,5011
|
|
132
|
+
django_cfg/apps/ipc/migrations/0002_rpclog_is_event.py,sha256=3uH9IVSFMyU2qymGZnnFykUzTZOfGspjpBeJKViJMpU,592
|
|
132
133
|
django_cfg/apps/ipc/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
133
134
|
django_cfg/apps/ipc/serializers/__init__.py,sha256=5b9-6f0Nrrf6g7lZR1-l5xhliB8yRWSCbym8GpkmMsw,705
|
|
134
135
|
django_cfg/apps/ipc/serializers/serializers.py,sha256=R6e4sNp7i58bQq9gflpoEsDVhItsDnQv548bJYX1WNg,9692
|
|
135
136
|
django_cfg/apps/ipc/services/__init__.py,sha256=YdHT1pd7FZbjYFaPAf3rw_t3NIHA-6TIWI7Il2ELLO4,96
|
|
136
137
|
django_cfg/apps/ipc/services/logging.py,sha256=FZCel-I2wcPAXoy1JethrpCB_TzqYs14Fc-h0oeIUeQ,7240
|
|
137
138
|
django_cfg/apps/ipc/services/monitor.py,sha256=5_ttY_D9EoZ3LUKb5S05_jwW6W6A8NyUkS1QbR6wfMw,15496
|
|
139
|
+
django_cfg/apps/ipc/services/rpc_log_consumer.py,sha256=mc6ch_0Ho2FD5l_LrS8qKdEmbXK34k3mpHEBhs1UCU0,11492
|
|
138
140
|
django_cfg/apps/ipc/services/client/__init__.py,sha256=7tMogbz93FefNoT5ul5_kGwnYnL29WS-rHh1T5lVl6w,456
|
|
139
141
|
django_cfg/apps/ipc/services/client/client.py,sha256=dopna1aWTia49pEjYRHnxlmvmwofNwbUnTFK9BfyCEQ,20296
|
|
140
142
|
django_cfg/apps/ipc/services/client/config.py,sha256=A6xWzRfVb03hatoHmT8saePy3zPHgm0FOwXKT8QShOY,5895
|
|
@@ -1029,9 +1031,9 @@ django_cfg/utils/version_check.py,sha256=WO51J2m2e-wVqWCRwbultEwu3q1lQasV67Mw2aa
|
|
|
1029
1031
|
django_cfg/CHANGELOG.md,sha256=jtT3EprqEJkqSUh7IraP73vQ8PmKUMdRtznQsEnqDZk,2052
|
|
1030
1032
|
django_cfg/CONTRIBUTING.md,sha256=DU2kyQ6PU0Z24ob7O_OqKWEYHcZmJDgzw-lQCmu6uBg,3041
|
|
1031
1033
|
django_cfg/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
|
1032
|
-
django_cfg/pyproject.toml,sha256=
|
|
1033
|
-
django_cfg-1.4.
|
|
1034
|
-
django_cfg-1.4.
|
|
1035
|
-
django_cfg-1.4.
|
|
1036
|
-
django_cfg-1.4.
|
|
1037
|
-
django_cfg-1.4.
|
|
1034
|
+
django_cfg/pyproject.toml,sha256=vxC98l-KeMSq1PPeO6568xe-xI3SETS9TJZbwQkmZkQ,8260
|
|
1035
|
+
django_cfg-1.4.62.dist-info/METADATA,sha256=Fc-RLaoMzfZbzUccUVgrS3Z7Zdsb-f7OeUTulwwG6cE,22589
|
|
1036
|
+
django_cfg-1.4.62.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
1037
|
+
django_cfg-1.4.62.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
|
|
1038
|
+
django_cfg-1.4.62.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
|
1039
|
+
django_cfg-1.4.62.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|