Skip to content

Commit 112b07e

Browse files
committed
optionally gather line_errors from each source
1 parent 4b6fd3d commit 112b07e

File tree

1 file changed

+27
-1
lines changed

1 file changed

+27
-1
lines changed

pydantic_settings/main.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import asyncio
44
import inspect
5+
import json
56
import threading
67
from argparse import Namespace
78
from types import SimpleNamespace
@@ -13,6 +14,7 @@
1314
from pydantic._internal._utils import deep_update, is_model_class
1415
from pydantic.dataclasses import is_pydantic_dataclass
1516
from pydantic.main import BaseModel
17+
from pydantic_core import ValidationError, InitErrorDetails
1618

1719
from .sources import (
1820
ENV_FILE_SENTINEL,
@@ -32,7 +34,6 @@
3234

3335
T = TypeVar('T')
3436

35-
3637
class 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

Comments
 (0)