From 75a07f24bf01a31225ee687f3e2b3fc1981b67ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 18 Jan 2020 18:03:51 +0100 Subject: [PATCH] :lock: Fix clone field implementation to handle sub-models in response_model (#889) --- fastapi/utils.py | 2 +- tests/test_filter_pydantic_sub_model.py | 91 +++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 tests/test_filter_pydantic_sub_model.py diff --git a/fastapi/utils.py b/fastapi/utils.py index a068cc582..6a0c1bfd7 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -97,7 +97,7 @@ def create_cloned_field(field: ModelField) -> ModelField: original_type.__name__, __config__=original_type.__config__ ) for f in original_type.__fields__.values(): - use_type.__fields__[f.name] = f + use_type.__fields__[f.name] = create_cloned_field(f) use_type.__validators__ = original_type.__validators__ if PYDANTIC_1: new_field = ModelField( diff --git a/tests/test_filter_pydantic_sub_model.py b/tests/test_filter_pydantic_sub_model.py new file mode 100644 index 000000000..1b2285339 --- /dev/null +++ b/tests/test_filter_pydantic_sub_model.py @@ -0,0 +1,91 @@ +from fastapi import Depends, FastAPI +from pydantic import BaseModel +from starlette.testclient import TestClient + +app = FastAPI() + + +class ModelB(BaseModel): + username: str + + +class ModelC(ModelB): + password: str + + +class ModelA(BaseModel): + name: str + description: str = None + model_b: ModelB + + +async def get_model_c() -> ModelC: + return ModelC(username="test-user", password="test-password") + + +@app.get("/model", response_model=ModelA) +async def get_model_a(model_c=Depends(get_model_c)): + return {"name": "model-a-name", "description": "model-a-desc", "model_b": model_c} + + +client = TestClient(app) + + +openapi_schema = { + "openapi": "3.0.2", + "info": {"title": "Fast API", "version": "0.1.0"}, + "paths": { + "/model": { + "get": { + "summary": "Get Model A", + "operationId": "get_model_a_model_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/ModelA"} + } + }, + } + }, + } + } + }, + "components": { + "schemas": { + "ModelA": { + "title": "ModelA", + "required": ["name", "model_b"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "model_b": {"$ref": "#/components/schemas/ModelB"}, + }, + }, + "ModelB": { + "title": "ModelB", + "required": ["username"], + "type": "object", + "properties": {"username": {"title": "Username", "type": "string"}}, + }, + } + }, +} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == openapi_schema + + +def test_filter_sub_model(): + response = client.get("/model") + assert response.status_code == 200 + assert response.json() == { + "name": "model-a-name", + "description": "model-a-desc", + "model_b": {"username": "test-user"}, + }