Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
hramezani authored Aug 13, 2024
2 parents 8ba15f0 + 5c3a817 commit abf01f7
Show file tree
Hide file tree
Showing 13 changed files with 687 additions and 143 deletions.
28 changes: 8 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: set up python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.10'

Expand All @@ -31,20 +31,8 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-13, macos-latest, windows-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
python: ['3.8', '3.9', '3.10', '3.11', '3.12']
exclude:
# Python 3.8 and 3.9 are not available on macOS 14
- os: macos-13
python: '3.10'
- os: macos-13
python: '3.11'
- os: macos-13
python: '3.12'
- os: macos-latest
python: '3.8'
- os: macos-latest
python: '3.9'

env:
PYTHON: ${{ matrix.python }}
Expand All @@ -53,10 +41,10 @@ jobs:
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: set up python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}

Expand All @@ -80,7 +68,7 @@ jobs:
- run: coverage combine
- run: coverage xml

- uses: codecov/codecov-action@v3
- uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
env_vars: PYTHON,OS
Expand Down Expand Up @@ -108,10 +96,10 @@ jobs:
id-token: write

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: set up python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.10'

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ refresh-lockfiles:
find requirements/ -name '*.txt' ! -name 'all.txt' -type f -delete
pip-compile -q --no-emit-index-url --resolver backtracking -o requirements/linting.txt requirements/linting.in
pip-compile -q --no-emit-index-url --resolver backtracking -o requirements/testing.txt requirements/testing.in
pip-compile -q --no-emit-index-url --resolver backtracking --extra toml --extra yaml -o requirements/pyproject.txt pyproject.toml
pip-compile -q --no-emit-index-url --resolver backtracking --extra toml --extra yaml --extra azure-key-vault -o requirements/pyproject.txt pyproject.toml
pip install --dry-run -r requirements/all.txt

.PHONY: format
Expand Down
100 changes: 91 additions & 9 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,28 @@ options:
"""
```

#### Change Whether CLI Should Exit on Error

Change whether the CLI internal parser will exit on error or raise a `SettingsError` exception by using
`cli_exit_on_error`. By default, the CLI internal parser will exit on error.

```py
import sys

from pydantic_settings import BaseSettings, SettingsError


class Settings(BaseSettings, cli_parse_args=True, cli_exit_on_error=False): ...


try:
sys.argv = ['example.py', '--bad-arg']
Settings()
except SettingsError as e:
print(e)
#> error parsing CLI: unrecognized arguments: --bad-arg
```

#### Enforce Required Arguments at CLI

Pydantic settings is designed to pull values in from various sources when instantating a model. This means a field that
Expand All @@ -884,10 +906,15 @@ import sys

from pydantic import Field

from pydantic_settings import BaseSettings
from pydantic_settings import BaseSettings, SettingsError


class Settings(BaseSettings, cli_parse_args=True, cli_enforce_required=True):
class Settings(
BaseSettings,
cli_parse_args=True,
cli_enforce_required=True,
cli_exit_on_error=False,
):
my_required_field: str = Field(description='a top level required field')


Expand All @@ -896,13 +923,9 @@ os.environ['MY_REQUIRED_FIELD'] = 'hello from environment'
try:
sys.argv = ['example.py']
Settings()
except SystemExit as e:
except SettingsError as e:
print(e)
#> 2
"""
usage: example.py [-h] --my_required_field str
example.py: error: the following arguments are required: --my_required_field
"""
#> error parsing CLI: the following arguments are required: --my_required_field
```

#### Change the None Type Parse String
Expand Down Expand Up @@ -1097,7 +1120,7 @@ parser methods that can be customised, along with their argparse counterparts (t
* `add_argument_group_method` - (`argparse.ArgumentParser.add_argument_group`)
* `add_parser_method` - (`argparse._SubParsersAction.add_parser`)
* `add_subparsers_method` - (`argparse.ArgumentParser.add_subparsers`)
* `formatter_class` - (`argparse.HelpFormatter`)
* `formatter_class` - (`argparse.RawDescriptionHelpFormatter`)

For a non-argparse parser the parser methods can be set to `None` if not supported. The CLI settings will only raise an
error when connecting to the root parser if a parser method is necessary but set to `None`.
Expand Down Expand Up @@ -1173,6 +1196,65 @@ Last, run your application inside a Docker container and supply your newly creat
docker service create --name pydantic-with-secrets --secret my_secret_data pydantic-app:latest
```

## Azure Key Vault

You must set two parameters:

- `url`: For example, `https://my-resource.vault.azure.net/`.
- `credential`: If you use `DefaultAzureCredential`, in local you can execute `az login` to get your identity credentials. The identity must have a role assignment (the recommended one is `Key Vault Secrets User`), so you can access the secrets.

You must have the same naming convention in the field name as in the Key Vault secret name. For example, if the secret is named `SqlServerPassword`, the field name must be the same. You can use an alias too.

In Key Vault, nested models are supported with the `--` separator. For example, `SqlServer--Password`.

Key Vault arrays (e.g. `MySecret--0`, `MySecret--1`) are not supported.

```py
import os
from typing import Tuple, Type

from azure.identity import DefaultAzureCredential
from pydantic import BaseModel

from pydantic_settings import (
AzureKeyVaultSettingsSource,
BaseSettings,
PydanticBaseSettingsSource,
)


class SubModel(BaseModel):
a: str


class AzureKeyVaultSettings(BaseSettings):
foo: str
bar: int
sub: SubModel

@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, ...]:
az_key_vault_settings = AzureKeyVaultSettingsSource(
settings_cls,
os.environ['AZURE_KEY_VAULT_URL'],
DefaultAzureCredential(),
)
return (
init_settings,
env_settings,
dotenv_settings,
file_secret_settings,
az_key_vault_settings,
)
```

## Other settings source

Other settings sources are available for common configuration files:
Expand Down
4 changes: 4 additions & 0 deletions pydantic_settings/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .main import BaseSettings, SettingsConfigDict
from .sources import (
AzureKeyVaultSettingsSource,
CliPositionalArg,
CliSettingsSource,
CliSubCommand,
Expand All @@ -10,6 +11,7 @@
PydanticBaseSettingsSource,
PyprojectTomlConfigSettingsSource,
SecretsSettingsSource,
SettingsError,
TomlConfigSettingsSource,
YamlConfigSettingsSource,
)
Expand All @@ -28,8 +30,10 @@
'PydanticBaseSettingsSource',
'SecretsSettingsSource',
'SettingsConfigDict',
'SettingsError',
'TomlConfigSettingsSource',
'YamlConfigSettingsSource',
'AzureKeyVaultSettingsSource',
'__version__',
)

Expand Down
11 changes: 11 additions & 0 deletions pydantic_settings/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class SettingsConfigDict(ConfigDict, total=False):
cli_avoid_json: bool
cli_enforce_required: bool
cli_use_class_docs_for_groups: bool
cli_exit_on_error: bool
cli_prefix: str
secrets_dir: str | Path | None
json_file: PathType | None
Expand Down Expand Up @@ -110,6 +111,8 @@ class BaseSettings(BaseModel):
_cli_enforce_required: Enforce required fields at the CLI. Defaults to `False`.
_cli_use_class_docs_for_groups: Use class docstrings in CLI group help text instead of field descriptions.
Defaults to `False`.
_cli_exit_on_error: Determines whether or not the internal parser exits with error info when an error occurs.
Defaults to `True`.
_cli_prefix: The root parser command line arguments prefix. Defaults to "".
_secrets_dir: The secret files directory. Defaults to `None`.
"""
Expand All @@ -132,6 +135,7 @@ def __init__(
_cli_avoid_json: bool | None = None,
_cli_enforce_required: bool | None = None,
_cli_use_class_docs_for_groups: bool | None = None,
_cli_exit_on_error: bool | None = None,
_cli_prefix: str | None = None,
_secrets_dir: str | Path | None = None,
**values: Any,
Expand All @@ -156,6 +160,7 @@ def __init__(
_cli_avoid_json=_cli_avoid_json,
_cli_enforce_required=_cli_enforce_required,
_cli_use_class_docs_for_groups=_cli_use_class_docs_for_groups,
_cli_exit_on_error=_cli_exit_on_error,
_cli_prefix=_cli_prefix,
_secrets_dir=_secrets_dir,
)
Expand Down Expand Up @@ -204,6 +209,7 @@ def _settings_build_values(
_cli_avoid_json: bool | None = None,
_cli_enforce_required: bool | None = None,
_cli_use_class_docs_for_groups: bool | None = None,
_cli_exit_on_error: bool | None = None,
_cli_prefix: str | None = None,
_secrets_dir: str | Path | None = None,
) -> dict[str, Any]:
Expand Down Expand Up @@ -250,6 +256,9 @@ def _settings_build_values(
if _cli_use_class_docs_for_groups is not None
else self.model_config.get('cli_use_class_docs_for_groups')
)
cli_exit_on_error = (
_cli_exit_on_error if _cli_exit_on_error is not None else self.model_config.get('cli_exit_on_error')
)
cli_prefix = _cli_prefix if _cli_prefix is not None else self.model_config.get('cli_prefix')

secrets_dir = _secrets_dir if _secrets_dir is not None else self.model_config.get('secrets_dir')
Expand Down Expand Up @@ -300,6 +309,7 @@ def _settings_build_values(
cli_avoid_json=cli_avoid_json,
cli_enforce_required=cli_enforce_required,
cli_use_class_docs_for_groups=cli_use_class_docs_for_groups,
cli_exit_on_error=cli_exit_on_error,
cli_prefix=cli_prefix,
case_sensitive=case_sensitive,
)
Expand Down Expand Up @@ -346,6 +356,7 @@ def _settings_build_values(
cli_avoid_json=False,
cli_enforce_required=False,
cli_use_class_docs_for_groups=False,
cli_exit_on_error=True,
cli_prefix='',
json_file=None,
json_file_encoding=None,
Expand Down
Loading

0 comments on commit abf01f7

Please sign in to comment.