Structured, typed, auditable Django settings management powered by Pydantic
Project description
django-aqueduct
Structured, typed, auditable Django settings management powered by Pydantic.
django-aqueduct channels configuration from multiple sources — environment variables, YAML files, HashiCorp Vault, AWS SSM Parameter Store — into a single typed, validated model, making settings auditable and K8s-friendly without changing any application code.
Installation
pip install django-aqueduct
# Optional extras
pip install django-aqueduct[vault] # HashiCorp Vault support (hvac)
pip install django-aqueduct[aws] # AWS SSM Parameter Store (boto3)
pip install django-aqueduct[mitol] # mitol-django-common EnvParser integration
Add to INSTALLED_APPS:
INSTALLED_APPS = [
...
"django_aqueduct",
]
Quickstart
Step 1 — Generate a scaffold
Point generate_aqueduct_settings at your existing settings module:
python manage.py generate_aqueduct_settings \
--modules myapp.settings.common \
--output src/myapp/settings_model.py
This emits a typed AqueductSettings(BaseSettings) class with every
UPPERCASE name from your settings module as a Pydantic field, grouped
under section comments by source module.
Step 2 — Refine the scaffold
Open settings_model.py and:
- Fix any
# TODO: refine typeannotations - Add
model_validatormethods to derive complex objects from primitives:
from pydantic import model_validator
import dj_database_url
class AqueductSettings(BaseSettings):
DATABASE_URL: str = Field(default="sqlite:///db.sqlite3")
# Derived — populated by the validator below
DATABASES: dict[str, Any] = Field(default_factory=dict)
@model_validator(mode="after")
def build_databases(self) -> "AqueductSettings":
self.DATABASES = {"default": dj_database_url.parse(self.DATABASE_URL)}
return self
Step 3 — Wire the shim
Replace your host settings file with a thin shim:
# myapp/settings/production.py
from django_aqueduct import configure_django_settings
from myapp.settings_model import AqueductSettings
configure_django_settings(AqueductSettings)
That's it. DJANGO_SETTINGS_MODULE stays the same. All existing
django.conf.settings.FOO access in application code continues to work
with zero changes.
Kubernetes deployment pattern
In Kubernetes, configuration typically arrives from multiple sources:
| Source | Typical content |
|---|---|
| Pod environment variables | Non-secret config from ConfigMaps |
| Vault (Kubernetes SA auth) | Database passwords, API keys |
| AWS SSM Parameter Store | Secrets in AWS-hosted deployments |
Configure all three in your settings model:
from django_aqueduct import configure_django_settings
from django_aqueduct.sources.vault import VaultSettingsSource
from django_aqueduct.sources.aws_ssm import AWSParameterStoreSource
from pydantic_settings import BaseSettings, SettingsConfigDict
class ProductionSettings(BaseSettings):
model_config = SettingsConfigDict(extra="allow")
SECRET_KEY: str = Field(...)
DATABASE_URL: str = Field(...)
@classmethod
def settings_customise_sources(cls, settings_cls, **kwargs):
return (
# 1. Environment variables (from K8s ConfigMaps)
kwargs["env_settings"],
# 2. Vault via Kubernetes SA — reads JWT from default mount path
# /var/run/secrets/kubernetes.io/serviceaccount/token
VaultSettingsSource(
settings_cls,
vault_url="https://vault.example.com",
vault_path="myapp/production",
auth_method="kubernetes",
role="myapp",
# Optional: custom JWT path for projected service accounts
# jwt_path="/var/run/secrets/custom/token",
),
)
# myapp/settings/production.py
configure_django_settings(ProductionSettings)
Vault authentication methods
| Method | When to use |
|---|---|
"token" |
Local dev, CI with a static token |
"oidc" |
Interactive / browser-based login |
"kubernetes" |
Production K8s — uses the pod's service account JWT |
# Token auth (dev/CI)
VaultSettingsSource(settings_cls, ..., auth_method="token", vault_token="s.xxx")
# OIDC (interactive)
VaultSettingsSource(settings_cls, ..., auth_method="oidc", role="myapp")
# Kubernetes SA (production) — custom JWT path
VaultSettingsSource(
settings_cls,
...,
auth_method="kubernetes",
role="myapp",
jwt_path="/var/run/secrets/tokens/vault", # projected SA token
)
AWS SSM Parameter Store
from django_aqueduct.sources.aws_ssm import AWSParameterStoreSource
# All parameters under /myapp/production/ are fetched with full pagination.
# The prefix is stripped: /myapp/production/SECRET_KEY → SECRET_KEY
AWSParameterStoreSource(
settings_cls,
path_prefix="/myapp/production/",
region_name="us-east-1",
)
Adapter modes
Option A — Shim settings file (recommended)
DJANGO_SETTINGS_MODULE stays unchanged. The settings file becomes a
thin shim:
# myapp/settings/production.py
from django_aqueduct import configure_django_settings
from myapp.settings_model import ProductionSettings
configure_django_settings(ProductionSettings)
Works with gunicorn, Celery, pytest-django, management commands, and every
other tool that reads DJANGO_SETTINGS_MODULE — no changes required.
Option B — Programmatic configure (greenfield)
For new projects or container-native apps where you control all entry points
and want no DJANGO_SETTINGS_MODULE:
# manage.py or WSGI/ASGI entry point — call before django.setup()
from django_aqueduct import configure_django_programmatic
from myapp.settings_model import AppSettings
configure_django_programmatic(AppSettings)
import django
django.setup()
edx-platform migration walkthrough
edx-platform's lms/envs/production.py currently loads a YAML file and
applies hundreds of lines of post-processing. With django-aqueduct:
-
Generate the scaffold from
common.py:python manage.py generate_aqueduct_settings \ --modules lms.envs.common \ --output lms/envs/settings_model.py
-
Review
settings_model.py— fix# TODO: refine typeentries, movederive_settingslogic into@model_validatormethods. -
Replace
lms/envs/production.py:# lms/envs/production.py from django_aqueduct import configure_django_settings from lms.envs.settings_model import LMSSettings configure_django_settings(LMSSettings)
-
Set
DJANGO_SETTINGS_MODULE=lms.envs.productionas before. All LMS app code usingfrom django.conf import settingsis unchanged.
[mitol] extra — EnvParser integration
If your project uses mitol-django-common's EnvParser, install the
[mitol] extra and pass --include-envparser to the generator:
pip install django-aqueduct[mitol]
python manage.py generate_aqueduct_settings \
--modules myapp.settings \
--include-envparser
The EnvParserInspector reads the global env._configured_vars registry
and emits precisely-typed fields for every get_string/get_bool/get_int
call, preserving description, required, and dev_only metadata.
Contributing
git clone https://github.com/mitodl/django-aqueduct
cd django-aqueduct
uv sync
uv run pytest
uv run mypy src/django_aqueduct
Install pre-commit hooks with prek:
pip install prek
prek install
Please open an issue before submitting a pull request for significant changes.
License
BSD-3-Clause © MIT Open Learning Engineering
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_aqueduct-0.1.0.tar.gz.
File metadata
- Download URL: django_aqueduct-0.1.0.tar.gz
- Upload date:
- Size: 14.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
68c2920ef04a17452d2c6653464f3eeb626e5a2f68b02fb66f50a395dba09bc3
|
|
| MD5 |
830062d15459f1b9cef34a134c7f8c8f
|
|
| BLAKE2b-256 |
bef399d1d8bc3790d436e4402108a0d9123eb5258b145b251234d36494d9f06c
|
Provenance
The following attestation bundles were made for django_aqueduct-0.1.0.tar.gz:
Publisher:
publish.yml on mitodl/django-aqueduct
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_aqueduct-0.1.0.tar.gz -
Subject digest:
68c2920ef04a17452d2c6653464f3eeb626e5a2f68b02fb66f50a395dba09bc3 - Sigstore transparency entry: 1659131487
- Sigstore integration time:
-
Permalink:
mitodl/django-aqueduct@92565d1b05814654053128b9d0149753d1df30a6 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mitodl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@92565d1b05814654053128b9d0149753d1df30a6 -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_aqueduct-0.1.0-py3-none-any.whl.
File metadata
- Download URL: django_aqueduct-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e826837888504902c8806185723f05872b2c9e07e4ff04e6f00ada796685e93
|
|
| MD5 |
37dfb23dd895ac3e88df3a316dc58991
|
|
| BLAKE2b-256 |
151ee729098155f9c6e5fcc6390a287128117518d2332963b8aac31202e00a9c
|
Provenance
The following attestation bundles were made for django_aqueduct-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on mitodl/django-aqueduct
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_aqueduct-0.1.0-py3-none-any.whl -
Subject digest:
1e826837888504902c8806185723f05872b2c9e07e4ff04e6f00ada796685e93 - Sigstore transparency entry: 1659131611
- Sigstore integration time:
-
Permalink:
mitodl/django-aqueduct@92565d1b05814654053128b9d0149753d1df30a6 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/mitodl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@92565d1b05814654053128b9d0149753d1df30a6 -
Trigger Event:
push
-
Statement type: