Browse Source

🐛 Fix `jsonable_encoder` for dataclasses with pydantic-compatible fields (#3607)

Co-authored-by: Sebastián Ramírez <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
pull/4200/merge
Luis R 3 years ago
committed by GitHub
parent
commit
22bed0008c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      fastapi/encoders.py
  2. 130
      tests/test_serialize_response_dataclass.py

9
fastapi/encoders.py

@ -71,7 +71,14 @@ def jsonable_encoder(
sqlalchemy_safe=sqlalchemy_safe, sqlalchemy_safe=sqlalchemy_safe,
) )
if dataclasses.is_dataclass(obj): if dataclasses.is_dataclass(obj):
return dataclasses.asdict(obj) obj_dict = dataclasses.asdict(obj)
return jsonable_encoder(
obj_dict,
exclude_none=exclude_none,
exclude_defaults=exclude_defaults,
custom_encoder=custom_encoder,
sqlalchemy_safe=sqlalchemy_safe,
)
if isinstance(obj, Enum): if isinstance(obj, Enum):
return obj.value return obj.value
if isinstance(obj, PurePath): if isinstance(obj, PurePath):

130
tests/test_serialize_response_dataclass.py

@ -1,8 +1,9 @@
from dataclasses import dataclass
from datetime import datetime
from typing import List, Optional from typing import List, Optional
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from pydantic.dataclasses import dataclass
app = FastAPI() app = FastAPI()
@ -10,54 +11,64 @@ app = FastAPI()
@dataclass @dataclass
class Item: class Item:
name: str name: str
date: datetime
price: Optional[float] = None price: Optional[float] = None
owner_ids: Optional[List[int]] = None owner_ids: Optional[List[int]] = None
@app.get("/items/valid", response_model=Item) @app.get("/items/valid", response_model=Item)
def get_valid(): def get_valid():
return {"name": "valid", "price": 1.0} return {"name": "valid", "date": datetime(2021, 7, 26), "price": 1.0}
@app.get("/items/object", response_model=Item) @app.get("/items/object", response_model=Item)
def get_object(): def get_object():
return Item(name="object", price=1.0, owner_ids=[1, 2, 3]) return Item(
name="object", date=datetime(2021, 7, 26), price=1.0, owner_ids=[1, 2, 3]
)
@app.get("/items/coerce", response_model=Item) @app.get("/items/coerce", response_model=Item)
def get_coerce(): def get_coerce():
return {"name": "coerce", "price": "1.0"} return {"name": "coerce", "date": datetime(2021, 7, 26).isoformat(), "price": "1.0"}
@app.get("/items/validlist", response_model=List[Item]) @app.get("/items/validlist", response_model=List[Item])
def get_validlist(): def get_validlist():
return [ return [
{"name": "foo"}, {"name": "foo", "date": datetime(2021, 7, 26)},
{"name": "bar", "price": 1.0}, {"name": "bar", "date": datetime(2021, 7, 26), "price": 1.0},
{"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]}, {
"name": "baz",
"date": datetime(2021, 7, 26),
"price": 2.0,
"owner_ids": [1, 2, 3],
},
] ]
@app.get("/items/objectlist", response_model=List[Item]) @app.get("/items/objectlist", response_model=List[Item])
def get_objectlist(): def get_objectlist():
return [ return [
Item(name="foo"), Item(name="foo", date=datetime(2021, 7, 26)),
Item(name="bar", price=1.0), Item(name="bar", date=datetime(2021, 7, 26), price=1.0),
Item(name="baz", price=2.0, owner_ids=[1, 2, 3]), Item(name="baz", date=datetime(2021, 7, 26), price=2.0, owner_ids=[1, 2, 3]),
] ]
@app.get("/items/no-response-model/object") @app.get("/items/no-response-model/object")
def get_no_response_model_object(): def get_no_response_model_object():
return Item(name="object", price=1.0, owner_ids=[1, 2, 3]) return Item(
name="object", date=datetime(2021, 7, 26), price=1.0, owner_ids=[1, 2, 3]
)
@app.get("/items/no-response-model/objectlist") @app.get("/items/no-response-model/objectlist")
def get_no_response_model_objectlist(): def get_no_response_model_objectlist():
return [ return [
Item(name="foo"), Item(name="foo", date=datetime(2021, 7, 26)),
Item(name="bar", price=1.0), Item(name="bar", date=datetime(2021, 7, 26), price=1.0),
Item(name="baz", price=2.0, owner_ids=[1, 2, 3]), Item(name="baz", date=datetime(2021, 7, 26), price=2.0, owner_ids=[1, 2, 3]),
] ]
@ -67,28 +78,58 @@ client = TestClient(app)
def test_valid(): def test_valid():
response = client.get("/items/valid") response = client.get("/items/valid")
response.raise_for_status() response.raise_for_status()
assert response.json() == {"name": "valid", "price": 1.0, "owner_ids": None} assert response.json() == {
"name": "valid",
"date": datetime(2021, 7, 26).isoformat(),
"price": 1.0,
"owner_ids": None,
}
def test_object(): def test_object():
response = client.get("/items/object") response = client.get("/items/object")
response.raise_for_status() response.raise_for_status()
assert response.json() == {"name": "object", "price": 1.0, "owner_ids": [1, 2, 3]} assert response.json() == {
"name": "object",
"date": datetime(2021, 7, 26).isoformat(),
"price": 1.0,
"owner_ids": [1, 2, 3],
}
def test_coerce(): def test_coerce():
response = client.get("/items/coerce") response = client.get("/items/coerce")
response.raise_for_status() response.raise_for_status()
assert response.json() == {"name": "coerce", "price": 1.0, "owner_ids": None} assert response.json() == {
"name": "coerce",
"date": datetime(2021, 7, 26).isoformat(),
"price": 1.0,
"owner_ids": None,
}
def test_validlist(): def test_validlist():
response = client.get("/items/validlist") response = client.get("/items/validlist")
response.raise_for_status() response.raise_for_status()
assert response.json() == [ assert response.json() == [
{"name": "foo", "price": None, "owner_ids": None}, {
{"name": "bar", "price": 1.0, "owner_ids": None}, "name": "foo",
{"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]}, "date": datetime(2021, 7, 26).isoformat(),
"price": None,
"owner_ids": None,
},
{
"name": "bar",
"date": datetime(2021, 7, 26).isoformat(),
"price": 1.0,
"owner_ids": None,
},
{
"name": "baz",
"date": datetime(2021, 7, 26).isoformat(),
"price": 2.0,
"owner_ids": [1, 2, 3],
},
] ]
@ -96,23 +137,58 @@ def test_objectlist():
response = client.get("/items/objectlist") response = client.get("/items/objectlist")
response.raise_for_status() response.raise_for_status()
assert response.json() == [ assert response.json() == [
{"name": "foo", "price": None, "owner_ids": None}, {
{"name": "bar", "price": 1.0, "owner_ids": None}, "name": "foo",
{"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]}, "date": datetime(2021, 7, 26).isoformat(),
"price": None,
"owner_ids": None,
},
{
"name": "bar",
"date": datetime(2021, 7, 26).isoformat(),
"price": 1.0,
"owner_ids": None,
},
{
"name": "baz",
"date": datetime(2021, 7, 26).isoformat(),
"price": 2.0,
"owner_ids": [1, 2, 3],
},
] ]
def test_no_response_model_object(): def test_no_response_model_object():
response = client.get("/items/no-response-model/object") response = client.get("/items/no-response-model/object")
response.raise_for_status() response.raise_for_status()
assert response.json() == {"name": "object", "price": 1.0, "owner_ids": [1, 2, 3]} assert response.json() == {
"name": "object",
"date": datetime(2021, 7, 26).isoformat(),
"price": 1.0,
"owner_ids": [1, 2, 3],
}
def test_no_response_model_objectlist(): def test_no_response_model_objectlist():
response = client.get("/items/no-response-model/objectlist") response = client.get("/items/no-response-model/objectlist")
response.raise_for_status() response.raise_for_status()
assert response.json() == [ assert response.json() == [
{"name": "foo", "price": None, "owner_ids": None}, {
{"name": "bar", "price": 1.0, "owner_ids": None}, "name": "foo",
{"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]}, "date": datetime(2021, 7, 26).isoformat(),
"price": None,
"owner_ids": None,
},
{
"name": "bar",
"date": datetime(2021, 7, 26).isoformat(),
"price": 1.0,
"owner_ids": None,
},
{
"name": "baz",
"date": datetime(2021, 7, 26).isoformat(),
"price": 2.0,
"owner_ids": [1, 2, 3],
},
] ]

Loading…
Cancel
Save