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

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

10 

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

16 

17 

18class Person: 1adbc

19 def __init__(self, name: str): 1adbc

20 self.name = name 1oepqfrg

21 

22 

23class Pet: 1adbc

24 def __init__(self, owner: Person, name: str): 1adbc

25 self.owner = owner 1oepqfrg

26 self.name = name 1oepqfrg

27 

28 

29@dataclass 1adbc

30class Item: 1adbc

31 name: str 1abc

32 count: int 1abc

33 

34 

35class DictablePerson(Person): 1adbc

36 def __iter__(self): 1adbc

37 return ((k, v) for k, v in self.__dict__.items()) 1etfg

38 

39 

40class DictablePet(Pet): 1adbc

41 def __iter__(self): 1adbc

42 return ((k, v) for k, v in self.__dict__.items()) 1etfg

43 

44 

45class Unserializable: 1adbc

46 def __iter__(self): 1adbc

47 raise NotImplementedError() 1VWXY

48 

49 @property 1adbc

50 def __dict__(self): 1adbc

51 raise NotImplementedError() 1VWXY

52 

53 

54class RoleEnum(Enum): 1adbc

55 admin = "admin" 1adbc

56 normal = "normal" 1adbc

57 

58 

59class ModelWithConfig(BaseModel): 1adbc

60 role: RoleEnum | None = None 1adbc

61 

62 model_config = {"use_enum_values": True} 1adbc

63 

64 

65class ModelWithAlias(BaseModel): 1adbc

66 foo: str = Field(alias="Foo") 1adbc

67 

68 

69class ModelWithDefault(BaseModel): 1adbc

70 foo: str = ... # type: ignore 1adbc

71 bar: str = "bar" 1adbc

72 bla: str = "bla" 1adbc

73 

74 

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 } 

85 

86 

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 } 

97 

98 

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 } 

110 

111 

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 } 

123 

124 

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

132 

133 

134def test_encode_unsupported(): 1adbc

135 unserializable = Unserializable() 1VWXY

136 with pytest.raises(ValueError): 1VWXY

137 jsonable_encoder(unserializable) 1VWXY

138 

139 

140def test_encode_custom_json_encoders_model_pydanticv2(): 1adbc

141 from pydantic import field_serializer 1hkij

142 

143 class ModelWithCustomEncoder(BaseModel): 1hkij

144 dt_field: datetime 1hij

145 

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

149 

150 class ModelWithCustomEncoderSubclass(ModelWithCustomEncoder): 1hkij

151 pass 1hkij

152 

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

157 

158 

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

163 

164 class ModelV1(v1.BaseModel): 1yBzA

165 name: str 1yzA

166 

167 data = ModelV1(name="test") 1yBzA

168 with pytest.raises(PydanticV1NotSupportedError): 1yBzA

169 jsonable_encoder(data) 1yBzA

170 

171 

172def test_encode_model_with_config(): 1adbc

173 model = ModelWithConfig(role=RoleEnum.admin) 1)*+,

174 assert jsonable_encoder(model) == {"role": "admin"} 1)*+,

175 

176 

177def test_encode_model_with_alias_raises(): 1adbc

178 with pytest.raises(ValidationError): 1-./:

179 ModelWithAlias(foo="Bar") 1-./:

180 

181 

182def test_encode_model_with_alias(): 1adbc

183 model = ModelWithAlias(Foo="Bar") 1;=?@

184 assert jsonable_encoder(model) == {"Foo": "Bar"} 1;=?@

185 

186 

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 } 

203 

204 

205def test_custom_encoders(): 1adbc

206 class safe_datetime(datetime): 1lsmn

207 pass 1lsmn

208 

209 class MyDict(TypedDict): 1lsmn

210 dt_field: safe_datetime 1lmn

211 

212 instance = MyDict(dt_field=safe_datetime.now()) 1lsmn

213 

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

218 

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

223 

224 encoded_instance2 = jsonable_encoder(instance) 1lsmn

225 assert encoded_instance2["dt_field"] == instance["dt_field"].isoformat() 1lsmn

226 

227 

228def test_custom_enum_encoders(): 1adbc

229 def custom_enum_encoder(v: Enum): 1CDEF

230 return v.value.lower() 1CDEF

231 

232 class MyEnum(Enum): 1CDEF

233 ENUM_VAL_1 = "ENUM_VAL_1" 1CDEF

234 

235 instance = MyEnum.ENUM_VAL_1 1CDEF

236 

237 encoded_instance = jsonable_encoder( 1CDEF

238 instance, custom_encoder={MyEnum: custom_enum_encoder} 

239 ) 

240 assert encoded_instance == custom_enum_encoder(instance) 1CDEF

241 

242 

243def test_encode_model_with_pure_path(): 1adbc

244 class ModelWithPath(BaseModel): 1SZTU

245 path: PurePath 1STU

246 

247 model_config = {"arbitrary_types_allowed": True} 1SZTU

248 

249 test_path = PurePath("/foo", "bar") 1SZTU

250 obj = ModelWithPath(path=test_path) 1SZTU

251 assert jsonable_encoder(obj) == {"path": str(test_path)} 1SZTU

252 

253 

254def test_encode_model_with_pure_posix_path(): 1adbc

255 class ModelWithPath(BaseModel): 10612

256 path: PurePosixPath 1012

257 

258 model_config = {"arbitrary_types_allowed": True} 10612

259 

260 obj = ModelWithPath(path=PurePosixPath("/foo", "bar")) 10612

261 assert jsonable_encoder(obj) == {"path": "/foo/bar"} 10612

262 

263 

264def test_encode_model_with_pure_windows_path(): 1adbc

265 class ModelWithPath(BaseModel): 13745

266 path: PureWindowsPath 1345

267 

268 model_config = {"arbitrary_types_allowed": True} 13745

269 

270 obj = ModelWithPath(path=PureWindowsPath("/foo", "bar")) 13745

271 assert jsonable_encoder(obj) == {"path": "\\foo\\bar"} 13745

272 

273 

274def test_encode_pure_path(): 1adbc

275 test_path = PurePath("/foo", "bar") 1[]^_

276 

277 assert jsonable_encoder({"path": test_path}) == {"path": str(test_path)} 1[]^_

278 

279 

280def test_decimal_encoder_float(): 1adbc

281 data = {"value": Decimal(1.23)} 1`{|}

282 assert jsonable_encoder(data) == {"value": 1.23} 1`{|}

283 

284 

285def test_decimal_encoder_int(): 1adbc

286 data = {"value": Decimal(2)} 2~ abbbcb

287 assert jsonable_encoder(data) == {"value": 2} 2~ abbbcb

288 

289 

290def test_decimal_encoder_nan(): 1adbc

291 data = {"value": Decimal("NaN")} 2dbebfbgb

292 assert isnan(jsonable_encoder(data)["value"]) 2dbebfbgb

293 

294 

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!#

300 

301 

302def test_encode_deque_encodes_child_models(): 1adbc

303 class Model(BaseModel): 1$(%'

304 test: str 1$%'

305 

306 dq = deque([Model(test="test")]) 1$(%'

307 

308 assert jsonable_encoder(dq)[0]["test"] == "test" 1$(%'

309 

310 

311def test_encode_pydantic_undefined(): 1adbc

312 data = {"value": Undefined} 2hbibjbkb

313 assert jsonable_encoder(data) == {"value": None} 2hbibjbkb