@@ -35,6 +35,8 @@ settings = (
3535- ` .env ` file support
3636- Typed validation with Pydantic v2
3737- Compatible with ` dict[str, SubModel] ` , lists, booleans, integers, etc.
38+ - Direct path access with ` config["section:key"] `
39+ - Typed subsections with ` .get_section("...").get(MyModel) `
3840
3941## Installation
4042
@@ -60,6 +62,16 @@ from pydantic import Field
6062from axa_fr_app_settings import SettingsModel
6163
6264
65+ class EndpointSettings (SettingsModel ):
66+ name: str
67+ url: str
68+
69+
70+ class RegionSettings (SettingsModel ):
71+ name: str
72+ endpoints: list[EndpointSettings] = Field(default_factory = list )
73+
74+
6375class OIDCSettings (SettingsModel ):
6476 endpoint_url: str
6577 issuer: str
@@ -91,6 +103,8 @@ class AppSettings(SettingsModel):
91103 http_timeout: int = 45
92104 http_verify: bool = False
93105 cache: CacheSettings = Field(default_factory = CacheSettings)
106+ allowed_hosts: list[str ] = Field(default_factory = list )
107+ regions: list[RegionSettings] = Field(default_factory = list )
94108```
95109
96110### 2. Build the configuration
@@ -121,18 +135,43 @@ export DEBUG=true
121135export HTTP_TIMEOUT=30
122136export CACHE__REDIS__EXPIRY_TIME=120
123137export DATABASE__main__ENDPOINT_URL=" postgresql://localhost:5432/app"
138+ export ALLOWED_HOSTS__0=" api.local"
139+ export ALLOWED_HOSTS__1=" admin.local"
140+ export REGIONS__0__NAME=" eu-west"
141+ export REGIONS__0__ENDPOINTS__0__NAME=" catalog"
142+ export REGIONS__0__ENDPOINTS__0__URL=" https://eu-west/catalog"
143+ export REGIONS__0__ENDPOINTS__1__NAME=" orders"
144+ export REGIONS__0__ENDPOINTS__1__URL=" https://eu-west/orders"
145+ export REGIONS__1__NAME=" us-east"
146+ export REGIONS__1__ENDPOINTS__0__NAME=" catalog"
147+ export REGIONS__1__ENDPOINTS__0__URL=" https://us-east/catalog"
124148```
125149
126150### 4. YAML example
127151
128152``` yaml
129153debug : false
130154http_timeout : 45
155+ allowed_hosts :
156+ - api.local
157+ - admin.local
131158
132159database :
133160 main :
134161 endpoint_url : " postgresql://localhost:5432/app"
135162
163+ regions :
164+ - name : eu-west
165+ endpoints :
166+ - name : catalog
167+ url : " https://eu-west/catalog"
168+ - name : orders
169+ url : " https://eu-west/orders"
170+ - name : us-east
171+ endpoints :
172+ - name : catalog
173+ url : " https://us-east/catalog"
174+
136175cache :
137176 type : redis
138177 redis :
@@ -147,11 +186,36 @@ cache:
147186{
148187 " debug " : false,
149188 " http_timeout " : 45,
189+ " allowed_hosts " : ["api.local", "admin.local"],
150190 " database " : {
151191 " main " : {
152192 " endpoint_url " : " postgresql://localhost:5432/app"
153193 }
154194 },
195+ " regions " : [
196+ {
197+ " name " : " eu-west" ,
198+ " endpoints " : [
199+ {
200+ " name " : " catalog" ,
201+ " url " : " https://eu-west/catalog"
202+ },
203+ {
204+ " name " : " orders" ,
205+ " url " : " https://eu-west/orders"
206+ }
207+ ]
208+ },
209+ {
210+ " name " : " us-east" ,
211+ " endpoints " : [
212+ {
213+ " name " : " catalog" ,
214+ " url " : " https://us-east/catalog"
215+ }
216+ ]
217+ }
218+ ],
155219 " cache " : {
156220 " type " : " redis" ,
157221 " redis " : {
@@ -165,6 +229,48 @@ cache:
165229
166230YAML and JSON sources can be mixed freely. The last source added always wins.
167231
232+ ## Arrays and nested arrays
233+
234+ Arrays are supported in file sources (` yaml ` , ` json ` ) and also in flat sources
235+ like environment variables and ` .env ` files.
236+
237+ ### Simple array
238+
239+ ``` yaml
240+ allowed_hosts :
241+ - api.local
242+ - admin.local
243+ ` ` `
244+
245+ ### Nested array with ` regions`
246+
247+ ` ` ` yaml
248+ regions:
249+ - name: eu-west
250+ endpoints:
251+ - name: catalog
252+ url: https://eu-west/catalog
253+ - name: orders
254+ url: https://eu-west/orders
255+ - name: us-east
256+ endpoints:
257+ - name: catalog
258+ url: https://us-east/catalog
259+ ` ` `
260+
261+ Equivalent environment variables :
262+
263+ ` ` ` bash
264+ export REGIONS__0__NAME="eu-west"
265+ export REGIONS__0__ENDPOINTS__0__NAME="catalog"
266+ export REGIONS__0__ENDPOINTS__0__URL="https://eu-west/catalog"
267+ export REGIONS__0__ENDPOINTS__1__NAME="orders"
268+ export REGIONS__0__ENDPOINTS__1__URL="https://eu-west/orders"
269+ export REGIONS__1__NAME="us-east"
270+ export REGIONS__1__ENDPOINTS__0__NAME="catalog"
271+ export REGIONS__1__ENDPOINTS__0__URL="https://us-east/catalog"
272+ ` ` `
273+
168274# # Priority order
169275
170276As in .NET, **the last source added wins**.
@@ -198,6 +304,7 @@ Here:
198304| `add_source(source)` | Add a custom source (any object with a `load()` method) |
199305| `build()` | Build and return the validated settings model |
200306| `build_data()` | Build and return the raw merged dict |
307+ | `build_configuration()` | Build and return a navigable configuration root |
201308| `build_watched(*, debounce_seconds=0.3, polling_interval_seconds=None)` | Build and return a `SettingsWatcher` with auto-reload |
202309
203310**Key parameters:**
@@ -353,9 +460,83 @@ The Key Vault source (or any custom source) follows the same priority rule: **th
353460
354461A complete example is available in [`examples/custom_keyvault_source.py`](examples/custom_keyvault_source.py).
355462
463+ # # Direct key access and typed sections
464+
465+ Like in .NET, you can also work with the raw merged configuration tree.
466+ This is useful when you want direct path access or when you only want to bind
467+ one subsection.
468+
469+ ` ` ` python
470+ from pydantic import Field
471+
472+ from axa_fr_app_settings import ConfigurationBuilder, SettingsModel
473+
474+
475+ class EndpointSettings(SettingsModel):
476+ name: str
477+ url: str
478+
479+
480+ class RegionSettings(SettingsModel):
481+ name: str
482+ endpoints: list[EndpointSettings] = Field(default_factory=list)
483+
484+
485+ class AppSettings(SettingsModel):
486+ application_name: str
487+ max_users: int
488+ feature_toggle: bool = False
489+ allowed_hosts: list[str] = Field(default_factory=list)
490+ regions: list[RegionSettings] = Field(default_factory=list)
491+
492+
493+ class RootSettings(SettingsModel):
494+ appsettings: AppSettings
495+
496+
497+ config = (
498+ ConfigurationBuilder(RootSettings)
499+ .add_json_file("appsettings.json")
500+ .build_configuration()
501+ )
502+
503+ app_name = config["appsettings:application_name"]
504+ max_users = config["appsettings:max_users"]
505+ first_region = config["appsettings:regions:0:name"]
506+ first_endpoint_url = config["appsettings:regions:0:endpoints:0:url"]
507+
508+ app_settings = config.get_section("appsettings").get(AppSettings)
509+ print(f"FeatureToggle: {app_settings.feature_toggle}")
510+ print(f"Application Name: {app_settings.application_name}")
511+ print(f"Max Users: {app_settings.max_users}")
512+ print(f"First Region: {first_region}")
513+ print(f"First Endpoint URL: {first_endpoint_url}")
514+ ` ` `
515+
516+ You can also bind the full root model directly :
517+
518+ ` ` ` python
519+ root_settings = config.bind()
520+ ` ` `
521+
522+ For environment variables or `.env` files, use numeric indexes with `__` :
523+
524+ ` ` ` bash
525+ export APPSETTINGS__ALLOWED_HOSTS__0=api.local
526+ export APPSETTINGS__ALLOWED_HOSTS__1=admin.local
527+ export APPSETTINGS__REGIONS__0__NAME="eu-west"
528+ export APPSETTINGS__REGIONS__0__ENDPOINTS__0__NAME="catalog"
529+ export APPSETTINGS__REGIONS__0__ENDPOINTS__0__URL="https://eu/catalog"
530+ ` ` `
531+
532+ A complete runnable example is available in [`examples/configuration_sections.py`](examples/configuration_sections.py).
533+
356534# # Full example
357535
358- A complete example is provided in [ ` examples/api_settings.py ` ] ( examples/api_settings.py ) .
536+ Complete examples are provided in :
537+
538+ - [`examples/api_settings.py`](examples/api_settings.py)
539+ - [`examples/configuration_sections.py`](examples/configuration_sections.py)
359540
360541# # Publishing to PyPI with GitHub Actions
361542
0 commit comments