FastAPI, devuelve una respuesta de archivo con el resultado de una consulta SQL

Resuelto SDuma asked hace 4 años • 3 respuestas

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

SDuma avatar Apr 10 '20 19:04 SDuma
Aceptado

Basado MUY en esto https://github.com/tiangolo/fastapi/issues/1277

  1. Convierta su marco de datos en una secuencia
  2. usar una respuesta de transmisión
  3. 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
Tom Greenwood avatar May 20 '2020 10:05 Tom Greenwood

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
dmm98 avatar Nov 01 '2021 15:11 dmm98

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
David W. avatar Sep 12 '2021 15:09 David W.