Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/instance level source customization #350

Closed
Closed
Show file tree
Hide file tree
Changes from 2 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
130 changes: 102 additions & 28 deletions pydantic_settings/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from __future__ import annotations as _annotations

from collections.abc import Sequence
from pathlib import Path
from typing import Any, ClassVar
from typing import Any, Callable, ClassVar

from pydantic import ConfigDict
from pydantic._internal._config import config_keys
Expand Down Expand Up @@ -212,56 +213,115 @@ def _settings_build_values(
_cli_exit_on_error: bool | None = None,
_cli_prefix: str | None = None,
_secrets_dir: str | Path | None = None,
_settings_customize_instance: (
Callable[
[tuple[PydanticBaseSettingsSource, ...]],
tuple[PydanticBaseSettingsSource, ...],
]
| None
) = None,
) -> dict[str, Any]:
# Determine settings config values
case_sensitive = _case_sensitive if _case_sensitive is not None else self.model_config.get('case_sensitive')
env_prefix = _env_prefix if _env_prefix is not None else self.model_config.get('env_prefix')
env_file = _env_file if _env_file != ENV_FILE_SENTINEL else self.model_config.get('env_file')
case_sensitive = (
_case_sensitive
if _case_sensitive is not None
else self.model_config.get("case_sensitive")
)
env_prefix = (
_env_prefix
if _env_prefix is not None
else self.model_config.get("env_prefix")
)
env_file = (
_env_file
if _env_file != ENV_FILE_SENTINEL
else self.model_config.get("env_file")
)
env_file_encoding = (
_env_file_encoding if _env_file_encoding is not None else self.model_config.get('env_file_encoding')
_env_file_encoding
if _env_file_encoding is not None
else self.model_config.get("env_file_encoding")
)
env_ignore_empty = (
_env_ignore_empty if _env_ignore_empty is not None else self.model_config.get('env_ignore_empty')
_env_ignore_empty
if _env_ignore_empty is not None
else self.model_config.get("env_ignore_empty")
)
env_nested_delimiter = (
_env_nested_delimiter
if _env_nested_delimiter is not None
else self.model_config.get('env_nested_delimiter')
else self.model_config.get("env_nested_delimiter")
)
env_parse_none_str = (
_env_parse_none_str if _env_parse_none_str is not None else self.model_config.get('env_parse_none_str')
_env_parse_none_str
if _env_parse_none_str is not None
else self.model_config.get("env_parse_none_str")
)
env_parse_enums = (
_env_parse_enums
if _env_parse_enums is not None
else self.model_config.get("env_parse_enums")
)
env_parse_enums = _env_parse_enums if _env_parse_enums is not None else self.model_config.get('env_parse_enums')

cli_prog_name = _cli_prog_name if _cli_prog_name is not None else self.model_config.get('cli_prog_name')
cli_parse_args = _cli_parse_args if _cli_parse_args is not None else self.model_config.get('cli_parse_args')
cli_prog_name = (
_cli_prog_name
if _cli_prog_name is not None
else self.model_config.get("cli_prog_name")
)
cli_parse_args = (
_cli_parse_args
if _cli_parse_args is not None
else self.model_config.get("cli_parse_args")
)
cli_settings_source = (
_cli_settings_source if _cli_settings_source is not None else self.model_config.get('cli_settings_source')
_cli_settings_source
if _cli_settings_source is not None
else self.model_config.get("cli_settings_source")
)
cli_parse_none_str = (
_cli_parse_none_str
if _cli_parse_none_str is not None
else self.model_config.get("cli_parse_none_str")
)
cli_parse_none_str = (
_cli_parse_none_str if _cli_parse_none_str is not None else self.model_config.get('cli_parse_none_str')
cli_parse_none_str if not env_parse_none_str else env_parse_none_str
)
cli_parse_none_str = cli_parse_none_str if not env_parse_none_str else env_parse_none_str
cli_hide_none_type = (
_cli_hide_none_type if _cli_hide_none_type is not None else self.model_config.get('cli_hide_none_type')
_cli_hide_none_type
if _cli_hide_none_type is not None
else self.model_config.get("cli_hide_none_type")
)
cli_avoid_json = (
_cli_avoid_json
if _cli_avoid_json is not None
else self.model_config.get("cli_avoid_json")
)
cli_avoid_json = _cli_avoid_json if _cli_avoid_json is not None else self.model_config.get('cli_avoid_json')
cli_enforce_required = (
_cli_enforce_required
if _cli_enforce_required is not None
else self.model_config.get('cli_enforce_required')
else self.model_config.get("cli_enforce_required")
)
cli_use_class_docs_for_groups = (
_cli_use_class_docs_for_groups
if _cli_use_class_docs_for_groups is not None
else self.model_config.get('cli_use_class_docs_for_groups')
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_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")
)
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')
secrets_dir = (
_secrets_dir
if _secrets_dir is not None
else self.model_config.get("secrets_dir")
)

# Configure built-in sources
init_settings = InitSettingsSource(self.__class__, init_kwargs=init_kwargs)
Expand All @@ -287,7 +347,10 @@ def _settings_build_values(
)

file_secret_settings = SecretsSettingsSource(
self.__class__, secrets_dir=secrets_dir, case_sensitive=case_sensitive, env_prefix=env_prefix
self.__class__,
secrets_dir=secrets_dir,
case_sensitive=case_sensitive,
env_prefix=env_prefix,
)
# Provide a hook to set built-in sources priority and add / remove sources
sources = self.settings_customise_sources(
Expand All @@ -297,7 +360,14 @@ def _settings_build_values(
dotenv_settings=dotenv_settings,
file_secret_settings=file_secret_settings,
)
if not any([source for source in sources if isinstance(source, CliSettingsSource)]):

# Instance hook since :meth:`settings_customise_sources` is a classmethod.
if _settings_customize_instance is not None:
sources = _settings_customize_instance(sources)

if not any(
[source for source in sources if isinstance(source, CliSettingsSource)]
):
if cli_parse_args is not None or cli_settings_source is not None:
cli_settings = (
CliSettingsSource(
Expand Down Expand Up @@ -325,7 +395,11 @@ def _settings_build_values(
source._set_current_state(state)
source._set_settings_sources_data(states)

source_name = source.__name__ if hasattr(source, '__name__') else type(source).__name__
source_name = (
source.__name__
if hasattr(source, "__name__")
else type(source).__name__
)
source_state = source()

states[source_name] = source_state
Expand All @@ -337,11 +411,11 @@ def _settings_build_values(
return {}

model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(
extra='forbid',
extra="forbid",
arbitrary_types_allowed=True,
validate_default=True,
case_sensitive=False,
env_prefix='',
env_prefix="",
env_file=None,
env_file_encoding=None,
env_ignore_empty=False,
Expand All @@ -357,12 +431,12 @@ def _settings_build_values(
cli_enforce_required=False,
cli_use_class_docs_for_groups=False,
cli_exit_on_error=True,
cli_prefix='',
cli_prefix="",
json_file=None,
json_file_encoding=None,
yaml_file=None,
yaml_file_encoding=None,
toml_file=None,
secrets_dir=None,
protected_namespaces=('model_', 'settings_'),
protected_namespaces=("model_", "settings_"),
)
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ dependencies = [
dynamic = ['version']

[project.optional-dependencies]
test = ["pytest>=8.3.0", "pytest-mock>=3.14.0", "pytest-examples>=0.0.12"]
acederberg marked this conversation as resolved.
Show resolved Hide resolved
yaml = ["pyyaml>=6.0.1"]
toml = ["tomli>=2.0.1"]
azure-key-vault = ["azure-keyvault-secrets>=4.8.0", "azure-identity>=1.16.0"]
Expand Down
Loading