FastAPI, devuelve una respuesta de archivo con el resultado de una consulta SQL
Estoy usando FastAPI y actualmente devuelvo un csv que leo del servidor SQL con pandas. (pd.read_sql()) Sin embargo, el csv es bastante grande para el navegador y quiero devolverlo con una respuesta de Archivo: https://fastapi.tiangolo.com/advanced/custom-response/ (fin de la página). Parece que no puedo hacer esto sin escribirlo primero en un archivo csv que parece lento y saturará el sistema de archivos con csv en cada solicitud.
Entonces, mi pregunta es: ¿hay alguna forma de devolver un FileResponse desde una base de datos SQL o un marco de datos de Pandas?
Y si no, ¿hay alguna manera de eliminar los archivos csv generados, después de que el cliente los haya leído todos?
¡Gracias por tu ayuda!
Atentamente,
Esteban
Basado MUY en esto https://github.com/tiangolo/fastapi/issues/1277
- Convierta su marco de datos en una secuencia
- usar una respuesta de transmisión
- Modificar los encabezados para que sea una descarga (opcional)
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import io
import pandas as pd
app = FastAPI()
@app.get("/get_csv")
async def get_csv():
df = pd.DataFrame(dict(col1 = 1, col2 = 2), index=[0])
stream = io.StringIO()
df.to_csv(stream, index = False)
response = StreamingResponse(iter([stream.getvalue()]),
media_type="text/csv"
)
response.headers["Content-Disposition"] = "attachment; filename=export.csv"
return response
Agregando al código que se mencionó anteriormente, me resultó útil colocar otro encabezado de respuesta para que el cliente pueda ver la "Disposición de contenido". Esto se debe al hecho de que el cliente solo puede ver de forma predeterminada los encabezados de respuesta incluidos en la lista segura de CORS. "Content-Disposition" no forma parte de esta lista, por lo que debe agregarse explícitamente https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers .
No sé si hay otra manera de especificar esto, para el cliente o servidor de una manera más general para que se aplique a todos los endpoints necesarios, pero así es como lo apliqué.
@router.post("/files", response_class = StreamingResponse)
async def anonymization(file: bytes = File(...), config: str = Form(...)):
# file as str
inputFileAsStr = StringIO(str(file,'utf-8'))
# dataframe
df = pd.read_csv(inputFileAsStr)
# send to function to handle anonymization
results_df = anonymize(df, config)
# output file
outFileAsStr = StringIO()
results_df.to_csv(outFileAsStr, index = False)
response = StreamingResponse(
iter([outFileAsStr.getvalue()]),
media_type='text/csv',
headers={
'Content-Disposition': 'attachment;filename=dataset.csv',
'Access-Control-Expose-Headers': 'Content-Disposition'
}
)
# return
return response
En este también me estaba golpeando la cabeza contra la pared. Mi caso de uso es ligeramente diferente ya que almaceno imágenes, archivos PDF, etc. como blobs en mi base de datos maria. Descubrí que el truco consistía en pasar el contenido del blob a BytesIO y el resto era sencillo.
from fastapi.responses import StreamingResponse
from io import BytesIO
@router.get('/attachment/{id}')
async def get_attachment(id: int):
mdb = messages(s.MARIADB)
attachment = mdb.getAttachment(id)
memfile = BytesIO(attachment['content'])
response = StreamingResponse(memfile, media_type=attachment['contentType'])
response.headers["Content-Disposition"] = f"inline; filename={attachment['name']}"
return response