]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
🐛 Fix `jsonable_encoder` for dataclasses with pydantic-compatible fields (#3607)
authorLuis R <lri@me.com>
Fri, 26 Aug 2022 13:56:47 +0000 (15:56 +0200)
committerGitHub <noreply@github.com>
Fri, 26 Aug 2022 13:56:47 +0000 (15:56 +0200)
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>
fastapi/encoders.py
tests/test_serialize_response_dataclass.py

index 6f571edb261d7af5fc1729db8dceda4b57fbf310..f64e4b86ea3b48aaf6de3fa1761c24f034f93ce1 100644 (file)
@@ -71,7 +71,14 @@ def jsonable_encoder(
             sqlalchemy_safe=sqlalchemy_safe,
         )
     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):
         return obj.value
     if isinstance(obj, PurePath):
index e520338ec68f0aff23aba7a29daf790ad46a39e1..1e3bf3b28bcc3743f69802545fa4c103de22c566 100644 (file)
@@ -1,8 +1,9 @@
+from dataclasses import dataclass
+from datetime import datetime
 from typing import List, Optional
 
 from fastapi import FastAPI
 from fastapi.testclient import TestClient
-from pydantic.dataclasses import dataclass
 
 app = FastAPI()
 
@@ -10,54 +11,64 @@ app = FastAPI()
 @dataclass
 class Item:
     name: str
+    date: datetime
     price: Optional[float] = None
     owner_ids: Optional[List[int]] = None
 
 
 @app.get("/items/valid", response_model=Item)
 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)
 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)
 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])
 def get_validlist():
     return [
-        {"name": "foo"},
-        {"name": "bar", "price": 1.0},
-        {"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]},
+        {"name": "foo", "date": datetime(2021, 7, 26)},
+        {"name": "bar", "date": datetime(2021, 7, 26), "price": 1.0},
+        {
+            "name": "baz",
+            "date": datetime(2021, 7, 26),
+            "price": 2.0,
+            "owner_ids": [1, 2, 3],
+        },
     ]
 
 
 @app.get("/items/objectlist", response_model=List[Item])
 def get_objectlist():
     return [
-        Item(name="foo"),
-        Item(name="bar", price=1.0),
-        Item(name="baz", price=2.0, owner_ids=[1, 2, 3]),
+        Item(name="foo", date=datetime(2021, 7, 26)),
+        Item(name="bar", date=datetime(2021, 7, 26), price=1.0),
+        Item(name="baz", date=datetime(2021, 7, 26), price=2.0, owner_ids=[1, 2, 3]),
     ]
 
 
 @app.get("/items/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")
 def get_no_response_model_objectlist():
     return [
-        Item(name="foo"),
-        Item(name="bar", price=1.0),
-        Item(name="baz", price=2.0, owner_ids=[1, 2, 3]),
+        Item(name="foo", date=datetime(2021, 7, 26)),
+        Item(name="bar", date=datetime(2021, 7, 26), price=1.0),
+        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():
     response = client.get("/items/valid")
     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():
     response = client.get("/items/object")
     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():
     response = client.get("/items/coerce")
     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():
     response = client.get("/items/validlist")
     response.raise_for_status()
     assert response.json() == [
-        {"name": "foo", "price": None, "owner_ids": None},
-        {"name": "bar", "price": 1.0, "owner_ids": None},
-        {"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]},
+        {
+            "name": "foo",
+            "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.raise_for_status()
     assert response.json() == [
-        {"name": "foo", "price": None, "owner_ids": None},
-        {"name": "bar", "price": 1.0, "owner_ids": None},
-        {"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]},
+        {
+            "name": "foo",
+            "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():
     response = client.get("/items/no-response-model/object")
     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():
     response = client.get("/items/no-response-model/objectlist")
     response.raise_for_status()
     assert response.json() == [
-        {"name": "foo", "price": None, "owner_ids": None},
-        {"name": "bar", "price": 1.0, "owner_ids": None},
-        {"name": "baz", "price": 2.0, "owner_ids": [1, 2, 3]},
+        {
+            "name": "foo",
+            "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],
+        },
     ]