Skip to content

Commit 30f7a4c

Browse files
committed
Provide field metadata utility function to properly handle Annotated types.
1 parent 11c6db2 commit 30f7a4c

File tree

3 files changed

+33
-1
lines changed

3 files changed

+33
-1
lines changed

pydantic_settings/sources/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from .utils import (
2525
_annotation_is_complex,
2626
_get_alias_names,
27+
_get_field_metadata,
2728
_get_model_fields,
2829
_strip_annotated,
2930
_union_is_complex,
@@ -179,7 +180,7 @@ def decode_complex_value(self, field_name: str, field: FieldInfo, value: Any) ->
179180
The decoded value for further preparation
180181
"""
181182
if field and (
182-
NoDecode in field.metadata
183+
NoDecode in _get_field_metadata(field)
183184
or (self.config.get('enable_decoding') is False and ForceDecode not in field.metadata)
184185
):
185186
return value

pydantic_settings/sources/utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pydantic import BaseModel, Json, RootModel, Secret
1212
from pydantic._internal._utils import is_model_class
1313
from pydantic.dataclasses import is_pydantic_dataclass
14+
from pydantic.fields import FieldInfo
1415
from typing_inspection import typing_objects
1516

1617
from ..exceptions import SettingsError
@@ -72,6 +73,18 @@ def _annotation_is_complex(annotation: Any, metadata: list[Any]) -> bool:
7273
)
7374

7475

76+
def _get_field_metadata(field: FieldInfo) -> list[Any]:
77+
annotation = field.annotation
78+
metadata = field.metadata
79+
if typing_objects.is_typealiastype(annotation) or typing_objects.is_typealiastype(get_origin(annotation)):
80+
annotation = annotation.__value__ # type: ignore[union-attr]
81+
origin = get_origin(annotation)
82+
if typing_objects.is_annotated(origin):
83+
_, *meta = get_args(annotation)
84+
metadata += meta
85+
return metadata
86+
87+
7588
def _annotation_is_complex_inner(annotation: type[Any] | None) -> bool:
7689
if _lenient_issubclass(annotation, (str, bytes)):
7790
return False

tests/test_settings.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,24 @@ class AnnotatedComplexSettings(BaseSettings):
499499
assert s.apples == ['russet', 'granny smith']
500500

501501

502+
def test_annotated_with_type_no_decode(env):
503+
A = TypeAliasType('A', Annotated[list[str], NoDecode])
504+
505+
class Settings(BaseSettings):
506+
a: A
507+
508+
# decode the value here. the field value won't be decoded because of NoDecode
509+
@field_validator('a', mode='before')
510+
@classmethod
511+
def decode_a(cls, v: str) -> list[str]:
512+
return json.loads(v)
513+
514+
env.set('a', '["one", "two"]')
515+
516+
s = Settings()
517+
assert s.model_dump() == {'a': ['one', 'two']}
518+
519+
502520
def test_set_dict_model(env):
503521
env.set('bananas', '[1, 2, 3, 3]')
504522
env.set('CARROTS', '{"a": null, "b": 4}')

0 commit comments

Comments
 (0)