diff --git a/pydantic_settings/sources/base.py b/pydantic_settings/sources/base.py index 4e733f0f..6fd87d7b 100644 --- a/pydantic_settings/sources/base.py +++ b/pydantic_settings/sources/base.py @@ -24,6 +24,7 @@ from .utils import ( _annotation_is_complex, _get_alias_names, + _get_field_metadata, _get_model_fields, _strip_annotated, _union_is_complex, @@ -179,7 +180,7 @@ def decode_complex_value(self, field_name: str, field: FieldInfo, value: Any) -> The decoded value for further preparation """ if field and ( - NoDecode in field.metadata + NoDecode in _get_field_metadata(field) or (self.config.get('enable_decoding') is False and ForceDecode not in field.metadata) ): return value diff --git a/pydantic_settings/sources/utils.py b/pydantic_settings/sources/utils.py index 9d00472b..755cb16e 100644 --- a/pydantic_settings/sources/utils.py +++ b/pydantic_settings/sources/utils.py @@ -11,6 +11,7 @@ from pydantic import BaseModel, Json, RootModel, Secret from pydantic._internal._utils import is_model_class from pydantic.dataclasses import is_pydantic_dataclass +from pydantic.fields import FieldInfo from typing_inspection import typing_objects from ..exceptions import SettingsError @@ -72,6 +73,18 @@ def _annotation_is_complex(annotation: Any, metadata: list[Any]) -> bool: ) +def _get_field_metadata(field: FieldInfo) -> list[Any]: + annotation = field.annotation + metadata = field.metadata + if typing_objects.is_typealiastype(annotation) or typing_objects.is_typealiastype(get_origin(annotation)): + annotation = annotation.__value__ # type: ignore[union-attr] + origin = get_origin(annotation) + if typing_objects.is_annotated(origin): + _, *meta = get_args(annotation) + metadata += meta + return metadata + + def _annotation_is_complex_inner(annotation: type[Any] | None) -> bool: if _lenient_issubclass(annotation, (str, bytes)): return False diff --git a/tests/test_settings.py b/tests/test_settings.py index f7c84059..8c7c3780 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -499,6 +499,24 @@ class AnnotatedComplexSettings(BaseSettings): assert s.apples == ['russet', 'granny smith'] +def test_annotated_with_type_no_decode(env): + A = TypeAliasType('A', Annotated[list[str], NoDecode]) + + class Settings(BaseSettings): + a: A + + # decode the value here. the field value won't be decoded because of NoDecode + @field_validator('a', mode='before') + @classmethod + def decode_a(cls, v: str) -> list[str]: + return json.loads(v) + + env.set('a', '["one", "two"]') + + s = Settings() + assert s.model_dump() == {'a': ['one', 'two']} + + def test_set_dict_model(env): env.set('bananas', '[1, 2, 3, 3]') env.set('CARROTS', '{"a": null, "b": 4}')