fastapi: Handling Exception Response format

Hello. I want to change the validation error response and make it inside app object.

I’ve found this example:

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
    )

But can’t understand how to add this function to app object without decorator:

app = FastAPI(
    title='Bla API',
    description='Bla description',
    APIRoute('/api', api.toggle, methods=['POST'],
             description='Switch interface state',
             response_description='Interface successfully switched',
             response_class=JSONResponse,
             response_model=api.Success,
             responses={**api.post_responses},
             ),
...

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 20 (6 by maintainers)

Most upvoted comments

@MacMacky Ok. Thanks again for your help.

may be you can try this:

"""For managing validation error for complete application."""
from typing import Union
from fastapi.exceptions import RequestValidationError
from fastapi.openapi.constants import REF_PREFIX
from fastapi.openapi.utils import validation_error_response_definition
from pydantic import ValidationError
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY


async def http422_error_handler(
    _: Request, exc: Union[RequestValidationError, ValidationError],
) -> JSONResponse:
    """To handle unprocessable request(HTTP_422) and log accordingly to file.

    Args:
        _ (Request): Request object containing metadata about Request
        exc (Union[RequestValidationError, ValidationError]): to handle RequestValidationError,
            ValidationError

    Returns:
        JSONResponse: JSON response with required status code.
    """
    return JSONResponse(
        {"error": exc.errors()}, status_code=HTTP_422_UNPROCESSABLE_ENTITY,
    )


validation_error_response_definition["properties"] = {
    "errors": {
        "title": "Errors",
        "type": "array",
        "items": {"$ref": "{0}ValidationError".format(REF_PREFIX)},
    },
}

And in main.py

app.add_exception_handler(RequestValidationError, http422_error_handler)

If I understood the idea correctly this is how I should have done it:

GUI part:

<my imports> 

class FormSchema(typesystem.Schema):
    <my schema>

def render(request: Request, form=forms.Form(FormSchema), vl_status: Optional[str] = None) -> _TemplateResponse:
    context = {'request': request, 'form': form, 'vl_status': vl_status}
    return templates.TemplateResponse('index.html', context)


async def home(request: Request) -> _TemplateResponse:
    if request.method == "POST":
        <my gui app login>

    return render(request)


vlantoggler_web_app = Starlette(
    routes=[
        Route('/vlantoggler', home, methods=['GET', 'POST']),
        Mount('/statics', statics, name='static'),
    ],
)

Api part:

<my imports> 


class Status(str, Enum):
    ok = 'ok'
    error = 'error'


class State(str, Enum):
    prod = 'prod'
    setup = 'setup'
    unknown = 'unknown'
    l3 = 'l3'


<my other response classes>


get_responses = {
    400: {
        'description': 'ToR received invalid RPC',
        'model': Error
    },
    ...
}
post_responses = {
    403: {
        'description': 'Not allowed to switch interface',
        'model': Error
    },
    **get_responses
}


class GetDeviceData(BaseModel):
    tor: str
    interface: str


class PostDeviceData(BaseModel):
    tor: str
    interface: str
    state: DesiredState


async def check(data: GetDeviceData):
    <some logic>
        return JSONResponse(status_code=result['code'], content=result)


async def toggle(data: PostDeviceData):
    <some logic>
        return JSONResponse(status_code=result['code'], content=result)


vlantoggler_api_app = FastAPI(
    title='',
    description='',
    openapi_prefix='/myapp/api',
    routes=[
        APIRoute('/', check, methods=['GET'], tags=['VlanToggler'],
                 name='Get current interface state',
                 # summary='String replaces function name AND name on Swagger API page',
                 description='ToR and Interface names are validated and current interface state is returned',
                 response_description='Successfully got interface state',
                 response_class=JSONResponse,
                 response_model=Success,
                 responses={**get_responses}
                 ),
        APIRoute('/', toggle, methods=['POST'], tags=['VlanToggler'],
                 name='Switch interface state',
                 description='ToR and Interface names are validated and ToR interface is toggled to desired state',
                 response_description='Interface successfully switched',
                 response_class=JSONResponse,
                 response_model=Success,
                 responses={**post_responses},
                 ),
    ],
)


@vlantoggler_api_app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content={
            'code': 422,
            'status': 'error',
            'message': '; '.join([f"{e['loc'][2]}: {e['msg']}" for e in exc.errors()])
        }
    )

And my start point

import uvicorn

from api import vlantoggler_api_app
from views import vlantoggler_web_app

app = vlantoggler_web_app
app.mount('/vlantoggler/api', vlantoggler_api_app)

if __name__ == "__main__":
    uvicorn.run(app, loop='uvloop', log_level='debug')

It works perfectly! =)

But I still have a question regarding validator: I rewrote exception_handler:

async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content={
            'code': 422,
            'status': 'error',
            'message': '; '.join([f"{e['loc'][2]}: {e['msg']}" for e in exc.errors()])
        }
    )

and I have validation handling format I wanted:

{"code":422,"status":"error","message":"state: value is not a valid enumeration member; permitted: 'prod', 'setup'"}

But on Swagger page 422 status still has default schema: image

How can I change it?

Why is the use of decorators problematic? Defininig pretty much anything inside the FastAPI constructor like that is certainly an uncommon way to do things and much of the discussion in #687 was about how that approach would be likely to be less ergonomic for routes when taking FastAPI’s goals into account (like how Path parameters would end up split between the route declaration and the function signature).

It’s not currently possible to do that with exception_hadler with FastAPI right now (upstream Starlette versions support that, but it causes compatibility issues with FastAPI, see #683), but why do you want to do that with the exception handler?