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 CHANGED
@@ -32,7 +32,7 @@ Example:
32
32
  default_app_config = "django_cfg.apps.DjangoCfgConfig"
33
33
 
34
34
  # Version information
35
- __version__ = "1.4.60"
35
+ __version__ = "1.4.62"
36
36
  __license__ = "MIT"
37
37
 
38
38
  # Import registry for organized lazy loading
@@ -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."""
@@ -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
+ ]
@@ -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.60"
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.60
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=fJ_0Ac-Ttb6P-1aXu8SCuaQz3nt3pb_KPKKpx7VO6To,1620
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=QzaJLsMPreK1sPSkzXceRsPW7r1iOxzNLwQ_Hn__gUo,6100
127
- django_cfg/apps/ipc/apps.py,sha256=FJrHrcxeyk-SR8V_mv4kC6IClczffligz9K8y-WevJc,716
128
- django_cfg/apps/ipc/models.py,sha256=yr5PLYr8Vb639eLlNwMmK5ltoWE9UWCnmZ9BaKgDaMU,6251
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=uRiQ0bh7I2s6i1PaZbVrLukJxc76LqNVAahy3DZ-fLU,8260
1033
- django_cfg-1.4.60.dist-info/METADATA,sha256=JN6wZGPFqI3Aa0DcXJn77Dc-8Q2qAjEWCtfigCni29o,22589
1034
- django_cfg-1.4.60.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1035
- django_cfg-1.4.60.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
1036
- django_cfg-1.4.60.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
1037
- django_cfg-1.4.60.dist-info/RECORD,,
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,,