mirror of https://github.com/tiangolo/fastapi.git
🐛 Fix separation of schemas with nested models introduced in 0.119.0 (#14246)
This commit is contained in:
parent
7132a69046
commit
6a657f360d
|
|
@ -207,11 +207,31 @@ def get_definitions(
|
||||||
override_mode: Union[Literal["validation"], None] = (
|
override_mode: Union[Literal["validation"], None] = (
|
||||||
None if separate_input_output_schemas else "validation"
|
None if separate_input_output_schemas else "validation"
|
||||||
)
|
)
|
||||||
flat_models = get_flat_models_from_fields(fields, known_models=set())
|
validation_fields = [field for field in fields if field.mode == "validation"]
|
||||||
flat_model_fields = [
|
serialization_fields = [field for field in fields if field.mode == "serialization"]
|
||||||
ModelField(field_info=FieldInfo(annotation=model), name=model.__name__)
|
flat_validation_models = get_flat_models_from_fields(
|
||||||
for model in flat_models
|
validation_fields, known_models=set()
|
||||||
|
)
|
||||||
|
flat_serialization_models = get_flat_models_from_fields(
|
||||||
|
serialization_fields, known_models=set()
|
||||||
|
)
|
||||||
|
flat_validation_model_fields = [
|
||||||
|
ModelField(
|
||||||
|
field_info=FieldInfo(annotation=model),
|
||||||
|
name=model.__name__,
|
||||||
|
mode="validation",
|
||||||
|
)
|
||||||
|
for model in flat_validation_models
|
||||||
]
|
]
|
||||||
|
flat_serialization_model_fields = [
|
||||||
|
ModelField(
|
||||||
|
field_info=FieldInfo(annotation=model),
|
||||||
|
name=model.__name__,
|
||||||
|
mode="serialization",
|
||||||
|
)
|
||||||
|
for model in flat_serialization_models
|
||||||
|
]
|
||||||
|
flat_model_fields = flat_validation_model_fields + flat_serialization_model_fields
|
||||||
input_types = {f.type_ for f in fields}
|
input_types = {f.type_ for f in fields}
|
||||||
unique_flat_model_fields = {
|
unique_flat_model_fields = {
|
||||||
f for f in flat_model_fields if f.type_ not in input_types
|
f for f in flat_model_fields if f.type_ not in input_types
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,203 @@
|
||||||
|
# Test with parts from, and to verify the report in:
|
||||||
|
# https://github.com/fastapi/fastapi/discussions/14177
|
||||||
|
# Made an issue in:
|
||||||
|
# https://github.com/fastapi/fastapi/issues/14247
|
||||||
|
from enum import Enum
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
from inline_snapshot import snapshot
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from tests.utils import pydantic_snapshot
|
||||||
|
|
||||||
|
|
||||||
|
class MessageEventType(str, Enum):
|
||||||
|
alpha = "alpha"
|
||||||
|
beta = "beta"
|
||||||
|
|
||||||
|
|
||||||
|
class MessageEvent(BaseModel):
|
||||||
|
event_type: MessageEventType = Field(default=MessageEventType.alpha)
|
||||||
|
output: str
|
||||||
|
|
||||||
|
|
||||||
|
class MessageOutput(BaseModel):
|
||||||
|
body: str = ""
|
||||||
|
events: List[MessageEvent] = []
|
||||||
|
|
||||||
|
|
||||||
|
class Message(BaseModel):
|
||||||
|
input: str
|
||||||
|
output: MessageOutput
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI(title="Minimal FastAPI App", version="1.0.0")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/messages", response_model=Message)
|
||||||
|
async def create_message(input_message: str) -> Message:
|
||||||
|
return Message(
|
||||||
|
input=input_message,
|
||||||
|
output=MessageOutput(body=f"Processed: {input_message}"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_message():
|
||||||
|
response = client.post("/messages", params={"input_message": "Hello"})
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
assert response.json() == {
|
||||||
|
"input": "Hello",
|
||||||
|
"output": {"body": "Processed: Hello", "events": []},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_openapi_schema():
|
||||||
|
response = client.get("/openapi.json")
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
assert response.json() == snapshot(
|
||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {"title": "Minimal FastAPI App", "version": "1.0.0"},
|
||||||
|
"paths": {
|
||||||
|
"/messages": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Create Message",
|
||||||
|
"operationId": "create_message_messages_post",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "input_message",
|
||||||
|
"in": "query",
|
||||||
|
"required": True,
|
||||||
|
"schema": {"type": "string", "title": "Input Message"},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Message"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"HTTPValidationError": {
|
||||||
|
"properties": {
|
||||||
|
"detail": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ValidationError"
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Detail",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"title": "HTTPValidationError",
|
||||||
|
},
|
||||||
|
"Message": {
|
||||||
|
"properties": {
|
||||||
|
"input": {"type": "string", "title": "Input"},
|
||||||
|
"output": {"$ref": "#/components/schemas/MessageOutput"},
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": ["input", "output"],
|
||||||
|
"title": "Message",
|
||||||
|
},
|
||||||
|
"MessageEvent": {
|
||||||
|
"properties": {
|
||||||
|
"event_type": pydantic_snapshot(
|
||||||
|
v2=snapshot(
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/MessageEventType",
|
||||||
|
"default": "alpha",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
v1=snapshot(
|
||||||
|
{
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/MessageEventType"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": "alpha",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"output": {"type": "string", "title": "Output"},
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": ["output"],
|
||||||
|
"title": "MessageEvent",
|
||||||
|
},
|
||||||
|
"MessageEventType": pydantic_snapshot(
|
||||||
|
v2=snapshot(
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["alpha", "beta"],
|
||||||
|
"title": "MessageEventType",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
v1=snapshot(
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["alpha", "beta"],
|
||||||
|
"title": "MessageEventType",
|
||||||
|
"description": "An enumeration.",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"MessageOutput": {
|
||||||
|
"properties": {
|
||||||
|
"body": {"type": "string", "title": "Body", "default": ""},
|
||||||
|
"events": {
|
||||||
|
"items": {"$ref": "#/components/schemas/MessageEvent"},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Events",
|
||||||
|
"default": [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"title": "MessageOutput",
|
||||||
|
},
|
||||||
|
"ValidationError": {
|
||||||
|
"properties": {
|
||||||
|
"loc": {
|
||||||
|
"items": {
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "integer"}]
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Location",
|
||||||
|
},
|
||||||
|
"msg": {"type": "string", "title": "Message"},
|
||||||
|
"type": {"type": "string", "title": "Error Type"},
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": ["loc", "msg", "type"],
|
||||||
|
"title": "ValidationError",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -1028,17 +1028,6 @@ def test_openapi_schema():
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "HTTPValidationError",
|
"title": "HTTPValidationError",
|
||||||
},
|
},
|
||||||
"SubItem-Output": {
|
|
||||||
"properties": {
|
|
||||||
"new_sub_name": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "New Sub Name",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"required": ["new_sub_name"],
|
|
||||||
"title": "SubItem",
|
|
||||||
},
|
|
||||||
"ValidationError": {
|
"ValidationError": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"loc": {
|
"loc": {
|
||||||
|
|
@ -1113,11 +1102,11 @@ def test_openapi_schema():
|
||||||
"title": "New Description",
|
"title": "New Description",
|
||||||
},
|
},
|
||||||
"new_sub": {
|
"new_sub": {
|
||||||
"$ref": "#/components/schemas/SubItem-Output"
|
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
|
||||||
},
|
},
|
||||||
"new_multi": {
|
"new_multi": {
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/SubItem-Output"
|
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
|
||||||
},
|
},
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"title": "New Multi",
|
"title": "New Multi",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue