mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-27 08:10:57 -05:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a085898309 | ||
|
|
3b40c557ce | ||
|
|
75a07f24bf | ||
|
|
7cea84b74c |
@@ -1,5 +1,10 @@
|
||||
## Latest changes
|
||||
|
||||
## 0.47.1
|
||||
|
||||
* Fix model filtering in `response_model`, cloning sub-models. PR [#889](https://github.com/tiangolo/fastapi/pull/889).
|
||||
* Fix FastAPI serialization of Pydantic models using ORM mode blocking the event loop. PR [#888](https://github.com/tiangolo/fastapi/pull/888).
|
||||
|
||||
## 0.47.0
|
||||
|
||||
* Refactor documentation to make a simpler and shorter [Tutorial - User Guide](https://fastapi.tiangolo.com/tutorial/) and an additional [Advanced User Guide](https://fastapi.tiangolo.com/advanced/) with all the additional docs. PR [#887](https://github.com/tiangolo/fastapi/pull/887).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.47.0"
|
||||
__version__ = "0.47.1"
|
||||
|
||||
from starlette.background import BackgroundTasks
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ except ImportError: # pragma: nocover
|
||||
from pydantic.fields import Field as ModelField # type: ignore
|
||||
|
||||
|
||||
def serialize_response(
|
||||
async def serialize_response(
|
||||
*,
|
||||
field: ModelField = None,
|
||||
response: Response,
|
||||
@@ -55,6 +55,7 @@ def serialize_response(
|
||||
exclude: Union[SetIntStr, DictIntStrAny] = set(),
|
||||
by_alias: bool = True,
|
||||
exclude_unset: bool = False,
|
||||
is_coroutine: bool = True,
|
||||
) -> Any:
|
||||
if field:
|
||||
errors = []
|
||||
@@ -63,7 +64,12 @@ def serialize_response(
|
||||
response = response.dict(exclude_unset=exclude_unset)
|
||||
else:
|
||||
response = response.dict(skip_defaults=exclude_unset) # pragma: nocover
|
||||
value, errors_ = field.validate(response, {}, loc=("response",))
|
||||
if is_coroutine:
|
||||
value, errors_ = field.validate(response, {}, loc=("response",))
|
||||
else:
|
||||
value, errors_ = await run_in_threadpool(
|
||||
field.validate, response, {}, loc=("response",)
|
||||
)
|
||||
if isinstance(errors_, ErrorWrapper):
|
||||
errors.append(errors_)
|
||||
elif isinstance(errors_, list):
|
||||
@@ -131,13 +137,14 @@ def get_request_handler(
|
||||
if raw_response.background is None:
|
||||
raw_response.background = background_tasks
|
||||
return raw_response
|
||||
response_data = serialize_response(
|
||||
response_data = await serialize_response(
|
||||
field=response_field,
|
||||
response=raw_response,
|
||||
include=response_model_include,
|
||||
exclude=response_model_exclude,
|
||||
by_alias=response_model_by_alias,
|
||||
exclude_unset=response_model_exclude_unset,
|
||||
is_coroutine=is_coroutine,
|
||||
)
|
||||
response = response_class(
|
||||
content=response_data,
|
||||
|
||||
@@ -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(
|
||||
|
||||
91
tests/test_filter_pydantic_sub_model.py
Normal file
91
tests/test_filter_pydantic_sub_model.py
Normal file
@@ -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"},
|
||||
}
|
||||
Reference in New Issue
Block a user