Home > database >  FastAPI FileResponse not entering the except block
FastAPI FileResponse not entering the except block

Time:10-19

Good afternoon friends.

I am having issues with FastApi's FileResponse and catching FileNotFoundError (or any, really) exceptions.

Here's my script:

import os
import sys

from fastapi import FastAPI, HTTPException, File, UploadFile
from fastapi.responses import FileResponse, JSONResponse
from fastapi.encoders import jsonable_encoder

@app.get("/download/{namespace}/{file_name}", response_class=FileResponse)
async def processed_file(namespace: str, file_name: str):
    try:
        logger.info("About to return file")
        return FileResponse(
            path=f'{data/{namespace}/{file_name}',
            media_type="application/pdf",
            filename=file_name,
        )
    except:
        logger.info("Should log this, it doesn't")
        raise HTTPException(status_code=404, detail="File not found")

If I call the API endpoint with a path to a non-existing file (which the user can do just by changing one letter in the name of the file, hence the need for this exception),

I can read in the console the following stack trace error:

[2022-10-17 21:00:24  0000] [8] [ERROR] Exception in ASGI application
Traceback (most recent call last):
  File "/opt/venv/lib/python3.9/site-packages/uvicorn/protocols/http/h11_impl.py", line 404, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
  File "/opt/venv/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
    return await self.app(scope, receive, send)
  File "/opt/venv/lib/python3.9/site-packages/fastapi/applications.py", line 199, in __call__
    await super().__call__(scope, receive, send)
  File "/opt/venv/lib/python3.9/site-packages/sentry_sdk/integrations/starlette.py", line 293, in _sentry_patched_asgi_app
    return await middleware(scope, receive, send)
  File "/opt/venv/lib/python3.9/site-packages/sentry_sdk/integrations/asgi.py", line 138, in _run_asgi3
    return await self._run_app(scope, lambda: self.app(scope, receive, send))
  File "/opt/venv/lib/python3.9/site-packages/sentry_sdk/integrations/asgi.py", line 187, in _run_app
    raise exc from None
  File "/opt/venv/lib/python3.9/site-packages/sentry_sdk/integrations/asgi.py", line 182, in _run_app
    return await callback()
  File "/opt/venv/lib/python3.9/site-packages/starlette/applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/opt/venv/lib/python3.9/site-packages/sentry_sdk/integrations/starlette.py", line 98, in _create_span_call
    await old_call(*args, **kwargs)
  File "/opt/venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 181, in __call__
    raise exc from None
  File "/opt/venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/opt/venv/lib/python3.9/site-packages/sentry_sdk/integrations/asgi.py", line 138, in _run_asgi3
    return await self._run_app(scope, lambda: self.app(scope, receive, send))
  File "/opt/venv/lib/python3.9/site-packages/sentry_sdk/integrations/asgi.py", line 148, in _run_app
    raise exc from None
  File "/opt/venv/lib/python3.9/site-packages/sentry_sdk/integrations/asgi.py", line 145, in _run_app
    return await callback()
  File "/opt/venv/lib/python3.9/site-packages/sentry_sdk/integrations/starlette.py", line 191, in _sentry_exceptionmiddleware_call
    await old_call(self, scope, receive, send)
  File "/opt/venv/lib/python3.9/site-packages/sentry_sdk/integrations/starlette.py", line 98, in _create_span_call
    await old_call(*args, **kwargs)
  File "/opt/venv/lib/python3.9/site-packages/starlette/exceptions.py", line 82, in __call__
    raise exc from None
  File "/opt/venv/lib/python3.9/site-packages/starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "/opt/venv/lib/python3.9/site-packages/starlette/routing.py", line 580, in __call__
    await route.handle(scope, receive, send)
  File "/opt/venv/lib/python3.9/site-packages/starlette/routing.py", line 241, in handle
    await self.app(scope, receive, send)
  File "/opt/venv/lib/python3.9/site-packages/starlette/routing.py", line 55, in app
    await response(scope, receive, send)
  File "/opt/venv/lib/python3.9/site-packages/starlette/responses.py", line 286, in __call__
raise RuntimeError(f"File at path {self.path} does not exist.")
RuntimeError: File at path /app/src/../data/new-namespace/wrong.pdf does not exist.

Note: I originally had except FileNotFoundError: instead of the general exception, still nothing works.

The log file correctly shows the "About to return file" entry, it never reaches the exception with the "should log this...". It also, obviously, does not return the 404 HTTPException. The stack trace error never gets to "my own" file, they are exceptions that are all within the site-packages folder.

I am baffled by this as I would have hoped that my solution would catch any exception. Still, the process is not doing what I was expecting. I have a hunch that I'm missing something obvious.

Thanks for any help, best regards, Rafa.

CodePudding user response:

The exception is raised after the return - i.e. the exception doesn't get raised when you create FileResponse - it gets raised when Starlette (the library under FastAPI) tries to read the file and return it.

Since it never gets raised in your code, there is nothing for you to catch.

Instead you could validate that whether the requested file is valid or not yourself - Starlette calls os.stat on the file and checks whether the result is None - you could do that (it's probably a good idea to validate that the filename is among the allowed files instead of blindly trusting information from a user) - or you could wrap the existing FileResponse and handle the exception there (in your own implementation of __call__ that calls into the parent class).

  • Related