Skip to main content

Use file secrets in nested models of Pydantic Settings.

Project description

pydantic-file-secrets 🔑

Use file secrets in nested Pydantic Settings models.

GitHub License Tests Coverage linting - Ruff Code style: black
pypi versions Pydantic v2

This project is inspired by discussions in pydantic-settings issue #154.

This package unties secrets from environment variables config options and implements other long waited features.

Features

  • Use secret file source in nested settings models
  • Drop-in replacement of standard SecretsSettingsSource
  • Plain or nested directory layout: /run/secrets/dir__key or /run/secrets/dir/key
  • Respects env_prefix, env_nested_delimiter and other config options
  • Has secrets_prefix, secrets_nested_delimiter, etc. to configure secrets and env vars separately
  • Pure Python thin wrapper over standard EnvSettingsSource
  • No third party dependencies except pydantic-settings
  • 100% test coverage

Motivation

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.

Solution

The new FileSecretsSettingsSource is a drop-in replacement of stock SecretsSettingsSource.

Installation

$ pip install pydantic-file-secrets

Plain secrets directory layout

# /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 (
            env_settings,
            init_settings,
            FileSecretsSettingsSource(settings_cls),
        )

Nested secrets directory layout

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,
    )
...

Configuration options

secrets_dir

Path to secrets directory, same as SecretsSettingsSource.secrets_dir.

secrets_dir_missing

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 if secrets_dir does not exist
  • 'warn' (default) — print warning, same as SecretsSettingsSource
  • 'error' — raise SettingsError

secrets_dir_max_size

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.

secrets_case_sensitive

Same as case_sensitive, but works for secrets only. If not specified, defaults to case_sensitive.

secrets_nested_delimiter

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.

secrets_nested_subdir

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.

secrets_prefix

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'.

Not supported config options

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).

Testing

We ensure 100% test coverage for latest Python release (3.12).

We test all minor Pydantic Settings v2 versions and all minor Python 3 versions supported by Pydantic Settings:

  • Python 3.13 + pydantic-settings 2.{0,1,2,3,4}
  • Python 3.12 + pydantic-settings 2.{0,1,2,3,4}
  • Python 3.11 + pydantic-settings 2.{0,1,2,3,4}
  • Python 3.10 + pydantic-settings 2.{0,1,2,3,4}
  • Python 3.9 + pydantic-settings 2.{0,1,2,3,4}
  • Python 3.8 + pydantic-settings 2.{0,1,2,3,4}

Roadmap

  • Support _FILE environment variables to set secret file name.
  • Per-field secret file name override.

Authors

License

MIT License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pydantic_file_secrets-0.1.3.tar.gz (10.6 kB view details)

Uploaded Source

Built Distribution

pydantic_file_secrets-0.1.3-py3-none-any.whl (6.4 kB view details)

Uploaded Python 3

File details

Details for the file pydantic_file_secrets-0.1.3.tar.gz.

File metadata

  • Download URL: pydantic_file_secrets-0.1.3.tar.gz
  • Upload date:
  • Size: 10.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: pdm/2.18.0 CPython/3.12.5 Darwin/21.6.0

File hashes

Hashes for pydantic_file_secrets-0.1.3.tar.gz
Algorithm Hash digest
SHA256 eaf04bfbe65e4b5538e7e492168571f7b4664fb92d891bf54b75ad3eb99aa529
MD5 1ab91f0129d446af65cdaf7d19460216
BLAKE2b-256 212a648a2b280355b73b6950ce014b2bf12ae590062f6d186de55ea1e30f7657

See more details on using hashes here.

File details

Details for the file pydantic_file_secrets-0.1.3-py3-none-any.whl.

File metadata

File hashes

Hashes for pydantic_file_secrets-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 4278083ca018ad7e3808d6bc1cca27a6f8f5608af7fafd08b16aeb6286694dc4
MD5 bdbfeef7ebbb5816f14b755a5434283e
BLAKE2b-256 65cbd381e61699140108db8d85d03baee753bd64bf992e84776786f98668a949

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page