Skip to content

Commit 1144885

Browse files
hramezanidmontagusamuelcolvin
authored
Adopt pydantic-settings with Pydantic V2 (#17)
Co-authored-by: David Montague <[email protected]> Co-authored-by: Samuel Colvin <[email protected]>
1 parent 0ecac22 commit 1144885

File tree

9 files changed

+619
-530
lines changed

9 files changed

+619
-530
lines changed

pydantic_settings/main.py

Lines changed: 52 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
from __future__ import annotations as _annotations
22

3-
import warnings
4-
from typing import AbstractSet, Any, ClassVar, Dict, List, Optional, Tuple, Type, Union
3+
from pathlib import Path
4+
from typing import Any, Dict, Optional, Tuple, Type, Union
55

6-
from pydantic.config import BaseConfig, Extra
7-
from pydantic.fields import ModelField
6+
from pydantic import ConfigDict
7+
from pydantic._internal._utils import deep_update
88
from pydantic.main import BaseModel
9-
from pydantic.typing import StrPath, display_as_type
10-
from pydantic.utils import deep_update, sequence_like
119

1210
from .sources import (
1311
DotEnvSettingsSource,
@@ -18,7 +16,16 @@
1816
SecretsSettingsSource,
1917
)
2018

21-
env_file_sentinel = str(object())
19+
env_file_sentinel: DotenvType = Path('')
20+
21+
22+
class SettingsConfigDict(ConfigDict):
23+
case_sensitive: bool
24+
env_prefix: str
25+
env_file: Optional[DotenvType]
26+
env_file_encoding: Optional[str]
27+
env_nested_delimiter: Optional[str]
28+
secrets_dir: Optional[Union[str, Path]]
2229

2330

2431
class BaseSettings(BaseModel):
@@ -34,12 +41,12 @@ def __init__(
3441
_env_file: Optional[DotenvType] = env_file_sentinel,
3542
_env_file_encoding: Optional[str] = None,
3643
_env_nested_delimiter: Optional[str] = None,
37-
_secrets_dir: Optional[StrPath] = None,
44+
_secrets_dir: Optional[Union[str, Path]] = None,
3845
**values: Any,
3946
) -> None:
4047
# Uses something other than `self` the first arg to allow "self" as a settable attribute
4148
super().__init__(
42-
**__pydantic_self__._build_values(
49+
**__pydantic_self__._settings_build_values(
4350
values,
4451
_env_file=_env_file,
4552
_env_file_encoding=_env_file_encoding,
@@ -48,40 +55,55 @@ def __init__(
4855
)
4956
)
5057

51-
def _build_values(
58+
@classmethod
59+
def settings_customise_sources(
60+
cls,
61+
settings_cls: Type[BaseSettings],
62+
init_settings: PydanticBaseSettingsSource,
63+
env_settings: PydanticBaseSettingsSource,
64+
dotenv_settings: PydanticBaseSettingsSource,
65+
file_secret_settings: PydanticBaseSettingsSource,
66+
) -> Tuple[PydanticBaseSettingsSource, ...]:
67+
return init_settings, env_settings, dotenv_settings, file_secret_settings
68+
69+
def _settings_build_values(
5270
self,
5371
init_kwargs: Dict[str, Any],
5472
_env_file: Optional[DotenvType] = None,
5573
_env_file_encoding: Optional[str] = None,
5674
_env_nested_delimiter: Optional[str] = None,
57-
_secrets_dir: Optional[StrPath] = None,
75+
_secrets_dir: Optional[Union[str, Path]] = None,
5876
) -> Dict[str, Any]:
5977
# Configure built-in sources
6078
init_settings = InitSettingsSource(self.__class__, init_kwargs=init_kwargs)
6179
env_settings = EnvSettingsSource(
6280
self.__class__,
6381
env_nested_delimiter=(
64-
_env_nested_delimiter if _env_nested_delimiter is not None else self.__config__.env_nested_delimiter
82+
_env_nested_delimiter
83+
if _env_nested_delimiter is not None
84+
else self.model_config.get('env_nested_delimiter')
6585
),
66-
env_prefix_len=len(self.__config__.env_prefix),
86+
env_prefix_len=len(self.model_config.get('env_prefix', '')),
6787
)
6888
dotenv_settings = DotEnvSettingsSource(
6989
self.__class__,
70-
env_file=(_env_file if _env_file != env_file_sentinel else self.__config__.env_file),
90+
env_file=(_env_file if _env_file != env_file_sentinel else self.model_config.get('env_file')),
7191
env_file_encoding=(
72-
_env_file_encoding if _env_file_encoding is not None else self.__config__.env_file_encoding
92+
_env_file_encoding if _env_file_encoding is not None else self.model_config.get('env_file_encoding')
7393
),
7494
env_nested_delimiter=(
75-
_env_nested_delimiter if _env_nested_delimiter is not None else self.__config__.env_nested_delimiter
95+
_env_nested_delimiter
96+
if _env_nested_delimiter is not None
97+
else self.model_config.get('env_nested_delimiter')
7698
),
77-
env_prefix_len=len(self.__config__.env_prefix),
99+
env_prefix_len=len(self.model_config.get('env_prefix', '')),
78100
)
79101

80102
file_secret_settings = SecretsSettingsSource(
81-
self.__class__, secrets_dir=_secrets_dir or self.__config__.secrets_dir
103+
self.__class__, secrets_dir=_secrets_dir or self.model_config.get('secrets_dir')
82104
)
83105
# Provide a hook to set built-in sources priority and add / remove sources
84-
sources = self.__config__.customise_sources(
106+
sources = self.settings_customise_sources(
85107
self.__class__,
86108
init_settings=init_settings,
87109
env_settings=env_settings,
@@ -95,59 +117,14 @@ def _build_values(
95117
# to an informative error and much better than a confusing error
96118
return {}
97119

98-
class Config(BaseConfig):
99-
env_prefix: str = ''
100-
env_file: Optional[DotenvType] = None
101-
env_file_encoding: Optional[str] = None
102-
env_nested_delimiter: Optional[str] = None
103-
secrets_dir: Optional[StrPath] = None
104-
validate_all: bool = True
105-
extra: Extra = Extra.forbid
106-
arbitrary_types_allowed: bool = True
107-
case_sensitive: bool = False
108-
109-
@classmethod
110-
def prepare_field(cls, field: ModelField) -> None:
111-
env_names: Union[List[str], AbstractSet[str]]
112-
field_info_from_config = cls.get_field_info(field.name)
113-
114-
env = field_info_from_config.get('env') or field.field_info.extra.get('env')
115-
if env is None:
116-
if field.has_alias:
117-
warnings.warn(
118-
'aliases are no longer used by BaseSettings to define which environment variables to read. '
119-
'Instead use the "env" field setting. '
120-
'See https://pydantic-docs.helpmanual.io/usage/settings/#environment-variable-names',
121-
FutureWarning,
122-
)
123-
env_names = {cls.env_prefix + field.name}
124-
elif isinstance(env, str):
125-
env_names = {env}
126-
elif isinstance(env, (set, frozenset)):
127-
env_names = env
128-
elif sequence_like(env):
129-
env_names = list(env)
130-
else:
131-
raise TypeError(f'invalid field env: {env!r} ({display_as_type(env)}); should be string, list or set')
132-
133-
if not cls.case_sensitive:
134-
env_names = env_names.__class__(n.lower() for n in env_names)
135-
field.field_info.extra['env_names'] = env_names
136-
137-
@classmethod
138-
def customise_sources(
139-
cls,
140-
settings_cls: Type[BaseSettings],
141-
init_settings: PydanticBaseSettingsSource,
142-
env_settings: PydanticBaseSettingsSource,
143-
dotenv_settings: PydanticBaseSettingsSource,
144-
file_secret_settings: PydanticBaseSettingsSource,
145-
) -> Tuple[PydanticBaseSettingsSource, ...]:
146-
return init_settings, env_settings, dotenv_settings, file_secret_settings
147-
148-
@classmethod
149-
def parse_env_var(cls, field_name: str, raw_val: str) -> Any:
150-
return cls.json_loads(raw_val)
151-
152-
# populated by the metaclass using the Config class defined above, annotated here to help IDEs only
153-
__config__: ClassVar[Type[Config]]
120+
model_config = SettingsConfigDict(
121+
extra='forbid',
122+
arbitrary_types_allowed=True,
123+
validate_default=True,
124+
case_sensitive=False,
125+
env_prefix='',
126+
env_file=None,
127+
env_file_encoding=None,
128+
env_nested_delimiter=None,
129+
secrets_dir=None,
130+
)

0 commit comments

Comments
 (0)