django-cfg 1.4.24__py3-none-any.whl → 1.4.26__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.
@@ -131,6 +131,8 @@ class DjangoCfgRPCConfig(BaseModel):
131
131
  """
132
132
  Validate Redis URL format.
133
133
 
134
+ Allows environment variable templates like ${VAR:-default}.
135
+
134
136
  Args:
135
137
  v: Redis URL to validate
136
138
 
@@ -140,6 +142,11 @@ class DjangoCfgRPCConfig(BaseModel):
140
142
  Raises:
141
143
  ValueError: If URL format is invalid
142
144
  """
145
+ # Skip validation for environment variable templates
146
+ if v.startswith("${") and "}" in v:
147
+ return v
148
+
149
+ # Validate actual URLs
143
150
  if not v.startswith("redis://") and not v.startswith("rediss://"):
144
151
  raise ValueError(
145
152
  "redis_url must start with 'redis://' or 'rediss://' "
django_cfg/apps/urls.py CHANGED
@@ -4,10 +4,15 @@ Django CFG API URLs
4
4
  Built-in API endpoints for django_cfg functionality.
5
5
  """
6
6
 
7
+ import traceback
7
8
  from typing import List
8
9
 
9
10
  from django.urls import include, path
10
11
 
12
+ from django_cfg.modules.django_logging import get_logger, sanitize_extra
13
+
14
+ logger = get_logger(__name__)
15
+
11
16
 
12
17
  def get_enabled_cfg_apps() -> List[str]:
13
18
  """
@@ -85,10 +90,39 @@ def get_default_cfg_group():
85
90
 
86
91
 
87
92
  def _safe_include(pattern_path: str, module_path: str):
88
- """Helper to safely include URL module if it exists."""
93
+ """
94
+ Helper to safely include URL module if it exists.
95
+
96
+ Args:
97
+ pattern_path: URL pattern (e.g., 'cfg/knowbase/')
98
+ module_path: Module path (e.g., 'django_cfg.apps.knowbase.urls')
99
+
100
+ Returns:
101
+ URLPattern if successful, None if import fails
102
+ """
89
103
  try:
90
104
  return path(pattern_path, include(module_path))
91
- except ImportError:
105
+ except ImportError as e:
106
+ logger.warning(
107
+ f"Failed to import URL module '{module_path}' for pattern '{pattern_path}': {e}",
108
+ extra=sanitize_extra({
109
+ 'pattern': pattern_path,
110
+ 'module': module_path,
111
+ 'error': str(e),
112
+ })
113
+ )
114
+ logger.debug(f"Traceback for '{module_path}':\n{traceback.format_exc()}")
115
+ return None
116
+ except Exception as e:
117
+ logger.error(
118
+ f"Unexpected error including URL module '{module_path}' for pattern '{pattern_path}': {e}",
119
+ extra=sanitize_extra({
120
+ 'pattern': pattern_path,
121
+ 'module': module_path,
122
+ 'error': str(e),
123
+ 'traceback': traceback.format_exc(),
124
+ })
125
+ )
92
126
  return None
93
127
 
94
128
 
@@ -72,7 +72,13 @@ class SecurityBuilder:
72
72
  "localhost",
73
73
  "127.0.0.1",
74
74
  "[::1]", # IPv6 localhost
75
+ ".localhost", # Allow subdomains of localhost
75
76
  ])
77
+ # Allow all IP addresses in development (for Docker internal IPs, health checks, etc.)
78
+ # Regex pattern matches any IPv4 address
79
+ allowed_hosts.append(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
80
+ # Allow common Docker service names
81
+ allowed_hosts.extend(["django", "postgres", "redis", "frontend-cloud", "frontend-3d"])
76
82
 
77
83
  # Add security domains (both dev and prod)
78
84
  allowed_hosts.extend(self.config.security_domains)
@@ -93,10 +93,14 @@ class CacheConfig(BaseModel):
93
93
  @field_validator('redis_url')
94
94
  @classmethod
95
95
  def validate_redis_url(cls, v: Optional[str]) -> Optional[str]:
96
- """Validate Redis URL format."""
96
+ """Validate Redis URL format. Allows environment variable templates like ${VAR:-default}."""
97
97
  if v is None:
98
98
  return v
99
99
 
100
+ # Skip validation for environment variable templates
101
+ if v.startswith("${") and "}" in v:
102
+ return v
103
+
100
104
  if not v.startswith(('redis://', 'rediss://')):
101
105
  raise ValueError(
102
106
  "Redis URL must start with 'redis://' or 'rediss://' "
@@ -49,7 +49,11 @@ def validate_engine(v: Optional[str]) -> Optional[str]:
49
49
 
50
50
 
51
51
  def validate_name(v: str) -> str:
52
- """Validate database name or parse connection string."""
52
+ """Validate database name or parse connection string. Allows environment variable templates like ${VAR:-default}."""
53
+ # Skip validation for environment variable templates
54
+ if v.startswith("${") and "}" in v:
55
+ return v
56
+
53
57
  # Check if it's a connection string
54
58
  if "://" in v:
55
59
  try:
@@ -124,10 +124,14 @@ class TelegramConfig(BaseModel):
124
124
  @field_validator('webhook_url')
125
125
  @classmethod
126
126
  def validate_webhook_url(cls, v: Optional[str]) -> Optional[str]:
127
- """Validate webhook URL format."""
127
+ """Validate webhook URL format. Allows environment variable templates like ${VAR:-default}."""
128
128
  if v is None:
129
129
  return v
130
130
 
131
+ # Skip validation for environment variable templates
132
+ if v.startswith("${") and "}" in v:
133
+ return v
134
+
131
135
  if not v.startswith('https://'):
132
136
  raise ValueError("Webhook URL must use HTTPS")
133
137
 
@@ -4,11 +4,18 @@ Django Logging Modules for django_cfg.
4
4
  Auto-configuring logging utilities.
5
5
  """
6
6
 
7
- from .django_logger import DjangoLogger, get_logger
7
+ from .django_logger import (
8
+ RESERVED_LOG_ATTRS,
9
+ DjangoLogger,
10
+ get_logger,
11
+ sanitize_extra,
12
+ )
8
13
  from .logger import logger
9
14
 
10
15
  __all__ = [
11
16
  "logger",
12
17
  "DjangoLogger",
13
18
  "get_logger",
19
+ "sanitize_extra",
20
+ "RESERVED_LOG_ATTRS",
14
21
  ]
@@ -6,11 +6,53 @@ KISS principle: simple, unified logging configuration.
6
6
 
7
7
  import logging
8
8
  from pathlib import Path
9
- from typing import Dict
9
+ from typing import Any, Dict, Optional
10
10
 
11
11
  from ..base import BaseCfgModule
12
12
 
13
13
 
14
+ # Reserved LogRecord attributes that cannot be used in 'extra'
15
+ # Source: https://docs.python.org/3/library/logging.html#logrecord-attributes
16
+ RESERVED_LOG_ATTRS = {
17
+ 'name', 'msg', 'args', 'created', 'filename', 'funcName', 'levelname',
18
+ 'levelno', 'lineno', 'module', 'msecs', 'message', 'pathname', 'process',
19
+ 'processName', 'relativeCreated', 'thread', 'threadName', 'exc_info',
20
+ 'exc_text', 'stack_info', 'asctime', 'taskName'
21
+ }
22
+
23
+
24
+ def sanitize_extra(extra: Optional[Dict[str, Any]]) -> Dict[str, Any]:
25
+ """
26
+ Sanitize extra dict by prefixing reserved LogRecord attributes.
27
+
28
+ Python's logging module reserves certain attribute names in LogRecord.
29
+ Using these names in the 'extra' parameter causes a KeyError.
30
+ This function automatically prefixes conflicting keys with 'ctx_'.
31
+
32
+ Args:
33
+ extra: Dictionary of extra logging context
34
+
35
+ Returns:
36
+ Sanitized dictionary with no reserved attribute conflicts
37
+
38
+ Example:
39
+ >>> sanitize_extra({'module': 'myapp', 'user_id': 123})
40
+ {'ctx_module': 'myapp', 'user_id': 123}
41
+ """
42
+ if not extra:
43
+ return {}
44
+
45
+ sanitized = {}
46
+ for key, value in extra.items():
47
+ if key in RESERVED_LOG_ATTRS:
48
+ # Prefix reserved attributes with 'ctx_'
49
+ sanitized[f'ctx_{key}'] = value
50
+ else:
51
+ sanitized[key] = value
52
+
53
+ return sanitized
54
+
55
+
14
56
  class DjangoLogger(BaseCfgModule):
15
57
  """Simple auto-configuring logger."""
16
58
 
@@ -205,4 +247,4 @@ def get_logger(name: str = "django_cfg") -> logging.Logger:
205
247
 
206
248
 
207
249
  # Export public API
208
- __all__ = ['DjangoLogger', 'get_logger']
250
+ __all__ = ['DjangoLogger', 'get_logger', 'sanitize_extra', 'RESERVED_LOG_ATTRS']
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.24"
7
+ version = "1.4.26"
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.24
3
+ Version: 1.4.26
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
@@ -3,7 +3,7 @@ django_cfg/__init__.py,sha256=gzibt9NN48r10Bwz4ajKNEpxa_BIuaC2z2vE103-TKg,1619
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
6
- django_cfg/apps/urls.py,sha256=tTVZyVdXBAETqjWiztCNBcNyz-_Oz_eJy8WHoqbRIho,4974
6
+ django_cfg/apps/urls.py,sha256=ZCfTW14N4sVR6SUElheIuWN0UnLvQ-nJprBdMbmlNIM,6094
7
7
  django_cfg/apps/accounts/README.md,sha256=YkUYJ3iKMYTmm9ALK2PDnX75SDqZxgnkzNLCD5efxRs,8227
8
8
  django_cfg/apps/accounts/__init__.py,sha256=I9pq6H5oOFv59Du0VP-g7KsAUDhjKeUcP0spMvhtIjk,99
9
9
  django_cfg/apps/accounts/__models.py,sha256=fYhobXbGIYKnG4mZ_pwNePei8O-SHHqgBMhe6qhH3KQ,10075
@@ -125,7 +125,7 @@ django_cfg/apps/ipc/services/__init__.py,sha256=YdHT1pd7FZbjYFaPAf3rw_t3NIHA-6TI
125
125
  django_cfg/apps/ipc/services/monitor.py,sha256=NjZdNACiFggi9HgjXRwVDdj5DfloYeO5OuOe6XIAtZs,15368
126
126
  django_cfg/apps/ipc/services/client/__init__.py,sha256=diiN9vU_Tpq8_zML3dMHldpqycsbCt51JKiQC6pJ0ZU,444
127
127
  django_cfg/apps/ipc/services/client/client.py,sha256=aSCa_wjw0XPntd2KL2tQUsu3iOXdwT2KHOwFK9vb4xo,17479
128
- django_cfg/apps/ipc/services/client/config.py,sha256=JArEm7Gg4eEa3C3iuJ63Nr3QIZi_-n2sHbFWiIlOukg,5668
128
+ django_cfg/apps/ipc/services/client/config.py,sha256=A6xWzRfVb03hatoHmT8saePy3zPHgm0FOwXKT8QShOY,5895
129
129
  django_cfg/apps/ipc/services/client/exceptions.py,sha256=vAnZgH4ZYnqs7HJs11Rm6oqM-aTVAdPVcQZ9NcpE6Ik,5866
130
130
  django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs,sha256=FTumHeB_IwfsQvgm5_teC8UAw-baTGIrrR1nXDSMocc,16187
131
131
  django_cfg/apps/ipc/templates/django_cfg_ipc/base.html,sha256=naTYIAlETt00FVVBmz-dB5iPWekITagkTgGgKb25N60,2405
@@ -580,7 +580,7 @@ django_cfg/core/base/config_model.py,sha256=wVmeFkfdsAyJsHAGPBygsFI-oJltaROQcZFP
580
580
  django_cfg/core/builders/__init__.py,sha256=jkInI7_jbxcjitapohw6QmbJPpacnnID6V1JovqtOFM,282
581
581
  django_cfg/core/builders/apps_builder.py,sha256=QfgyaLTj--tQWRjYo4Negybq6Dr7YHi0l15n3ohMQtI,8312
582
582
  django_cfg/core/builders/middleware_builder.py,sha256=IiSSwnwnwAFpJCt0V6Y94vzhppG-qoITZAjE_O0tvqM,3704
583
- django_cfg/core/builders/security_builder.py,sha256=2Ynrcm8ZvR09xovtW4k33Ds1wjkhmPMbRSy1QT5sJQk,2724
583
+ django_cfg/core/builders/security_builder.py,sha256=CXCmwPpsZskLHxe3QcmbNvy4H2zpfsGrLQGP7kWAu6Y,3160
584
584
  django_cfg/core/environment/__init__.py,sha256=sMOIe9z1i51j8B1VGjpLHJMaeDsBfsgn1TmL03FIeNo,141
585
585
  django_cfg/core/environment/detector.py,sha256=bI9k4fdK8Nbfl61ZcQ3yNbm3WlmK-L0xB-g-aXKUdhA,8923
586
586
  django_cfg/core/generation/__init__.py,sha256=0cYgeQ5EDN6sscQuFnXsFCy6m1BKgv_izNz0a1TlYSc,1355
@@ -675,7 +675,7 @@ django_cfg/models/django/environment.py,sha256=lBCHBs1lphv9tlu1BCTfLZeH_kUame0p6
675
675
  django_cfg/models/django/openapi.py,sha256=avE3iapaCj8eyOqVUum_v2EExR3V-hwHrexqtXMHtTQ,3739
676
676
  django_cfg/models/django/revolution_legacy.py,sha256=Z4SPUS7QSv62EuPAeFFoXGEgqLmdXnVEr7Ofk1IDtVc,8918
677
677
  django_cfg/models/infrastructure/__init__.py,sha256=VdOapoHa_yPtme8dLEJsmOlmT2klGs8G62W5CES8e2E,369
678
- django_cfg/models/infrastructure/cache.py,sha256=nEKlvVGWkj6kaaH9DxZpiJD0_NZQysqtclk0NA8s9Q0,11825
678
+ django_cfg/models/infrastructure/cache.py,sha256=-eN2GjDJo-4Ta6b4hamG-AuBHNEKoYK_LqVOt_fJvZE,12012
679
679
  django_cfg/models/infrastructure/logging.py,sha256=dbbDJ0hjJKigKKTmqKnh7hkLzmipm3_abp-whaK1lOY,10314
680
680
  django_cfg/models/infrastructure/security.py,sha256=buBduEiZT2PfpjqxBna7jeXC8x_dJHKFhpYa9SzNHzw,6202
681
681
  django_cfg/models/infrastructure/database/__init__.py,sha256=3BXDKeJwvjyFws4MAjox_PIp_g0N1YuXv6qNjRb7wqQ,500
@@ -683,7 +683,7 @@ django_cfg/models/infrastructure/database/config.py,sha256=GLGwUPmZ3s_wyjRJWOmV8
683
683
  django_cfg/models/infrastructure/database/converters.py,sha256=xRqL5rOSqOGA06KK5Z6nbx6waUm9WMAtHM4mxgXSI-0,3083
684
684
  django_cfg/models/infrastructure/database/parsers.py,sha256=8q1hq95SHKHxL2_AcHRc9HkSB_ide1zTjbDn_WDyQXk,2667
685
685
  django_cfg/models/infrastructure/database/routing.py,sha256=Z22AeCjKJB-aV0Scq9u1RDTRspKEf4jmCKwzWLIqDOk,1999
686
- django_cfg/models/infrastructure/database/validators.py,sha256=bQWTHb7VQo-GKC0qt0RLI0OdZFrodwSFsPnoAoMq3Mo,5679
686
+ django_cfg/models/infrastructure/database/validators.py,sha256=VA6Y-t7DdU7cWI42kXTxUKMhf1XwNJ6_NkxPWlXv-l8,5854
687
687
  django_cfg/models/ngrok/__init__.py,sha256=O_fMQ9teQDluusZr2XcTUirSJReaI_zaXN_vU_aoR_k,163
688
688
  django_cfg/models/ngrok/auth.py,sha256=Domjz612_9GQit5lf2v2GmcVLc4gBfyAyJxO8LaVXCg,920
689
689
  django_cfg/models/ngrok/config.py,sha256=nzW0_4FjeBvWDxVZCVzkOuUSBftt-8bmyofbm7nMaHc,2421
@@ -697,7 +697,7 @@ django_cfg/models/payments/providers/nowpayments.py,sha256=ds0FruxRkH1Oi-0AYoYjO
697
697
  django_cfg/models/services/__init__.py,sha256=K3mit3rKumqC4XiCUU0BziZC3143j1SM9j0lEAwhtRY,419
698
698
  django_cfg/models/services/base.py,sha256=q_QggR8i_6qoiIvbAubddCklpfjVfeLP3Bv2bsdT4BQ,1607
699
699
  django_cfg/models/services/email.py,sha256=LyBGG1OVM8RefwMEZDhYwADmnBexEcQWxzYwOeF0du8,4694
700
- django_cfg/models/services/telegram.py,sha256=MwdJjGtG8dtbmmt55DxNzEtK4FWAeWIOLs7ONkxSLLE,4623
700
+ django_cfg/models/services/telegram.py,sha256=wrMqOGZCp1KAxRmCn5oxMU0BMBgbzsQD6o0Fi_7kfIY,4810
701
701
  django_cfg/models/tasks/__init__.py,sha256=6t33r91zv_nyve1NfZeEziF0_mwgfAFuJpsnm38Ps3k,1320
702
702
  django_cfg/models/tasks/backends.py,sha256=mGB92TdD-zwrVckFRZ0EA0WWWLiR-gRM2fVv5rINOzM,6823
703
703
  django_cfg/models/tasks/config.py,sha256=dqDoiqMWsSdKJrLAyGcKyzBukZdjhz7kR5_LKFZBiss,9305
@@ -957,8 +957,8 @@ django_cfg/modules/django_llm/translator/utils/prompt_builder.py,sha256=ZSOnm9_e
957
957
  django_cfg/modules/django_llm/translator/utils/text_utils.py,sha256=_7Z4n1oi2_Zl3NU8kbRiLa2dCRViyzFG6-sUIcEmnmE,2925
958
958
  django_cfg/modules/django_logging/FIXES_SUMMARY.md,sha256=O2Dvecw-9gEbQkZVq6HfMrC3lfqkRMfn5YaHAp_Ay1s,7741
959
959
  django_cfg/modules/django_logging/LOGGING_GUIDE.md,sha256=WVXefLk7tSEud7wo6sN-lk07buwQpkFAa4hV7Y5WYSk,14402
960
- django_cfg/modules/django_logging/__init__.py,sha256=we0tbKOore_cO3GQBtOgEovFi6EUP5SoGTHncBofTws,231
961
- django_cfg/modules/django_logging/django_logger.py,sha256=WPSiJq_TiDs6BzzuYxLVu_e3E4iY3NkFdcalB7iZD18,8427
960
+ django_cfg/modules/django_logging/__init__.py,sha256=T-R62HonMGkyyuyWzWtS94R5vteJlPgfT7GUBGMFJzg,336
961
+ django_cfg/modules/django_logging/django_logger.py,sha256=naiuFtiFueZtHH211hXSFNkPqc5apNST9WXXJ7aF84g,9866
962
962
  django_cfg/modules/django_logging/logger.py,sha256=_8v70X5imm1KLDjMsgID5tTtpG6eSh4ozFZfYnb0IU4,10915
963
963
  django_cfg/modules/django_ngrok/__init__.py,sha256=CYHHSKRIHIP3E_NpUnFX-D2exWZK8o_6wfqh1T8ZIyo,792
964
964
  django_cfg/modules/django_ngrok/service.py,sha256=Xkh9Gl6Rth32UcT0UYjD0ckROHFw6FJLKDtg3VzWLQY,9304
@@ -1129,9 +1129,9 @@ django_cfg/utils/version_check.py,sha256=WO51J2m2e-wVqWCRwbultEwu3q1lQasV67Mw2aa
1129
1129
  django_cfg/CHANGELOG.md,sha256=jtT3EprqEJkqSUh7IraP73vQ8PmKUMdRtznQsEnqDZk,2052
1130
1130
  django_cfg/CONTRIBUTING.md,sha256=DU2kyQ6PU0Z24ob7O_OqKWEYHcZmJDgzw-lQCmu6uBg,3041
1131
1131
  django_cfg/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
1132
- django_cfg/pyproject.toml,sha256=6k1IBPo3C2q8FyNhuoZAuWMrcP5xpnBmUS-XSyVg0X0,8210
1133
- django_cfg-1.4.24.dist-info/METADATA,sha256=aDzSjUVtIFs_Vjf_SN-s4vY41l5A3RAACK-mRrUy5oE,22533
1134
- django_cfg-1.4.24.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1135
- django_cfg-1.4.24.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
1136
- django_cfg-1.4.24.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
1137
- django_cfg-1.4.24.dist-info/RECORD,,
1132
+ django_cfg/pyproject.toml,sha256=OWxt7DEhKm-uRtTJpClmBxxZJFcAmrZfzWj4VTvtOPY,8210
1133
+ django_cfg-1.4.26.dist-info/METADATA,sha256=POQ9QT88Nx-kOV53_fuQ4bWAXFAJEb13C0blL5NbMsQ,22533
1134
+ django_cfg-1.4.26.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1135
+ django_cfg-1.4.26.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
1136
+ django_cfg-1.4.26.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
1137
+ django_cfg-1.4.26.dist-info/RECORD,,