22
33import asyncio
44import inspect
5+ import json
56import threading
67from argparse import Namespace
78from types import SimpleNamespace
1314from pydantic ._internal ._utils import deep_update , is_model_class
1415from pydantic .dataclasses import is_pydantic_dataclass
1516from pydantic .main import BaseModel
17+ from pydantic_core import ValidationError , InitErrorDetails
1618
1719from .sources import (
1820 ENV_FILE_SENTINEL ,
3234
3335T = TypeVar ('T' )
3436
35-
3637class SettingsConfigDict (ConfigDict , total = False ):
3738 case_sensitive : bool
3839 nested_model_default_partial_update : bool | None
@@ -58,6 +59,7 @@ class SettingsConfigDict(ConfigDict, total=False):
5859 cli_ignore_unknown_args : bool | None
5960 cli_kebab_case : bool | None
6061 secrets_dir : PathType | None
62+ validate_each_source : bool | None
6163 json_file : PathType | None
6264 json_file_encoding : str | None
6365 yaml_file : PathType | None
@@ -257,6 +259,7 @@ def _settings_build_values(
257259 _cli_ignore_unknown_args : bool | None = None ,
258260 _cli_kebab_case : bool | None = None ,
259261 _secrets_dir : PathType | None = None ,
262+ _validate_each_source : bool | None = None
260263 ) -> dict [str , Any ]:
261264 # Determine settings config values
262265 case_sensitive = _case_sensitive if _case_sensitive is not None else self .model_config .get ('case_sensitive' )
@@ -332,6 +335,8 @@ def _settings_build_values(
332335
333336 secrets_dir = _secrets_dir if _secrets_dir is not None else self .model_config .get ('secrets_dir' )
334337
338+ validate_each_source = _validate_each_source if _validate_each_source is not None else self .model_config .get ("validate_each_source" )
339+
335340 # Configure built-in sources
336341 default_settings = DefaultSettingsSource (
337342 self .__class__ , nested_model_default_partial_update = nested_model_default_partial_update
@@ -400,6 +405,7 @@ def _settings_build_values(
400405 if sources :
401406 state : dict [str , Any ] = {}
402407 states : dict [str , dict [str , Any ]] = {}
408+ all_line_errors : list [dict [str , Any ]] = []
403409 for source in sources :
404410 if isinstance (source , PydanticBaseSettingsSource ):
405411 source ._set_current_state (state )
@@ -410,6 +416,26 @@ def _settings_build_values(
410416
411417 states [source_name ] = source_state
412418 state = deep_update (source_state , state )
419+
420+ if validate_each_source :
421+ if not source_state :
422+ continue
423+ try :
424+ _ = super (BaseSettings , self ).__init__ (** source_state )
425+ except ValidationError as e :
426+ line_errors = json .loads (e .json ())
427+ for line in line_errors :
428+ ctx = line .get ("ctx" , {})
429+ ctx = {"source" : source_name }
430+ line ['ctx' ] = ctx
431+ all_line_errors .extend (line_errors )
432+
433+ if all_line_errors and validate_each_source :
434+ raise ValidationError .from_exception_data (
435+ title = self .__class__ .__name__ ,
436+ line_errors = [InitErrorDetails (** l ) for l in all_line_errors ],
437+ input_type = "python"
438+ )
413439 return state
414440 else :
415441 # no one should mean to do this, but I think returning an empty dict is marginally preferable
0 commit comments