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).