Load pydantic settings from AWS Parameter Store
pydantic-settings reads environment variables and .env files out of the box, but not AWS Parameter Store. The clean integration is a custom settings source, not a pile of boto3 calls scattered through your app. This page builds it, extending Settings from AWS Parameter Store.
Problem 1: fetching parameters one by one
# ANTI-PATTERN: N API calls, no validation, scattered everywhere
db = boto3.client("ssm").get_parameter(Name="/myapp/database_url")["Parameter"]["Value"]
key = boto3.client("ssm").get_parameter(Name="/myapp/api_key")["Parameter"]["Value"]
Per-parameter calls are slow, untyped, and bypass the settings model entirely.
Problem 2: forgetting decryption
# ANTI-PATTERN: SecureString returned still encrypted
ssm.get_parameters_by_path(Path="/myapp/") # WithDecryption defaults to False
Without WithDecryption=True, SecureString parameters come back as ciphertext.
Secure implementation
# config/ssm_source.py
import boto3
from pydantic import SecretStr
from pydantic_settings import (
BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict,
)
class SSMSource(PydanticBaseSettingsSource):
def __call__(self) -> dict[str, object]:
ssm = boto3.client("ssm")
values: dict[str, object] = {}
for page in ssm.get_paginator("get_parameters_by_path").paginate(
Path="/myapp/", Recursive=True, WithDecryption=True, # decrypt SecureStrings
):
for p in page["Parameters"]:
values[p["Name"].rsplit("/", 1)[-1].lower()] = p["Value"]
return values
def get_field_value(self, field, field_name):
return None, field_name, False
class Settings(BaseSettings):
model_config = SettingsConfigDict(extra="forbid")
database_url: str
api_key: SecretStr
@classmethod
def settings_customise_sources(cls, settings_cls, init_settings,
env_settings, dotenv_settings, file_secret_settings):
return (init_settings, env_settings, SSMSource(settings_cls)) # env still wins
One paginated call fetches the whole prefix, decryption is explicit, and the model validates the result. Environment variables remain above SSM so local overrides still work.
Gotchas & version-specific behaviour
settings_customise_sourcesreturns sources highest-priority first — putenv_settingsbefore the SSM source.get_parameters_by_pathis paginated; use the paginator or you will miss parameters past the first page.WithDecryption=Truerequireskms:Decrypton the parameter’s KMS key.- Strip the path prefix to map
/myapp/database_urlto the fielddatabase_url.
Production parity checklist
- A single custom source, not scattered
get_parametercalls. WithDecryption=Truewithkms:Decryptgranted.- IAM scoped to the exact path prefix.
- Secret fields typed
SecretStr;extra="forbid"set. - Environment variables outrank SSM for local overrides.
Conclusion
A PydanticBaseSettingsSource subclass makes Parameter Store just another validated source under the environment. To keep it fast at scale, add caching — see Cache Parameter Store Values to Reduce API Calls.