mirror of https://github.com/tiangolo/fastapi.git
🐛 Fix `jsonable_encoder` for dataclasses with pydantic-compatible fields (#3607)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
de6ccd7754
commit
22bed0008c
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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…
Reference in New Issue