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.
-
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.pycan 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"], ) -
A lightweight
SettingsGroupbase classclass 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.