mirror of https://github.com/tiangolo/fastapi.git
✅ Update response_model_by_alias (#1642)
* Make openapi models honor response_model_by_alias * Add test for response_model_by_alias working with openapi models * ⏪ Revert changes * ✅ Update and extend tests for response_model_by_alias * ⏪ Revert test name change * 📌 Pin Pytest and Pytest-Cov Co-authored-by: Martin Zaťko <martin.zatko@kiwi.com>
This commit is contained in:
parent
50926faead
commit
e5594e860f
|
|
@ -52,7 +52,6 @@ except ImportError: # pragma: nocover
|
|||
def _prepare_response_content(
|
||||
res: Any,
|
||||
*,
|
||||
by_alias: bool = True,
|
||||
exclude_unset: bool,
|
||||
exclude_defaults: bool = False,
|
||||
exclude_none: bool = False,
|
||||
|
|
@ -60,14 +59,14 @@ def _prepare_response_content(
|
|||
if isinstance(res, BaseModel):
|
||||
if PYDANTIC_1:
|
||||
return res.dict(
|
||||
by_alias=by_alias,
|
||||
by_alias=True,
|
||||
exclude_unset=exclude_unset,
|
||||
exclude_defaults=exclude_defaults,
|
||||
exclude_none=exclude_none,
|
||||
)
|
||||
else:
|
||||
return res.dict(
|
||||
by_alias=by_alias, skip_defaults=exclude_unset,
|
||||
by_alias=True, skip_defaults=exclude_unset,
|
||||
) # pragma: nocover
|
||||
elif isinstance(res, list):
|
||||
return [
|
||||
|
|
@ -108,7 +107,6 @@ async def serialize_response(
|
|||
errors = []
|
||||
response_content = _prepare_response_content(
|
||||
response_content,
|
||||
by_alias=by_alias,
|
||||
exclude_unset=exclude_unset,
|
||||
exclude_defaults=exclude_defaults,
|
||||
exclude_none=exclude_none,
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ Documentation = "https://fastapi.tiangolo.com/"
|
|||
|
||||
[tool.flit.metadata.requires-extra]
|
||||
test = [
|
||||
"pytest >=5.4.3",
|
||||
"pytest-cov",
|
||||
"pytest==5.4.3",
|
||||
"pytest-cov==2.10.0",
|
||||
"mypy",
|
||||
"black",
|
||||
"isort",
|
||||
|
|
|
|||
|
|
@ -1,23 +1,82 @@
|
|||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
def test_get_openapi():
|
||||
app = FastAPI()
|
||||
|
||||
class Model(BaseModel):
|
||||
class Model(BaseModel):
|
||||
pass
|
||||
|
||||
class Model2(BaseModel):
|
||||
|
||||
class Model2(BaseModel):
|
||||
a: Model
|
||||
|
||||
class Model3(BaseModel):
|
||||
|
||||
class Model3(BaseModel):
|
||||
c: Model
|
||||
d: Model2
|
||||
|
||||
@app.get("/", response_model=Model3)
|
||||
def f():
|
||||
pass # pragma: no cover
|
||||
|
||||
openapi = app.openapi()
|
||||
assert isinstance(openapi, dict)
|
||||
@app.get("/", response_model=Model3)
|
||||
def f():
|
||||
return {"c": {}, "d": {"a": {}}}
|
||||
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/": {
|
||||
"get": {
|
||||
"summary": "F",
|
||||
"operationId": "f__get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Model3"}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Model": {"title": "Model", "type": "object", "properties": {}},
|
||||
"Model2": {
|
||||
"title": "Model2",
|
||||
"required": ["a"],
|
||||
"type": "object",
|
||||
"properties": {"a": {"$ref": "#/components/schemas/Model"}},
|
||||
},
|
||||
"Model3": {
|
||||
"title": "Model3",
|
||||
"required": ["c", "d"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"c": {"$ref": "#/components/schemas/Model"},
|
||||
"d": {"$ref": "#/components/schemas/Model2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == openapi_schema
|
||||
|
||||
|
||||
def test_get_api_route():
|
||||
response = client.get("/")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"c": {}, "d": {"a": {}}}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,323 @@
|
|||
from typing import List
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Model(BaseModel):
|
||||
name: str = Field(alias="alias")
|
||||
|
||||
|
||||
class ModelNoAlias(BaseModel):
|
||||
name: str
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"description": (
|
||||
"response_model_by_alias=False is basically a quick hack, to support "
|
||||
"proper OpenAPI use another model with the correct field names"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@app.get("/dict", response_model=Model, response_model_by_alias=False)
|
||||
def read_dict():
|
||||
return {"alias": "Foo"}
|
||||
|
||||
|
||||
@app.get("/model", response_model=Model, response_model_by_alias=False)
|
||||
def read_model():
|
||||
return Model(alias="Foo")
|
||||
|
||||
|
||||
@app.get("/list", response_model=List[Model], response_model_by_alias=False)
|
||||
def read_list():
|
||||
return [{"alias": "Foo"}, {"alias": "Bar"}]
|
||||
|
||||
|
||||
@app.get("/by-alias/dict", response_model=Model)
|
||||
def by_alias_dict():
|
||||
return {"alias": "Foo"}
|
||||
|
||||
|
||||
@app.get("/by-alias/model", response_model=Model)
|
||||
def by_alias_model():
|
||||
return Model(alias="Foo")
|
||||
|
||||
|
||||
@app.get("/by-alias/list", response_model=List[Model])
|
||||
def by_alias_list():
|
||||
return [{"alias": "Foo"}, {"alias": "Bar"}]
|
||||
|
||||
|
||||
@app.get("/no-alias/dict", response_model=ModelNoAlias)
|
||||
def by_alias_dict():
|
||||
return {"name": "Foo"}
|
||||
|
||||
|
||||
@app.get("/no-alias/model", response_model=ModelNoAlias)
|
||||
def by_alias_model():
|
||||
return ModelNoAlias(name="Foo")
|
||||
|
||||
|
||||
@app.get("/no-alias/list", response_model=List[ModelNoAlias])
|
||||
def by_alias_list():
|
||||
return [{"name": "Foo"}, {"name": "Bar"}]
|
||||
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "FastAPI", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/dict": {
|
||||
"get": {
|
||||
"summary": "Read Dict",
|
||||
"operationId": "read_dict_dict_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Model"}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"/model": {
|
||||
"get": {
|
||||
"summary": "Read Model",
|
||||
"operationId": "read_model_model_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Model"}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"/list": {
|
||||
"get": {
|
||||
"summary": "Read List",
|
||||
"operationId": "read_list_list_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response Read List List Get",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/Model"},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"/by-alias/dict": {
|
||||
"get": {
|
||||
"summary": "By Alias Dict",
|
||||
"operationId": "by_alias_dict_by_alias_dict_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Model"}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"/by-alias/model": {
|
||||
"get": {
|
||||
"summary": "By Alias Model",
|
||||
"operationId": "by_alias_model_by_alias_model_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/Model"}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"/by-alias/list": {
|
||||
"get": {
|
||||
"summary": "By Alias List",
|
||||
"operationId": "by_alias_list_by_alias_list_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response By Alias List By Alias List Get",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/Model"},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"/no-alias/dict": {
|
||||
"get": {
|
||||
"summary": "By Alias Dict",
|
||||
"operationId": "by_alias_dict_no_alias_dict_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/ModelNoAlias"}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"/no-alias/model": {
|
||||
"get": {
|
||||
"summary": "By Alias Model",
|
||||
"operationId": "by_alias_model_no_alias_model_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {"$ref": "#/components/schemas/ModelNoAlias"}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"/no-alias/list": {
|
||||
"get": {
|
||||
"summary": "By Alias List",
|
||||
"operationId": "by_alias_list_no_alias_list_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Response By Alias List No Alias List Get",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ModelNoAlias"
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Model": {
|
||||
"title": "Model",
|
||||
"required": ["alias"],
|
||||
"type": "object",
|
||||
"properties": {"alias": {"title": "Alias", "type": "string"}},
|
||||
},
|
||||
"ModelNoAlias": {
|
||||
"title": "ModelNoAlias",
|
||||
"required": ["name"],
|
||||
"type": "object",
|
||||
"properties": {"name": {"title": "Name", "type": "string"}},
|
||||
"description": "response_model_by_alias=False is basically a quick hack, to support proper OpenAPI use another model with the correct field names",
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == openapi_schema
|
||||
|
||||
|
||||
def test_read_dict():
|
||||
response = client.get("/dict")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"name": "Foo"}
|
||||
|
||||
|
||||
def test_read_model():
|
||||
response = client.get("/model")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"name": "Foo"}
|
||||
|
||||
|
||||
def test_read_list():
|
||||
response = client.get("/list")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [
|
||||
{"name": "Foo"},
|
||||
{"name": "Bar"},
|
||||
]
|
||||
|
||||
|
||||
def test_read_dict_by_alias():
|
||||
response = client.get("/by-alias/dict")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"alias": "Foo"}
|
||||
|
||||
|
||||
def test_read_model_by_alias():
|
||||
response = client.get("/by-alias/model")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"alias": "Foo"}
|
||||
|
||||
|
||||
def test_read_list_by_alias():
|
||||
response = client.get("/by-alias/list")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [
|
||||
{"alias": "Foo"},
|
||||
{"alias": "Bar"},
|
||||
]
|
||||
|
||||
|
||||
def test_read_dict_no_alias():
|
||||
response = client.get("/no-alias/dict")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"name": "Foo"}
|
||||
|
||||
|
||||
def test_read_model_no_alias():
|
||||
response = client.get("/no-alias/model")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"name": "Foo"}
|
||||
|
||||
|
||||
def test_read_list_no_alias():
|
||||
response = client.get("/no-alias/list")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [
|
||||
{"name": "Foo"},
|
||||
{"name": "Bar"},
|
||||
]
|
||||
Loading…
Reference in New Issue