Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2457,6 +2457,62 @@ For nested models, Secret Manager supports the `env_nested_delimiter` setting as

For more details on creating and managing secrets in Google Cloud Secret Manager, see the [official Google Cloud documentation](https://cloud.google.com/secret-manager/docs).

## Keyring

This integration allows you to securely load sensitive values such as passwords, API tokens, and other secrets directly from the system keyring. Instead of storing secrets in .env files or configuration files, you can fetch them at runtime using the operating system's secure credential store.

The Keyring integration requires additional dependencies:

```bash
pip install "pydantic-settings[keyring]"
```

There is one mandatory parameter:
- `keyring_service`: keyring service name

Add `KeyringConfigSettingsSource` to your settings sources:

```python
from pydantic import SecretStr
from pydantic_settings import (
BaseSettings,
KeyringConfigSettingsSource,
PydanticBaseSettingsSource,
SettingsConfigDict,
)

class Settings(BaseSettings):
secret_key: SecretStr

@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
keyring_settings = KeyringConfigSettingsSource(
settings_cls,
keyring_service=os.environ["KEYRING_SERVICE"],
)

return (
init_settings,
env_settings,
dotenv_settings,
file_secret_settings,
keyring_settings,
)
```

To set a secret, you can use `keyring set <keyring_service> <field_name>` in your terminal:

```bash
$ keyring set myapp secret_key
```

## Other settings source

Other settings sources are available for common configuration files:
Expand Down
2 changes: 2 additions & 0 deletions pydantic_settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
GoogleSecretManagerSettingsSource,
InitSettingsSource,
JsonConfigSettingsSource,
KeyringConfigSettingsSource,
NestedSecretsSettingsSource,
NoDecode,
PydanticBaseSettingsSource,
Expand Down Expand Up @@ -53,6 +54,7 @@
'GoogleSecretManagerSettingsSource',
'InitSettingsSource',
'JsonConfigSettingsSource',
'KeyringConfigSettingsSource',
'NestedSecretsSettingsSource',
'NoDecode',
'PydanticBaseSettingsSource',
Expand Down
2 changes: 2 additions & 0 deletions pydantic_settings/sources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .providers.env import EnvSettingsSource
from .providers.gcp import GoogleSecretManagerSettingsSource
from .providers.json import JsonConfigSettingsSource
from .providers.keyring import KeyringConfigSettingsSource
from .providers.nested_secrets import NestedSecretsSettingsSource
from .providers.pyproject import PyprojectTomlConfigSettingsSource
from .providers.secrets import SecretsSettingsSource
Expand Down Expand Up @@ -58,6 +59,7 @@
'GoogleSecretManagerSettingsSource',
'InitSettingsSource',
'JsonConfigSettingsSource',
'KeyringConfigSettingsSource',
'NestedSecretsSettingsSource',
'NoDecode',
'PathType',
Expand Down
2 changes: 2 additions & 0 deletions pydantic_settings/sources/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .env import EnvSettingsSource
from .gcp import GoogleSecretManagerSettingsSource
from .json import JsonConfigSettingsSource
from .keyring import KeyringConfigSettingsSource
from .pyproject import PyprojectTomlConfigSettingsSource
from .secrets import SecretsSettingsSource
from .toml import TomlConfigSettingsSource
Expand All @@ -38,6 +39,7 @@
'EnvSettingsSource',
'GoogleSecretManagerSettingsSource',
'JsonConfigSettingsSource',
'KeyringConfigSettingsSource',
'PyprojectTomlConfigSettingsSource',
'SecretsSettingsSource',
'TomlConfigSettingsSource',
Expand Down
62 changes: 62 additions & 0 deletions pydantic_settings/sources/providers/keyring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from typing import TYPE_CHECKING, Mapping

from pydantic_settings import BaseSettings, EnvSettingsSource

if TYPE_CHECKING:
import keyring
else:
keyring = None

def import_keyring():
global keyring
if keyring is not None:
return
try:
import keyring

return
except ImportError as e:
raise ImportError("Keyring is not installed, run `pip install keyring`") from e


class KeyringConfigSettingsSource(EnvSettingsSource):
def __init__(
self,
settings_cls: type[BaseSettings],
keyring_service: str,
case_sensitive: bool | None = None,
env_prefix: str | None = None,
):
self.keyring_service = (
keyring_service if case_sensitive else keyring_service.lower()
)
super().__init__(
settings_cls, case_sensitive=case_sensitive, env_prefix=env_prefix
)

def _load_env_vars(self) -> Mapping[str, str | None]:
import_keyring()

prefix = self.env_prefix
if not self.case_sensitive:
prefix = self.env_prefix.lower()
env_vars = {}
for field in self.settings_cls.model_fields.keys():
if not self.case_sensitive:
field = field.lower()
credential = keyring.get_credential(self.keyring_service, prefix + field)
if credential is not None:
key = credential.username
if not self.case_sensitive:
key = key.lower()
env_vars[key] = credential.password

return env_vars

def __repr__(self) -> str:
return f"{self.__class__.__name__}(keyring_service={self.keyring_service})"


__all__ = [
"KeyringConfigSettingsSource",
]
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ aws-secrets-manager = ["boto3>=1.35.0", "boto3-stubs[secretsmanager]"]
gcp-secret-manager = [
"google-cloud-secret-manager>=2.23.1",
]
keyring = ["keyring>=25.7.0"]

[project.urls]
Homepage = 'https://github.com/pydantic/pydantic-settings'
Expand Down
Loading