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