]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
:bug: Fix automatic embedding with dependencies and sub-dependencies (#1079)
authorToan Vuong <Toad2186@users.noreply.github.com>
Mon, 30 Mar 2020 19:44:43 +0000 (12:44 -0700)
committerGitHub <noreply@github.com>
Mon, 30 Mar 2020 19:44:43 +0000 (21:44 +0200)
* Handle automatic embedding with Depends

* :bug: Fix body embeds for sub-dependencies and simplify implementation

* :white_check_mark: Add/update tests for body embeds in dependencies

* :construction_worker: Trigger Travis

Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
fastapi/dependencies/utils.py
tests/test_dependency_duplicates.py [new file with mode: 0644]

index 543479be8814bbc1d226022f3eef8d31d45e02a0..43ab4a0985bc3a7e5dc061e017aa2772dac05a7d 100644 (file)
@@ -704,8 +704,14 @@ def get_body_field(*, dependant: Dependant, name: str) -> Optional[ModelField]:
     first_param = flat_dependant.body_params[0]
     field_info = get_field_info(first_param)
     embed = getattr(field_info, "embed", None)
-    if len(flat_dependant.body_params) == 1 and not embed:
+    body_param_names_set = set([param.name for param in flat_dependant.body_params])
+    if len(body_param_names_set) == 1 and not embed:
         return get_schema_compatible_field(field=first_param)
+    # If one field requires to embed, all have to be embedded
+    # in case a sub-dependency is evaluated with a single unique body field
+    # That is combined (embedded) with other body fields
+    for param in flat_dependant.body_params:
+        setattr(get_field_info(param), "embed", True)
     model_name = "Body_" + name
     BodyModel = create_model(model_name)
     for f in flat_dependant.body_params:
diff --git a/tests/test_dependency_duplicates.py b/tests/test_dependency_duplicates.py
new file mode 100644 (file)
index 0000000..0462b43
--- /dev/null
@@ -0,0 +1,232 @@
+from typing import List
+
+from fastapi import Depends, FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+app = FastAPI()
+
+client = TestClient(app)
+
+
+class Item(BaseModel):
+    data: str
+
+
+def duplicate_dependency(item: Item):
+    return item
+
+
+def dependency(item2: Item):
+    return item2
+
+
+def sub_duplicate_dependency(
+    item: Item, sub_item: Item = Depends(duplicate_dependency)
+):
+    return [item, sub_item]
+
+
+@app.post("/with-duplicates")
+async def with_duplicates(item: Item, item2: Item = Depends(duplicate_dependency)):
+    return [item, item2]
+
+
+@app.post("/no-duplicates")
+async def no_duplicates(item: Item, item2: Item = Depends(dependency)):
+    return [item, item2]
+
+
+@app.post("/with-duplicates-sub")
+async def no_duplicates_sub(
+    item: Item, sub_items: List[Item] = Depends(sub_duplicate_dependency)
+):
+    return [item, sub_items]
+
+
+openapi_schema = {
+    "openapi": "3.0.2",
+    "info": {"title": "FastAPI", "version": "0.1.0"},
+    "paths": {
+        "/with-duplicates": {
+            "post": {
+                "summary": "With Duplicates",
+                "operationId": "with_duplicates_with_duplicates_post",
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {"$ref": "#/components/schemas/Item"}
+                        }
+                    },
+                    "required": True,
+                },
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+            }
+        },
+        "/no-duplicates": {
+            "post": {
+                "summary": "No Duplicates",
+                "operationId": "no_duplicates_no_duplicates_post",
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "$ref": "#/components/schemas/Body_no_duplicates_no_duplicates_post"
+                            }
+                        }
+                    },
+                    "required": True,
+                },
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+            }
+        },
+        "/with-duplicates-sub": {
+            "post": {
+                "summary": "No Duplicates Sub",
+                "operationId": "no_duplicates_sub_with_duplicates_sub_post",
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {"$ref": "#/components/schemas/Item"}
+                        }
+                    },
+                    "required": True,
+                },
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+            }
+        },
+    },
+    "components": {
+        "schemas": {
+            "Body_no_duplicates_no_duplicates_post": {
+                "title": "Body_no_duplicates_no_duplicates_post",
+                "required": ["item", "item2"],
+                "type": "object",
+                "properties": {
+                    "item": {"$ref": "#/components/schemas/Item"},
+                    "item2": {"$ref": "#/components/schemas/Item"},
+                },
+            },
+            "HTTPValidationError": {
+                "title": "HTTPValidationError",
+                "type": "object",
+                "properties": {
+                    "detail": {
+                        "title": "Detail",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/ValidationError"},
+                    }
+                },
+            },
+            "Item": {
+                "title": "Item",
+                "required": ["data"],
+                "type": "object",
+                "properties": {"data": {"title": "Data", "type": "string"}},
+            },
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"type": "string"},
+                    },
+                    "msg": {"title": "Message", "type": "string"},
+                    "type": {"title": "Error Type", "type": "string"},
+                },
+            },
+        }
+    },
+}
+
+
+def test_openapi_schema():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200
+    assert response.json() == openapi_schema
+
+
+def test_no_duplicates_invalid():
+    response = client.post("/no-duplicates", json={"item": {"data": "myitem"}})
+    assert response.status_code == 422
+    assert response.json() == {
+        "detail": [
+            {
+                "loc": ["body", "item2"],
+                "msg": "field required",
+                "type": "value_error.missing",
+            }
+        ]
+    }
+
+
+def test_no_duplicates():
+    response = client.post(
+        "/no-duplicates",
+        json={"item": {"data": "myitem"}, "item2": {"data": "myitem2"}},
+    )
+    assert response.status_code == 200
+    assert response.json() == [{"data": "myitem"}, {"data": "myitem2"}]
+
+
+def test_duplicates():
+    response = client.post("/with-duplicates", json={"data": "myitem"})
+    assert response.status_code == 200
+    assert response.json() == [{"data": "myitem"}, {"data": "myitem"}]
+
+
+def test_sub_duplicates():
+    response = client.post("/with-duplicates-sub", json={"data": "myitem"})
+    assert response.status_code == 200
+    assert response.json() == [
+        {"data": "myitem"},
+        [{"data": "myitem"}, {"data": "myitem"}],
+    ]