@@ -144,6 +144,65 @@ def _extract_field_info(self, field: FieldInfo, field_name: str) -> List[Tuple[s
144
144
145
145
return field_info
146
146
147
+ def _replace_field_names_case_insensitively (self , field : FieldInfo , field_values : Dict [str , Any ]) -> Dict [str , Any ]:
148
+ """
149
+ Replace field names in values dict by looking in models fields insensitively.
150
+
151
+ By having the following models:
152
+
153
+ ```py
154
+ class SubSubSub(BaseModel):
155
+ VaL3: str
156
+
157
+ class SubSub(BaseModel):
158
+ Val2: str
159
+ SUB_sub_SuB: SubSubSub
160
+
161
+ class Sub(BaseModel):
162
+ VAL1: str
163
+ SUB_sub: SubSub
164
+
165
+ class Settings(BaseSettings):
166
+ nested: Sub
167
+
168
+ model_config = ConfigDict(env_nested_delimiter='__')
169
+ ```
170
+
171
+ Then:
172
+ _replace_field_names_case_insensitively(
173
+ field,
174
+ {"val1": "v1", "sub_SUB": {"VAL2": "v2", "sub_SUB_sUb": {"vAl3": "v3"}}}
175
+ )
176
+ Returns {'VAL1': 'v1', 'SUB_sub': {'Val2': 'v2', 'SUB_sub_SuB': {'VaL3': 'v3'}}}
177
+ """
178
+ values : Dict [str , Any ] = {}
179
+
180
+ for name , value in field_values .items ():
181
+ sub_model_field : Optional [FieldInfo ] = None
182
+
183
+ # This is here to make mypy happy
184
+ # Item "None" of "Optional[Type[Any]]" has no attribute "model_fields"
185
+ if not field .annotation or not hasattr (field .annotation , 'model_fields' ):
186
+ values [name ] = value
187
+ continue
188
+
189
+ # Find field in sub model by looking in fields case insensitively
190
+ for sub_model_field_name , f in field .annotation .model_fields .items ():
191
+ if not f .validation_alias and sub_model_field_name .lower () == name .lower ():
192
+ sub_model_field = f
193
+ break
194
+
195
+ if not sub_model_field :
196
+ values [name ] = value
197
+ continue
198
+
199
+ if lenient_issubclass (sub_model_field .annotation , BaseModel ):
200
+ values [sub_model_field_name ] = self ._replace_field_names_case_insensitively (sub_model_field , value )
201
+ else :
202
+ values [sub_model_field_name ] = value
203
+
204
+ return values
205
+
147
206
def __call__ (self ) -> Dict [str , Any ]:
148
207
d : Dict [str , Any ] = {}
149
208
@@ -163,7 +222,10 @@ def __call__(self) -> Dict[str, Any]:
163
222
) from e
164
223
165
224
if field_value is not None :
166
- d [field_key ] = field_value
225
+ if not self .config .get ('case_sensitive' , False ) and lenient_issubclass (field .annotation , BaseModel ):
226
+ d [field_key ] = self ._replace_field_names_case_insensitively (field , field_value )
227
+ else :
228
+ d [field_key ] = field_value
167
229
168
230
return d
169
231
@@ -300,6 +362,7 @@ def next_field(field: Optional[FieldInfo], key: str) -> Optional[FieldInfo]:
300
362
301
363
By having the following models:
302
364
365
+ ```py
303
366
class SubSubModel(BaseSettings):
304
367
dvals: Dict
305
368
@@ -309,6 +372,7 @@ class SubModel(BaseSettings):
309
372
310
373
class Cfg(BaseSettings):
311
374
sub_model: SubModel
375
+ ```
312
376
313
377
Then:
314
378
next_field(sub_model, 'vals') Returns the `vals` field of `SubModel` class
0 commit comments