Custom Exception Responses
Find application-specific custom exceptions¶
- If implementing custom exception handlers, responses for your custom
Exception
types can also be documented. They can be included by passing acustomError
argument to thecustom_openapi
function. - The
customError
needs to inherit from the PythonException
class. EachcustomError
and subclass ofcustomError
should be instantiable without required arguments. i.e. if you are parameterising your custom exceptions, they should have defaults in their class constructor. If you use required positional arguments in yourcustomError
constructors,fastapi-docx
will throw an error when attempting to evaluate your custom Exception from source code. - If using custom exception handlers, you can pass your
customError
Base class, along with a PydanticcustomErrSchema
to thecustom_openapi
function when creating your OpenAPI spec:
from typing import Any
from fastapi import Request
from fastapi.utils import is_body_allowed_for_status_code
from pydantic import BaseModel
from starlette.responses import JSONResponse, Response
from fastapi_docx.openapi import custom_openapi
class AppExecptionSchema(BaseModel):
exception: str | None = None
detail: str | None = None
context: dict[str, Any] | None = None
class AppExceptionCase(Exception):
def __init__(
self,
status_code: int,
detail: str,
context: dict[str, Any] | None = None,
headers: dict[str, Any] | None = None,
):
self.case = self.__class__.__name__
self.status_code = status_code
self.headers = headers
self.detail = detail
self.context = context or ""
async def app_exception_handler(
request: Request, exc: AppExceptionCase
) -> JSONResponse:
if not is_body_allowed_for_status_code(exc.status_code):
return Response(status_code=exc.status_code, headers=exc.headers)
return JSONResponse(
status_code=exc.status_code,
headers=exc.headers,
content={
"exception": exc.case,
"detail": exc.detail,
"context": exc.context,
},
)
class AppExc:
class CreateFailed(AppExceptionCase):
def __init__(self, context: dict[str, Any] | None = None):
self.context = context or {}
status_code = 500
obj = self.context.get("obj", "object")
detail = f"{obj} creation failed"
super().__init__(status_code, detail)
class ConnectionClosed(AppExceptionCase):
def __init__(self) -> None:
status_code = 444
detail = "Conection closed without response"
super().__init__(status_code, detail)
class Unauthorized(AppExceptionCase):
def __init__(self, context: dict[str, Any] | None = None):
status_code = 401
detail = "permission required"
super().__init__(status_code, detail, context)
class RetryWith(AppExceptionCase):
def __init__(self, context: dict[str, Any] | None = None):
status_code = 449
detail = "Retry the request"
super().__init__(status_code, detail, context)
app = FastAPI()
@app.get("/")
def get_user(user_in):
if not user_in:
raise AppExc.CreateFailed({"obj": "User"})
elif user_in == "John":
raise RetryWith(context="abc")
elif user_in == "Saran":
raise AppExc.ConnectionClosed()
elif user_in == "Sam":
raise AppExc.Unauthorized({"obj": "User", "name": "Sam"})
return user_in
@app.exception_handler(AppExceptionCase)
async def custom_app_exception_handler(request, e):
return await app_exception_handler(request, e)
app.openapi = custom_openapi(
app,
customError=AppExceptionCase,
customErrSchema=AppExecptionSchema,
)
- Visiting http://localhost:8080/docs (or whatever host and port your app is running on), should display the below documented responses:
Note
For adding different schemas to the default HTTPExcSchema
for HTTPException
responses, see Response Schemas.