5.7 KiB
Transmitir datos
Si quieres transmitir datos que se puedan estructurar como JSON, deberías Transmitir JSON Lines.
Pero si quieres transmitir datos binarios puros o strings, aquí tienes cómo hacerlo.
/// note | Nota
Añadido en FastAPI 0.134.0.
///
Casos de uso
Podrías usar esto si quieres transmitir strings puros, por ejemplo directamente de la salida de un servicio de AI LLM.
También podrías usarlo para transmitir archivos binarios grandes, donde transmites cada bloque de datos a medida que lo lees, sin tener que leerlo todo en memoria de una sola vez.
También podrías transmitir video o audio de esta manera; incluso podría generarse mientras lo procesas y lo envías.
Un StreamingResponse con yield
Si declaras un response_class=StreamingResponse en tu path operation function, puedes usar yield para enviar cada bloque de datos a su vez.
{* ../../docs_src/stream_data/tutorial001_py310.py ln[1:23] hl[20,23] *}
FastAPI entregará cada bloque de datos a StreamingResponse tal cual, no intentará convertirlo a JSON ni nada parecido.
path operation functions no async
También puedes usar funciones def normales (sin async) y usar yield de la misma manera.
{* ../../docs_src/stream_data/tutorial001_py310.py ln[26:29] hl[27] *}
Sin anotación
Realmente no necesitas declarar la anotación de tipo de retorno para transmitir datos binarios.
Como FastAPI no intentará convertir los datos a JSON con Pydantic ni serializarlos de ninguna manera, en este caso la anotación de tipos es solo para que la use tu editor y tus herramientas; FastAPI no la usará.
{* ../../docs_src/stream_data/tutorial001_py310.py ln[32:35] hl[33] *}
Esto también significa que con StreamingResponse tienes la libertad y la responsabilidad de producir y codificar los bytes de datos exactamente como necesites enviarlos, independientemente de las anotaciones de tipos. 🤓
Transmitir bytes
Uno de los casos de uso principales sería transmitir bytes en lugar de strings; por supuesto puedes hacerlo.
{* ../../docs_src/stream_data/tutorial001_py310.py ln[44:47] hl[47] *}
Un PNGStreamingResponse personalizado
En los ejemplos anteriores, se transmitieron los bytes de datos, pero la response no tenía un header Content-Type, así que el cliente no sabía qué tipo de datos estaba recibiendo.
Puedes crear una subclase personalizada de StreamingResponse que establezca el header Content-Type al tipo de datos que estás transmitiendo.
Por ejemplo, puedes crear un PNGStreamingResponse que establezca el header Content-Type a image/png usando el atributo media_type:
{* ../../docs_src/stream_data/tutorial002_py310.py ln[6,19:20] hl[20] *}
Luego puedes usar esta nueva clase en response_class=PNGStreamingResponse en tu path operation function:
{* ../../docs_src/stream_data/tutorial002_py310.py ln[23:27] hl[23] *}
Simular un archivo
En este ejemplo estamos simulando un archivo con io.BytesIO, que es un objeto tipo archivo que vive solo en memoria, pero nos permite usar la misma interfaz.
Por ejemplo, podemos iterarlo para consumir su contenido, como podríamos con un archivo.
{* ../../docs_src/stream_data/tutorial002_py310.py ln[1:27] hl[3,12:13,25] *}
/// note | Detalles técnicos
Las otras dos variables, image_base64 y binary_image, son una imagen codificada en Base64 y luego convertida a bytes, para después pasarla a io.BytesIO.
Solo para que pueda vivir en el mismo archivo para este ejemplo y puedas copiarlo y ejecutarlo tal cual. 🥚
///
Al usar un bloque with, nos aseguramos de que el objeto tipo archivo se cierre cuando termine la función generadora (la función con yield). Es decir, después de que termine de enviar la response.
No sería tan importante en este ejemplo específico porque es un archivo falso en memoria (con io.BytesIO), pero con un archivo real sí sería importante asegurarse de que el archivo se cierre al terminar de trabajar con él.
Archivos y async
En la mayoría de los casos, los objetos tipo archivo no son compatibles con async y await por defecto.
Por ejemplo, no tienen un await file.read(), ni un async for chunk in file.
Y en muchos casos leerlos sería una operación bloqueante (que podría bloquear el event loop), porque se leen desde disco o desde la red.
/// note | Nota
El ejemplo anterior es en realidad una excepción, porque el objeto io.BytesIO ya está en memoria, así que leerlo no bloqueará nada.
Pero en muchos casos leer un archivo u objeto tipo archivo sí bloquearía.
///
Para evitar bloquear el event loop, puedes simplemente declarar la path operation function con un def normal en lugar de async def; de esa forma FastAPI la ejecutará en un worker de threadpool para evitar bloquear el loop principal.
{* ../../docs_src/stream_data/tutorial002_py310.py ln[30:34] hl[31] *}
/// tip | Consejo
Si necesitas llamar código bloqueante desde dentro de una función async, o una función async desde dentro de una función bloqueante, podrías usar Asyncer, un paquete hermano de FastAPI.
///
yield from
Cuando estés iterando sobre algo, como un objeto tipo archivo, y estés haciendo yield para cada elemento, también podrías usar yield from para hacer yield de cada elemento directamente y saltarte el for.
Esto no es particular de FastAPI, es simplemente Python, pero es un truco útil que conviene conocer. 😎
{* ../../docs_src/stream_data/tutorial002_py310.py ln[37:40] hl[40] *}