Declarative “Settings Objects” (roadmap)

Some time ago we had a roadmap session in which I said I’d take a look at the idea of declarative “Settings Objects”. The rough outline/idea from the meeting for this was;

A better way to encapsulate related settings. See the EMAIL_ settings. Other examples include: various security headers, cookie settings, and I’m sure there are more. A better approach would be some kind of settings object — maybe dataclass based, or similar to those in Pydantic or attrs or elsewhere.

So to first consider those EMAIL_ settings.

EMAIL_BACKEND = '...'
EMAIL_HOST = 'smtp.example.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_TIMEOUT = None

They are grouped only by naming convention, with no enforcement/type hints/validation.

Thinking about how this might be improved, I think there may be two potential approaches, where the idea is to not introduce dependencies and trying to keep things familiar.

  1. Dataclass-based “settings groups”

    from dataclasses import dataclass, field
    
    @dataclass
    class EmailSettings:
        BACKEND: str = "django.core.mail.backends.smtp.EmailBackend"
        HOST: str = "localhost"
        PORT: int = 25
        HOST_USER: str = ""
        HOST_PASSWORD: str = ""
        USE_TLS: bool = False
        USE_SSL: bool = False
        TIMEOUT: int | None = None
    
        def __post_init__(self):
            if self.USE_TLS and self.USE_SSL:
                raise ValueError()
    
    
    

    A project settings.py can then define what it needs;

    
    from django.conf.settings_types import EmailSettings
    
    EMAIL = EmailSettings(
        HOST="smtp.sendgrid.net",
        PORT=587,
        USE_TLS=True,
        HOST_USER="apikey",
        HOST_PASSWORD=os.environ["SENDGRID_KEY"],
    )
    
    
  2. A lightweight SettingsGroupbase class

    class SettingsGroup:
    “”“Base class for grouped Django settings.”“”
    
    def __init_subclass__(cls, prefix: str = "", **kwargs):
        super().__init_subclass__(**kwargs)
        cls._prefix = prefix  # e.g. 'EMAIL' — used for legacy compat shim
    
    def validate(self):
        """Override to add cross-field validation."""
        pass
    
    class EmailSettings(SettingsGroup, prefix=“EMAIL”):
        BACKEND: str = “django.core.mail.backends.smtp.EmailBackend”
        HOST: str = “localhost”
        PORT: int = 25
        USE_TLS: bool = False
        USE_SSL: bool = False
        ...
    
        def validate(self):
            if self.USE_TLS and self.USE_SSL:
                sdraise ImproperlyConfigured()
    

Here are some thoughts on potential settings groups, starting with the email group;

Group Prefix Note
EMAIL EMAIL_* Likely candidate for initial implementation
CSRF CSRF_* Small set of security related settings
SESSION SESSION_* Slightly larger - stable API
CACHES CACHES Potential for typing
SECURITY various - SECURE_*, X_FRAME_* Great candidate for grouping
STATICFILES STATIC_*, STATICFILES_* Useful to group
AUTH AUTH_*, LOGIN_*, LOGOUT_* Big group

Curious what people’s thoughts are on this.

1 Like

(Just a heads up: Django 6.1 will deprecate the email settings in favor of EMAIL_PROVIDERS, so you may wish to prototype with something else.)

1 Like