Coverage for tests/test_jsonable_encoder.py: 100%
198 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-05-05 00:03 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2025-05-05 00:03 +0000
1from collections import deque 1abcdef
2from dataclasses import dataclass 1abcdef
3from datetime import datetime, timezone 1abcdef
4from decimal import Decimal 1abcdef
5from enum import Enum 1abcdef
6from pathlib import PurePath, PurePosixPath, PureWindowsPath 1abcdef
7from typing import Optional 1abcdef
9import pytest 1abcdef
10from fastapi._compat import PYDANTIC_V2, Undefined 1abcdef
11from fastapi.encoders import jsonable_encoder 1abcdef
12from pydantic import BaseModel, Field, ValidationError 1abcdef
14from .utils import needs_pydanticv1, needs_pydanticv2 1abcdef
17class Person: 1abcdef
18 def __init__(self, name: str): 1abcdef
19 self.name = name 1ygzhAiBjCkDl
22class Pet: 1abcdef
23 def __init__(self, owner: Person, name: str): 1abcdef
24 self.owner = owner 1ygzhAiBjCkDl
25 self.name = name 1ygzhAiBjCkDl
28@dataclass 1abcdef
29class Item: 1abcdef
30 name: str 1abcdef
31 count: int 1abcdef
34class DictablePerson(Person): 1abcdef
35 def __iter__(self): 1abcdef
36 return ((k, v) for k, v in self.__dict__.items()) 1ghijkl
39class DictablePet(Pet): 1abcdef
40 def __iter__(self): 1abcdef
41 return ((k, v) for k, v in self.__dict__.items()) 1ghijkl
44class Unserializable: 1abcdef
45 def __iter__(self): 1abcdef
46 raise NotImplementedError() 1?@[]^_
48 @property 1abcdef
49 def __dict__(self): 1abcdef
50 raise NotImplementedError() 1?@[]^_
53class RoleEnum(Enum): 1abcdef
54 admin = "admin" 1abcdef
55 normal = "normal" 1abcdef
58class ModelWithConfig(BaseModel): 1abcdef
59 role: Optional[RoleEnum] = None 1abcdef
61 if PYDANTIC_V2: 1abcdef
62 model_config = {"use_enum_values": True} 1abcdef
63 else:
65 class Config: 1abcdef
66 use_enum_values = True 1abcdef
69class ModelWithAlias(BaseModel): 1abcdef
70 foo: str = Field(alias="Foo") 1abcdef
73class ModelWithDefault(BaseModel): 1abcdef
74 foo: str = ... # type: ignore 1abcdef
75 bar: str = "bar" 1abcdef
76 bla: str = "bla" 1abcdef
79def test_encode_dict(): 1abcdef
80 pet = {"name": "Firulais", "owner": {"name": "Foo"}} 1'()*+,
81 assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}} 1'()*+,
82 assert jsonable_encoder(pet, include={"name"}) == {"name": "Firulais"} 1'()*+,
83 assert jsonable_encoder(pet, exclude={"owner"}) == {"name": "Firulais"} 1'()*+,
84 assert jsonable_encoder(pet, include={}) == {} 1'()*+,
85 assert jsonable_encoder(pet, exclude={}) == { 1'()*+,
86 "name": "Firulais",
87 "owner": {"name": "Foo"},
88 }
91def test_encode_class(): 1abcdef
92 person = Person(name="Foo") 1yzABCD
93 pet = Pet(owner=person, name="Firulais") 1yzABCD
94 assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}} 1yzABCD
95 assert jsonable_encoder(pet, include={"name"}) == {"name": "Firulais"} 1yzABCD
96 assert jsonable_encoder(pet, exclude={"owner"}) == {"name": "Firulais"} 1yzABCD
97 assert jsonable_encoder(pet, include={}) == {} 1yzABCD
98 assert jsonable_encoder(pet, exclude={}) == { 1yzABCD
99 "name": "Firulais",
100 "owner": {"name": "Foo"},
101 }
104def test_encode_dictable(): 1abcdef
105 person = DictablePerson(name="Foo") 1ghijkl
106 pet = DictablePet(owner=person, name="Firulais") 1ghijkl
107 assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}} 1ghijkl
108 assert jsonable_encoder(pet, include={"name"}) == {"name": "Firulais"} 1ghijkl
109 assert jsonable_encoder(pet, exclude={"owner"}) == {"name": "Firulais"} 1ghijkl
110 assert jsonable_encoder(pet, include={}) == {} 1ghijkl
111 assert jsonable_encoder(pet, exclude={}) == { 1ghijkl
112 "name": "Firulais",
113 "owner": {"name": "Foo"},
114 }
117def test_encode_dataclass(): 1abcdef
118 item = Item(name="foo", count=100) 1-./:;=
119 assert jsonable_encoder(item) == {"name": "foo", "count": 100} 1-./:;=
120 assert jsonable_encoder(item, include={"name"}) == {"name": "foo"} 1-./:;=
121 assert jsonable_encoder(item, exclude={"count"}) == {"name": "foo"} 1-./:;=
122 assert jsonable_encoder(item, include={}) == {} 1-./:;=
123 assert jsonable_encoder(item, exclude={}) == {"name": "foo", "count": 100} 1-./:;=
126def test_encode_unsupported(): 1abcdef
127 unserializable = Unserializable() 1?@[]^_
128 with pytest.raises(ValueError): 1?@[]^_
129 jsonable_encoder(unserializable) 1?@[]^_
132@needs_pydanticv2 1abcdef
133def test_encode_custom_json_encoders_model_pydanticv2(): 1abcdef
134 from pydantic import field_serializer 1mnopqr
136 class ModelWithCustomEncoder(BaseModel): 1mnopqr
137 dt_field: datetime 1mnopqr
139 @field_serializer("dt_field") 1mnopqr
140 def serialize_dt_field(self, dt): 1mnopqr
141 return dt.replace(microsecond=0, tzinfo=timezone.utc).isoformat() 1mnopqr
143 class ModelWithCustomEncoderSubclass(ModelWithCustomEncoder): 1mnopqr
144 pass 1mnopqr
146 model = ModelWithCustomEncoder(dt_field=datetime(2019, 1, 1, 8)) 1mnopqr
147 assert jsonable_encoder(model) == {"dt_field": "2019-01-01T08:00:00+00:00"} 1mnopqr
148 subclass_model = ModelWithCustomEncoderSubclass(dt_field=datetime(2019, 1, 1, 8)) 1mnopqr
149 assert jsonable_encoder(subclass_model) == {"dt_field": "2019-01-01T08:00:00+00:00"} 1mnopqr
152# TODO: remove when deprecating Pydantic v1
153@needs_pydanticv1 1abcdef
154def test_encode_custom_json_encoders_model_pydanticv1(): 1abcdef
155 class ModelWithCustomEncoder(BaseModel): 1stuvwx
156 dt_field: datetime 1stuvwx
158 class Config: 1stuvwx
159 json_encoders = { 1stuvwx
160 datetime: lambda dt: dt.replace(
161 microsecond=0, tzinfo=timezone.utc
162 ).isoformat()
163 }
165 class ModelWithCustomEncoderSubclass(ModelWithCustomEncoder): 1stuvwx
166 class Config: 1stuvwx
167 pass 1stuvwx
169 model = ModelWithCustomEncoder(dt_field=datetime(2019, 1, 1, 8)) 1stuvwx
170 assert jsonable_encoder(model) == {"dt_field": "2019-01-01T08:00:00+00:00"} 1stuvwx
171 subclass_model = ModelWithCustomEncoderSubclass(dt_field=datetime(2019, 1, 1, 8)) 1stuvwx
172 assert jsonable_encoder(subclass_model) == {"dt_field": "2019-01-01T08:00:00+00:00"} 1stuvwx
175def test_encode_model_with_config(): 1abcdef
176 model = ModelWithConfig(role=RoleEnum.admin) 2hbibjbkblbmb
177 assert jsonable_encoder(model) == {"role": "admin"} 2hbibjbkblbmb
180def test_encode_model_with_alias_raises(): 1abcdef
181 with pytest.raises(ValidationError): 2nbobpbqbrbsb
182 ModelWithAlias(foo="Bar") 2nbobpbqbrbsb
185def test_encode_model_with_alias(): 1abcdef
186 model = ModelWithAlias(Foo="Bar") 2tbubvbwbxbyb
187 assert jsonable_encoder(model) == {"Foo": "Bar"} 2tbubvbwbxbyb
190def test_encode_model_with_default(): 1abcdef
191 model = ModelWithDefault(foo="foo", bar="bar") 1EFGHIJ
192 assert jsonable_encoder(model) == {"foo": "foo", "bar": "bar", "bla": "bla"} 1EFGHIJ
193 assert jsonable_encoder(model, exclude_unset=True) == {"foo": "foo", "bar": "bar"} 1EFGHIJ
194 assert jsonable_encoder(model, exclude_defaults=True) == {"foo": "foo"} 1EFGHIJ
195 assert jsonable_encoder(model, exclude_unset=True, exclude_defaults=True) == { 1EFGHIJ
196 "foo": "foo"
197 }
198 assert jsonable_encoder(model, include={"foo"}) == {"foo": "foo"} 1EFGHIJ
199 assert jsonable_encoder(model, exclude={"bla"}) == {"foo": "foo", "bar": "bar"} 1EFGHIJ
200 assert jsonable_encoder(model, include={}) == {} 1EFGHIJ
201 assert jsonable_encoder(model, exclude={}) == { 1EFGHIJ
202 "foo": "foo",
203 "bar": "bar",
204 "bla": "bla",
205 }
208@needs_pydanticv1 1abcdef
209def test_custom_encoders(): 1abcdef
210 class safe_datetime(datetime): 1234567
211 pass 1234567
213 class MyModel(BaseModel): 1234567
214 dt_field: safe_datetime 1234567
216 instance = MyModel(dt_field=safe_datetime.now()) 1234567
218 encoded_instance = jsonable_encoder( 1234567
219 instance, custom_encoder={safe_datetime: lambda o: o.isoformat()}
220 )
221 assert encoded_instance["dt_field"] == instance.dt_field.isoformat() 1234567
224def test_custom_enum_encoders(): 1abcdef
225 def custom_enum_encoder(v: Enum): 189!#$%
226 return v.value.lower() 189!#$%
228 class MyEnum(Enum): 189!#$%
229 ENUM_VAL_1 = "ENUM_VAL_1" 189!#$%
231 instance = MyEnum.ENUM_VAL_1 189!#$%
233 encoded_instance = jsonable_encoder( 189!#$%
234 instance, custom_encoder={MyEnum: custom_enum_encoder}
235 )
236 assert encoded_instance == custom_enum_encoder(instance) 189!#$%
239def test_encode_model_with_pure_path(): 1abcdef
240 class ModelWithPath(BaseModel): 1KLMNOP
241 path: PurePath 1KLMNOP
243 if PYDANTIC_V2: 1KLMNOP
244 model_config = {"arbitrary_types_allowed": True} 1KLMNOP
245 else:
247 class Config: 1KLMNOP
248 arbitrary_types_allowed = True 1KLMNOP
250 test_path = PurePath("/foo", "bar") 1KLMNOP
251 obj = ModelWithPath(path=test_path) 1KLMNOP
252 assert jsonable_encoder(obj) == {"path": str(test_path)} 1KLMNOP
255def test_encode_model_with_pure_posix_path(): 1abcdef
256 class ModelWithPath(BaseModel): 1QRSTUV
257 path: PurePosixPath 1QRSTUV
259 if PYDANTIC_V2: 1QRSTUV
260 model_config = {"arbitrary_types_allowed": True} 1QRSTUV
261 else:
263 class Config: 1QRSTUV
264 arbitrary_types_allowed = True 1QRSTUV
266 obj = ModelWithPath(path=PurePosixPath("/foo", "bar")) 1QRSTUV
267 assert jsonable_encoder(obj) == {"path": "/foo/bar"} 1QRSTUV
270def test_encode_model_with_pure_windows_path(): 1abcdef
271 class ModelWithPath(BaseModel): 1WXYZ01
272 path: PureWindowsPath 1WXYZ01
274 if PYDANTIC_V2: 1WXYZ01
275 model_config = {"arbitrary_types_allowed": True} 1WXYZ01
276 else:
278 class Config: 1WXYZ01
279 arbitrary_types_allowed = True 1WXYZ01
281 obj = ModelWithPath(path=PureWindowsPath("/foo", "bar")) 1WXYZ01
282 assert jsonable_encoder(obj) == {"path": "\\foo\\bar"} 1WXYZ01
285@needs_pydanticv1 1abcdef
286def test_encode_root(): 1abcdef
287 class ModelWithRoot(BaseModel): 2` { | } ~ ab
288 __root__: str 2` { | } ~ ab
290 model = ModelWithRoot(__root__="Foo") 2` { | } ~ ab
291 assert jsonable_encoder(model) == "Foo" 2` { | } ~ ab
294@needs_pydanticv2 1abcdef
295def test_decimal_encoder_float(): 1abcdef
296 data = {"value": Decimal(1.23)} 2zbAbBbCbDbEb
297 assert jsonable_encoder(data) == {"value": 1.23} 2zbAbBbCbDbEb
300@needs_pydanticv2 1abcdef
301def test_decimal_encoder_int(): 1abcdef
302 data = {"value": Decimal(2)} 2FbGbHbIbJbKb
303 assert jsonable_encoder(data) == {"value": 2} 2FbGbHbIbJbKb
306def test_encode_deque_encodes_child_models(): 1abcdef
307 class Model(BaseModel): 2bbcbdbebfbgb
308 test: str 2bbcbdbebfbgb
310 dq = deque([Model(test="test")]) 2bbcbdbebfbgb
312 assert jsonable_encoder(dq)[0]["test"] == "test" 2bbcbdbebfbgb
315@needs_pydanticv2 1abcdef
316def test_encode_pydantic_undefined(): 1abcdef
317 data = {"value": Undefined} 2LbMbNbObPbQb
318 assert jsonable_encoder(data) == {"value": None} 2LbMbNbObPbQb