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:

this error

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!

0

There are 0 best solutions below