Enganchado
Usando el módulo de python API rápida, no puedo averiguar cómo devolver una imagen. En el matraz haría algo como esto:
@app.route("/vector_image", methods=["POST"])
def image_endpoint():
# img = ... # Create the image here
return Response(img, mimetype="image/png")
¿Cuál es la llamada correspondiente en este módulo?
Tuve un problema similar pero con una imagen cv2. Esto puede ser útil para otros. Utiliza el StreamingResponse
.
import io
from starlette.responses import StreamingResponse
app = FastAPI()
@app.post("/vector_image")
def image_endpoint(*, vector):
# Returns a cv2 image array from the document vector
cv2img = my_function(vector)
res, im_png = cv2.imencode(".png", cv2img)
return StreamingResponse(io.BytesIO(im_png.tobytes()), media_type="image/png")
-
¡Gracias! Creo que esta es una respuesta mucho mejor que mi truco que requería un archivo temporal.
– enganchado
6 de enero de 2020 a las 20:05
-
si estás usando
BytesIO
especialmente con PIL/skimage, asegúrese de hacer tambiénimg.seek(0)
¡antes de volver!– Hendy Irawan
16 de abril de 2020 a las 4:49
-
Esto también funciona muy bien para devolver objetos GridFS, por ejemplo:
val = grid_fs_file.read()
return StreamingResponse(io.BytesIO(val), media_type="application/pdf")
¡Muchos gracias!– BrettJ
26 de diciembre de 2020 a las 8:32
-
Es posible que las cosas hayan cambiado desde que se escribió esta respuesta, pero el uso de
StreamingResponse
en esta respuesta parece mal hoy. Mira mi respuesta.– Máxpm
12 de mayo de 2021 a las 3:52
-
@HendyIrawan ¿Por qué es importante usar img.seek(0)?
– Alpensina
26 de febrero a las 14:25
Maxpm
Si ya tienes los bytes de la imagen en memoria
devolver un fastapi.responses.Response
con tu costumbre content
y media_type
.
También deberá jugar con el decorador de puntos finales para que FastAPI coloque el tipo de medio correcto en la especificación de OpenAPI.
@app.get(
"/image",
# Set what the media type will be in the autogenerated OpenAPI specification.
# fastapi.tiangolo.com/advanced/additional-responses/#additional-media-types-for-the-main-response
responses = {
200: {
"content": {"image/png": {}}
}
}
# Prevent FastAPI from adding "application/json" as an additional
# response media type in the autogenerated OpenAPI specification.
# https://github.com/tiangolo/fastapi/issues/3258
response_class=Response,
)
def get_image()
image_bytes: bytes = generate_cat_picture()
# media_type here sets the media type of the actual response sent to the client.
return Response(content=image_bytes, media_type="image/png")
Ver el Response
documentación.
Si su imagen existe solo en el sistema de archivos
devolver un fastapi.responses.FileResponse
.
Ver el FileResponse
documentación.
Ten cuidado con StreamingResponse
Otras respuestas sugieren StreamingResponse
. StreamingResponse
es más difícil de usar correctamente, así que no lo recomiendo a menos que esté seguro de que no puede usar Response
o FileResponse
.
En particular, un código como este no tiene sentido. No “transmitirá” la imagen de ninguna manera útil.
@app.get("/image")
def get_image()
image_bytes: bytes = generate_cat_picture()
# ❌ Don't do this.
image_stream = io.BytesIO(image_bytes)
return StreamingResponse(content=image_stream, media_type="image/png")
Ante todo, StreamingResponse(content=my_iterable)
flujos iterando sobre los fragmentos proporcionados por my_iterable
. Pero cuando ese iterable es un BytesIO
, los trozos seran \n
-líneas terminadasque no tendrá sentido para una imagen binaria.
E incluso si las divisiones de fragmentos tuvieran sentido, la fragmentación no tiene sentido aquí porque teníamos todo el image_bytes
bytes
objeto disponible desde el principio. También podríamos haber pasado todo el asunto a un Response
desde el principio. No ganamos nada reteniendo datos de FastAPI.
Segundo, StreamingResponse
corresponde a Codificación de transferencia fragmentada HTTP. (Esto puede depender de su servidor ASGI, pero es el caso de uvicornioal menos). Y este no es un buen caso de uso para la codificación de transferencia fragmentada.
La codificación de transferencia fragmentada tiene sentido cuando no conoce el tamaño de su salida con anticipación y no desea esperar a recopilarlo todo para averiguarlo antes de comenzar a enviarlo al cliente. Eso puede aplicarse a cosas como entregar los resultados de consultas lentas a bases de datos, pero generalmente no se aplica a entregar imágenes.
La codificación de transferencia fragmentada innecesaria puede ser dañina. Por ejemplo, significa que los clientes no pueden mostrar barras de progreso cuando descargan el archivo. Ver:
- Encabezado de longitud de contenido frente a codificación fragmentada
- ¿Es una buena idea usar Transfer-Encoding: fragmentado en archivos estáticos?
-
Buena respuesta, sin embargo, con esto, el documento OpenAPI seguirá enumerando
application/json
como una posible respuesta 200, además deimage/png
. Incluso enumera esto primero, por lo que es la primera respuesta posible que se muestra en los documentos generados. ¿Sabes cómo hacer que sea solo una lista?image/png
? Ver también mi pregunta sobre esto en github.com/tiangolo/fastapi/issues/3258– estan
21 de mayo de 2021 a las 7:29
-
@estan Buena captura. Parece que ya encontraste una solución en ese problema de GitHub. Tengo un enfoque alternativo; Respondí a ese problema de GitHub y lo agregué a mi respuesta aquí.
– Máxpm
21 mayo 2021 a las 19:11
-
No StreamingResponse no corresponde a la codificación fragmentada. FastAPI/starlette no tienen el control de esto según la especificación WSGI (consulte “Manejo del encabezado Content-Length”). Otras clases de respuesta establecen el
Content-Length
encabezado para usted. StreamingResponse no lo hace.StreamingResponse(content, headers={'Content-Length': str(content_length)})
es poco probable que se fragmente. Para el servidor (uvicornio), esto se vería igual que cualquier otra respuesta estática.– Felipe Couling
6 de agosto de 2021 a las 9:07
-
@PhilipCouling “Corresponde” es quizás la palabra incorrecta, sí. ¿Sería algo como “
StreamingResponse()
es probable que sea manejado por el servidor con codificación de transferencia fragmentada” ¿Sería mejor?– Máxpm
6 de agosto de 2021 a las 23:36
-
Hombre, me enfrenté a esta elección hace unos días. En primer lugar, hice como en su ejemplo “StreamingResponse sin sentido”. Noté que TTFB no es bueno y tuve algunos problemas al intentar enviar archivos de más de 50 MB; era muy lento. Después de eso llegué a la variante de Respuesta. Funciona muy bien porque mi servicio envía archivos de 50 a 200 KB. Tu publicación me dio mucha información útil. ¡Gracias!
– Alpensina
26 de febrero a las 14:31
Yaguiz Degirmenci
Todas las demás respuestas son correctas, pero ahora es muy fácil devolver una imagen.
from fastapi.responses import FileResponse
@app.get("/")
async def main():
return FileResponse("your_image.jpeg")
-
también necesitas instalar
aiofiles
biblioteca para esto– Ígor Alex
19 de julio de 2021 a las 12:06
-
Gracias. Estoy usando esto para devolver archivos que se guardan usando fastAPI. ¡También gracias @Igor por señalar que también necesito aiofiles!
– jcdevilleres
2 de octubre de 2021 a las 6:39
-
Fácil y sencillo 👍
-Mike Olszak
10 oct a las 7:11
Todavía no está debidamente documentado, pero puede usar cualquier cosa de Starlette.
Entonces, puedes usar un FileResponse
si es un archivo en disco con una ruta: https://www.starlette.io/responses/#fileresponse
Si es un objeto similar a un archivo creado en su operación de rutaen la próxima versión estable de Starlette (utilizada internamente por FastAPI) también podrá devolverlo en un StreamingResponse
.
Gracias a la respuesta de @biophetik, con un recordatorio importante que me causó confusión: si estás usando BytesIO
especialmente con PIL/skimage, asegúrese de hacer también img.seek(0)
¡antes de volver!
@app.get("/generate")
def generate(data: str):
img = generate_image(data)
print('img=%s' % (img.shape,))
buf = BytesIO()
imsave(buf, img, format="JPEG", quality=100)
buf.seek(0) # important here!
return StreamingResponse(buf, media_type="image/jpeg",
headers={'Content-Disposition': 'inline; filename="%s.jpg"' %(data,)})
-
¡¡¡Guau!!!
buf.seek(0)
me salvó– Evgeny Kolyakov
15 de junio a las 13:29
-
@EvgenyKolyakov me alegra saber que es útil 2 años después 🙂 Perdí algo de cabello debido a eso jajaja
– Hendy Irawan
16 de junio a las 11:41
david fiocco
La respuesta de @SebastiánRamírez me indicó la dirección correcta, pero para aquellos que buscan resolver el problema, necesitaba algunas líneas de código para hacerlo funcionar. necesitaba importar FileResponse
desde starlette (¿no fastAPI?), agregue compatibilidad con CORS y regrese desde un archivo temporal. Tal vez haya una mejor manera, pero no pude hacer que la transmisión funcionara:
from starlette.responses import FileResponse
from starlette.middleware.cors import CORSMiddleware
import tempfile
app = FastAPI()
app.add_middleware(
CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
)
@app.post("/vector_image")
def image_endpoint(*, vector):
# Returns a raw PNG from the document vector (define here)
img = my_function(vector)
with tempfile.NamedTemporaryFile(mode="w+b", suffix=".png", delete=False) as FOUT:
FOUT.write(img)
return FileResponse(FOUT.name, media_type="image/png")
-
¡¡¡Guau!!!
buf.seek(0)
me salvó– Evgeny Kolyakov
15 de junio a las 13:29
-
@EvgenyKolyakov me alegra saber que es útil 2 años después 🙂 Perdí algo de cabello debido a eso jajaja
– Hendy Irawan
16 de junio a las 11:41
presa
Mis necesidades no se cumplieron con lo anterior porque mi imagen se creó con PIL. Mi punto final fastapi toma un nombre de archivo de imagen, lo lee como una imagen PIL y genera un jpeg en miniatura en la memoria que se puede usar en HTML como:
<img src="http://localhost:8000/images/thumbnail/bigimage.jpg">
import io
from PIL import Image
from fastapi.responses import StreamingResponse
@app.get('/images/thumbnail/{filename}',
response_description="Returns a thumbnail image from a larger image",
response_class="StreamingResponse",
responses= {200: {"description": "an image", "content": {"image/jpeg": {}}}})
def thumbnail_image (filename: str):
# read the high-res image file
image = Image.open(filename)
# create a thumbnail image
image.thumbnail((100, 100))
imgio = io.BytesIO()
image.save(imgio, 'JPEG')
imgio.seek(0)
return StreamingResponse(content=imgio, media_type="image/jpeg")