Fastapi: [BUG] Overwriting HTTPException and RequestValidationError does not work via FastAPI exceptions handlers init

Created on 20 May 2020  路  13Comments  路  Source: tiangolo/fastapi

When initilizing FastAPI, an exception_handlers dictionary can be passed, mapping Exceptions/Error Codes to callables. When using this method to set the exception handlers, the HTTPException and RequestValidationError can not be overwritten.

Code to Reproducte


import logging

import uvicorn
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from fastapi.exception_handlers import request_validation_exception_handler


logger = logging.getLogger(__name__)

# app = FastAPI()
# @app.exception_handler(RequestValidationError)
async def my_request_validation_exception_handler(
    req: Request, exc: RequestValidationError
):
    logger.info("my_request_validation_exception_handler")
    return await request_validation_exception_handler(req, exc)

app = FastAPI(exception_handlers={RequestValidationError: my_request_validation_exception_handler})

@app.get("/test")
def test(param: int):

    return JSONResponse(
        status_code=200,
        content={"message": "successful test with param {}".format(param)},
    )


if __name__ == "__main__":
    uvicorn.run("api:app", host="localhost", port=2000, reload=True)

Now a curl "http://localhost:2000/test" will not show the logger.info("my_request_validation_exception_handler") log statement. However, when using the commented out code it works fine. I want to use the other way to keep the handlers in a separate file.

Environment

  • OS: Windows/WSL
  • FastAPI Version: 0.54.2
  • Python version: 3.6.9

Origin

As far as i can see this is coming from the setup method

self.add_exception_handler(HTTPException, http_exception_handler)
self.add_exception_handler(
    RequestValidationError, request_validation_exception_handler
)

Starlette will overwrite the custom handlers in the add_exception_handler method

def add_exception_handler(
        self,
        exc_class_or_status_code: typing.Union[int, typing.Type[Exception]],
        handler: typing.Callable,
    ) -> None:
        self.exception_handlers[exc_class_or_status_code] = handler
        self.middleware_stack = self.build_middleware_stack()

Would be great if setup() could first check whether a handler for this exception exists already.

answered bug

Most helpful comment

Thanks for all the discussion here everyone! :coffee:

@uriyyo fixed this in https://github.com/tiangolo/fastapi/pull/1924, available in FastAPI version 0.61.2. :tada:

All 13 comments

It looks like FastAPI 0.60.1 keeps acting the same (wrong?) way,

It works here... But it didn't show anything with the logger above (I've stolen the uvicorn), I've created a minimal example:

import logging

from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse

logger = logging.getLogger("uvicorn")

class CustomException(HTTPException):
    pass

async def custom_exception_handler(
    req: Request, exc: RequestValidationError
):
    logger.info("IT WORKS!!!")
    return JSONResponse(status_code=exc.status_code)

app = FastAPI(exception_handlers={CustomException: custom_exception_handler})

@app.get("/test")
def test(param: int):
    raise CustomException(status_code=400)

Edit: about the logger, it needs a handler, so it's normal that it didn't work...

from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from fastapi.exception_handlers import request_validation_exception_handler
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException


async def validation_exception_handler(request, exception):
    print("validation_exception_handler WORKS")
    return await request_validation_exception_handler(request, exception)


async def http_exception_handler(request, exception):
    print("http_exception_handler WORKS")
    return JSONResponse(status_code=exception.status_code)


async def starlette_http_exception_handler(request, exception):
    print("starlette_http_exception_handler WORKS")
    return JSONResponse(status_code=exception.status_code)


app = FastAPI(exception_handlers={
    StarletteHTTPException: starlette_http_exception_handler,
    HTTPException: http_exception_handler,
    RequestValidationError: validation_exception_handler
})


@app.get("/test")
def test(param: int):
    raise HTTPException(status_code=400, detail="OOPS")

@Kludex It looks like only HTTPException call the handler..

Ah, I see what you mean, confirmed here as well. Anyone wants to fix it? :tada:

What is the reason for not defining these handlers on instantiation?

Hi @Kludex

I fix this issue in #1924 PR.

Looks like there are two issues related to the same bug.
I looked through opened PR but didn't find #1887 PR.

Basically #1924 and #1887 a little bit different, what I should do with my PR?

Well, in the past when @tiangolo saw one first, he basically merged that one (if it was good) and the next was discharged. So I recommend you to mention #1887 on your solution and explain that you've implemented it without knowing that it was already implemented. Then you just wait. :sunglasses: :+1:

Any tips on where this will be solved (merged in this case)?

Same issue with me, any update if this is solved/merged?

@michaeltoohig @kozhushman You can inherit from FastAPI and override setup method.

If update original code provided @timbmg it will look like this:

import logging
import uvicorn

from fastapi import FastAPI as BaseFastAPI, Request
from fastapi.exception_handlers import request_validation_exception_handler, http_exception_handler
from fastapi.exceptions import RequestValidationError, HTTPException
from fastapi.responses import JSONResponse


class FastAPI(BaseFastAPI):
    def setup(self) -> None:
        _original_add_exception_handler = self.add_exception_handler

        def _add_exception_handler(*_):
            pass

        self.add_exception_handler = _add_exception_handler
        super().setup()

        if HTTPException not in self.exception_handlers:
            self.add_exception_handler(HTTPException, http_exception_handler)
        if RequestValidationError not in self.exception_handlers:
            self.add_exception_handler(RequestValidationError, request_validation_exception_handler)


logger = logging.getLogger(__name__)


async def my_request_validation_exception_handler(req: Request, exc: RequestValidationError):
    logger.info("my_request_validation_exception_handler")
    return await request_validation_exception_handler(req, exc)


app = FastAPI(exception_handlers={RequestValidationError: my_request_validation_exception_handler})


@app.get("/test")
def test(param: int):
    return JSONResponse(
        status_code=200,
        content={"message": "successful test with param {}".format(param)},
    )


if __name__ == "__main__":
    uvicorn.run("api:app", host="localhost", port=2000, reload=True)

Thanks for all the discussion here everyone! :coffee:

@uriyyo fixed this in https://github.com/tiangolo/fastapi/pull/1924, available in FastAPI version 0.61.2. :tada:

Was this page helpful?
0 / 5 - 0 ratings