psyplus 1.1.0__tar.gz → 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {psyplus-1.1.0 → psyplus-1.2.0}/PKG-INFO +150 -39
- {psyplus-1.1.0 → psyplus-1.2.0}/README.md +143 -33
- psyplus-1.2.0/psyplus/field_info.py +532 -0
- psyplus-1.2.0/psyplus/main.py +244 -0
- psyplus-1.2.0/psyplus/markdown_generator.py +242 -0
- psyplus-1.2.0/psyplus/sources.py +641 -0
- {psyplus-1.1.0 → psyplus-1.2.0}/psyplus/utils.py +120 -0
- psyplus-1.2.0/psyplus/yaml_generator.py +380 -0
- {psyplus-1.1.0 → psyplus-1.2.0}/pyproject.toml +7 -6
- psyplus-1.2.0/tests/models/env_cases.py +247 -0
- {psyplus-1.1.0 → psyplus-1.2.0}/tests/models/nested.py +1 -1
- {psyplus-1.1.0 → psyplus-1.2.0}/tests/models/readme_example.py +1 -1
- {psyplus-1.1.0 → psyplus-1.2.0}/tests/models/simple.py +3 -3
- psyplus-1.2.0/tests/test_aliases.py +550 -0
- {psyplus-1.1.0 → psyplus-1.2.0}/tests/test_env_roundtrip.py +130 -12
- {psyplus-1.1.0 → psyplus-1.2.0}/tests/test_field_info.py +118 -4
- psyplus-1.2.0/tests/test_list_index_env.py +755 -0
- {psyplus-1.1.0 → psyplus-1.2.0}/tests/test_main.py +171 -3
- {psyplus-1.1.0 → psyplus-1.2.0}/tests/test_markdown.py +182 -6
- psyplus-1.2.0/tests/test_required_fields.py +269 -0
- {psyplus-1.1.0 → psyplus-1.2.0}/tests/test_utils.py +88 -1
- {psyplus-1.1.0 → psyplus-1.2.0}/tests/test_yaml_generation.py +129 -89
- psyplus-1.1.0/psyplus/field_info.py +0 -269
- psyplus-1.1.0/psyplus/main.py +0 -148
- psyplus-1.1.0/psyplus/markdown_generator.py +0 -185
- psyplus-1.1.0/psyplus/yaml_generator.py +0 -256
- psyplus-1.1.0/tests/models/env_cases.py +0 -98
- {psyplus-1.1.0 → psyplus-1.2.0}/LICENSE +0 -0
- {psyplus-1.1.0 → psyplus-1.2.0}/psyplus/__init__.py +0 -0
- {psyplus-1.1.0 → psyplus-1.2.0}/tests/__init__.py +0 -0
- {psyplus-1.1.0 → psyplus-1.2.0}/tests/models/__init__.py +0 -0
- {psyplus-1.1.0 → psyplus-1.2.0}/tests/models/nullable.py +0 -0
- {psyplus-1.1.0 → psyplus-1.2.0}/tests/test_nullable.py +0 -0
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: psyplus
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Generate, read and document pydantic-settings config files in YAML
|
|
5
5
|
Keywords: DZD,pydantic,pydantic-settings,yaml,config
|
|
6
6
|
Author-Email: Tim Bleimehl <bleimehl@helmholtz-munich.de>
|
|
7
7
|
License: MIT
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
10
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
11
12
|
Classifier: Programming Language :: Python :: 3.14
|
|
12
13
|
Project-URL: Source, https://github.com/DZD-eV-Diabetes-Research/pydantic-settings-yaml-plus
|
|
13
14
|
Project-URL: Issues, https://github.com/DZD-eV-Diabetes-Research/pydantic-settings-yaml-plus/issues
|
|
14
|
-
Requires-Python: >=3.
|
|
15
|
-
Requires-Dist: pydantic>=2.
|
|
16
|
-
Requires-Dist: pydantic-settings>=2.
|
|
17
|
-
Requires-Dist: ruamel.yaml>=0.17
|
|
18
|
-
Requires-Dist: pyyaml>=6.0
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Requires-Dist: pydantic>=2.10
|
|
17
|
+
Requires-Dist: pydantic-settings>=2.13.1
|
|
18
|
+
Requires-Dist: ruamel.yaml>=0.17.5
|
|
19
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
19
20
|
Provides-Extra: test
|
|
20
21
|
Requires-Dist: pytest>=8.0; extra == "test"
|
|
21
22
|
Requires-Dist: pytest-cov>=5.0; extra == "test"
|
|
@@ -23,14 +24,14 @@ Description-Content-Type: text/markdown
|
|
|
23
24
|
|
|
24
25
|
# pydantic-settings-yaml-plus
|
|
25
26
|
|
|
26
|
-
A helper library that builds upon [pydantic-settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/) to **generate**, **read**, and **document** YAML config files
|
|
27
|
+
A helper library that builds upon [pydantic-settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/) to **generate**, **read**, and **document** YAML config files, with comment headers that explain every setting's type, default value, allowed values, environment variable override, and description.
|
|
27
28
|
|
|
28
29
|
- [pydantic-settings-yaml-plus](#pydantic-settings-yaml-plus)
|
|
29
30
|
- [Features](#features)
|
|
30
31
|
- [Installation](#installation)
|
|
31
32
|
- [Quick start](#quick-start)
|
|
32
|
-
- [1
|
|
33
|
-
- [2
|
|
33
|
+
- [1. Define your settings model](#1-define-your-settings-model)
|
|
34
|
+
- [2. Generate the YAML config file](#2-generate-the-yaml-config-file)
|
|
34
35
|
- [Generated YAML output](#generated-yaml-output)
|
|
35
36
|
- [Reading the config](#reading-the-config)
|
|
36
37
|
- [Generating Markdown documentation](#generating-markdown-documentation)
|
|
@@ -44,6 +45,7 @@ A helper library that builds upon [pydantic-settings](https://docs.pydantic.dev/
|
|
|
44
45
|
- [Useful `Field()` kwargs](#useful-field-kwargs)
|
|
45
46
|
- [Configuring env vars](#configuring-env-vars)
|
|
46
47
|
- [Environment variable overrides](#environment-variable-overrides)
|
|
48
|
+
- [Single list elements](#single-list-elements)
|
|
47
49
|
- [Development](#development)
|
|
48
50
|
- [Smoke test](#smoke-test)
|
|
49
51
|
- [Running tests with coverage](#running-tests-with-coverage)
|
|
@@ -53,11 +55,12 @@ A helper library that builds upon [pydantic-settings](https://docs.pydantic.dev/
|
|
|
53
55
|
|
|
54
56
|
## Features
|
|
55
57
|
|
|
56
|
-
- **Generate documented YAML
|
|
57
|
-
- **Nested models
|
|
58
|
-
- **Read back with env-var override
|
|
59
|
-
- **
|
|
60
|
-
- **
|
|
58
|
+
- **Generate documented YAML:** each field gets a comment block with its type, required flag, default value, allowed enum values, corresponding environment variable, description, and examples.
|
|
59
|
+
- **Nested models:** `BaseModel` sub-models are rendered as indented YAML sections with full comment headers.
|
|
60
|
+
- **Read back with env-var override:** `load()` loads the YAML file and still applies environment variable overrides using pydantic-settings' standard machinery.
|
|
61
|
+
- **Single list elements from env vars:** `load()` also reads env vars like `APP_SERVERS__0__HOST` that change one element of a list from the YAML file.
|
|
62
|
+
- **Markdown docs:** generate a Markdown reference document straight from your settings class.
|
|
63
|
+
- **Zero boilerplate:** define your settings in a standard `pydantic_settings.BaseSettings` subclass; psyplus does the rest.
|
|
61
64
|
|
|
62
65
|
---
|
|
63
66
|
|
|
@@ -67,13 +70,13 @@ A helper library that builds upon [pydantic-settings](https://docs.pydantic.dev/
|
|
|
67
70
|
pip install psyplus
|
|
68
71
|
```
|
|
69
72
|
|
|
70
|
-
**Requirements:** Python ≥ 3.
|
|
73
|
+
**Requirements:** Python ≥ 3.11, pydantic ≥ 2.10, pydantic-settings ≥ 2.13.1, ruamel.yaml ≥ 0.17.5, PyYAML ≥ 6.0.1
|
|
71
74
|
|
|
72
75
|
---
|
|
73
76
|
|
|
74
77
|
## Quick start
|
|
75
78
|
|
|
76
|
-
### 1
|
|
79
|
+
### 1. Define your settings model
|
|
77
80
|
|
|
78
81
|
```python
|
|
79
82
|
# config.py
|
|
@@ -128,7 +131,7 @@ class MyAppConfig(BaseSettings):
|
|
|
128
131
|
|
|
129
132
|
> **Note on `model_config`:** use `SettingsConfigDict` (pydantic-settings v2 style) rather than the old inner `class Config`. Both work, but `SettingsConfigDict` is preferred.
|
|
130
133
|
|
|
131
|
-
### 2
|
|
134
|
+
### 2. Generate the YAML config file
|
|
132
135
|
|
|
133
136
|
```python
|
|
134
137
|
from psyplus import YamlSettingsPlus
|
|
@@ -179,7 +182,7 @@ storage_dir: /home/user/.config/myapp
|
|
|
179
182
|
# Required: True
|
|
180
183
|
# Env-var: 'APP_ADMIN_PW'
|
|
181
184
|
# Description: The init password for the admin account
|
|
182
|
-
admin_pw:
|
|
185
|
+
admin_pw:
|
|
183
186
|
|
|
184
187
|
# ## database_server ###
|
|
185
188
|
# Type: Object (DatabaseServerSettings)
|
|
@@ -218,22 +221,28 @@ database_server:
|
|
|
218
221
|
# Type: List of str
|
|
219
222
|
# Required: True
|
|
220
223
|
# Env-var: 'APP_DATABASE_SERVER__DATABASE_NAMES'
|
|
224
|
+
# 'APP_DATABASE_SERVER__DATABASE_NAMES__<list-index>' (one element, 0 is the first; psyplus load() only)
|
|
221
225
|
# Description: The names of the databases to use
|
|
222
226
|
# Example:
|
|
223
227
|
# >database_names:
|
|
224
228
|
# >- mydb
|
|
225
229
|
# >- theotherdb
|
|
226
|
-
database_names:
|
|
230
|
+
database_names:
|
|
227
231
|
|
|
228
232
|
# ## init_values ###
|
|
229
233
|
# Type: Dictionary of (str, str)
|
|
230
234
|
# Required: True
|
|
231
235
|
# Env-var: 'APP_INIT_VALUES'
|
|
232
|
-
|
|
236
|
+
# 'APP_INIT_VALUES__<dict-key>' sets one entry, its key arrives in lower case
|
|
237
|
+
init_values:
|
|
233
238
|
```
|
|
234
239
|
|
|
235
|
-
Required fields without
|
|
236
|
-
|
|
240
|
+
Required fields without a default are written with an empty value (`admin_pw:`, which YAML reads as null),
|
|
241
|
+
whatever their type. A required sub-model without a default shows its fields, so the structure is
|
|
242
|
+
visible, with its own required fields empty. The `Required: True` comment marks every value to fill in.
|
|
243
|
+
Until that is done, `load()` raises a pydantic `ValidationError` that names each empty required field,
|
|
244
|
+
so an app does not start with an unfilled config. (A required `Optional[...]` field is the exception:
|
|
245
|
+
null is a valid value for it.)
|
|
237
246
|
|
|
238
247
|
---
|
|
239
248
|
|
|
@@ -250,6 +259,8 @@ print(config.database_server.host) # → "localhost" (or whatever is in the YA
|
|
|
250
259
|
print(config.log_level) # → "INFO"
|
|
251
260
|
```
|
|
252
261
|
|
|
262
|
+
`load()` returns an instance of your model class itself (`type(config) is MyAppConfig`), validated once, exactly as if you had called `MyAppConfig()`. It compares equal to and pickles like any other `MyAppConfig` instance.
|
|
263
|
+
|
|
253
264
|
`load()` honours environment variable overrides through pydantic-settings' standard source priority: **env vars beat YAML values**. For example:
|
|
254
265
|
|
|
255
266
|
```bash
|
|
@@ -258,10 +269,10 @@ export APP_LOG_LEVEL=DEBUG
|
|
|
258
269
|
|
|
259
270
|
…will make `config.log_level == "DEBUG"` even if the YAML file says `INFO`.
|
|
260
271
|
|
|
261
|
-
> **Tip:** if you prefer to keep psyplus out of your runtime path, configure `yaml_file` directly in your model and
|
|
272
|
+
> **Tip:** if you prefer to keep psyplus out of your runtime path, configure `yaml_file` directly in your model and add a YAML source (setting `yaml_file` alone is not enough, pydantic-settings does not read it by default):
|
|
262
273
|
>
|
|
263
274
|
> ```python
|
|
264
|
-
> from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
275
|
+
> from pydantic_settings import BaseSettings, SettingsConfigDict, YamlConfigSettingsSource
|
|
265
276
|
>
|
|
266
277
|
> class MyAppConfig(BaseSettings):
|
|
267
278
|
> model_config = SettingsConfigDict(
|
|
@@ -270,8 +281,19 @@ export APP_LOG_LEVEL=DEBUG
|
|
|
270
281
|
> env_nested_delimiter="__",
|
|
271
282
|
> )
|
|
272
283
|
>
|
|
273
|
-
>
|
|
284
|
+
> @classmethod
|
|
285
|
+
> def settings_customise_sources(
|
|
286
|
+
> cls, settings_cls, init_settings, env_settings, dotenv_settings, file_secret_settings
|
|
287
|
+
> ):
|
|
288
|
+
> return (
|
|
289
|
+
> init_settings, env_settings, dotenv_settings, file_secret_settings,
|
|
290
|
+
> YamlConfigSettingsSource(settings_cls),
|
|
291
|
+
> )
|
|
292
|
+
>
|
|
293
|
+
> config = MyAppConfig() # reads YAML + env vars
|
|
274
294
|
> ```
|
|
295
|
+
>
|
|
296
|
+
> Such a model still works with `load()`: psyplus then reads its own `file_path` in place of `yaml_file` (see [Environment variable overrides](#environment-variable-overrides)).
|
|
275
297
|
|
|
276
298
|
---
|
|
277
299
|
|
|
@@ -294,6 +316,11 @@ md = YamlSettingsPlus(MyAppConfig).render_markdown()
|
|
|
294
316
|
|
|
295
317
|
Both produce a document with a property table for every field: type, required flag, default, allowed values, environment variable, description, and examples.
|
|
296
318
|
|
|
319
|
+
The fields of a nested model get their own sections, headed by their full path (`database.host`,
|
|
320
|
+
`servers[*].port`). A field that holds one of several models (`Union[Cat, Dog]`) gets one section
|
|
321
|
+
per model, labelled `option 1 of 2` and so on. Markdown has no headings below level 6, so deeper
|
|
322
|
+
fields stay at level 6 and the path in the heading shows where they belong.
|
|
323
|
+
|
|
297
324
|
---
|
|
298
325
|
|
|
299
326
|
## Example gallery
|
|
@@ -304,7 +331,7 @@ The table below links the bundled test models to their pre-generated output so y
|
|
|
304
331
|
|---|---|---|---|---|
|
|
305
332
|
| `SimpleModel` | [tests/models/simple.py](tests/models/simple.py) | Flat settings: scalars, Optional, Literal, List, Dict, Annotated metadata, `default_factory` | [SimpleModel.yaml](smoke_output/SimpleModel.yaml) | [SimpleModel.md](smoke_output/SimpleModel.md) |
|
|
306
333
|
| `NestedModel` | [tests/models/nested.py](tests/models/nested.py) | Nested `BaseModel` sub-field, optional sub-model, env-var path generation with `env_nested_delimiter` | [NestedModel.yaml](smoke_output/NestedModel.yaml) | [NestedModel.md](smoke_output/NestedModel.md) |
|
|
307
|
-
| `MultiNestedModel` | [tests/models/nested.py](tests/models/nested.py) | `List[BaseModel]` and `Dict[str, BaseModel]` fields
|
|
334
|
+
| `MultiNestedModel` | [tests/models/nested.py](tests/models/nested.py) | `List[BaseModel]` and `Dict[str, BaseModel]` fields; YAML renders each item, Markdown recurses into the item schema | [MultiNestedModel.yaml](smoke_output/MultiNestedModel.yaml) | [MultiNestedModel.md](smoke_output/MultiNestedModel.md) |
|
|
308
335
|
| `MyAppConfig` | [tests/models/readme_example.py](tests/models/readme_example.py) | The full README quick-start model with nested database settings and field examples | [MyAppConfig.yaml](smoke_output/MyAppConfig.yaml) | [MyAppConfig.md](smoke_output/MyAppConfig.md) |
|
|
309
336
|
|
|
310
337
|
These files are regenerated automatically by the smoke script (`pdm run smoke`) and committed to the repository, so they are always in sync with the current code.
|
|
@@ -328,7 +355,7 @@ These files are regenerated automatically by the smoke script (`pdm run smoke`)
|
|
|
328
355
|
| `generate_markdown_doc(output_path)` | Write a Markdown reference document to `output_path`. |
|
|
329
356
|
| `render_yaml() → str` | Render the documented YAML as a string without touching the filesystem. |
|
|
330
357
|
| `render_markdown() → str` | Render the Markdown reference document as a string without touching the filesystem. |
|
|
331
|
-
| `load() →
|
|
358
|
+
| `load() → model` | Load `file_path`, apply env var overrides (including [single list elements](#single-list-elements)), return a validated instance of `model`. |
|
|
332
359
|
|
|
333
360
|
### `YamlFileGenerator(settings_class, indent_size=2)`
|
|
334
361
|
|
|
@@ -368,7 +395,8 @@ psyplus works with any standard `pydantic_settings.BaseSettings` subclass. The r
|
|
|
368
395
|
| `title="…"` | Appended to the `## field_name - Title ###` header |
|
|
369
396
|
| `examples=[…]` | `# Example:` block with rendered YAML |
|
|
370
397
|
| `default=…` | `# Default:` comment row |
|
|
371
|
-
| `default_factory=lambda: …` | `# Default:` not shown (factory evaluated at generation time) |
|
|
398
|
+
| `default_factory=lambda: …` | `# Default:` not shown (factory evaluated at generation time, its result is the value in the YAML file) |
|
|
399
|
+
| `default_factory=lambda data: …` | Same, the factory gets the values of the fields defined before it. If it needs a required value without a default, the field is written empty with a note to remove the key |
|
|
372
400
|
| `max_length=…` / `gt=…` / etc. | `# Constraints:` comment row |
|
|
373
401
|
|
|
374
402
|
### Configuring env vars
|
|
@@ -400,15 +428,19 @@ What pydantic-settings can set through env vars, and what the generated files sh
|
|
|
400
428
|
|---|---|---|
|
|
401
429
|
| Field of a nested model, any depth | `MYAPP_DATABASE__SERVER__HOST=db` | Sets only this value. The other values from the YAML file stay. |
|
|
402
430
|
| Whole nested model | `MYAPP_DATABASE='{"name": "x"}'` | JSON, merged into the values from the YAML file. |
|
|
403
|
-
| List (`List[str]`, `List[SubModel]
|
|
404
|
-
|
|
|
431
|
+
| List (`List[str]`, `List[SubModel]`, tuple, set) | `MYAPP_HOSTS='["a", "b"]'` | JSON, replaces the whole list. |
|
|
432
|
+
| One list element, or a field of it | `MYAPP_HOSTS__1=b`, `MYAPP_SERVERS__0__PORT=8080` | Only with `load()`, see [Single list elements](#single-list-elements). `<list-index>` stands for the position, 0 is the first. Changes this element of the list from the YAML file, the other elements stay. |
|
|
433
|
+
| Dict value (`Dict[str, ...]`) | `MYAPP_LABELS__team=core` | `<dict-key>` stands for the key. Env var names are case-insensitive by default, so the key arrives in lower case, and the generated files say so next to the env var of the dict field. Keys in a JSON value keep their case. |
|
|
405
434
|
|
|
406
435
|
Only the root model's `env_prefix` and `env_nested_delimiter` count. A nested `BaseSettings`
|
|
407
436
|
sub-model's own `env_prefix` is not used for its fields. Without `env_nested_delimiter`, nested
|
|
408
437
|
fields have no env var of their own and can only be set through the JSON value of the top-level
|
|
409
|
-
field. With `case_sensitive=True`, the env var names keep the case of the field names.
|
|
438
|
+
field. With `case_sensitive=True`, the env var names keep the case of the field names. With
|
|
439
|
+
`env_nested_max_split`, only fields that many levels below the top-level field have an env var
|
|
440
|
+
(a list index counts as a level).
|
|
410
441
|
A sub-model that defaults to null has no fields in the YAML file, so its comment lists the env
|
|
411
|
-
vars of its fields under `Env-vars of the sub-fields:`.
|
|
442
|
+
vars of its fields under `Env-vars of the sub-fields:`. For a field that holds one of several
|
|
443
|
+
models (`Union[Cat, Dog]`), this list covers the fields of every model.
|
|
412
444
|
|
|
413
445
|
#### Nullable fields
|
|
414
446
|
|
|
@@ -432,23 +464,102 @@ For a nullable field whose default is not null, the `Env-var:` hint says which c
|
|
|
432
464
|
without it. A field that defaults to null gets no hint, since leaving the env var unset already
|
|
433
465
|
gives null.
|
|
434
466
|
|
|
467
|
+
### Fields with an alias
|
|
468
|
+
|
|
469
|
+
pydantic-settings reads the YAML file and the env vars by a field's validation alias, so psyplus
|
|
470
|
+
uses it as the YAML key, in the YAML path and in the env var name. That covers `alias=`,
|
|
471
|
+
`validation_alias=` and an `alias_generator` such as `to_camel`, also with `populate_by_name=True`.
|
|
472
|
+
A `serialization_alias` changes nothing.
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
class Database(BaseModel):
|
|
476
|
+
host_name: str = Field("localhost", alias="hostName")
|
|
477
|
+
|
|
478
|
+
class MyConfig(BaseSettings):
|
|
479
|
+
model_config = SettingsConfigDict(env_prefix="MYAPP_", env_nested_delimiter="__")
|
|
480
|
+
|
|
481
|
+
log_level: str = Field("INFO", alias="logLevel")
|
|
482
|
+
# → YAML key: logLevel, Env-var: 'LOGLEVEL'
|
|
483
|
+
database: Database = Database()
|
|
484
|
+
# → YAML path: database.hostName, Env-var: 'MYAPP_DATABASE__HOSTNAME'
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
| Field | YAML key | Env var |
|
|
488
|
+
|---|---|---|
|
|
489
|
+
| `alias="logLevel"` or `validation_alias="logLevel"` | `logLevel` | `LOGLEVEL` |
|
|
490
|
+
| `validation_alias=AliasChoices("a", "b")` | `a`, the first string choice | `A` |
|
|
491
|
+
| `validation_alias=AliasPath("database", "host")` | `host` inside a `database` map | none |
|
|
492
|
+
| `serialization_alias="x"` only | the field name | as without an alias |
|
|
493
|
+
|
|
494
|
+
* **Prefix:** pydantic-settings puts `env_prefix` in front of an alias only when the model sets
|
|
495
|
+
`env_prefix_target="alias"` or `"all"`. With the default `"variable"` only field names get it.
|
|
496
|
+
What counts is the top-level field: `MYAPP_DATABASE__HOSTNAME` above gets the prefix because
|
|
497
|
+
`database` has no alias.
|
|
498
|
+
* **Case:** without `case_sensitive=True` env var names arrive in lower case, and
|
|
499
|
+
pydantic-settings matches them back to a camelCase key only along plain sub-model fields. Below a
|
|
500
|
+
dict, a Union of models, or an `Optional` sub-model deeper down, a key with upper case letters can
|
|
501
|
+
not be set through its own env var, so none is shown there. The JSON value of the field above
|
|
502
|
+
still works.
|
|
503
|
+
* **`populate_by_name`:** below a top-level field with an alias, the fields and dict entries have
|
|
504
|
+
no env var of their own. pydantic-settings files them under the field name, which clashes with the alias key
|
|
505
|
+
from the YAML file. The JSON value of the top-level field still works.
|
|
506
|
+
* **`AliasPath`:** a path of names is written as nested maps, which several fields may share. A
|
|
507
|
+
path with a list index (`AliasPath("servers", 0)`) or one that clashes with another key is not
|
|
508
|
+
written; its comment block says where to add it. No env var sets an `AliasPath` field:
|
|
509
|
+
pydantic-settings would read each element of the path as an env var of its own.
|
|
510
|
+
* **Alias and validation alias differ:** pydantic-settings reads a top-level field from the YAML
|
|
511
|
+
file only under its `alias`. When validation expects another name (`alias="a",
|
|
512
|
+
validation_alias="b"`, an `alias_generator` next to an explicit `validation_alias`, or
|
|
513
|
+
`validate_by_alias=False`), no key works, so the field is left out of the YAML file. A note in
|
|
514
|
+
the YAML file and in the Markdown doc says so. Set it through its env var instead.
|
|
515
|
+
|
|
435
516
|
---
|
|
436
517
|
|
|
437
518
|
## Environment variable overrides
|
|
438
519
|
|
|
439
|
-
`load()`
|
|
520
|
+
`load()` uses the same sources pydantic-settings uses for your model and adds the YAML file as the lowest priority source, just above the field defaults. For a model without its own `settings_customise_sources` the effective source priority (highest → lowest) is:
|
|
440
521
|
|
|
441
|
-
1. **
|
|
442
|
-
2.
|
|
443
|
-
3.
|
|
444
|
-
4. **
|
|
445
|
-
5. **
|
|
446
|
-
6. **Model defaults**
|
|
522
|
+
1. **Environment variables** (respects `env_prefix` / `env_nested_delimiter`)
|
|
523
|
+
2. **`.env` file** (if the model sets `env_file`)
|
|
524
|
+
3. **Secrets directory** (if the model sets `secrets_dir`)
|
|
525
|
+
4. **YAML file** values
|
|
526
|
+
5. **Model defaults**
|
|
447
527
|
|
|
448
528
|
No changes to your settings class are required.
|
|
449
529
|
|
|
530
|
+
If your model defines `settings_customise_sources`, `load()` respects it: the YAML file is added after the sources it returns, and a source it leaves out stays out. A model that drops `env_settings`, for example, ignores env vars in `load()` too. If it already returns a `YamlConfigSettingsSource`, `load()` adds no second one but reads `file_path` through it, at the position your model chose.
|
|
531
|
+
|
|
532
|
+
Command line parsing configured in `model_config` (`cli_parse_args`, `cli_settings_source`) also applies to `load()`, with the command line beating all other sources.
|
|
533
|
+
|
|
450
534
|
A missing YAML file is not an error: `load()` then returns the model defaults plus whatever the env vars, the `.env` file and the secrets directory set.
|
|
451
535
|
|
|
536
|
+
### Single list elements
|
|
537
|
+
|
|
538
|
+
pydantic-settings itself can only replace a whole list through an env var (`APP_SERVERS='[{"host": "a"}]'`). `load()` also reads env vars that address one element by its position, in the environment and in the `.env` file:
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
export APP_SERVERS__0__HOST=db.internal # field of the first element
|
|
542
|
+
export APP_TAGS__1=beta # second element of a List[str]
|
|
543
|
+
export APP_GROUPS__admins__0=ann # list inside a dict
|
|
544
|
+
export APP_MATRIX__1__0=7 # list inside a list
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
The generated YAML comments and Markdown docs show these names with `<list-index>` in place of the position (`APP_SERVERS__<list-index>__HOST`). They work with `load()` only: a model instantiated directly (`MyAppConfig()`) reads them the way pydantic-settings does, which fails list validation.
|
|
548
|
+
|
|
549
|
+
The env var changes the list that the lower priority sources provide (usually the YAML file) instead of replacing it:
|
|
550
|
+
|
|
551
|
+
- **Index below the list length:** changes that element. The fields of an element model are merged into it (`APP_SERVERS__0__HOST` keeps the element's `port`), the same goes for a JSON object (`APP_SERVERS__0='{"host": "x"}'`). Any other value replaces the element.
|
|
552
|
+
- **Index equal to the list length:** adds an element. Several new elements need consecutive indices (2, 3, 4 for a list of two). A new element that is a model needs its required fields set too.
|
|
553
|
+
- **Index further beyond the end:** `load()` fails with an error that names the env var.
|
|
554
|
+
- **List not in the YAML file:** the env var changes the field's default list. A field without a default (or with `null`) starts as an empty list, so its indices start at 0. A default from a `default_factory` that takes the other values (`lambda data: ...`) can not be changed this way: `load()` fails with an error, put the list in the YAML file or set it as a whole.
|
|
555
|
+
- **Aliases:** the list is found in the YAML file under the field name or any of its aliases, and the changed list stays under that key.
|
|
556
|
+
- **Whole list and elements together:** an index env var changes the whole list value from the same place (`APP_TAGS='["a", "b"]'` plus `APP_TAGS__1=c` gives `["a", "c"]`). A whole value that is not a JSON list there (a comma separated string for a `NoDecode` field, for example) can not be changed this way, `load()` fails with an error. A whole list from a higher priority source replaces everything below it, as usual.
|
|
557
|
+
- **Not a list index:** `-1` or `01` where a list is expected is an error that names the env var. Other names (`APP_TAGS__NAME`) are left to pydantic-settings, as without psyplus.
|
|
558
|
+
- **A list or a dict (`Union[List[str], Dict[str, str]]`):** a number is a list index only where the value it changes is a list. Where it is a dict, the number is a dict key, as pydantic-settings reads it.
|
|
559
|
+
- **Tuples and sets** work like lists. For a set, the index counts the elements in the order the YAML file lists them.
|
|
560
|
+
|
|
561
|
+
Only the env and `.env` sources that `load()` hands to your model's `settings_customise_sources` (`env_settings`, `dotenv_settings`) read list indices. A source your model builds itself (`EnvSettingsSource(settings_cls, env_prefix=...)`) works as pydantic-settings makes it, without list index support. Under `load()`, `env_settings` and `dotenv_settings` are subclasses of `EnvSettingsSource` and `DotEnvSettingsSource`: `isinstance()` checks on them work, `type(...) is EnvSettingsSource` does not.
|
|
562
|
+
|
|
452
563
|
---
|
|
453
564
|
|
|
454
565
|
## Development
|