1

Question 1: Is this the right way to implement Custom Exceptions?


I have a class with custom exceptions that I want to throw in the exception handler: status_codes.py

from enum import Enum
class StatusCodes(Enum):
    """
    Subset of suitable HTTP status codes that are good fit to describe the scenario of the custom exceptions.
    """

    NO_CONTENT = 204
    BAD_REQUEST = 400
    NOT_AUTHORIZED = 401
    NOT_FOUND = 404
    NOT_ACCEPTABLE = 406
    REQUEST_TIMEOUT = 408
    EXPECTATION_FAILED = 412
    UNPROCESSABLE_ENTITY = 422
    INTERNAL_SERVER_ERROR = 500
    BAD_GATEWAY = 502
    SERVICE_UNAVAILABLE = 503
    GATEWAY_TIMEOUT = 504

So I created a custom exception handler with the status_name as a parameter:

from fastapi import FastAPI, Request
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from status_codes import StatusCodes

app = FastAPI()


class CustomException(Exception):
    def __init__(self, status_name: str):
        self.status_name = status_name
        self.status_code = status_name.value()

    def check_if_exception_exists(self):
        # TODO


@app.exception_handler(CustomException)
async def validation_exception_handler(request: Request, exc: CustomException):
    return JSONResponse(
        status_code=exc.status_code,
        content=jsonable_encoder({exc.status_code: exc.status_name}),
    )


@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
    if name == "yolo":
        raise CustomException(status_name=StatusCodes.NOT_ACCEPTABLE)
    return {"unicorn_name": name}

In the end it should look like this (I hardcoded the response for demonstration purposes): This is how it should look

Question 2: Why is my approach not working?


I don't want to pass two parameters, because status_code.py contains all information already:

Example of: I pass the status_code and the status_name as a parameter:

from fastapi import FastAPI, Request
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from status_codes import StatusCodes

app = FastAPI()


class CustomException(Exception):
    def __init__(self, status_name: str, status_code: int):
        self.status_name = status_name
        self.status_code = status_code


@app.exception_handler(CustomException)
async def validation_exception_handler(request: Request, exc: CustomException):
    return JSONResponse(
        status_code=exc.status_code,
        content=jsonable_encoder({exc.status_code: exc.status_name}),
    )


@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
    if name == "yolo":
        raise CustomException(status_name=StatusCodes.NOT_ACCEPTABLE, status_code=StatusCodes.NOT_ACCEPTABLE.value)
    return {"unicorn_name": name}

This is the output of the code above (Why is the output "status_code" : "status_code" and not "status_code" : "status_name"?) This is how it looks

I'd really happy if someone can assist me, especially whether I am doing the right thing here.

daniel guo
  • 45
  • 1
  • 7
  • Please have a look at related answers [here](https://stackoverflow.com/a/72833284/17865804) and [here](https://stackoverflow.com/a/71682274/17865804), as well as [here](https://stackoverflow.com/a/71800464/17865804) and [here](https://stackoverflow.com/a/70954531/17865804) – Chris Jun 22 '23 at 03:08

2 Answers2

1

I am not sure what do you want to achieve, But I recently implemented custom exception.

How?

  1. Create a base class BaseHTTPException. Now this class is your base class

     from starlette.exceptions import HTTPException
    
     class BaseHttpException(HTTPException):
         status_code: int = None
         detail: str = None
         headers: Dict[str, Any] = None
    
         def __init__(self):
             super().__init__(status_code=self.status_code, detail=self.detail, headers=self.headers)
    
  2. Now create a class CustomException. This is your actual custom exception

     class CustomException(BaseHttpException):
         status_code = 400
         detail = 'Invalid Input'
    
  3. Now you can use this exception class to handle exception

     @app.get("/unicorns/{name}")
     async def read_unicorn(name: str):
         if name == "yolo":
             raise CustomException(status_name=StatusCodes.NOT_ACCEPTABLE)
         return {"unicorn_name": name}
    
  4. You don't have to add

     @app.exception_handler(CustomException)
     async def validation_exception_handler(request: Request, exc: CustomException):
         return JSONResponse(
             status_code=exc.status_code,
             content=jsonable_encoder({exc.status_code: exc.status_name}),
         )
    

You can extend those classes and implement even more complex things.

I hope it will help. If you need additional info Let me know.

JRudransh
  • 77
  • 13
0

I see two problems. First in your Enum definition, you did not specify the type. Notice the int in class definition.

class StatusCodes(int, Enum):
    """
    Subset of suitable HTTP status codes that are good fit to describe the scenario of the custom exceptions.
    """

    NO_CONTENT = 204
    BAD_REQUEST = 400
    NOT_AUTHORIZED = 401
    NOT_FOUND = 404
    NOT_ACCEPTABLE = 406
    REQUEST_TIMEOUT = 408
    EXPECTATION_FAILED = 412
    UNPROCESSABLE_ENTITY = 422
    INTERNAL_SERVER_ERROR = 500
    BAD_GATEWAY = 502
    SERVICE_UNAVAILABLE = 503
    GATEWAY_TIMEOUT = 504

The second issue is when you invoke or call the Enum name. You need to do it this way.

@app.get("/unicorns/{name}")
async def read_unicorn(name: str):
    if name == "yolo":
        raise CustomException(status_name=StatusCodes.NOT_ACCEPTABLE.name, status_code=StatusCodes.NOT_ACCEPTABLE)
    return {"unicorn_name": name}
Irfanuddin
  • 2,295
  • 1
  • 15
  • 29