Skip to content

Commit 0de9feb

Browse files
authored
Add Pydantic Json field support (#79)
1 parent a6f6d60 commit 0de9feb

File tree

2 files changed

+51
-3
lines changed

2 files changed

+51
-3
lines changed

pydantic_settings/sources.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from pathlib import Path
1010
from typing import TYPE_CHECKING, Any, List, Mapping, Sequence, Tuple, Union, cast
1111

12-
from pydantic import AliasChoices, AliasPath, BaseModel
12+
from pydantic import AliasChoices, AliasPath, BaseModel, Json
1313
from pydantic._internal._typing_extra import origin_is_union
1414
from pydantic._internal._utils import deep_update, lenient_issubclass
1515
from pydantic.fields import FieldInfo
@@ -68,7 +68,7 @@ def field_is_complex(self, field: FieldInfo) -> bool:
6868
Returns:
6969
Whether the field is complex.
7070
"""
71-
return _annotation_is_complex(field.annotation)
71+
return _annotation_is_complex(field.annotation, field.metadata)
7272

7373
def prepare_field_value(self, field_name: str, field: FieldInfo, value: Any, value_is_complex: bool) -> Any:
7474
"""
@@ -611,7 +611,9 @@ def read_env_file(
611611
return file_vars
612612

613613

614-
def _annotation_is_complex(annotation: type[Any] | None) -> bool:
614+
def _annotation_is_complex(annotation: type[Any] | None, metadata: list[Any]) -> bool:
615+
if any(isinstance(md, Json) for md in metadata): # type: ignore[misc]
616+
return False
615617
origin = get_origin(annotation)
616618
return (
617619
_annotation_is_complex_inner(annotation)

tests/test_settings.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
ConfigDict,
1616
Field,
1717
HttpUrl,
18+
Json,
1819
SecretStr,
1920
ValidationError,
2021
)
@@ -1652,3 +1653,48 @@ class Settings(BaseSettings):
16521653
env.set('foobar_apple', 'has_prefix')
16531654
s = Settings(_env_prefix='foobar_')
16541655
assert s.apple == 'has_prefix'
1656+
1657+
1658+
def test_env_json_field(env):
1659+
class Settings(BaseSettings):
1660+
x: Json
1661+
1662+
env.set('x', '{"foo": "bar"}')
1663+
1664+
s = Settings()
1665+
assert s.x == {'foo': 'bar'}
1666+
1667+
env.set('x', 'test')
1668+
with pytest.raises(ValidationError) as exc_info:
1669+
Settings()
1670+
assert exc_info.value.errors(include_url=False) == [
1671+
{
1672+
'type': 'json_invalid',
1673+
'loc': ('x',),
1674+
'msg': 'Invalid JSON: expected ident at line 1 column 2',
1675+
'input': 'test',
1676+
'ctx': {'error': 'expected ident at line 1 column 2'},
1677+
}
1678+
]
1679+
1680+
1681+
def test_env_json_field_dict(env):
1682+
class Settings(BaseSettings):
1683+
x: Json[Dict[str, int]]
1684+
1685+
env.set('x', '{"foo": 1}')
1686+
1687+
s = Settings()
1688+
assert s.x == {'foo': 1}
1689+
1690+
env.set('x', '{"foo": "bar"}')
1691+
with pytest.raises(ValidationError) as exc_info:
1692+
Settings()
1693+
assert exc_info.value.errors(include_url=False) == [
1694+
{
1695+
'type': 'int_parsing',
1696+
'loc': ('x', 'foo'),
1697+
'msg': 'Input should be a valid integer, unable to parse string as an integer',
1698+
'input': 'bar',
1699+
}
1700+
]

0 commit comments

Comments
 (0)