Skip to content

Routing

Once you have created your response schemas you can define the SchemaAPIRoute that will be used for your API set.

A SchemaAPIRoute just inherits from fastapi.routing.APIRoute.

from fastapi_responseschema import SchemaAPIRoute
from .myschemas import StandardResponseSchema  # The response schema you defined

class StandardAPIRoute(SchemaAPIRoute):
    response_schema = StandardResponseSchema  # This attribute is required

If you want to handle different schemas for success and error responses you can set the error response schema.

from fastapi_responseschema import SchemaAPIRoute
from .myschemas import OKResponseSchema, KOResponseSchema   # The response schemas you defined

class StandardAPIRoute(SchemaAPIRoute):
    response_schema = OKResponseSchema  # This attribute is required
    error_response_schema = KOResponseSchema  # If not set defaults to `SchemaAPIRoute.response_schema`

Integrating in your API

You can set the defined SchemaAPIRoute in you FastAPI application.

from fastapi import FastAPI
from .myroutes import StandardAPIRoute  # the SchemaAPIRoute you defined

app = FastAPI()

app.router.route_class = StandardAPIRoute

@app.get("/")
def just_a_route():
    return {"message": "It Works!"}

You can even integrate the schema api route in APIRouter.

from pydantic import BaseModel
from fastapi import APIRouter
from .myroutes import StandardAPIRoute  # the SchemaAPIRoute you defined

router = APIRouter(route_class=StandardAPIRoute)

class ParrotMessage(BaseModel):
    message: str


@router.post("/parrot")
def repeat(body: ParrotMessage):
    return {"parrot_says": body.message}

Handling errors

You can wrap all error responses with the wrap_error_responses helper.

from fastapi import FastAPI
from fastapi_responseschema import wrap_error_responses
from .myroutes import StandardAPIRoute
from .myschemas import KOResponseSchema

app = FastAPI()

app.router.route_class = StandardAPIRoute
wrap_error_responses(app, error_response_schema=KOResponseSchema)

@app.get("/")
def just_a_route():
    return {"message": "It Works!"}

Handling errors and override application APIRoute

The same functionality as:

...

app.router.route_class = StandardAPIRoute
wrap_error_responses(app, error_response_schema=KOResponseSchema)

...

Can be achieved with wrap_app_responses:

from fastapi import FastAPI
from fastapi_responseschema import wrap_app_responses
from .myroutes import StandardAPIRoute  # the SchemaAPIRoute you defined

app = FastAPI()

wrap_app_responses(app, route_class=StandardAPIRoute)

@app.get("/")
def just_a_route():
    return {"message": "It Works!"}

You still need to configure the route class for every fastapi.APIRouter.

About response_model_exclude, response_model_include and others response_model_* parametrs

When using response fields modifiers on-the-fly. you must consider that the final output of response_model will be wrapped by the configured ResponseSchema.

For this snippet:

from typing import TypeVar, Generic
from pydantic import BaseModel
from fastapi import APIRouter
from fastapi_responseschema import AbstractResponseSchema, SchemaAPIRoute

T = TypeVar("T")

class ResponseSchema(AbstractResponseSchema[T], Generic[T]):
    data: T
    error: bool
    message: Optional[str]

    ... # constructors etc.

class Item(BaseModel):
    id: int
    name: str
    additional_desc: Optional[str]

class MainAPIRoute(SchemaAPIRoute):
    response_schema = ResponseSchema

router = APIRouter(route_class=MainAPIRoute)

@router.get("/item", response_model=Item)
def show_item():
    return {"id": 11, "name": "Just a Teapot!"}

The resulting response payload of GET /items will be:

{
    "data": {
        "id": 11, 
        "name": "Just a Teapot!",
        "additional_desc": null
    },
    "error": false,
    "message": null
}

When applying the response_model_exclude and additional_model_include for the response_model remeber to consider the nested output.

For Example:

...

@router.get("/item", response_model=Item, response_model_exclude={"data": {"name"}})  # Exclusion of nested fields
def show_item():
    return {"id": 11, "name": "Just a Teapot!"}
Returns:

{
    "data": {
        "id": 11, 
        "additional_desc": null
    },
    "error": false,
    "message": null
}

When you use response_model_exclude_none and similar parameters the configuration will be applyed to all the response schema.

For example:

...

@router.get("/item", response_model=Item, response_model_exclude_none=True)  # Exclusion of nested fields
def show_item():
    return {"id": 11, "name": "Just a Teapot!"}
Returns:

{
    "data": {
        "id": 11, 
        "name": "Just a Teapot!"
    },
    "error": false
}

To modify the response content you should prefer the definition of dedicated models.