django-cfg 1.2.2__py3-none-any.whl → 1.2.5__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.
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.2.2"
35
+ __version__ = "1.2.5"
36
36
  __license__ = "MIT"
37
37
 
38
38
  # Import registry for organized lazy loading
django_cfg/core/config.py CHANGED
@@ -245,14 +245,16 @@ class DjangoConfig(BaseModel):
245
245
  )
246
246
 
247
247
  # === API Configuration ===
248
+ # Note: DRF base configuration is handled by django-revolution
249
+ # These fields provide additional/extended settings on top of Revolution
248
250
  drf: Optional[DRFConfig] = Field(
249
251
  default=None,
250
- description="Django REST Framework configuration",
252
+ description="Extended Django REST Framework configuration (supplements Revolution)",
251
253
  )
252
254
 
253
255
  spectacular: Optional[SpectacularConfig] = Field(
254
256
  default=None,
255
- description="DRF Spectacular OpenAPI/Swagger configuration",
257
+ description="Extended DRF Spectacular configuration (supplements Revolution)",
256
258
  )
257
259
 
258
260
  # === Limits Configuration ===
@@ -440,30 +440,52 @@ class SettingsGenerator:
440
440
  settings.update(revolution_settings)
441
441
  integrations.append("django_revolution")
442
442
 
443
- # Automatically generate DRF configuration if RevolutionConfig has DRF parameters
444
- if hasattr(config.revolution, "get_drf_config_kwargs"):
445
- try:
446
- from django_revolution.drf_config import create_drf_config
447
-
448
- # Get DRF config parameters from RevolutionConfig
449
- drf_kwargs = config.revolution.get_drf_config_kwargs()
450
-
451
- # Create DRF config with Django Revolution integration
452
- drf_config = create_drf_config(**drf_kwargs)
453
-
454
- # Get Django settings from DRF config
455
- drf_settings = drf_config.get_django_settings()
456
- settings.update(drf_settings)
457
- integrations.append("drf_spectacular")
458
-
459
- except ImportError as e:
460
- logger.warning(f"Could not import django_revolution.drf_config: {e}")
461
- except Exception as e:
462
- logger.warning(f"Could not generate DRF config from Revolution: {e}")
463
-
464
- # Note: DRF and Spectacular configuration is now handled through Revolution
465
- # The old config.drf and config.spectacular fields are deprecated
466
- # in favor of Revolution's integrated DRF configuration
443
+ # Automatically generate DRF configuration using Revolution's core_config
444
+ try:
445
+ from django_revolution import create_drf_spectacular_config
446
+
447
+ # Extract DRF parameters from RevolutionConfig
448
+ drf_kwargs = {
449
+ "title": getattr(config.revolution, "drf_title", "API"),
450
+ "description": getattr(config.revolution, "drf_description", "RESTful API"),
451
+ "version": getattr(config.revolution, "drf_version", "1.0.0"),
452
+ "schema_path_prefix": f"/{config.revolution.api_prefix}/",
453
+ "enable_browsable_api": getattr(config.revolution, "drf_enable_browsable_api", False),
454
+ "enable_throttling": getattr(config.revolution, "drf_enable_throttling", False),
455
+ }
456
+
457
+ # Create DRF + Spectacular config with Revolution's comprehensive settings (includes proper enum config)
458
+ drf_settings = create_drf_spectacular_config(**drf_kwargs)
459
+ settings.update(drf_settings)
460
+ logger.info("🚀 Generated DRF + Spectacular settings using Revolution's create_drf_spectacular_config")
461
+ integrations.append("drf_spectacular")
462
+
463
+ except ImportError as e:
464
+ logger.warning(f"Could not import django_revolution.create_drf_spectacular_config: {e}")
465
+ except Exception as e:
466
+ logger.warning(f"Could not generate DRF config from Revolution: {e}")
467
+
468
+ # Apply django-cfg DRF/Spectacular extensions (only if explicitly configured by user)
469
+ try:
470
+ if config.drf or config.spectacular:
471
+ # Extend REST_FRAMEWORK settings if config.drf is provided
472
+ if config.drf and "REST_FRAMEWORK" in settings:
473
+ drf_extensions = config.drf.get_rest_framework_settings()
474
+ # Merge with existing settings (django-cfg extensions take priority)
475
+ settings["REST_FRAMEWORK"].update(drf_extensions)
476
+ logger.info("🔧 Extended REST_FRAMEWORK settings with django-cfg DRF config")
477
+
478
+ # Extend SPECTACULAR_SETTINGS if config.spectacular is provided
479
+ if config.spectacular and "SPECTACULAR_SETTINGS" in settings:
480
+ spectacular_extensions = config.spectacular.get_spectacular_settings()
481
+ # Merge with existing settings (django-cfg extensions take priority)
482
+ settings["SPECTACULAR_SETTINGS"].update(spectacular_extensions)
483
+ logger.info("🔧 Extended SPECTACULAR_SETTINGS with django-cfg Spectacular config")
484
+
485
+ integrations.append("drf_spectacular_extended")
486
+
487
+ except Exception as e:
488
+ logger.warning(f"Could not apply DRF/Spectacular extensions from django-cfg: {e}")
467
489
 
468
490
  # Add integration info for debugging
469
491
  if integrations:
@@ -0,0 +1,99 @@
1
+ """
2
+ Base Configuration Module for Django CFG
3
+
4
+ Provides a unified base class for all auto-configuration modules.
5
+ """
6
+
7
+ from typing import Optional, TYPE_CHECKING
8
+ from abc import ABC, abstractmethod
9
+
10
+ if TYPE_CHECKING:
11
+ from django_cfg.core.config import DjangoConfig
12
+
13
+
14
+ class BaseCfgAutoModule(ABC):
15
+ """
16
+ Base class for all django-cfg auto-configuration modules.
17
+
18
+ Provides unified configuration access and smart defaults.
19
+ Designed to be used in generation.py without circular imports.
20
+ """
21
+
22
+ def __init__(self, config: Optional["DjangoConfig"] = None):
23
+ """
24
+ Initialize the auto-configuration module.
25
+
26
+ Args:
27
+ config: DjangoConfig instance (passed from generation.py)
28
+ """
29
+ self._config = config
30
+
31
+ def set_config(self, config: "DjangoConfig") -> None:
32
+ """
33
+ Set the configuration instance.
34
+
35
+ Args:
36
+ config: The DjangoConfig instance
37
+ """
38
+ self._config = config
39
+
40
+ def get_config(self) -> Optional["DjangoConfig"]:
41
+ """Get the current configuration instance."""
42
+ return self._config
43
+
44
+ def has_config_field(self, field_name: str) -> bool:
45
+ """
46
+ Check if config has a specific field with a non-None value.
47
+
48
+ Args:
49
+ field_name: Name of the field to check
50
+
51
+ Returns:
52
+ True if field exists and is not None
53
+ """
54
+ if not self._config:
55
+ return False
56
+ return hasattr(self._config, field_name) and getattr(self._config, field_name) is not None
57
+
58
+ def get_config_field(self, field_name: str, default=None):
59
+ """
60
+ Get a field value from config with fallback.
61
+
62
+ Args:
63
+ field_name: Name of the field to get
64
+ default: Default value if field doesn't exist or is None
65
+
66
+ Returns:
67
+ Field value or default
68
+ """
69
+ if not self._config:
70
+ return default
71
+ return getattr(self._config, field_name, default)
72
+
73
+ @abstractmethod
74
+ def get_smart_defaults(self):
75
+ """
76
+ Get smart default configuration for this module.
77
+
78
+ Returns:
79
+ Configuration object with intelligent defaults
80
+ """
81
+ pass
82
+
83
+ @abstractmethod
84
+ def get_module_config(self):
85
+ """
86
+ Get the final configuration for this module.
87
+
88
+ Uses project config if available, otherwise returns smart defaults.
89
+
90
+ Returns:
91
+ Configuration object ready for Django settings generation
92
+ """
93
+ pass
94
+
95
+
96
+ # Export the base class
97
+ __all__ = [
98
+ "BaseCfgAutoModule",
99
+ ]
@@ -0,0 +1,118 @@
1
+ """
2
+ CORS Configuration Models
3
+
4
+ Handles CORS settings with smart defaults and custom header support.
5
+ """
6
+
7
+ from typing import List, Optional
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class CORSConfig(BaseModel):
12
+ """CORS configuration with smart defaults"""
13
+
14
+ # Custom headers (user-defined)
15
+ custom_headers: List[str] = Field(
16
+ default_factory=list,
17
+ description="Custom headers to allow (e.g., ['x-api-key'])"
18
+ )
19
+
20
+ # Origins
21
+ allowed_origins: Optional[List[str]] = Field(
22
+ default=None,
23
+ description="Allowed origins (None = all origins allowed)"
24
+ )
25
+
26
+ # Credentials
27
+ allow_credentials: bool = Field(
28
+ default=True,
29
+ description="Allow credentials in CORS requests"
30
+ )
31
+
32
+ # Methods
33
+ allowed_methods: List[str] = Field(
34
+ default_factory=lambda: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
35
+ description="Allowed HTTP methods"
36
+ )
37
+
38
+ # Max age
39
+ max_age: int = Field(
40
+ default=86400, # 24 hours
41
+ description="Max age for preflight requests in seconds"
42
+ )
43
+
44
+ def get_all_headers(self) -> List[str]:
45
+ """Get all headers (standard + custom)"""
46
+ # Standard headers that are always needed
47
+ standard_headers = [
48
+ "accept",
49
+ "accept-encoding",
50
+ "authorization",
51
+ "content-type",
52
+ "dnt",
53
+ "origin",
54
+ "user-agent",
55
+ "x-csrftoken",
56
+ "x-requested-with",
57
+ ]
58
+
59
+ # Combine with custom headers
60
+ all_headers = standard_headers + self.custom_headers
61
+
62
+ # Remove duplicates while preserving order
63
+ seen = set()
64
+ unique_headers = []
65
+ for header in all_headers:
66
+ if header.lower() not in seen:
67
+ seen.add(header.lower())
68
+ unique_headers.append(header)
69
+
70
+ return unique_headers
71
+
72
+
73
+ # ===== AUTO-CONFIGURATION MODULE =====
74
+
75
+ try:
76
+ from .cfg import BaseCfgAutoModule
77
+
78
+ class DjangoCORSModule(BaseCfgAutoModule):
79
+ """Django CORS auto-configuration module"""
80
+
81
+ def get_smart_defaults(self) -> CORSConfig:
82
+ """Get smart default CORS configuration"""
83
+ return CORSConfig(
84
+ custom_headers=["x-api-key"],
85
+ allowed_origins=None, # Allow all origins by default
86
+ allow_credentials=True,
87
+ allowed_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
88
+ max_age=86400, # 24 hours
89
+ )
90
+
91
+ def get_module_config(self) -> CORSConfig:
92
+ """Get CORS configuration with project overrides"""
93
+ if self.has_config_field('cors'):
94
+ return self.get_config_field('cors')
95
+ return self.get_smart_defaults()
96
+
97
+ def get_cors_config(self, custom_headers: Optional[List[str]] = None) -> CORSConfig:
98
+ """Get CORS configuration with custom headers support"""
99
+ # Get base config (project or defaults)
100
+ cors_config = self.get_module_config()
101
+
102
+ # Override custom headers if provided
103
+ if custom_headers:
104
+ cors_config = cors_config.model_copy()
105
+ cors_config.custom_headers = custom_headers
106
+
107
+ return cors_config
108
+
109
+ except ImportError:
110
+ # BaseCfgAutoModule not available - auto-configuration disabled
111
+ DjangoCORSModule = None
112
+
113
+
114
+ # Export all models
115
+ __all__ = [
116
+ "CORSConfig",
117
+ "DjangoCORSModule",
118
+ ]
django_cfg/models/drf.py CHANGED
@@ -122,6 +122,9 @@ class SpectacularConfig(BaseModel):
122
122
  description="Post-processing hooks"
123
123
  )
124
124
 
125
+ # NOTE: Enum generation settings are handled by django-revolution
126
+ # Only override if you need different values than Revolution defaults
127
+
125
128
  # Enum overrides
126
129
  enum_name_overrides: Dict[str, str] = Field(
127
130
  default_factory=lambda: {
@@ -131,22 +134,26 @@ class SpectacularConfig(BaseModel):
131
134
  )
132
135
 
133
136
  def get_spectacular_settings(self) -> Dict[str, Any]:
134
- """Get complete Spectacular settings."""
137
+ """
138
+ Get django-cfg Spectacular extensions.
139
+
140
+ NOTE: This extends Revolution's base settings, not replaces them.
141
+ Only include settings that are unique to django-cfg or critical fixes.
142
+ """
135
143
  settings = {
136
- # Basic information
137
- "TITLE": self.title,
138
- "DESCRIPTION": self.description,
139
- "VERSION": self.version,
140
- "SERVE_INCLUDE_SCHEMA": self.serve_include_schema,
141
- "SCHEMA_PATH_PREFIX": self.schema_path_prefix,
142
- "COMPONENT_SPLIT_REQUEST": self.component_split_request,
143
- "SORT_OPERATIONS": self.sort_operations,
144
- # UI Settings
145
- "SWAGGER_UI_SETTINGS": self.swagger_ui_settings.to_dict(),
146
- "REDOC_UI_SETTINGS": self.redoc_ui_settings.to_dict(),
147
- # Processing
148
- "POSTPROCESSING_HOOKS": self.postprocessing_hooks,
149
- "ENUM_NAME_OVERRIDES": self.enum_name_overrides,
144
+ # django-cfg specific UI enhancements
145
+ "REDOC_UI_SETTINGS": self.redoc_ui_settings.to_dict(), # Revolution doesn't have custom Redoc settings
146
+
147
+ # django-cfg specific processing extensions
148
+ "ENUM_NAME_OVERRIDES": self.enum_name_overrides, # Custom enum overrides
149
+
150
+ # CRITICAL: Ensure enum generation is always enabled (fix Revolution gaps)
151
+ # These settings ensure proper enum generation even if Revolution config changes
152
+ "GENERATE_ENUM_FROM_CHOICES": True,
153
+ "ENUM_GENERATE_CHOICE_FROM_PATH": True,
154
+ "ENUM_NAME_SUFFIX": "Enum",
155
+ "CAMELIZE_NAMES": False,
156
+ "ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE": False,
150
157
  }
151
158
 
152
159
  # Add optional fields if present
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-cfg
3
- Version: 1.2.2
4
- Summary: 🚀 Production-ready Django configuration framework with type-safe settings, smart automation, and modern developer experience
5
- Project-URL: Homepage, https://github.com/markolofsen/django-cfg
6
- Project-URL: Documentation, https://django-cfg.readthedocs.io
7
- Project-URL: Repository, https://github.com/markolofsen/django-cfg.git
8
- Project-URL: Issues, https://github.com/markolofsen/django-cfg/issues
9
- Project-URL: Changelog, https://github.com/markolofsen/django-cfg/blob/main/CHANGELOG.md
3
+ Version: 1.2.5
4
+ Summary: 🚀 Next-gen Django configuration: type-safety, AI features, blazing-fast setup, and automated best practices — all in one.
5
+ Project-URL: Homepage, https://django-cfg.com
6
+ Project-URL: Documentation, https://django-cfg.com
7
+ Project-URL: Repository, https://django-cfg.com
8
+ Project-URL: Issues, https://django-cfg.com
9
+ Project-URL: Changelog, https://django-cfg.com
10
10
  Author-email: ReformsAI Team <dev@reforms.ai>
11
11
  Maintainer-email: ReformsAI Team <dev@reforms.ai>
12
12
  License: MIT
@@ -47,7 +47,7 @@ Requires-Dist: django-import-export<5.0,>=4.3.0
47
47
  Requires-Dist: django-json-widget<3.0,>=2.0.0
48
48
  Requires-Dist: django-ratelimit<5.0.0,>=4.1.0
49
49
  Requires-Dist: django-redis<7.0,>=6.0.0
50
- Requires-Dist: django-revolution<2.0,>=1.0.35
50
+ Requires-Dist: django-revolution<2.0,>=1.0.43
51
51
  Requires-Dist: django-unfold<1.0,>=0.64.0
52
52
  Requires-Dist: djangorestframework-simplejwt<6.0,>=5.5.0
53
53
  Requires-Dist: djangorestframework-simplejwt[token-blacklist]<6.0,>=5.5.0
@@ -1,5 +1,5 @@
1
1
  django_cfg/README.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- django_cfg/__init__.py,sha256=vaV1dOHjmG0bQG8EVAfNGOmb37ySIF-WZ33RKip8VHI,1630
2
+ django_cfg/__init__.py,sha256=7xxzM_weNWt9rcTcCCF7ubv7Zep_cM7TAfgYUMq45hw,1630
3
3
  django_cfg/apps.py,sha256=k84brkeXJI7EgKZLEpTkM9YFZofKI4PzhFOn1cl9Msc,1656
4
4
  django_cfg/config.py,sha256=0cuRJVEnf03WzvEqhwzLvn9Zi1805C5KG1yk27ekABA,1190
5
5
  django_cfg/urls.py,sha256=bpRFjMonQuk4UCUMxx4ueBX3YDNB7HXKFwEghQ3KR3o,793
@@ -284,10 +284,10 @@ django_cfg/cli/commands/__init__.py,sha256=EKLXDAx-QttnGmdjsmVANAfhxWplxl2V_2I0S
284
284
  django_cfg/cli/commands/create_project.py,sha256=evR1AziJ_ofgW5biaT73dmQEUUkwDIwbCekTvX7iMro,21023
285
285
  django_cfg/cli/commands/info.py,sha256=ynOEvsAsAr4A2Xb1zhu2CCOB5-RBkDIA-iuWLo6DrxA,4922
286
286
  django_cfg/core/__init__.py,sha256=eVK57qFOok9kTeHoNEMQ1BplkUOaQ7NB9kP9eQK1vg0,358
287
- django_cfg/core/config.py,sha256=cjcDgVsnwd2ft8vB6PBSuWhmJcH5dR0NSUXY-2oJrwo,26106
287
+ django_cfg/core/config.py,sha256=XZmW4tqHYi8zUOywpJ_8DqeFUzwwflMshZnwrpddjtw,26302
288
288
  django_cfg/core/environment.py,sha256=MAoEPqIPsLVhSANT2Bz4nnus2wmbMW0RCOQxhQfDrDc,9106
289
289
  django_cfg/core/exceptions.py,sha256=RTQEoU3PfR8lqqNNv5ayd_HY2yJLs3eioqUy8VM6AG4,10378
290
- django_cfg/core/generation.py,sha256=U5kXV4sBcSaPyDkZbYc-6v-hK0xGGGlXC0uwBnyc0_I,21187
290
+ django_cfg/core/generation.py,sha256=OJjf54QmsbWAEEyrvO2AoPONP-8eK3zU4YDWe2w-H8E,22935
291
291
  django_cfg/core/integration.py,sha256=5kzkZSd45ij0rfrBdeNUYnDalObqvK__XpoP31xFYKo,5224
292
292
  django_cfg/core/validation.py,sha256=PhwDBTs24nuM3xqfIT3hXmN88VrhMT-Bk8Yb8FxjmPk,6590
293
293
  django_cfg/management/__init__.py,sha256=NrLAhiS59hqjy-bipOC1abNuRiNm5BpKXmjN05VzKbM,28
@@ -321,9 +321,11 @@ django_cfg/models/__init__.py,sha256=du24ZdqKdvc3VNXjgufEz_6B6gxdHtlqVHG4PJWaOQM
321
321
  django_cfg/models/api.py,sha256=bCsQR3CVsAUs78m1Q1aFpPTNbvGtr1cq-fuxdCK2-JI,4596
322
322
  django_cfg/models/base.py,sha256=Gy_B2dXSu-lYQjxlCaP6tkngFKhBFkCPeMNIpkkkKSU,10408
323
323
  django_cfg/models/cache.py,sha256=oAiBPM89pZfLlruIzDVFmqNmHdOPk3a2KAgB9jubUPg,12133
324
+ django_cfg/models/cfg.py,sha256=P6YowmE-VOqp_L25Ijxj2hjjNhB9xtlm8G35DHWqTfk,2702
324
325
  django_cfg/models/constance.py,sha256=hzO3GuyGk5vFRD7pNWhiQZ7jz_JgFfJ_ry2vsf4108s,6818
326
+ django_cfg/models/cors.py,sha256=lozVoQJVehhNL1qzOhvAkd4ZpgwAdOJ-OUX7wkJp5W4,3567
325
327
  django_cfg/models/database.py,sha256=LpcmEljjr-2pnR8UVSziUNSnucsojfh8PcgayhrWqK4,16875
326
- django_cfg/models/drf.py,sha256=zOCj8yiNFMxWVIYIBWnaipJ8DywAGSNsz-LlsTGHsGQ,9362
328
+ django_cfg/models/drf.py,sha256=ZzxYv1KjA-iYLRGZj5HUcIyO9tg2B0Gudp1aZz9QNVg,9741
327
329
  django_cfg/models/email.py,sha256=y97W9I-Mm3TyWWLCLRh_2GO0u2PBVT2olMInqOyBiq8,4843
328
330
  django_cfg/models/environment.py,sha256=jM_KPXrxQHTFOhbaJTkkZL0Yml8bAM2Q7DJ69QsedX8,9438
329
331
  django_cfg/models/jwt.py,sha256=3R_dpLmVZIcH4zdtwA4qKnuCB8RZQACrgsbbgWY2q4c,9025
@@ -434,8 +436,8 @@ django_cfg/utils/path_resolution.py,sha256=C9As6p4Q9l3VeoVkFDRPQWGrzAWf8O8UxLVka
434
436
  django_cfg/utils/smart_defaults.py,sha256=b6A1z7VO1NJGq0oUQXN5P97c3k_Ssgw6qUi0mK-4TlM,19786
435
437
  django_cfg/utils/toolkit.py,sha256=Td8_iXNaftonF_xdZP4Y3uO65nuA_4_zditn5Q_Pfcw,23310
436
438
  django_cfg/utils/version_check.py,sha256=jI4v3YMdQriUEeb_TvRl511sDghy6I75iKRDUaNpucs,4800
437
- django_cfg-1.2.2.dist-info/METADATA,sha256=lqpgLyCzW9gfyYxukos7i7ohkUmutMF63ceutC9tqgE,45738
438
- django_cfg-1.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
439
- django_cfg-1.2.2.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
440
- django_cfg-1.2.2.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
441
- django_cfg-1.2.2.dist-info/RECORD,,
439
+ django_cfg-1.2.5.dist-info/METADATA,sha256=NMqpUdKjOVPigv8vWj7V1omQJ3eBFlSEN0lJqAOZqeE,45616
440
+ django_cfg-1.2.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
441
+ django_cfg-1.2.5.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
442
+ django_cfg-1.2.5.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
443
+ django_cfg-1.2.5.dist-info/RECORD,,