dmontagu / fastapi-utils

Reusable utilities for FastAPI
MIT License
1.89k stars 165 forks source link

[QUESTION] There's a way to add a custom decorator to a class-based view? #261

Open JesusFragoso opened 1 year ago

JesusFragoso commented 1 year ago

I'm trying to do something like the following:

@cbv(users_router)
@ResponseHandler.class_decorator
class Users:

    controller = UserController()

    @users_router.post("/users", status_code=201)
    async def create_user(self, user_data: Dict, response: Response) -> Dict:
        return self.controller.create_user(**user_data)

    @users_router.get("/users/{user_id}", status_code=302)
    async def get_user(self, user_id: int, response: Response) -> Dict:
        return self.controller.obtain_user(user_id)

    @users_router.get("/users", status_code=302)
    async def get_all_users(self, response: Response) -> Dict:
        return self.controller.obtain_all_users()

The class_decorator decorator adds custom response for each one of the requests, but when I try to execute one of the services, then this error appears:

{"detail":[{"loc":["query","self"],"msg":"field required","type":"value_error.missing"}]}
JesusFragoso commented 1 year ago

The code for the decorator is the following:

class ResponseHandler:
    @staticmethod
    def custom_response(main_function: Callable) -> Callable:
        @wraps(main_function)
        async def handler_function(*args: Tuple, **kwargs: Dict) -> Dict:
            try:
                result, code = await main_function(*args, **kwargs)
            except tuple(SERVICE_EXCEPTIONS) as exception:
                ResponseHandler._modify_status_code(kwargs["response"], exception.code.value)

                return {
                    "exception_message": exception.message,
                    "code": exception.code,
                }
            except IntegrityError as exception:
                ResponseHandler._modify_status_code(kwargs["response"], 400)

                return {
                    "exception_message": exception._message(),
                    "code": 400,
                }

            return {"data": result, "status_code": code}

        return handler_function

    @staticmethod
    def _modify_status_code(response: Response, status_code: int) -> NoReturn:
        response.status_code = status_code

    @staticmethod
    def class_decorator(my_class):
        def wrapper(*args, **kwargs):
            members = list(filter(lambda member: "__" not in member[0], inspect.getmembers(my_class)))

            for name, method in members:
                setattr(my_class, name, ResponseHandler.custom_response(method))

            return my_class()

        return wrapper