django-cfg 1.4.5__py3-none-any.whl → 1.4.6__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.
@@ -6,7 +6,8 @@ Utility for checking all registered Django URL endpoints.
6
6
 
7
7
  import time
8
8
  import re
9
- from typing import List, Dict, Any, Optional
9
+ import uuid
10
+ from typing import List, Dict, Any, Optional, Tuple
10
11
  from django.urls import get_resolver, URLPattern, URLResolver
11
12
  from django.test import Client
12
13
  from django.utils import timezone
@@ -49,6 +50,7 @@ def should_check_endpoint(url_pattern: str, url_name: Optional[str] = None) -> b
49
50
  - Static/media files
50
51
  - Django internal endpoints
51
52
  - Schema/Swagger/Redoc documentation endpoints
53
+ - DRF format suffix patterns (causes kwarg errors)
52
54
 
53
55
  Args:
54
56
  url_pattern: URL pattern string
@@ -73,6 +75,11 @@ def should_check_endpoint(url_pattern: str, url_name: Optional[str] = None) -> b
73
75
  if re.match(pattern, url_pattern):
74
76
  return False
75
77
 
78
+ # Exclude DRF format suffix patterns (e.g., \.(?P<format>[a-z0-9]+))
79
+ # These cause "got an unexpected keyword argument 'format'" errors in action methods
80
+ if r'\.(?P<format>' in url_pattern or '<drf_format_suffix:' in url_pattern:
81
+ return False
82
+
76
83
  # Exclude URL names
77
84
  exclude_names = [
78
85
  'django_cfg_health',
@@ -89,6 +96,147 @@ def should_check_endpoint(url_pattern: str, url_name: Optional[str] = None) -> b
89
96
  return True
90
97
 
91
98
 
99
+ def get_test_value_for_parameter(param_name: str, param_pattern: str) -> str:
100
+ """
101
+ Generate appropriate test value for URL parameter based on name and pattern.
102
+
103
+ Args:
104
+ param_name: Parameter name (e.g., 'slug', 'pk', 'id', 'uuid')
105
+ param_pattern: Regex pattern for the parameter
106
+
107
+ Returns:
108
+ Test value string
109
+
110
+ Examples:
111
+ slug -> 'test-slug'
112
+ pk -> '1'
113
+ id -> '1'
114
+ uuid -> generated UUID
115
+ format -> 'json'
116
+ """
117
+ param_lower = param_name.lower()
118
+
119
+ # UUID parameters
120
+ if 'uuid' in param_lower:
121
+ return str(uuid.uuid4())
122
+
123
+ # Primary key / ID parameters
124
+ if param_lower in ['pk', 'id']:
125
+ return '1'
126
+
127
+ # Slug parameters
128
+ if 'slug' in param_lower:
129
+ return 'test-slug'
130
+
131
+ # Format parameters (for DRF format suffixes)
132
+ if 'format' in param_lower:
133
+ return 'json'
134
+
135
+ # Username parameters
136
+ if 'username' in param_lower:
137
+ return 'testuser'
138
+
139
+ # Year/Month/Day parameters
140
+ if param_lower == 'year':
141
+ return '2024'
142
+ if param_lower == 'month':
143
+ return '01'
144
+ if param_lower == 'day':
145
+ return '01'
146
+
147
+ # Generic string parameter - check pattern
148
+ if '[a-z0-9]' in param_pattern or '[\\w]' in param_pattern:
149
+ return 'test'
150
+
151
+ # Numeric parameter
152
+ if '[0-9]' in param_pattern or '\\d' in param_pattern:
153
+ return '1'
154
+
155
+ # Default
156
+ return 'test'
157
+
158
+
159
+ def resolve_parametrized_url(url_pattern: str) -> Optional[str]:
160
+ """
161
+ Resolve URL pattern with parameters to concrete URL with test values.
162
+
163
+ Supports both regex patterns and Django typed path converters.
164
+
165
+ Args:
166
+ url_pattern: URL pattern with Django parameters
167
+
168
+ Returns:
169
+ Resolved URL with test values, or None if cannot resolve
170
+
171
+ Examples:
172
+ Regex patterns:
173
+ '/api/products/(?P<slug>[^/]+)/' -> '/api/products/test-slug/'
174
+ '/api/users/(?P<pk>[0-9]+)/' -> '/api/users/1/'
175
+
176
+ Typed converters:
177
+ '/api/products/<int:pk>/' -> '/api/products/1/'
178
+ '/api/posts/<slug:slug>/' -> '/api/posts/test-slug/'
179
+ '/api/items/<uuid:item_id>/' -> '/api/items/<uuid>/'
180
+ """
181
+ resolved_url = url_pattern
182
+
183
+ # First, handle Django typed path converters: <converter:name>
184
+ # Pattern: <type:name>
185
+ typed_converter_regex = r'<([^:>]+):([^>]+)>'
186
+
187
+ typed_matches = list(re.finditer(typed_converter_regex, url_pattern))
188
+
189
+ for match in typed_matches:
190
+ converter_type = match.group(1)
191
+ param_name = match.group(2)
192
+ full_match = match.group(0)
193
+
194
+ # Get test value based on converter type
195
+ if converter_type == 'int':
196
+ test_value = '1'
197
+ elif converter_type == 'slug':
198
+ test_value = 'test-slug'
199
+ elif converter_type == 'uuid':
200
+ test_value = str(uuid.uuid4())
201
+ elif converter_type == 'str':
202
+ test_value = get_test_value_for_parameter(param_name, '')
203
+ elif converter_type == 'path':
204
+ test_value = 'test/path'
205
+ elif converter_type == 'drf_format_suffix':
206
+ test_value = 'json'
207
+ else:
208
+ # Unknown converter - use parameter name to guess
209
+ test_value = get_test_value_for_parameter(param_name, '')
210
+
211
+ # Replace typed converter with test value
212
+ resolved_url = resolved_url.replace(full_match, test_value, 1)
213
+
214
+ # Then handle regex patterns: (?P<name>pattern)
215
+ param_regex = r'\(\?P<([^>]+)>([^)]+)\)'
216
+
217
+ regex_matches = re.finditer(param_regex, resolved_url)
218
+
219
+ for match in regex_matches:
220
+ param_name = match.group(1)
221
+ param_pattern = match.group(2)
222
+ full_match = match.group(0)
223
+
224
+ # Get test value for this parameter
225
+ test_value = get_test_value_for_parameter(param_name, param_pattern)
226
+
227
+ # Replace parameter with test value
228
+ resolved_url = resolved_url.replace(full_match, test_value, 1)
229
+
230
+ # Clean up any remaining regex syntax
231
+ resolved_url = re.sub(r'[\^$\\]', '', resolved_url)
232
+
233
+ # Check if resolution was successful (no parameter patterns left)
234
+ if '(?P<' in resolved_url or '<' in resolved_url:
235
+ return None
236
+
237
+ return resolved_url
238
+
239
+
92
240
  def collect_endpoints(
93
241
  urlpatterns=None,
94
242
  prefix: str = '',
@@ -158,18 +306,6 @@ def collect_endpoints(
158
306
  if not should_check_endpoint(clean_pattern, url_name):
159
307
  continue
160
308
 
161
- # Skip patterns with required parameters (for now)
162
- if '<' in clean_pattern:
163
- endpoints.append({
164
- 'url': clean_pattern,
165
- 'url_name': url_name,
166
- 'namespace': namespace,
167
- 'group': get_url_group(clean_pattern),
168
- 'status': 'skipped',
169
- 'reason': 'requires_parameters',
170
- })
171
- continue
172
-
173
309
  # Get view info
174
310
  view_name = 'unknown'
175
311
  if hasattr(pattern, 'callback'):
@@ -179,14 +315,44 @@ def collect_endpoints(
179
315
  elif hasattr(callback, '__name__'):
180
316
  view_name = callback.__name__
181
317
 
182
- endpoints.append({
183
- 'url': clean_pattern,
184
- 'url_name': url_name,
185
- 'namespace': namespace,
186
- 'group': get_url_group(clean_pattern),
187
- 'view': view_name,
188
- 'status': 'pending',
189
- })
318
+ # Handle patterns with parameters
319
+ if '<' in clean_pattern or '(?P<' in clean_pattern:
320
+ # Try to resolve with test values
321
+ resolved_url = resolve_parametrized_url(clean_pattern)
322
+
323
+ if resolved_url:
324
+ # Successfully resolved - can test it
325
+ endpoints.append({
326
+ 'url': resolved_url,
327
+ 'url_pattern': clean_pattern, # Keep original pattern for reference
328
+ 'url_name': url_name,
329
+ 'namespace': namespace,
330
+ 'group': get_url_group(clean_pattern),
331
+ 'view': view_name,
332
+ 'status': 'pending',
333
+ 'has_parameters': True,
334
+ })
335
+ else:
336
+ # Cannot resolve - skip
337
+ endpoints.append({
338
+ 'url': clean_pattern,
339
+ 'url_name': url_name,
340
+ 'namespace': namespace,
341
+ 'group': get_url_group(clean_pattern),
342
+ 'view': view_name,
343
+ 'status': 'skipped',
344
+ 'reason': 'cannot_resolve_parameters',
345
+ })
346
+ else:
347
+ # No parameters - can test directly
348
+ endpoints.append({
349
+ 'url': clean_pattern,
350
+ 'url_name': url_name,
351
+ 'namespace': namespace,
352
+ 'group': get_url_group(clean_pattern),
353
+ 'view': view_name,
354
+ 'status': 'pending',
355
+ })
190
356
 
191
357
  return endpoints
192
358
 
@@ -266,7 +432,11 @@ def check_endpoint(
266
432
  start_time = time.time()
267
433
 
268
434
  # First attempt - without auth
269
- extra_headers = {'SERVER_NAME': 'localhost'}
435
+ # Add special header to bypass rate limiting for internal checks
436
+ extra_headers = {
437
+ 'SERVER_NAME': 'localhost',
438
+ 'HTTP_X_DJANGO_CFG_INTERNAL_CHECK': 'true'
439
+ }
270
440
  response = client.get(url, timeout=timeout, **extra_headers)
271
441
  response_time = (time.time() - start_time) * 1000 # Convert to ms
272
442
  status_code = response.status_code
@@ -294,6 +464,7 @@ def check_endpoint(
294
464
  # 401, 403: Auth required (expected, still healthy)
295
465
  # 404: Not found (might be OK if endpoint exists but has no data)
296
466
  # 405: Method not allowed (endpoint exists, just wrong method)
467
+ # 429: Rate limited (expected for rate-limited APIs, still healthy)
297
468
  # 500+: Server errors (unhealthy)
298
469
 
299
470
  is_healthy = status_code in [
@@ -301,13 +472,16 @@ def check_endpoint(
301
472
  301, 302, 303, 307, 308, # Redirects
302
473
  401, 403, # Auth required (expected)
303
474
  405, # Method not allowed (endpoint exists)
475
+ 429, # Rate limited (expected for rate-limited APIs)
304
476
  ]
305
477
 
306
478
  # Special handling for 404
479
+ reason = None
307
480
  if status_code == 404:
308
481
  # 404 might be OK for some endpoints (e.g., detail views with no data)
309
482
  # Mark as warning rather than unhealthy
310
483
  is_healthy = None # Will be marked as 'warning'
484
+ reason = 'Not Found - endpoint works but no data exists (empty list or test object not found)'
311
485
 
312
486
  endpoint.update({
313
487
  'status_code': status_code,
@@ -317,16 +491,39 @@ def check_endpoint(
317
491
  'last_checked': timezone.now().isoformat(),
318
492
  })
319
493
 
494
+ if reason:
495
+ endpoint['reason'] = reason
496
+
320
497
  if requires_auth:
321
498
  endpoint['required_auth'] = True
322
499
 
500
+ if status_code == 429:
501
+ endpoint['rate_limited'] = True
502
+
323
503
  except Exception as e:
504
+ from django.db import DatabaseError, OperationalError
505
+
506
+ # Multi-database compatibility: treat DB errors as warnings, not errors
507
+ # Common in multi-database setups with db_constraint=False ForeignKeys
508
+ is_db_error = isinstance(e, (DatabaseError, OperationalError))
509
+ error_message = str(e)[:200]
510
+
511
+ # Check for cross-database JOIN errors (common with SQLite multi-db)
512
+ is_cross_db_error = any(keyword in str(e).lower() for keyword in [
513
+ 'no such table',
514
+ 'no such column',
515
+ 'cannot join',
516
+ 'cross-database',
517
+ 'multi-database'
518
+ ])
519
+
324
520
  endpoint.update({
325
521
  'status_code': None,
326
522
  'response_time_ms': None,
327
- 'is_healthy': False,
328
- 'status': 'error',
329
- 'error': str(e)[:200], # Truncate long errors
523
+ 'is_healthy': False if not (is_db_error or is_cross_db_error) else None,
524
+ 'status': 'warning' if (is_db_error or is_cross_db_error) else 'error',
525
+ 'error': error_message,
526
+ 'error_type': 'database' if (is_db_error or is_cross_db_error) else 'general',
330
527
  'last_checked': timezone.now().isoformat(),
331
528
  })
332
529
 
@@ -23,6 +23,7 @@ class DRFEndpointsStatusView(APIView):
23
23
  Query Parameters:
24
24
  - include_unnamed: Include endpoints without names (default: false)
25
25
  - timeout: Request timeout in seconds (default: 5)
26
+ - auto_auth: Auto-retry with JWT on 401/403 (default: true)
26
27
 
27
28
  This endpoint uses DRF Browsable API with Tailwind CSS theme! 🎨
28
29
  """
@@ -35,11 +36,13 @@ class DRFEndpointsStatusView(APIView):
35
36
  # Get query parameters
36
37
  include_unnamed = request.query_params.get('include_unnamed', 'false').lower() == 'true'
37
38
  timeout = int(request.query_params.get('timeout', 5))
39
+ auto_auth = request.query_params.get('auto_auth', 'true').lower() == 'true'
38
40
 
39
41
  # Check all endpoints
40
42
  status_data = check_all_endpoints(
41
43
  include_unnamed=include_unnamed,
42
- timeout=timeout
44
+ timeout=timeout,
45
+ auto_auth=auto_auth
43
46
  )
44
47
 
45
48
  # Return appropriate HTTP status
@@ -11,7 +11,12 @@ class EndpointSerializer(serializers.Serializer):
11
11
  """Serializer for single endpoint status."""
12
12
 
13
13
  url = serializers.CharField(
14
- help_text="URL pattern of the endpoint"
14
+ help_text="Resolved URL (for parametrized URLs) or URL pattern"
15
+ )
16
+ url_pattern = serializers.CharField(
17
+ required=False,
18
+ allow_null=True,
19
+ help_text="Original URL pattern (for parametrized URLs)"
15
20
  )
16
21
  url_name = serializers.CharField(
17
22
  required=False,
@@ -53,21 +58,36 @@ class EndpointSerializer(serializers.Serializer):
53
58
  allow_blank=True,
54
59
  help_text="Error message if check failed"
55
60
  )
61
+ error_type = serializers.CharField(
62
+ required=False,
63
+ allow_blank=True,
64
+ help_text="Error type: database, general, etc."
65
+ )
56
66
  reason = serializers.CharField(
57
67
  required=False,
58
68
  allow_blank=True,
59
- help_text="Reason for skip (if skipped)"
69
+ help_text="Reason for warning/skip"
60
70
  )
61
71
  last_checked = serializers.DateTimeField(
62
72
  required=False,
63
73
  allow_null=True,
64
74
  help_text="Timestamp of last check"
65
75
  )
76
+ has_parameters = serializers.BooleanField(
77
+ required=False,
78
+ default=False,
79
+ help_text="Whether URL has parameters that were resolved with test values"
80
+ )
66
81
  required_auth = serializers.BooleanField(
67
82
  required=False,
68
83
  default=False,
69
84
  help_text="Whether endpoint required JWT authentication"
70
85
  )
86
+ rate_limited = serializers.BooleanField(
87
+ required=False,
88
+ default=False,
89
+ help_text="Whether endpoint returned 429 (rate limited)"
90
+ )
71
91
 
72
92
 
73
93
  class EndpointsStatusSerializer(serializers.Serializer):
@@ -52,6 +52,7 @@ class RateLimitingMiddleware(MiddlewareMixin):
52
52
  self.window_precision = 10 # sub-windows
53
53
  self.exempt_paths = [
54
54
  '/api/health/',
55
+ '/cfg/', # Exempt all django-cfg internal endpoints
55
56
  '/admin/',
56
57
  '/static/',
57
58
  '/media/',
@@ -73,7 +74,7 @@ class RateLimitingMiddleware(MiddlewareMixin):
73
74
  self.burst_allowance = 0.5
74
75
  self.window_size = 60
75
76
  self.window_precision = 10
76
- self.exempt_paths = ['/api/health/', '/admin/']
77
+ self.exempt_paths = ['/api/health/', '/cfg/', '/admin/']
77
78
  self.cache_timeout = 300
78
79
 
79
80
  logger.info(f"Rate Limiting Middleware initialized", extra={
@@ -92,9 +93,14 @@ class RateLimitingMiddleware(MiddlewareMixin):
92
93
  if not self.enabled:
93
94
  return None
94
95
 
95
- # Check if path is exempt
96
- if request.path in self.exempt_paths:
96
+ # Check if this is a django-cfg internal endpoint check (bypass rate limiting)
97
+ if request.META.get('HTTP_X_DJANGO_CFG_INTERNAL_CHECK') == 'true':
97
98
  return None
99
+
100
+ # Check if path is exempt (supports prefix matching)
101
+ for exempt_path in self.exempt_paths:
102
+ if request.path.startswith(exempt_path):
103
+ return None
98
104
 
99
105
  start_time = time.time()
100
106
 
@@ -113,7 +113,7 @@ class Command(BaseCommand):
113
113
  self.stdout.write(self.style.HTTP_INFO('🔗 Endpoints:'))
114
114
 
115
115
  for endpoint in data['endpoints']:
116
- name = endpoint.get('name', 'unnamed')
116
+ name = endpoint.get('url_name') or 'unnamed'
117
117
  url = endpoint['url']
118
118
  status = endpoint['status']
119
119
 
@@ -128,18 +128,41 @@ class Command(BaseCommand):
128
128
  style = self.style.ERROR
129
129
 
130
130
  self.stdout.write(f' {icon} {name}')
131
- self.stdout.write(f' URL: {url}')
132
- self.stdout.write(style(f' Status: {status}'))
133
131
 
134
- if endpoint.get('response_time'):
135
- self.stdout.write(f' Response time: {endpoint["response_time"]:.3f}s')
132
+ # Show both pattern and resolved URL for parametrized endpoints
133
+ if endpoint.get('has_parameters') and endpoint.get('url_pattern'):
134
+ self.stdout.write(f' Pattern: {endpoint["url_pattern"]}')
135
+ self.stdout.write(f' Resolved: {url}')
136
+ else:
137
+ self.stdout.write(f' URL: {url}')
138
+
139
+ # Show status with status code
140
+ status_code = endpoint.get('status_code')
141
+ if status_code:
142
+ self.stdout.write(style(f' Status: {status} ({status_code})'))
143
+ else:
144
+ self.stdout.write(style(f' Status: {status}'))
145
+
146
+ if endpoint.get('response_time_ms'):
147
+ self.stdout.write(f' Response time: {endpoint["response_time_ms"]:.2f}ms')
136
148
 
137
149
  if endpoint.get('error'):
138
- self.stdout.write(self.style.ERROR(f' Error: {endpoint["error"]}'))
150
+ error_type = endpoint.get('error_type', 'general')
151
+ if error_type == 'database':
152
+ self.stdout.write(self.style.WARNING(f' ⚠️ DB Error (multi-db): {endpoint["error"]}'))
153
+ else:
154
+ self.stdout.write(self.style.ERROR(f' Error: {endpoint["error"]}'))
155
+
156
+ # Show reason for warnings (e.g., 404 explanations)
157
+ if endpoint.get('reason') and status == 'warning':
158
+ self.stdout.write(self.style.WARNING(f' ⚠️ {endpoint["reason"]}'))
139
159
 
140
160
  if endpoint.get('required_auth'):
141
161
  self.stdout.write(f' 🔐 Required JWT authentication')
142
162
 
163
+ if endpoint.get('rate_limited'):
164
+ self.stdout.write(f' ⏱️ Rate limited (429)')
165
+
143
166
  self.stdout.write('')
144
167
 
145
168
  # Timestamp
@@ -117,7 +117,7 @@ class Command(BaseCommand):
117
117
 
118
118
  process_args = [
119
119
  executable_name,
120
- "django_cfg.modules.dramatiq_setup", # Broker module
120
+ "django_cfg.modules.django_tasks.dramatiq_setup", # Broker module
121
121
  "--processes", str(processes),
122
122
  "--threads", str(threads),
123
123
  "--worker-shutdown-timeout", str(worker_shutdown_timeout),
@@ -155,7 +155,7 @@ class Command(BaseCommand):
155
155
  # Build process arguments exactly like django_dramatiq
156
156
  process_args = [
157
157
  executable_name,
158
- "django_cfg.modules.dramatiq_setup", # Broker module
158
+ "django_cfg.modules.django_tasks.dramatiq_setup", # Broker module
159
159
  "--processes", str(processes),
160
160
  "--threads", str(threads),
161
161
  "--worker-shutdown-timeout", str(worker_shutdown_timeout),
@@ -213,7 +213,7 @@ class Command(BaseCommand):
213
213
  def _discover_tasks_modules(self):
214
214
  """Discover task modules like django_dramatiq does."""
215
215
  # Always include our broker setup module first
216
- tasks_modules = ["django_cfg.modules.dramatiq_setup"]
216
+ tasks_modules = ["django_cfg.modules.django_tasks.dramatiq_setup"]
217
217
 
218
218
  # Get task service for configuration
219
219
  task_service = get_task_service()
@@ -61,6 +61,7 @@ class DashboardManager(BaseCfgModule):
61
61
  NavigationItem(title="Overview", icon=Icons.DASHBOARD, link="/admin/"),
62
62
  NavigationItem(title="Settings", icon=Icons.SETTINGS, link="/admin/constance/config/"),
63
63
  NavigationItem(title="Health Check", icon=Icons.HEALTH_AND_SAFETY, link="/cfg/health/"),
64
+ NavigationItem(title="Endpoints Status", icon=Icons.API, link="/cfg/endpoints/drf/"),
64
65
  ]
65
66
  ),
66
67
  ]
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.5"
7
+ version = "1.4.6"
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",]
@@ -116,14 +116,6 @@ directory = "htmlcov"
116
116
  [tool.poetry.group.local]
117
117
  optional = true
118
118
 
119
- [tool.hatch.build.targets.wheel]
120
- packages = [ "src/django_cfg",]
121
- exclude = [ "scripts/",]
122
-
123
- [tool.hatch.build.targets.sdist]
124
- include = [ "src/django_cfg", "README.md", "LICENSE", "CHANGELOG.md", "CONTRIBUTING.md", "requirements*.txt", "MANIFEST.in",]
125
- exclude = [ "@*", "tests", "scripts", "*.log", ".env*",]
126
-
127
119
  [tool.poetry.group.dev.dependencies]
128
120
  tomlkit = "^0.13.3"
129
121
  build = "^1.3.0"
@@ -137,12 +129,20 @@ pytest = "^8.4.2"
137
129
  pytest-django = "^4.11.1"
138
130
  fakeredis = "^2.31.3"
139
131
 
132
+ [tool.hatch.build.targets.wheel]
133
+ packages = [ "src/django_cfg",]
134
+ exclude = [ "scripts/",]
135
+
136
+ [tool.hatch.build.targets.sdist]
137
+ include = [ "src/django_cfg", "README.md", "LICENSE", "CHANGELOG.md", "CONTRIBUTING.md", "requirements*.txt", "MANIFEST.in",]
138
+ exclude = [ "@*", "tests", "scripts", "*.log", ".env*",]
139
+
140
+ [tool.poetry.group.local.dependencies.django-ipc]
141
+ path = "/Users/markinmatrix/djangoipc"
142
+ develop = true
143
+
140
144
  [tool.hatch.build.targets.wheel.force-include]
141
145
  LICENSE = "django_cfg/LICENSE"
142
146
  "CONTRIBUTING.md" = "django_cfg/CONTRIBUTING.md"
143
147
  "CHANGELOG.md" = "django_cfg/CHANGELOG.md"
144
148
  "pyproject.toml" = "django_cfg/pyproject.toml"
145
-
146
- [tool.poetry.group.local.dependencies.django-ipc]
147
- path = "/Users/markinmatrix/djangoipc"
148
- develop = true
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-cfg
3
- Version: 1.4.5
3
+ Version: 1.4.6
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
@@ -103,9 +103,9 @@ django_cfg/apps/api/commands/__init__.py,sha256=FTmBMxSpI9rO6EljgkWn8e9pxh07ao5Y
103
103
  django_cfg/apps/api/commands/urls.py,sha256=k7auWLPi3FiCSkBwuDMK9R-jeHKfrFNzr7LvOWuGdxA,360
104
104
  django_cfg/apps/api/commands/views.py,sha256=OdSWzEjTM5SL9NJvaJB6pe1_wjevjWrOWWWwo4sB7Uo,10069
105
105
  django_cfg/apps/api/endpoints/__init__.py,sha256=uHjV4E24Aj0UFgv7bW1Z0kH_NFe8PItNFuS105vvRz0,108
106
- django_cfg/apps/api/endpoints/checker.py,sha256=n9jYoDHuwkMHRYGn5J5QEwtUQlS3nh3IV37B9B6Vsuc,12537
107
- django_cfg/apps/api/endpoints/drf_views.py,sha256=T5Fy4-0OY5nr2OpGM8YOdcXt53O7f1V79MQeXs0DUQo,1769
108
- django_cfg/apps/api/endpoints/serializers.py,sha256=bsTAKuk8rodeVPRln4bCGq95_Q0pw9ePnSRsFtb9RJY,2978
106
+ django_cfg/apps/api/endpoints/checker.py,sha256=mwaISxa26u0OTYEmM_ith_T03EeXkZQPA5bVrGrCsf0,19373
107
+ django_cfg/apps/api/endpoints/drf_views.py,sha256=oZLNq_OKs5DK9rZzGu5yVDwn7-GiIAS7MSokiQ3tGdo,1954
108
+ django_cfg/apps/api/endpoints/serializers.py,sha256=W5Az0m1jUi8rLLIMoKVBADk6KHFyAWrwJ_gzSinktno,3656
109
109
  django_cfg/apps/api/endpoints/tests.py,sha256=hvMgYVSWI_dfoSgb1ow9EwVXXUE9uZeCUEvNo1H995k,9992
110
110
  django_cfg/apps/api/endpoints/urls.py,sha256=QaGFwwA_rSq-qN0kcqM8LvTgTq_YlMd7MOAZCbC9E-k,372
111
111
  django_cfg/apps/api/endpoints/views.py,sha256=sqvl07krrJur4ZhzB3QWK1ppEvuVSEX0zSoIQ-farDQ,1242
@@ -358,7 +358,7 @@ django_cfg/apps/payments/management/commands/process_pending_payments.py,sha256=
358
358
  django_cfg/apps/payments/management/commands/test_providers.py,sha256=IvvJhTNw6KQm1EeWYTUMew0ZHzgUGWpG07JOhrpJEP0,18476
359
359
  django_cfg/apps/payments/middleware/__init__.py,sha256=eL5TmlCKmpW53Ift5rtwS8ss1wUqp4j2gzjGhcAQUQY,380
360
360
  django_cfg/apps/payments/middleware/api_access.py,sha256=lWX9A1UpIwPNC5320QcSVhHU_mg-LxzKaYG1bNjvN-I,16713
361
- django_cfg/apps/payments/middleware/rate_limiting.py,sha256=m7GqkbQvLQMGqXACwm8m-2DkcyhedCvqbH0pItBcfwU,14814
361
+ django_cfg/apps/payments/middleware/rate_limiting.py,sha256=nbObLQwIssxsVwovG6S_SiEpQcYmJIyAmA4tUtIt2cg,15163
362
362
  django_cfg/apps/payments/middleware/usage_tracking.py,sha256=dY7n6lZFUo-5a49VzJnZAT6UmbaU7kHei9Xu02KXACw,11815
363
363
  django_cfg/apps/payments/migrations/0001_initial.py,sha256=uLkgbaSvdPjoTHZ3pVB7aEgVPq_hAyQRWXFqC9-2oGc,48359
364
364
  django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py,sha256=MmxPMKYOzafTeVRj1cOOzVEDszKa_VYyqkUD8z7jZEk,1512
@@ -617,7 +617,7 @@ django_cfg/dashboard/sections/stats.py,sha256=k4ogZtZtR1CEkFTvoWgU-HTysr-Y2S4Lie
617
617
  django_cfg/dashboard/sections/system.py,sha256=IP4SJMPOL-gqDancE_g46ZbmlveYDvljpszRJmz1tSc,2025
618
618
  django_cfg/management/__init__.py,sha256=NrLAhiS59hqjy-bipOC1abNuRiNm5BpKXmjN05VzKbM,28
619
619
  django_cfg/management/commands/__init__.py,sha256=GqJDbjiwRa9Y9uvf695EZ-Y42vQIMHp5YkjhMoeAM9I,417
620
- django_cfg/management/commands/check_endpoints.py,sha256=-65NG14047mGTUnZZzupYp4vD7nFBxTLvAkwECbTQfg,5126
620
+ django_cfg/management/commands/check_endpoints.py,sha256=5I-8cbVD6aA9g6_j_JI76NoZCSwZTy21B7hxNKbR4OI,6275
621
621
  django_cfg/management/commands/check_settings.py,sha256=YyYKBMT3XaILD6PFKQGPALNnDv9rNtnF2U8RlkCoYiA,11770
622
622
  django_cfg/management/commands/clear_constance.py,sha256=tX6YUeJsmxJxXLQRDX3VUkUP9t6B1gAyVrBl4krQ6K0,8263
623
623
  django_cfg/management/commands/create_token.py,sha256=NspV-9j-T0dDjqY6ccJeuVqTB3v4ne1Jc43G2tKuioI,12015
@@ -625,7 +625,7 @@ django_cfg/management/commands/generate.py,sha256=tvBahlXOu63H7d-7Ree1WuR_6saebU
625
625
  django_cfg/management/commands/list_urls.py,sha256=3J1Lxhi6ImFOZQ9D047tR2lQ6Hl2Zd7f6YmEYohlmqQ,11261
626
626
  django_cfg/management/commands/migrate_all.py,sha256=gM5-Yp4k9dDWt8kpxBHAfvlSM8QW9ApUHV6tdRaVgVc,5454
627
627
  django_cfg/management/commands/migrator.py,sha256=qH1lARoKaInkTXN87lav7yxkVkWzHrZ6XZzwa5Sqrg0,16484
628
- django_cfg/management/commands/rundramatiq.py,sha256=DtwY5Qtr1wANrPVNvQZmDvHH9tzmVQK-2PNutQbOMgc,9385
628
+ django_cfg/management/commands/rundramatiq.py,sha256=I5_OlmfpeIujyF0G6nTr0JWQeQIFb6-kSTaYai2Oqck,9424
629
629
  django_cfg/management/commands/rundramatiq_simulator.py,sha256=dOLabc4bDVwpdWWFzi8H8SCIbVp0pg6dqviMLL0abxA,16101
630
630
  django_cfg/management/commands/runserver_ngrok.py,sha256=d_kiTqCgRN9MUNlJf_ERorPqTyd6QZVJPVTURd7mKLA,6520
631
631
  django_cfg/management/commands/script.py,sha256=Mrhpiz3leSzWOzULHTfsplIvRqmquaA67rVGQGKajsI,17538
@@ -856,7 +856,7 @@ django_cfg/modules/django_twilio/templates/guide.md,sha256=nZfwx-sgWyK5NApm93zOe
856
856
  django_cfg/modules/django_twilio/templates/sendgrid_otp_email.html,sha256=sXR6_D9hmOFfk9CrfPizpLddVhkRirBWpZd_ioEsxVk,6671
857
857
  django_cfg/modules/django_twilio/templates/sendgrid_test_data.json,sha256=fh1VyuSiDELHsS_CIz9gp7tlsMAEjaDOoqbAPSZ3yyo,339
858
858
  django_cfg/modules/django_unfold/__init__.py,sha256=Z91x1iGmkzlRbEb2L9OCFmYDKNAV9C4G3i15j5S0esc,1898
859
- django_cfg/modules/django_unfold/dashboard.py,sha256=8ZvrF45PFZVK5Jtu0kmAY7xT6A087_q6pAzKDicQ9So,18002
859
+ django_cfg/modules/django_unfold/dashboard.py,sha256=WXz7TUQp2mchdAcPBWn7HaFWwkHzEUomckKNZK224mY,18108
860
860
  django_cfg/modules/django_unfold/models.py,sha256=bY6QSSaH_-r9vOTkSQjxeIkl5RaED7XkxXkT8-W5stk,4014
861
861
  django_cfg/modules/django_unfold/system_monitor.py,sha256=cznZqldRJqiSLSJbs4U7R2rX8ClzoIpqdfXdXqI2iQw,6955
862
862
  django_cfg/modules/django_unfold/tailwind.py,sha256=X9o1K3QL0VwUISgJ26sLb6zkdK-00qiDuekqTw-fydc,10846
@@ -950,9 +950,9 @@ django_cfg/utils/version_check.py,sha256=jI4v3YMdQriUEeb_TvRl511sDghy6I75iKRDUaN
950
950
  django_cfg/CHANGELOG.md,sha256=jtT3EprqEJkqSUh7IraP73vQ8PmKUMdRtznQsEnqDZk,2052
951
951
  django_cfg/CONTRIBUTING.md,sha256=DU2kyQ6PU0Z24ob7O_OqKWEYHcZmJDgzw-lQCmu6uBg,3041
952
952
  django_cfg/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
953
- django_cfg/pyproject.toml,sha256=A3K-d7sTJDTOl2iTI727wbrLqllgOvg6TPp6w1ly5TQ,8216
954
- django_cfg-1.4.5.dist-info/METADATA,sha256=XthRxgznWbMSQD4kkLoeDm2wkOfVu3h1TJJ-Pw6osN8,22542
955
- django_cfg-1.4.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
956
- django_cfg-1.4.5.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
957
- django_cfg-1.4.5.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
958
- django_cfg-1.4.5.dist-info/RECORD,,
953
+ django_cfg/pyproject.toml,sha256=88nmJKNOuzteCBLdh9Py3ZzYRYY__N6BnFZiT5mSr4s,8216
954
+ django_cfg-1.4.6.dist-info/METADATA,sha256=7L9aQv_xWde-XuQHa12P4iVN_9-0tKNTJzdWJ0vfxmk,22542
955
+ django_cfg-1.4.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
956
+ django_cfg-1.4.6.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
957
+ django_cfg-1.4.6.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
958
+ django_cfg-1.4.6.dist-info/RECORD,,