Use file secrets in nested Pydantic Settings models, drop-in replacement for
SecretsSettingsSource
.
This project is inspired by discussions in Pydantic Settings and solves problems in issues #30, #154.
- Use secret file source in nested settings models
- Plain or nested directory layout:
/run/secrets/dir__key
or/run/secrets/dir/key
- Respects
env_prefix
,env_nested_delimiter
and other config options - Implements config options
secrets_prefix
,secrets_nested_delimiter
, etc. to configure secrets and env vars independently - Drop-in replacement of standard
SecretsSettingsSource
- Can be used to monkey patch
SecretsSettingsSource
- Pure Python thin wrapper over standard
EnvSettingsSource
- No third party dependencies except
pydantic-settings
- 100% test coverage
Nested Pydantic config can contain nested models with secret entries, as well as secrets in top level config. In dockerized environment, these entries may be read from file system, e.g. /run/secrets
when using Docker Secrets:
from pydantic import BaseModel, Secret
from pydantic_settings import BaseSettings, SettingsConfigDict
class DbSettings(BaseModel):
user: str
password: Secret[str] # secret in nested model
class Settings(BaseSettings):
db: DbSettings
app_key: Secret[str] # secret in root config
model_config = SettingsConfigDict(
secrets_dir='/run/secrets',
)
Pydantic Settings has a corresponding data source, SecretsSettingsSource
, but it does not load secrets in nested models. For things that DO NOT work in original Pydantic Settings, see test_pydantic_motivation.py.
The new FileSecretsSettingsSource
is a drop-in replacement of stock SecretsSettingsSource
.
$ pip install pydantic-file-secrets
# /run/secrets/app_key
secret1
# /run/secrets/db__password
secret2
from pydantic import BaseModel, Secret
from pydantic_file_secrets import FileSecretsSettingsSource
from pydantic_settings import BaseSettings, SettingsConfigDict
class DbSettings(BaseModel):
user: str
password: Secret[str]
class Settings(BaseSettings):
db: DbSettings
app_key: Secret[str]
model_config = SettingsConfigDict(
secrets_dir='/run/secrets',
env_nested_delimiter='__',
)
@classmethod
def settings_customise_sources(
cls,
settings_cls,
init_settings,
env_settings,
dotenv_settings,
file_secret_settings,
):
return (
init_settings,
env_settings,
dotenv_settings,
FileSecretsSettingsSource(file_secret_settings),
)
Config option secrets_nested_delimiter
overrides env_nested_delimiter
for files. In particular, this allows to use nested directory layout along with environmemt variables for other non-secret settings:
# /run/secrets/app_key
secret1
# /run/secrets/db/password
secret2
...
model_config = SettingsConfigDict(
secrets_dir='/run/secrets',
secrets_nested_subdir=True,
)
...
When passing list
to secrets_dir
, last match wins.
...
model_config = SettingsConfigDict(
secrets_dir=['/run/configs/', '/run/secrets'],
)
...
Path to secrets directory. Same as SecretsSettingsSource.secrets_dir
if str
or Path
. If list
, the last match wins. If secrets_dir
is passed in both source constructor and model config, values are not merged (constructor takes priority).
If secrets_dir
does not exist, original SecretsSettingsSource
issues a warning. However, this may be undesirable, for example if we don't mount Docker Secrets in e.g. dev environment. Now you have a choice:
'ok'
β do nothing ifsecrets_dir
does not exist'warn'
(default) β print warning, same asSecretsSettingsSource
'error'
β raiseSettingsError
If multiple secrets_dir
passed, the same secrets_dir_missing
action applies to each of them.
Limit the size of secrets_dir
for security reasons, defaults to 8 MiB.
FileSecretsSettingsSource
is a thin wrapper around EnvSettingsSource
, which loads all potential secrets on initialization. This could lead to MemoryError
if we mount a large file under secrets_dir
.
If multiple secrets_dir
passed, the limit applies to each directory independently.
Same as case_sensitive
, but works for secrets only. If not specified, defaults to case_sensitive
.
Same as env_nested_delimiter
, but works for secrets only. If not specified, defaults to env_nested_delimiter
. This option is used to implement nested secrets directory layout and allows to do even nastier things like /run/secrets/model/delim/nested1/delim/nested2
.
Boolean flag to turn on nested secrets directory mode, False
by default. If True
, sets secrets_nested_delimiter
to os.sep
. Raises SettingsError
if secrets_nested_delimiter
is already specified.
Secret path prefix, similar to env_prefix
, but works for secrets only. Defaults to env_prefix
if not specified. Works in both plain and nested directory modes, like '/run/secrets/prefix_model__nested'
and '/run/secrets/prefix_model/nested'
.
Some config options that are declared in SecretsSettingsSource
interface are actually not working and are not supported in FileSecretsSettingsSource
:
env_ignore_empty
env_parse_none_str
env_parse_enums
However, we make sure that the behaviour of FileSecretsSettingsSource
matches SecretsSettingsSource
to provide a drop-in replacement, although it is somewhat wierd (e.g. env_parse_enums
is always True
).
100% test coverage is ensured for latest stable Python release (3.12).
Tests are run for all minor Pydantic Settings v2 versions and all minor Python 3 versions supported by Pydantic Settings:
- Python v. 3.{8,9,10,11,12,13}
- pydantic-settings v. 2.{2,3,4,5,6}
- Several
secrets_dir
features were ported topydantic-settings
version 2.5.0