Coverage for tests / test_jsonable_encoder.py: 100%
195 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-12 18:15 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-12 18:15 +0000
1import warnings 1abcd
2from collections import deque 1abcd
3from dataclasses import dataclass 1abcd
4from datetime import datetime, timezone 1abcd
5from decimal import Decimal 1abcd
6from enum import Enum 1abcd
7from math import isinf, isnan 1abcd
8from pathlib import PurePath, PurePosixPath, PureWindowsPath 1abcd
9from typing import Optional, TypedDict 1abcd
11import pytest 1abcd
12from fastapi._compat import Undefined 1abcd
13from fastapi.encoders import jsonable_encoder 1abcd
14from fastapi.exceptions import PydanticV1NotSupportedError 1abcd
15from pydantic import BaseModel, Field, ValidationError 1abcd
18class Person: 1abcd
19 def __init__(self, name: str): 1abcd
20 self.name = name 1neofpg
23class Pet: 1abcd
24 def __init__(self, owner: Person, name: str): 1abcd
25 self.owner = owner 1neofpg
26 self.name = name 1neofpg
29@dataclass 1abcd
30class Item: 1abcd
31 name: str 1abcd
32 count: int 1abcd
35class DictablePerson(Person): 1abcd
36 def __iter__(self): 1abcd
37 return ((k, v) for k, v in self.__dict__.items()) 1efg
40class DictablePet(Pet): 1abcd
41 def __iter__(self): 1abcd
42 return ((k, v) for k, v in self.__dict__.items()) 1efg
45class Unserializable: 1abcd
46 def __iter__(self): 1abcd
47 raise NotImplementedError() 1LMN
49 @property 1abcd
50 def __dict__(self): 1abcd
51 raise NotImplementedError() 1LMN
54class RoleEnum(Enum): 1abcd
55 admin = "admin" 1abcd
56 normal = "normal" 1abcd
59class ModelWithConfig(BaseModel): 1abcd
60 role: Optional[RoleEnum] = None 1abcd
62 model_config = {"use_enum_values": True} 1abcd
65class ModelWithAlias(BaseModel): 1abcd
66 foo: str = Field(alias="Foo") 1abcd
69class ModelWithDefault(BaseModel): 1abcd
70 foo: str = ... # type: ignore 1abcd
71 bar: str = "bar" 1abcd
72 bla: str = "bla" 1abcd
75def test_encode_dict(): 1abcd
76 pet = {"name": "Firulais", "owner": {"name": "Foo"}} 1zAB
77 assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}} 1zAB
78 assert jsonable_encoder(pet, include={"name"}) == {"name": "Firulais"} 1zAB
79 assert jsonable_encoder(pet, exclude={"owner"}) == {"name": "Firulais"} 1zAB
80 assert jsonable_encoder(pet, include={}) == {} 1zAB
81 assert jsonable_encoder(pet, exclude={}) == { 1zAB
82 "name": "Firulais",
83 "owner": {"name": "Foo"},
84 }
87def test_encode_dict_include_exclude_list(): 1abcd
88 pet = {"name": "Firulais", "owner": {"name": "Foo"}} 1CDE
89 assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}} 1CDE
90 assert jsonable_encoder(pet, include=["name"]) == {"name": "Firulais"} 1CDE
91 assert jsonable_encoder(pet, exclude=["owner"]) == {"name": "Firulais"} 1CDE
92 assert jsonable_encoder(pet, include=[]) == {} 1CDE
93 assert jsonable_encoder(pet, exclude=[]) == { 1CDE
94 "name": "Firulais",
95 "owner": {"name": "Foo"},
96 }
99def test_encode_class(): 1abcd
100 person = Person(name="Foo") 1nop
101 pet = Pet(owner=person, name="Firulais") 1nop
102 assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}} 1nop
103 assert jsonable_encoder(pet, include={"name"}) == {"name": "Firulais"} 1nop
104 assert jsonable_encoder(pet, exclude={"owner"}) == {"name": "Firulais"} 1nop
105 assert jsonable_encoder(pet, include={}) == {} 1nop
106 assert jsonable_encoder(pet, exclude={}) == { 1nop
107 "name": "Firulais",
108 "owner": {"name": "Foo"},
109 }
112def test_encode_dictable(): 1abcd
113 person = DictablePerson(name="Foo") 1efg
114 pet = DictablePet(owner=person, name="Firulais") 1efg
115 assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}} 1efg
116 assert jsonable_encoder(pet, include={"name"}) == {"name": "Firulais"} 1efg
117 assert jsonable_encoder(pet, exclude={"owner"}) == {"name": "Firulais"} 1efg
118 assert jsonable_encoder(pet, include={}) == {} 1efg
119 assert jsonable_encoder(pet, exclude={}) == { 1efg
120 "name": "Firulais",
121 "owner": {"name": "Foo"},
122 }
125def test_encode_dataclass(): 1abcd
126 item = Item(name="foo", count=100) 1FGH
127 assert jsonable_encoder(item) == {"name": "foo", "count": 100} 1FGH
128 assert jsonable_encoder(item, include={"name"}) == {"name": "foo"} 1FGH
129 assert jsonable_encoder(item, exclude={"count"}) == {"name": "foo"} 1FGH
130 assert jsonable_encoder(item, include={}) == {} 1FGH
131 assert jsonable_encoder(item, exclude={}) == {"name": "foo", "count": 100} 1FGH
134def test_encode_unsupported(): 1abcd
135 unserializable = Unserializable() 1LMN
136 with pytest.raises(ValueError): 1LMN
137 jsonable_encoder(unserializable) 1LMN
140def test_encode_custom_json_encoders_model_pydanticv2(): 1abcd
141 from pydantic import field_serializer 1hij
143 class ModelWithCustomEncoder(BaseModel): 1hij
144 dt_field: datetime 1hij
146 @field_serializer("dt_field") 1hij
147 def serialize_dt_field(self, dt): 1hij
148 return dt.replace(microsecond=0, tzinfo=timezone.utc).isoformat() 1hij
150 class ModelWithCustomEncoderSubclass(ModelWithCustomEncoder): 1hij
151 pass 1hij
153 model = ModelWithCustomEncoder(dt_field=datetime(2019, 1, 1, 8)) 1hij
154 assert jsonable_encoder(model) == {"dt_field": "2019-01-01T08:00:00+00:00"} 1hij
155 subclass_model = ModelWithCustomEncoderSubclass(dt_field=datetime(2019, 1, 1, 8)) 1hij
156 assert jsonable_encoder(subclass_model) == {"dt_field": "2019-01-01T08:00:00+00:00"} 1hij
159def test_json_encoder_error_with_pydanticv1(): 1abcd
160 with warnings.catch_warnings(): 1tvu
161 warnings.simplefilter("ignore", UserWarning) 1tvu
162 from pydantic import v1 1tvu
164 class ModelV1(v1.BaseModel): 1tvu
165 name: str 1tu
167 data = ModelV1(name="test") 1tvu
168 with pytest.raises(PydanticV1NotSupportedError): 1tvu
169 jsonable_encoder(data) 1tvu
172def test_encode_model_with_config(): 1abcd
173 model = ModelWithConfig(role=RoleEnum.admin) 1012
174 assert jsonable_encoder(model) == {"role": "admin"} 1012
177def test_encode_model_with_alias_raises(): 1abcd
178 with pytest.raises(ValidationError): 1345
179 ModelWithAlias(foo="Bar") 1345
182def test_encode_model_with_alias(): 1abcd
183 model = ModelWithAlias(Foo="Bar") 1678
184 assert jsonable_encoder(model) == {"Foo": "Bar"} 1678
187def test_encode_model_with_default(): 1abcd
188 model = ModelWithDefault(foo="foo", bar="bar") 1qrs
189 assert jsonable_encoder(model) == {"foo": "foo", "bar": "bar", "bla": "bla"} 1qrs
190 assert jsonable_encoder(model, exclude_unset=True) == {"foo": "foo", "bar": "bar"} 1qrs
191 assert jsonable_encoder(model, exclude_defaults=True) == {"foo": "foo"} 1qrs
192 assert jsonable_encoder(model, exclude_unset=True, exclude_defaults=True) == { 1qrs
193 "foo": "foo"
194 }
195 assert jsonable_encoder(model, include={"foo"}) == {"foo": "foo"} 1qrs
196 assert jsonable_encoder(model, exclude={"bla"}) == {"foo": "foo", "bar": "bar"} 1qrs
197 assert jsonable_encoder(model, include={}) == {} 1qrs
198 assert jsonable_encoder(model, exclude={}) == { 1qrs
199 "foo": "foo",
200 "bar": "bar",
201 "bla": "bla",
202 }
205def test_custom_encoders(): 1abcd
206 class safe_datetime(datetime): 1klm
207 pass 1klm
209 class MyDict(TypedDict): 1klm
210 dt_field: safe_datetime 1klm
212 instance = MyDict(dt_field=safe_datetime.now()) 1klm
214 encoded_instance = jsonable_encoder( 1klm
215 instance, custom_encoder={safe_datetime: lambda o: o.strftime("%H:%M:%S")}
216 )
217 assert encoded_instance["dt_field"] == instance["dt_field"].strftime("%H:%M:%S") 1klm
219 encoded_instance = jsonable_encoder( 1klm
220 instance, custom_encoder={datetime: lambda o: o.strftime("%H:%M:%S")}
221 )
222 assert encoded_instance["dt_field"] == instance["dt_field"].strftime("%H:%M:%S") 1klm
224 encoded_instance2 = jsonable_encoder(instance) 1klm
225 assert encoded_instance2["dt_field"] == instance["dt_field"].isoformat() 1klm
228def test_custom_enum_encoders(): 1abcd
229 def custom_enum_encoder(v: Enum): 1wxy
230 return v.value.lower() 1wxy
232 class MyEnum(Enum): 1wxy
233 ENUM_VAL_1 = "ENUM_VAL_1" 1wxy
235 instance = MyEnum.ENUM_VAL_1 1wxy
237 encoded_instance = jsonable_encoder( 1wxy
238 instance, custom_encoder={MyEnum: custom_enum_encoder}
239 )
240 assert encoded_instance == custom_enum_encoder(instance) 1wxy
243def test_encode_model_with_pure_path(): 1abcd
244 class ModelWithPath(BaseModel): 1IJK
245 path: PurePath 1IJK
247 model_config = {"arbitrary_types_allowed": True} 1IJK
249 test_path = PurePath("/foo", "bar") 1IJK
250 obj = ModelWithPath(path=test_path) 1IJK
251 assert jsonable_encoder(obj) == {"path": str(test_path)} 1IJK
254def test_encode_model_with_pure_posix_path(): 1abcd
255 class ModelWithPath(BaseModel): 1OPQ
256 path: PurePosixPath 1OPQ
258 model_config = {"arbitrary_types_allowed": True} 1OPQ
260 obj = ModelWithPath(path=PurePosixPath("/foo", "bar")) 1OPQ
261 assert jsonable_encoder(obj) == {"path": "/foo/bar"} 1OPQ
264def test_encode_model_with_pure_windows_path(): 1abcd
265 class ModelWithPath(BaseModel): 1RST
266 path: PureWindowsPath 1RST
268 model_config = {"arbitrary_types_allowed": True} 1RST
270 obj = ModelWithPath(path=PureWindowsPath("/foo", "bar")) 1RST
271 assert jsonable_encoder(obj) == {"path": "\\foo\\bar"} 1RST
274def test_encode_pure_path(): 1abcd
275 test_path = PurePath("/foo", "bar") 19!#
277 assert jsonable_encoder({"path": test_path}) == {"path": str(test_path)} 19!#
280def test_decimal_encoder_float(): 1abcd
281 data = {"value": Decimal(1.23)} 1$%'
282 assert jsonable_encoder(data) == {"value": 1.23} 1$%'
285def test_decimal_encoder_int(): 1abcd
286 data = {"value": Decimal(2)} 1()*
287 assert jsonable_encoder(data) == {"value": 2} 1()*
290def test_decimal_encoder_nan(): 1abcd
291 data = {"value": Decimal("NaN")} 1+,-
292 assert isnan(jsonable_encoder(data)["value"]) 1+,-
295def test_decimal_encoder_infinity(): 1abcd
296 data = {"value": Decimal("Infinity")} 1UVW
297 assert isinf(jsonable_encoder(data)["value"]) 1UVW
298 data = {"value": Decimal("-Infinity")} 1UVW
299 assert isinf(jsonable_encoder(data)["value"]) 1UVW
302def test_encode_deque_encodes_child_models(): 1abcd
303 class Model(BaseModel): 1XYZ
304 test: str 1XYZ
306 dq = deque([Model(test="test")]) 1XYZ
308 assert jsonable_encoder(dq)[0]["test"] == "test" 1XYZ
311def test_encode_pydantic_undefined(): 1abcd
312 data = {"value": Undefined} 1./:
313 assert jsonable_encoder(data) == {"value": None} 1./: