-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from makukha/1-make-initial-release
Initial release
- Loading branch information
Showing
26 changed files
with
1,964 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
[tool.bumpversion] | ||
current_version = "0.1.0a1" | ||
allow_dirty = true | ||
files = [ | ||
{filename = "src/pydantic_file_secrets/__version__.py"}, | ||
{filename = "pyproject.toml"}, | ||
{filename = "README.md"}, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# dev core | ||
brew "keyring" | ||
brew "pdm" | ||
brew "pyenv" | ||
brew "ruff" | ||
brew "scorecard" | ||
brew "tox" | ||
|
||
# dev ux | ||
cask "alacritty" | ||
cask "font-jetbrains-mono-nerd-font" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#!/bin/sh | ||
|
||
alacritty --hold \ | ||
--config-file alacritty.toml \ | ||
--working-directory $(pwd)/.. \ | ||
--title $(basename $(pwd)/..) \ | ||
& |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[window] | ||
decorations = "Buttonless" | ||
dynamic_title = false | ||
dimensions = { columns = 120, lines = 120 } | ||
position = { x = 0, y = 0 } | ||
padding = { x = 10, y = 10 } | ||
opacity = 0.9 | ||
|
||
[font] | ||
normal = { family = "JetBrainsMono Nerd Font Mono", style = "Regular" } | ||
size = 12 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
root = true | ||
|
||
[*] | ||
indent_style = space | ||
end_of_line = crlf | ||
insert_final_newline = true | ||
trim_trailing_whitespace = true | ||
|
||
[*.{py,toml}] | ||
charset = utf-8 | ||
indent_size = 4 | ||
|
||
[*.md] | ||
trim_trailing_whitespace = false | ||
|
||
[*.{ini,yml}] | ||
indent_size = 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Brewfile.lock.json | ||
dist/ | ||
*.egg-info/ | ||
.idea/ | ||
.pdm-build/ | ||
.pdm-python | ||
__pycache__/ | ||
.task/ | ||
tmp/ | ||
.tox/ | ||
.venv/ | ||
|
||
# OS generated | ||
.DS_Store | ||
.DS_Store? | ||
._* | ||
.Spotlight-V100 | ||
.Trashes | ||
ehthumbs.db | ||
Thumbs.db |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
3.12.5 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
exclude = [ | ||
".git", | ||
".pdm-build", | ||
".tox", | ||
".venv", | ||
"dist", | ||
"tmp", | ||
] | ||
# Same as Black. | ||
line-length = 88 | ||
indent-width = 4 | ||
# Assume Python 3.12 | ||
target-version = "py312" | ||
|
||
[lint] | ||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. | ||
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or | ||
# McCabe complexity (`C901`) by default. | ||
select = ["E4", "E7", "E9", "F", "S"] | ||
ignore = [] | ||
# Allow fix for all enabled rules (when `--fix`) is provided. | ||
fixable = ["ALL"] | ||
unfixable = [] | ||
# Allow unused variables when underscore-prefixed. | ||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" | ||
|
||
[lint.per-file-ignores] | ||
"tests/**/*.py" = [ | ||
"ARG", # Unused function args -> fixtures nevertheless are functionally relevant... | ||
"FBT", # Don't care about booleans as positional arguments in tests, e.g. via @pytest.mark.parametrize() | ||
] | ||
"tests/**/test_*.py" = [ | ||
"S101", # Asserts allowed in tests... | ||
] | ||
|
||
[format] | ||
quote-style = "single" | ||
indent-style = "space" | ||
skip-magic-trailing-comma = false | ||
line-ending = "auto" | ||
docstring-code-format = true | ||
docstring-code-line-length = "dynamic" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,133 @@ | ||
# pydantic-settings-subsecret | ||
Pydantic settings source for secrets in nested submodels. | ||
# pydantic-file-secrets 📁🔑 | ||
> Use file secrets in nested models of Pydantic Settings. | ||
[![license](https://img.shields.io/github/license/makukha/pydantic-file-secrets.svg)](https://github.com/makukha/pydantic-file-secrets/blob/main/LICENSE) | ||
[![Tests](https://raw.githubusercontent.com/makukha/pydantic-file-secrets/0.1.0a1/docs/badge/tests.svg)](https://github.com/makukha/pydantic-file-secrets) | ||
[![Coverage](https://raw.githubusercontent.com/makukha/pydantic-file-secrets/0.1.0a1/docs/badge/coverage.svg)](https://github.com/makukha/pydantic-file-secrets) | ||
[![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v1.json)](https://github.com/astral-sh/ruff) | ||
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) \ | ||
[![pypi](https://img.shields.io/pypi/v/pydantic-file-secrets.svg#0.1.0a1)](https://pypi.python.org/pypi/pydantic-file-secrets) | ||
[![versions](https://img.shields.io/pypi/pyversions/pydantic-file-secrets.svg)](https://pypi.org/project/pydantic-file-secrets) | ||
[![Pydantic v2](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v2.json)](https://pydantic.dev) | ||
|
||
|
||
This package is inspired by and based on discussions in [pydantic-settings issue #154](https://github.com/pydantic/pydantic-settings/issues/154). | ||
|
||
|
||
## 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](#configuration-options) | ||
* Has `secrets_prefix`, `secrets_nested_delimiter`, [etc.](#configuration-options) 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: | ||
|
||
```python | ||
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`](https://docs.pydantic.dev/latest/api/pydantic_settings/#pydantic_settings.SecretsSettingsSource), but it does not load secrets in nested models. For methods that ***do not*** work in original Pydantic Settings, see [tests/test_pydantic_motivation.py](). | ||
|
||
|
||
## Solution | ||
|
||
The new `FileSecretsSettingsSource` is a drop-in replacement of stock `SecretsSettingsSource`. | ||
|
||
### Installation | ||
|
||
```shell | ||
$ pip install pydantic-file-secrets | ||
``` | ||
|
||
### Plain directory layout | ||
|
||
| file | content | | ||
|-----------------------------|-----------| | ||
| `/run/secrets/app_key` | `secret1` | | ||
| `/run/secrets/db__password` | `secret2` | | ||
|
||
```python | ||
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), | ||
) | ||
|
||
``` | ||
|
||
### Secrets in subdirectories | ||
|
||
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: | ||
|
||
| file | content | | ||
|----------------------------|-----------| | ||
| `/run/secrets/app_key` | `secret1` | | ||
| `/run/secrets/db/password` | `secret2` | | ||
|
||
```python | ||
... | ||
model_config = SettingsConfigDict( | ||
secrets_dir='/run/secrets', | ||
secrets_nested_subdir=True, | ||
) | ||
... | ||
``` | ||
|
||
## Configuration options | ||
|
||
TODO | ||
|
||
|
||
## Roadmap | ||
|
||
* Support `_FILE` environment variables to set secret file name. | ||
* Per-field secret file name override. | ||
|
||
|
||
## Changelog |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
version: '3' | ||
|
||
vars: | ||
GITHUB_REPO: makukha/pydantic-file-secrets | ||
|
||
tasks: | ||
|
||
init: | ||
desc: Initialize dev environment. | ||
cmds: | ||
- cd .dev && brew bundle | ||
- task: init:python | ||
- pyenv install --skip-existing $(pyenv local) | ||
- pdm venv create $(pyenv prefix $(pyenv local)) | ||
|
||
init:python: | ||
internal: true | ||
sources: | ||
- tox.ini | ||
vars: | ||
PYTHON_VERSIONS: | ||
sh: tox --listenvs | sed -e 's/^py\([23]\)\.\{0,1\}\(.*\)-.*/\1.\2/' | sort -mu | tr '\n' ' ' | ||
cmds: | ||
- cmd: pyenv install --skip-existing {{.ITEM}} | ||
for: {var: PYTHON_VERSIONS} | ||
|
||
install: | ||
desc: Install dev python environment. | ||
cmds: | ||
- task: dep:lock | ||
- pdm install --check --dev | ||
|
||
# dependencies lock in PDM is slow, run only when pyproject.toml changes | ||
dep:lock: | ||
internal: true | ||
sources: | ||
- pyproject.toml | ||
generates: | ||
- pdm.lock | ||
cmds: | ||
- pdm lock | ||
|
||
lint: | ||
desc: Run linters and code formatters. | ||
cmds: | ||
- ruff check | ||
- ruff format --check | ||
|
||
test: | ||
desc: Run tests. | ||
deps: [install] | ||
cmds: | ||
- tox run-parallel --parallel-live | ||
|
||
test:pdb: | ||
desc: Run tests and open debugger on errors. | ||
deps: [install] | ||
cmds: | ||
- pdm run pytest --pdb {{.CLI_ARGS}} | ||
|
||
version: | ||
desc: Bump project version. Use task version -- patch|minor|major|... | ||
cmds: | ||
- pdm run bump-my-version bump {{.CLI_ARGS}} | ||
|
||
publish: | ||
desc: Publish package on PyPi. | ||
preconditions: | ||
- test $(git rev-parse --abbrev-ref HEAD) = main | ||
cmds: | ||
- pdm publish | ||
|
||
scorecard: | ||
desc: Update security scorecard | ||
vars: | ||
GITHUB_TOKEN: {sh: keyring get token.github.caseutil scorecard} | ||
env: | ||
GITHUB_TOKEN: '{{.GITHUB_TOKEN}}' | ||
cmds: | ||
- scorecard --repo={{.GITHUB_REPO}} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.