I recently implemented an endpoint to create a some CSV data to send to my react-native application to download. The implementation:
app = FastAPI(openapi_tags=tags_metadata)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
...
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
...various endpoints:
@app.get("/download_questionnaire")
async def download_questionnaire(response: Response, token: Annotated[str, Depends(oauth2_scheme)], db: Session = Depends(get_db)):
try:
current_user = await get_current_user(db, token)
logger.info(f"Current user: {current_user}")
results = get_questionnaire_result_by_user(db=db, user_email=current_user.email)
stream = StringIO()
writer = csv.DictWriter(stream, fieldnames=["questionnaire_id", "gender", "vo2max", "bmi", "fmi", "tv_hours", "score", "classification", "timestamp"])
writer.writeheader()
for result in results:
writer.writerow({"questionnaire_id": result.questionnaire_id,
"gender": result.gender,
"vo2max": result.vo2max,
"bmi": result.bmi if result.bmi is not None else "",
"fmi": result.fmi if result.fmi is not None else "",
"tv_hours": result.tv_hours if result.tv_hours is not None else "",
"score": result.score,
"classification": result.classification,
"timestamp": result.timestamp})
response.headers["Content-Disposition"] = "attachment; filename=questionnaire_results.csv"
response.headers["Content-Type"] = "text/csv"
response.body = stream.getvalue().encode("utf-8")
logger.info(f"Body: {response.body}")
return response
except Exception as e:
logger.exception(f"An error occurred while processing the download_questionnaire request: {e} ")
raise HTTPException(status_code=500, detail="Internal server error occurred while processing the request")
functions get_current_user and get_questionnaire_result_by_user from other files function as expected (returning the correct current user and their results), and the response body when printed is exactly what I want it to be:
INFO:api.main:Body: b'questionnaire_id,gender,vo2max,bmi,fmi,tv_hours,score,classification,timestamp\r\ntest0902,female,36.0,0.0,11.0,2.0,10,low,2024-02-09 19:15:33.168030\r\ntest22,female,36.0,,11.0,2.0,10,very low,2024-02-12 14:41:34.043038\r\ntest23,female,36.0,,11.0,2.0,10,very low,2024-02-12 14:42:21.515154\r\ntest24,female,36.0,,11.0,2.0,10,very low,2024-02-12 14:45:15.474071\r\ntest52,male,35.0,24.0,,,0,very low,2024-02-12 14:47:42.738056\r\ntesttest,female,36.0,,11.0,2.0,10,very low,2024-02-16 11:29:45.979333\r\n'
The frontend function that waits for this data to download them:
export const downloadResults = async () => {
const jwtToken = await AsyncStorage.getItem('jwtToken');
try {
const response = await fetch(`${API_BASE_URL}/download_questionnaire`, {
method: 'GET',
headers: { 'Content-Type': 'text/csv', 'Authorization': `Bearer ${jwtToken}` },
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const csvData = await response.text();
const path = `${RNFetchBlob.fs.dirs.DownloadDir}/questionnaire_results.csv`
await RNFetchBlob.fs.writeFile(path, csvData);
Alert.alert('File downloaded successfully')
} catch (error) {
console.error('Error downloading file:', error);
Alert.alert('Error downloading file');
}
}
Gives TypeError: Network request failed in previous error handling implementations it gave:
Error downloading file: [Error: Download manager failed to download from http://10.0.2.2:8000/download_questionnaire. Status Code = 16]
The error I am getting in my docker container for the API is:
helena-ir-backend-1 | INFO:api.main:Current user: email='[email protected]' full_name='Test User' disabled=False
helena-ir-backend-1 | INFO:api.main:Body: b'questionnaire_id,gender,vo2max,bmi,fmi,tv_hours,score,classification,timestamp\r\ntest0902,female,36.0,0.0,11.0,2.0,10,low,2024-02-09 19:15:33.168030\r\ntest22,female,36.0,,11.0,2.0,10,very low,2024-02-12 14:41:34.043038\r\ntest23,female,36.0,,11.0,2.0,10,very low,2024-02-12 14:42:21.515154\r\ntest24,female,36.0,,11.0,2.0,10,very low,2024-02-12 14:45:15.474071\r\ntest52,male,35.0,24.0,,,0,very low,2024-02-12 14:47:42.738056\r\ntesttest,female,36.0,,11.0,2.0,10,very low,2024-02-16 11:29:45.979333\r\n'
helena-ir-backend-1 | --- Logging error ---
helena-ir-backend-1 | Traceback (most recent call last):
helena-ir-backend-1 | File "/usr/local/lib/python3.12/logging/__init__.py", line 1160, in emit
helena-ir-backend-1 | msg = self.format(record)
helena-ir-backend-1 | ^^^^^^^^^^^^^^^^^^^
helena-ir-backend-1 | File "/usr/local/lib/python3.12/logging/__init__.py", line 999, in format
helena-ir-backend-1 | return fmt.format(record)
helena-ir-backend-1 | ^^^^^^^^^^^^^^^^^^
helena-ir-backend-1 | File "/usr/local/lib/python3.12/logging/__init__.py", line 703, in format
helena-ir-backend-1 | record.message = record.getMessage()
helena-ir-backend-1 | ^^^^^^^^^^^^^^^^^^^
helena-ir-backend-1 | File "/usr/local/lib/python3.12/logging/__init__.py", line 392, in getMessage
helena-ir-backend-1 | msg = msg % self.args
helena-ir-backend-1 | ~~~~^~~~~~~~~~~
helena-ir-backend-1 | TypeError: %d format: a real number is required, not NoneType
helena-ir-backend-1 | Call stack:
helena-ir-backend-1 | File "/usr/local/bin/uvicorn", line 8, in <module>
helena-ir-backend-1 | sys.exit(main())
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/click/core.py", line 1157, in __call__
helena-ir-backend-1 | return self.main(*args, **kwargs)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/click/core.py", line 1078, in main
helena-ir-backend-1 | rv = self.invoke(ctx)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/click/core.py", line 1434, in invoke
helena-ir-backend-1 | return ctx.invoke(self.callback, **ctx.params)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/click/core.py", line 783, in invoke
helena-ir-backend-1 | return __callback(*args, **kwargs)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/uvicorn/main.py", line 416, in main
helena-ir-backend-1 | run(
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/uvicorn/main.py", line 587, in run
helena-ir-backend-1 | server.run()
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/uvicorn/server.py", line 61, in run
helena-ir-backend-1 | return asyncio.run(self.serve(sockets=sockets))
helena-ir-backend-1 | File "/usr/local/lib/python3.12/asyncio/runners.py", line 194, in run
helena-ir-backend-1 | return runner.run(main)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/asyncio/runners.py", line 118, in run
helena-ir-backend-1 | return self._loop.run_until_complete(task)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/asyncio/base_events.py", line 672, in run_until_complete
helena-ir-backend-1 | self.run_forever()
helena-ir-backend-1 | File "/usr/local/lib/python3.12/asyncio/base_events.py", line 639, in run_forever
helena-ir-backend-1 | self._run_once()
helena-ir-backend-1 | File "/usr/local/lib/python3.12/asyncio/base_events.py", line 1985, in _run_once
helena-ir-backend-1 | handle._run()
helena-ir-backend-1 | File "/usr/local/lib/python3.12/asyncio/events.py", line 88, in _run
helena-ir-backend-1 | self._context.run(self._callback, *self._args)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py", line 408, in run_asgi
helena-ir-backend-1 | result = await app( # type: ignore[func-returns-value]
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__
helena-ir-backend-1 | return await self.app(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/fastapi/applications.py", line 1115, in __call__
helena-ir-backend-1 | await super().__call__(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/applications.py", line 122, in __call__
helena-ir-backend-1 | await self.middleware_stack(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 162, in __call__
helena-ir-backend-1 | await self.app(scope, receive, _send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/middleware/cors.py", line 83, in __call__
helena-ir-backend-1 | await self.app(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
helena-ir-backend-1 | await self.app(scope, receive, sender)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/fastapi/middleware/asyncexitstack.py", line 17, in __call__
helena-ir-backend-1 | await self.app(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/routing.py", line 718, in __call__
helena-ir-backend-1 | await route.handle(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/routing.py", line 276, in handle
helena-ir-backend-1 | await self.app(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/routing.py", line 69, in app
helena-ir-backend-1 | await response(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/responses.py", line 164, in __call__
helena-ir-backend-1 | await send(
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 65, in sender
helena-ir-backend-1 | await send(message)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 159, in _send
helena-ir-backend-1 | await send(message)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py", line 478, in send
helena-ir-backend-1 | self.access_logger.info(
helena-ir-backend-1 | Message: '%s - "%s %s HTTP/%s" %d'
helena-ir-backend-1 | Arguments: ('172.20.0.1:57012', 'GET', '/download_questionnaire', '1.1', None)
helena-ir-backend-1 | ERROR: Exception in ASGI application
helena-ir-backend-1 | Traceback (most recent call last):
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py", line 408, in run_asgi
helena-ir-backend-1 | result = await app( # type: ignore[func-returns-value]
helena-ir-backend-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__
helena-ir-backend-1 | return await self.app(scope, receive, send)
helena-ir-backend-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/fastapi/applications.py", line 1115, in __call__
helena-ir-backend-1 | await super().__call__(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/applications.py", line 122, in __call__
helena-ir-backend-1 | await self.middleware_stack(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 184, in __call__
helena-ir-backend-1 | raise exc
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 162, in __call__
helena-ir-backend-1 | await self.app(scope, receive, _send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/middleware/cors.py", line 83, in __call__
helena-ir-backend-1 | await self.app(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
helena-ir-backend-1 | raise exc
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
helena-ir-backend-1 | await self.app(scope, receive, sender)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/fastapi/middleware/asyncexitstack.py", line 20, in __call__
helena-ir-backend-1 | raise e
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/fastapi/middleware/asyncexitstack.py", line 17, in __call__
helena-ir-backend-1 | await self.app(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/routing.py", line 718, in __call__
helena-ir-backend-1 | await route.handle(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/routing.py", line 276, in handle
helena-ir-backend-1 | await self.app(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/routing.py", line 69, in app
helena-ir-backend-1 | await response(scope, receive, send)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/responses.py", line 164, in __call__
helena-ir-backend-1 | await send(
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 65, in sender
helena-ir-backend-1 | await send(message)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 159, in _send
helena-ir-backend-1 | await send(message)
helena-ir-backend-1 | File "/usr/local/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py", line 488, in send
helena-ir-backend-1 | reason = STATUS_PHRASES[status]
helena-ir-backend-1 | ~~~~~~~~~~~~~~^^^^^^^^
I think the most crucial line is:
helena-ir-backend-1 | self.access_logger.info(
helena-ir-backend-1 | Message: '%s - "%s %s HTTP/%s" %d'
helena-ir-backend-1 | Arguments: ('172.20.0.1:57012', 'GET', '/download_questionnaire', '1.1', None)
helena-ir-backend-1 | ERROR: Exception in ASGI application
Most times I am getting it 2 times with just one click on the "Download csv" button that calls this endpoint.
Testing the endpoint in swagger returns:

but as you see above I have configured CORS to allow all origins, and my network connection is fine.
This problem doesn't arise with any other endpoint, and if I simply don't use the button, the entire app works as expected.
You can find the code here: https://github.com/IsabellaPap/helena-ir (Apologies in advance for the lack of documentation on how to run in Android studio. Swagger should be working fine (you need to first create a user and the authenticate to use the endpoint)
Let me know if I need to provide more config files or anything else!