diff --git a/pydantic_settings/sources/base.py b/pydantic_settings/sources/base.py index 748f75e..7022040 100644 --- a/pydantic_settings/sources/base.py +++ b/pydantic_settings/sources/base.py @@ -25,6 +25,7 @@ from .utils import ( _annotation_is_complex, _get_alias_names, + _get_field_metadata, _get_model_fields, _strip_annotated, _union_is_complex, @@ -180,7 +181,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 3c00460..d6c9d8c 100644 --- a/pydantic_settings/sources/utils.py +++ b/pydantic_settings/sources/utils.py @@ -73,6 +73,18 @@ def _annotation_is_complex(annotation: Any, metadata: list[Any]) -> bool: ) +def _get_field_metadata(field: Any) -> 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__ + 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 fd5c015..4c5e865 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -498,6 +498,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}')