fastapi icon indicating copy to clipboard operation
fastapi copied to clipboard

♻️ Allow callabe in dependant for get_request_handler

Open Reton2 opened this issue 2 months ago • 1 comments

I am using a callable object to wrap an endpoint function for a custom APIRoute. The callable object calls the endpoint function with the __call__ method. The is_coroutine check fails to determine the __call__ method is a coroutine since it's checking the instance of the object.

This change will allow for the use of decorators on endpoints to feed information to the APIRoute

class EndpointWrapper(Callable[..., Any]):
    def __init__(self, endpoint: Callable[..., Any]):
        self.endpoint = endpoint
        self.protected = False
        update_wrapper(self, endpoint)

    async def __call__(self, *args, **kwargs):
        return await self.endpoint(*args, **kwargs)
    
def dummy_secruity_check(token: HTTPAuthorizationCredentials = Depends(HTTPBearer())):
    if token.credentials != "fake-token":
        raise HTTPException(status_code=401, detail="Unauthorized")

def protect(endpoint: Callable[..., Any]):
    if not isinstance(endpoint, EndpointWrapper):
        endpoint = EndpointWrapper(endpoint)
    endpoint.protected = True
    return endpoint

class CustomAPIRoute(APIRoute):
    def __init__(self, path: str, endpoint: Callable[..., Any], dependencies=None, **kwargs) -> None:
        if dependencies is None:
            dependencies = []
        if (
            isinstance(endpoint, EndpointWrapper)
            and endpoint.protected
        ):
            dependencies.append(Depends(dummy_secruity_check))
        super().__init__(path, endpoint, dependencies=dependencies, **kwargs)

app = FastAPI()

app.router.route_class = CustomAPIRoute

@app.get("/protected")
@protect
async def protected_route():
    return {"message": "This is a protected route"}

Reton2 avatar May 01 '24 09:05 Reton2

mypy doesn't seem to be happy with callable either.

Reton2 avatar May 01 '24 10:05 Reton2