mirror of https://github.com/tiangolo/fastapi.git
Merge 20527a7148 into 272204c0c7
This commit is contained in:
commit
be0f035497
|
|
@ -234,11 +234,17 @@ def _get_model_config(model: BaseModel) -> Any:
|
|||
return model.model_config
|
||||
|
||||
|
||||
def _has_computed_fields(field: ModelField) -> bool:
|
||||
computed_fields = field._type_adapter.core_schema.get("schema", {}).get(
|
||||
"computed_fields", []
|
||||
)
|
||||
def _model_has_computed_fields(model_or_enum: Any) -> bool:
|
||||
if lenient_issubclass(model_or_enum, BaseModel):
|
||||
model_schema = model_or_enum.__pydantic_core_schema__.get("schema", {})
|
||||
computed_fields = model_schema.get("computed_fields", [])
|
||||
return len(computed_fields) > 0
|
||||
return False # pragma: no cover
|
||||
|
||||
|
||||
def _has_computed_fields(field: ModelField) -> bool:
|
||||
models = get_flat_models_from_field(field, known_models=set())
|
||||
return any(_model_has_computed_fields(model) for model in models)
|
||||
|
||||
|
||||
def get_schema_from_model_field(
|
||||
|
|
@ -250,7 +256,9 @@ def get_schema_from_model_field(
|
|||
],
|
||||
separate_input_output_schemas: bool = True,
|
||||
) -> Dict[str, Any]:
|
||||
override_mode: Union[Literal["validation"], None] = (
|
||||
override_mode: Union[Literal["validation"], None] = None
|
||||
if not separate_input_output_schemas:
|
||||
override_mode = (
|
||||
None
|
||||
if (separate_input_output_schemas or _has_computed_fields(field))
|
||||
else "validation"
|
||||
|
|
@ -260,7 +268,6 @@ def get_schema_from_model_field(
|
|||
if field.mode == "validation"
|
||||
else (field.serialization_alias or field.alias)
|
||||
)
|
||||
|
||||
# This expects that GenerateJsonSchema was already used to generate the definitions
|
||||
json_schema = field_mapping[(field, override_mode or field.mode)]
|
||||
if "$ref" not in json_schema:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,234 @@
|
|||
from typing import List
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .utils import needs_pydanticv2
|
||||
|
||||
|
||||
@pytest.fixture(name="client", params=[True, False])
|
||||
def get_client(request: pytest.FixtureRequest):
|
||||
from pydantic import computed_field
|
||||
|
||||
class MyModel(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
age: int
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def is_adult(self) -> bool:
|
||||
return self.age >= 18
|
||||
|
||||
app = FastAPI(separate_input_output_schemas=request.param)
|
||||
|
||||
@app.get("/list")
|
||||
def get_items() -> List[MyModel]:
|
||||
return [MyModel(id=1, name="Alice", age=30), MyModel(id=2, name="Bob", age=17)]
|
||||
|
||||
@app.post("/item")
|
||||
def create_item(item: MyModel) -> MyModel:
|
||||
return item
|
||||
|
||||
yield TestClient(app)
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_create_item(client: TestClient):
|
||||
response = client.post(
|
||||
"/item",
|
||||
json={"id": 1, "name": "Alice", "age": 30},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == {"id": 1, "name": "Alice", "age": 30, "is_adult": True}
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_get_items(client: TestClient):
|
||||
response = client.get("/list")
|
||||
assert response.status_code == 200, response.text
|
||||
assert response.json() == [
|
||||
{"id": 1, "name": "Alice", "age": 30, "is_adult": True},
|
||||
{"id": 2, "name": "Bob", "age": 17, "is_adult": False},
|
||||
]
|
||||
|
||||
|
||||
@needs_pydanticv2
|
||||
def test_openapi(client: TestClient):
|
||||
response = client.get("/openapi.json")
|
||||
openapi_schema = response.json()
|
||||
expected_schema = {
|
||||
"info": {
|
||||
"title": "FastAPI",
|
||||
"version": "0.1.0",
|
||||
},
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
"/item": {
|
||||
"post": {
|
||||
"operationId": "create_item_item_post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MyModel-Input",
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MyModel-Output",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "Successful Response",
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "Validation Error",
|
||||
},
|
||||
},
|
||||
"summary": "Create Item",
|
||||
},
|
||||
},
|
||||
"/list": {
|
||||
"get": {
|
||||
"operationId": "get_items_list_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/MyModel-Output",
|
||||
},
|
||||
"title": "Response Get Items List Get",
|
||||
"type": "array",
|
||||
},
|
||||
},
|
||||
},
|
||||
"description": "Successful Response",
|
||||
},
|
||||
},
|
||||
"summary": "Get Items",
|
||||
},
|
||||
},
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"HTTPValidationError": {
|
||||
"properties": {
|
||||
"detail": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ValidationError",
|
||||
},
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
},
|
||||
},
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
},
|
||||
"MyModel-Input": {
|
||||
"properties": {
|
||||
"age": {
|
||||
"title": "Age",
|
||||
"type": "integer",
|
||||
},
|
||||
"id": {
|
||||
"title": "Id",
|
||||
"type": "integer",
|
||||
},
|
||||
"name": {
|
||||
"title": "Name",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"age",
|
||||
],
|
||||
"title": "MyModel",
|
||||
"type": "object",
|
||||
},
|
||||
"MyModel-Output": {
|
||||
"properties": {
|
||||
"age": {
|
||||
"title": "Age",
|
||||
"type": "integer",
|
||||
},
|
||||
"id": {
|
||||
"title": "Id",
|
||||
"type": "integer",
|
||||
},
|
||||
"is_adult": {
|
||||
"readOnly": True,
|
||||
"title": "Is Adult",
|
||||
"type": "boolean",
|
||||
},
|
||||
"name": {
|
||||
"title": "Name",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"age",
|
||||
"is_adult",
|
||||
],
|
||||
"title": "MyModel",
|
||||
"type": "object",
|
||||
},
|
||||
"ValidationError": {
|
||||
"properties": {
|
||||
"loc": {
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
},
|
||||
],
|
||||
},
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
},
|
||||
"msg": {
|
||||
"title": "Message",
|
||||
"type": "string",
|
||||
},
|
||||
"type": {
|
||||
"title": "Error Type",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"loc",
|
||||
"msg",
|
||||
"type",
|
||||
],
|
||||
"title": "ValidationError",
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert openapi_schema == expected_schema
|
||||
Loading…
Reference in New Issue