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