Issue 1:
Why doesn´t FastAPI use python hints for returning types as well?

This code is more intuitive

@app.post("/items/")
async def create_item(item: Item) -> Item:
return item

than this

@app.post("/items/", response_model=Item)
async def create_item(item: Item):
return item

Issue 2:
First Check

I added a very descriptive title to this issue.

I used the GitHub search to find a similar issue and didn't find it.

I searched the FastAPI documentation, with the integrated search.

I already searched in Google "How to X in FastAPI" and didn't find any information.

I already read and followed all the tutorial in the docs and didn't find an answer.

I already checked if it is not related to FastAPI but to Pydantic.

I already checked if it is not related to FastAPI but to Swagger UI.

I already checked if it is not related to FastAPI but to ReDoc.

Commit to Help

I commit to help with one of those options 👆

Example Code

# see "Wanted Code" section

Description

Media type versioning is a recommended practice for versioning a RESTful API across the industry: Zalando recommends it, there are various articles that endorse it. And overall it seems like a very good and flexible solution for versioning APIs:

It allows versioning individual API endpoints when you need it.

Even better, you can version input and output schemas independently.

It allows the rollout of backward-incompatible changes in a backward compatible manner without bending the rules of RESTful API semantics, i.e. without the need to invent a new "object type" to put a new version of the endpoint on a separate URL.

It can be fully described with an OpenAPI schema, e.g. attached openapi.json.zip.

These articles I linked above further explain what I think "Media type versioning" means.

Other good descriptions about what I mean by "media type versioning" can be found here:

The Ultimate Solution to Versioning REST APIs: Content Negotiation

API Design: Media Type Versioning

Some of these pages describe only versioning the request or response object, but ideally both should be supported.

Wanted Solution

Ability to specify multiple request and response models tied to their respecful content types. (See Wanted Code.)

Correct generation of the OpenAPI schema based on that. (An example OpenAPI schema is attached to this issue.)

Parsing of a correct request model based on the request Content-Type.

A more streamlined access to the "Accept" header than inspecting Request.headers

Automatic setting of the response "Content-Type" header based on the model returned by path operation function.

The same validation of the returned model as what happens right now for unversioned endpoints.

With a little bit of your direction, I would be thrilled to make a contribution or create a supporting package for this behavior.

If this can be a supporting package, I need a little bit of guidance about how to intercept current flow of schema generation, request parsing and response validation from external code. I can use the code from InferringRouter in fastapi-utils to learn some of this, but any other high-level pointers would be greatly appreciated.

If you'd prefer contribution, then I would start from an overall discussion on how you see it done, with couple of references to source code.

Wanted Code

"""
Example implementation of the API versioning using media types.
"""

from fastapi import APIRouter, Body, HTTPException, Request
from pydantic import BaseModel, Field

class InputModelV1(BaseModel):
"First version of input data"
name: str = Field(description="Name")
count: int = Field(description="Count")

class InputModelV2(BaseModel):
"Second version of input data. Name is split, amount is a float now."
first_name: str = Field(description="First name")
last_name: str = Field(description="First name")
amount: float = Field(description="Total amount")

class OutputModelV1(BaseModel):
"First version of output data"
id: int | None = Field(description="Id")
owner: int | None = Field(description="Owner Id")

class OutputModelV2(BaseModel):
"Secnod version of output data. Ids moved to strings, owner name supplied as well."
id: str | None = Field(description="Id")
owner_id: str | None = Field(description="Owner Id")
owner_name: str | None = Field(description="Owner Name")

router = APIRouter()

@router.post(
"/test",
responses={
200: {
"content": {
"application/json;my_output_model_v=1": {"model": OutputModelV1},
"application/json;my_output_model_v=2": {"model": OutputModelV2},
},
"description": "Return versioned JSON based on either an Accept header or"
"the version of the input model",
}
},
)
def versioned_endpoint_v1(
req: Request,
v1: InputModelV1
| None = Body(None, media_type="application/json;my_input_model_v=1"),
v2: InputModelV2
| None = Body(None, media_type="application/json;my_input_model_v=2"),
) -> OutputModelV1 | OutputModelV2 | None:
"Example endpoint versioned with media types"

# this if block would ideally happen under the hood
if v1:
result = do_op_v1(v1)
elif v2:
result = do_op_v2(v2)
else:
raise HTTPException(status_code=415, detail="Unknown input version")

# this check would ideally be more abstracted
if req.headers.get("accept") == "application/json;my_output_model_v=1":
return to_v1(result)
elif req.headers.get("accept") == "application/json;my_output_model_v=2":
return to_v2(result)
else:
# this wouldn't work like this, the output type should be checked before
# running the operation
raise HTTPException(status_code=406, detail="Unsupported output version")

Alternatives

Use URL slugs and different path operation functions for versioning.

Other alternatives like versioning based on query parameter or a custom header are not compatible with OpenAPI and would have the same issues with multiple models for request/response body.

Operating System

Windows

Operating System Details

This feature is OS-agnostic.

FastAPI Version

0.75.0

Python Version

3.10.2

Additional Context

Issue #3910 describes the "Response" part of what I describe in this issue.

Here's a screenshot of what the Swagger UI looks like for the example openapi.json.zip file I provided: