Skip to content

Commit c05410f

Browse files
hramezaniKludex
andauthored
Improve docstrings (#72)
Co-authored-by: Marcelo Trylesinski <[email protected]>
1 parent 435a136 commit c05410f

File tree

3 files changed

+123
-33
lines changed

3 files changed

+123
-33
lines changed

docs/index.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,9 @@ from pydantic_settings import BaseSettings
9999

100100

101101
class Settings(BaseSettings):
102-
redis_host: str = 'localhost'
103-
104102
model_config = ConfigDict(case_sensitive=True)
103+
104+
redis_host: str = 'localhost'
105105
```
106106

107107
When `case_sensitive` is `True`, the environment variable names must match field names (optionally with a prefix),
@@ -163,11 +163,11 @@ class SubModel(BaseModel):
163163

164164

165165
class Settings(BaseSettings):
166+
model_config = ConfigDict(env_nested_delimiter='__')
167+
166168
v0: str
167169
sub_model: SubModel
168170

169-
model_config = ConfigDict(env_nested_delimiter='__')
170-
171171

172172
print(Settings().model_dump())
173173
"""
@@ -259,9 +259,10 @@ in a `BaseSettings` class:
259259

260260
```py test="skip" lint="skip"
261261
class Settings(BaseSettings):
262+
model_config = ConfigDict(env_file='.env', env_file_encoding = 'utf-8')
263+
262264
...
263265

264-
model_config = ConfigDict(env_file='.env', env_file_encoding = 'utf-8')
265266
```
266267

267268
**2.** instantiating a `BaseSettings` derived class with the `_env_file` keyword argument
@@ -297,12 +298,12 @@ from pydantic_settings import BaseSettings
297298

298299

299300
class Settings(BaseSettings):
300-
...
301-
302301
model_config = ConfigDict(
303302
# `.env.prod` takes priority over `.env`
304303
env_file=('.env', '.env.prod')
305304
)
305+
306+
...
306307
```
307308

308309
You can also use the keyword argument override to tell Pydantic not to load any file at all (even if one is set in
@@ -334,10 +335,11 @@ Once you have your secret files, *pydantic* supports loading it in two ways:
334335

335336
```py test="skip" lint="skip"
336337
class Settings(BaseSettings):
338+
model_config = ConfigDict(secrets_dir='/var/run')
339+
337340
...
338341
database_password: str
339342

340-
model_config = ConfigDict(secrets_dir='/var/run')
341343
```
342344

343345
**2.** instantiating a `BaseSettings` derived class with the `_secrets_dir` keyword argument:
@@ -366,9 +368,9 @@ and using secrets in Docker see the official
366368
First, define your Settings
367369
```py test="skip" lint="skip"
368370
class Settings(BaseSettings):
369-
my_secret_data: str
370-
371371
model_config = ConfigDict(secrets_dir='/run/secrets')
372+
373+
my_secret_data: str
372374
```
373375
!!! note
374376
By default Docker uses `/run/secrets` as the target mount point. If you want to use a different location, change
@@ -495,10 +497,10 @@ class JsonConfigSettingsSource(PydanticBaseSettingsSource):
495497

496498

497499
class Settings(BaseSettings):
498-
foobar: str
499-
500500
model_config = ConfigDict(env_file_encoding='utf-8')
501501

502+
foobar: str
503+
502504
@classmethod
503505
def settings_customise_sources(
504506
cls,

pydantic_settings/main.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ class BaseSettings(BaseModel):
3434
3535
This is useful in production for secrets you do not wish to save in code, it plays nicely with docker(-compose),
3636
Heroku and any 12 factor app design.
37+
38+
All the bellow attributes can be set via `model_config`.
39+
40+
Args:
41+
_env_file: The env file(s) to load settings values from. Defaults to `Path('')`.
42+
_env_file_encoding: The env file encoding. e.g. `'latin-1'`. Defaults to `None`.
43+
_env_nested_delimiter: The nested env values delimiter. Defaults to `None`.
44+
_secrets_dir: The secret files directory. Defaults to `None`.
3745
"""
3846

3947
def __init__(
@@ -64,6 +72,19 @@ def settings_customise_sources(
6472
dotenv_settings: PydanticBaseSettingsSource,
6573
file_secret_settings: PydanticBaseSettingsSource,
6674
) -> tuple[PydanticBaseSettingsSource, ...]:
75+
"""
76+
Define the sources and their order for loading the settings values.
77+
78+
Args:
79+
settings_cls: The Settings class.
80+
init_settings: The `InitSettingsSource` instance.
81+
env_settings: The `EnvSettingsSource` instance.
82+
dotenv_settings: The `DotEnvSettingsSource` instance.
83+
file_secret_settings: The `SecretsSettingsSource` instance.
84+
85+
Returns:
86+
A tuple containing the sources and their order for loading the settings values.
87+
"""
6788
return init_settings, env_settings, dotenv_settings, file_secret_settings
6889

6990
def _settings_build_values(

pydantic_settings/sources.py

Lines changed: 88 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str,
4545
This is an abstract method that should be overrided in every settings source classes.
4646
4747
Args:
48-
field (FieldInfo): The field.
49-
field_name (str): The field name.
48+
field: The field.
49+
field_name: The field name.
5050
5151
Returns:
52-
tuple[str, Any, bool]: The key, value and a flag to determine whether value is complex.
52+
A tuple contains the key, value and a flag to determine whether value is complex.
5353
"""
5454
pass
5555

@@ -58,10 +58,10 @@ def field_is_complex(self, field: FieldInfo) -> bool:
5858
Checks whether a field is complex, in which case it will attempt to be parsed as JSON.
5959
6060
Args:
61-
field (FieldInfo): The field.
61+
field: The field.
6262
6363
Returns:
64-
bool: Whether the field is complex.
64+
Whether the field is complex.
6565
"""
6666
return _annotation_is_complex(field.annotation)
6767

@@ -70,13 +70,13 @@ def prepare_field_value(self, field_name: str, field: FieldInfo, value: Any, val
7070
Prepares the value of a field.
7171
7272
Args:
73-
field_name (str): The field name.
74-
field (FieldInfo): The field.
75-
value (Any): The value of the field that has to be prepared.
73+
field_name: The field name.
74+
field: The field.
75+
value: The value of the field that has to be prepared.
7676
value_is_complex: A flag to determine whether value is complex.
7777
7878
Returns:
79-
Any: The prepared value.
79+
The prepared value.
8080
"""
8181
if self.field_is_complex(field) or value_is_complex:
8282
return json.loads(value)
@@ -88,6 +88,10 @@ def __call__(self) -> dict[str, Any]:
8888

8989

9090
class InitSettingsSource(PydanticBaseSettingsSource):
91+
"""
92+
Source class for loading values provided during settings class initialization.
93+
"""
94+
9195
def __init__(self, settings_cls: type[BaseSettings], init_kwargs: dict[str, Any]):
9296
self.init_kwargs = init_kwargs
9397
super().__init__(settings_cls)
@@ -236,6 +240,10 @@ def __call__(self) -> dict[str, Any]:
236240

237241

238242
class SecretsSettingsSource(PydanticBaseEnvSettingsSource):
243+
"""
244+
Source class for loading settings values from secret files.
245+
"""
246+
239247
def __init__(self, settings_cls: type[BaseSettings], secrets_dir: str | Path | None):
240248
self.secrets_dir = secrets_dir
241249
super().__init__(settings_cls)
@@ -264,6 +272,14 @@ def __call__(self) -> dict[str, Any]:
264272
def find_case_path(cls, dir_path: Path, file_name: str, case_sensitive: bool) -> Path | None:
265273
"""
266274
Find a file within path's directory matching filename, optionally ignoring case.
275+
276+
Args:
277+
dir_path: Directory path.
278+
file_name: File name.
279+
case_sensitive: Whether to search for file name case sensitively.
280+
281+
Returns:
282+
Whether file path or `None` if file does not exist in directory.
267283
"""
268284
for f in dir_path.iterdir():
269285
if f.name == file_name:
@@ -273,6 +289,18 @@ def find_case_path(cls, dir_path: Path, file_name: str, case_sensitive: bool) ->
273289
return None
274290

275291
def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]:
292+
"""
293+
Gets the value for field from secret file and a flag to determine whether value is complex.
294+
295+
Args:
296+
field: The field.
297+
field_name: The field name.
298+
299+
Returns:
300+
A tuple contains the key, value if the file exists otherwise `None`, and
301+
a flag to determine whether value is complex.
302+
"""
303+
276304
for field_key, env_name, value_is_complex in self._extract_field_info(field, field_name):
277305
path = self.find_case_path(
278306
self.secrets_path, env_name, self.settings_cls.model_config.get('case_sensitive', False)
@@ -296,6 +324,10 @@ def __repr__(self) -> str:
296324

297325

298326
class EnvSettingsSource(PydanticBaseEnvSettingsSource):
327+
"""
328+
Source class for loading settings values from environment variables.
329+
"""
330+
299331
def __init__(
300332
self,
301333
settings_cls: type[BaseSettings],
@@ -315,6 +347,18 @@ def _load_env_vars(self) -> Mapping[str, str | None]:
315347
return {k.lower(): v for k, v in os.environ.items()}
316348

317349
def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, bool]:
350+
"""
351+
Gets the value for field from environment variables and a flag to determine whether value is complex.
352+
353+
Args:
354+
field: The field.
355+
field_name: The field name.
356+
357+
Returns:
358+
A tuple contains the key, value if the file exists otherwise `None`, and
359+
a flag to determine whether value is complex.
360+
"""
361+
318362
env_val: str | None = None
319363
for field_key, env_name, value_is_complex in self._extract_field_info(field, field_name):
320364
env_val = self.env_vars.get(env_name)
@@ -324,6 +368,22 @@ def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str,
324368
return env_val, field_key, value_is_complex
325369

326370
def prepare_field_value(self, field_name: str, field: FieldInfo, value: Any, value_is_complex: bool) -> Any:
371+
"""
372+
Prepare value for the field.
373+
374+
* Extract value for nested field.
375+
* Deserialize value to python object for complex field.
376+
377+
Args:
378+
field: The field.
379+
field_name: The field name.
380+
381+
Returns:
382+
A tuple contains prepared value for the field.
383+
384+
Raises:
385+
ValuesError: When There is an error in deserializing value for complex field.
386+
"""
327387
is_complex, allow_parse_failure = self._field_is_complex(field)
328388
if is_complex or value_is_complex:
329389
if value is None:
@@ -382,6 +442,13 @@ class Cfg(BaseSettings):
382442
Then:
383443
next_field(sub_model, 'vals') Returns the `vals` field of `SubModel` class
384444
next_field(sub_model, 'sub_sub_model') Returns `sub_sub_model` field of `SubModel` class
445+
446+
Args:
447+
field: The field.
448+
key: The key (env name).
449+
450+
Returns:
451+
Field if it finds the next field otherwise `None`.
385452
"""
386453
if not field or origin_is_union(get_origin(field.annotation)):
387454
# no support for Unions of complex BaseSettings fields
@@ -396,6 +463,14 @@ def explode_env_vars(self, field_name: str, field: FieldInfo, env_vars: Mapping[
396463
Process env_vars and extract the values of keys containing env_nested_delimiter into nested dictionaries.
397464
398465
This is applied to a single field, hence filtering by env_var prefix.
466+
467+
Args:
468+
field_name: The field name.
469+
field: The field.
470+
env_vars: Environment variables.
471+
472+
Returns:
473+
A dictionaty contains extracted values from nested env values.
399474
"""
400475
prefixes = [
401476
f'{env_name}{self.env_nested_delimiter}' for _, env_name, _ in self._extract_field_info(field, field_name)
@@ -437,6 +512,10 @@ def __repr__(self) -> str:
437512

438513

439514
class DotEnvSettingsSource(EnvSettingsSource):
515+
"""
516+
Source class for loading settings values from env files.
517+
"""
518+
440519
def __init__(
441520
self,
442521
settings_cls: type[BaseSettings],
@@ -514,18 +593,6 @@ def read_env_file(
514593
return file_vars
515594

516595

517-
def find_case_path(dir_path: Path, file_name: str, case_sensitive: bool) -> Path | None:
518-
"""
519-
Find a file within path's directory matching filename, optionally ignoring case.
520-
"""
521-
for f in dir_path.iterdir():
522-
if f.name == file_name:
523-
return f
524-
elif not case_sensitive and f.name.lower() == file_name.lower():
525-
return f
526-
return None
527-
528-
529596
def _annotation_is_complex(annotation: type[Any] | None) -> bool:
530597
origin = get_origin(annotation)
531598
return (

0 commit comments

Comments
 (0)