Pero si retornas un Response directamente (o cualquier subclase, como JSONResponse), los datos no serán convertidos automáticamente (incluso si declaras un response_model), y la documentación no será generada automáticamente (por ejemplo, incluyendo el "media type" específico, en el encabezado HTTP Content-Type como parte del OpenAPI generado).
Pero también puedes declarar el Response que quieres que se use (por ejemplo, cualquier subclase de Response), en el decorador de la path operation usando el parámetro response_class.
Los contenidos que retornas desde tu función path operation serán puestos dentro de ese Response.
Nota
Si usas una clase de respuesta sin media type, FastAPI esperará que tu respuesta no tenga contenido, por lo que no documentará el formato de la respuesta en los docs de OpenAPI generados.
Si declaras un Response Model FastAPI lo usará para serializar los datos a JSON, usando Pydantic.
Si no declaras un response model, FastAPI usará el jsonable_encoder explicado en JSON Compatible Encoder y lo pondrá en un JSONResponse.
Si declaras un response_class con un media type de JSON (application/json), como es el caso con JSONResponse, los datos que retornes serán convertidos automáticamente (y filtrados) con cualquier response_model de Pydantic que hayas declarado en el decorador de la path operation. Pero los datos no serán serializados a bytes JSON con Pydantic, en su lugar serán convertidos con el jsonable_encoder y luego pasados a la clase JSONResponse, que los serializará a bytes usando la librería estándar de JSON en Python.
Como se vio en Retornar un Response directamente, también puedes sobrescribir la respuesta directamente en tu path operation, retornándola.
El mismo ejemplo de arriba, retornando un HTMLResponse, podría verse así:
fromfastapiimportFastAPIfromfastapi.responsesimportHTMLResponseapp=FastAPI()@app.get("/items/")asyncdefread_items():html_content=""" <html> <head> <title>Some HTML in here</title> </head> <body> <h1>Look ma! HTML!</h1> </body> </html> """returnHTMLResponse(content=html_content,status_code=200)
Aviso
Un Response retornado directamente por tu función path operation no será documentado en OpenAPI (por ejemplo, el Content-Type no será documentado) y no será visible en la documentación interactiva automática.
Nota
Por supuesto, el encabezado Content-Type real, el código de estado, etc., vendrán del objeto Response que retornaste.
Si quieres sobrescribir la respuesta desde dentro de la función pero al mismo tiempo documentar el "media type" en OpenAPI, puedes usar el parámetro response_class Y retornar un objeto Response.
El response_class entonces se usará solo para documentar la path operation en OpenAPI, pero tu Response se usará tal cual.
fromfastapiimportFastAPIfromfastapi.responsesimportHTMLResponseapp=FastAPI()defgenerate_html_response():html_content=""" <html> <head> <title>Some HTML in here</title> </head> <body> <h1>Look ma! HTML!</h1> </body> </html> """returnHTMLResponse(content=html_content,status_code=200)@app.get("/items/",response_class=HTMLResponse)asyncdefread_items():returngenerate_html_response()
En este ejemplo, la función generate_html_response() ya genera y retorna un Response en lugar de retornar el HTML en un str.
Al retornar el resultado de llamar a generate_html_response(), ya estás retornando un Response que sobrescribirá el comportamiento por defecto de FastAPI.
Pero como también pasaste el HTMLResponse en el response_class, FastAPI sabrá cómo documentarlo en OpenAPI y en la documentación interactiva como HTML con text/html:
Ten en cuenta que puedes usar Response para retornar cualquier otra cosa, o incluso crear una subclase personalizada.
Detalles Técnicos
También podrías usar from starlette.responses import HTMLResponse.
FastAPI proporciona las mismas starlette.responses que fastapi.responses solo como una conveniencia para ti, el desarrollador. Pero la mayoría de las respuestas disponibles vienen directamente de Starlette.
La clase principal Response, todas las demás respuestas heredan de ella.
Puedes retornarlo directamente.
Acepta los siguientes parámetros:
content - Un str o bytes.
status_code - Un código de estado HTTP int.
headers - Un dict de strings.
media_type - Un str que indica el media type. Por ejemplo "text/html".
FastAPI (en realidad Starlette) incluirá automáticamente un encabezado Content-Length. También incluirá un encabezado Content-Type, basado en el media_type y añadiendo un charset para los tipos de texto.
fromfastapiimportFastAPI,Responseapp=FastAPI()@app.get("/legacy/")defget_legacy_data():data="""<?xml version="1.0"?> <shampoo> <Header> Apply shampoo here. </Header> <Body> You'll have to use soap here. </Body> </shampoo> """returnResponse(content=data,media_type="application/xml")
Toma algunos datos y retorna una respuesta codificada como application/json.
Esta es la respuesta por defecto usada en FastAPI, como leíste arriba.
Detalles Técnicos
Pero si declaras un response model o un tipo de retorno, este se usará directamente para serializar los datos a JSON, y se retornará directamente una respuesta con el media type correcto para JSON, sin usar la clase JSONResponse.
Esta es la forma ideal de obtener el mejor rendimiento.
Toma un generador async o un generador/iterador normal (una función con yield) y transmite el cuerpo de la respuesta.
importanyiofromfastapiimportFastAPIfromfastapi.responsesimportStreamingResponseapp=FastAPI()asyncdeffake_video_streamer():foriinrange(10):yieldb"some fake video bytes"awaitanyio.sleep(0)@app.get("/")asyncdefmain():returnStreamingResponse(fake_video_streamer())
Detalles Técnicos
Una tarea async solo puede ser cancelada cuando alcanza un await. Si no hay await, el generador (función con yield) no puede ser cancelado correctamente y puede seguir ejecutándose incluso después de que se solicite la cancelación.
Como este pequeño ejemplo no necesita ninguna sentencia await, añadimos un await anyio.sleep(0) para darle al event loop la oportunidad de manejar la cancelación.
Esto sería aún más importante con streams grandes o infinitos.
Consejo
En lugar de retornar un StreamingResponse directamente, probablemente deberías seguir el estilo de Stream Data, es mucho más conveniente y maneja la cancelación detrás de escena por ti.
Si estás transmitiendo JSON Lines, sigue el tutorial Stream JSON Lines.
Puedes crear tu propia clase de respuesta personalizada, heredando de Response y usándola.
Por ejemplo, digamos que quieres usar orjson con algunas configuraciones.
Digamos que quieres que retorne JSON indentado y formateado, así que quieres usar la opción de orjson orjson.OPT_INDENT_2.
Podrías crear un CustomORJSONResponse. Lo principal que tienes que hacer es crear un método Response.render(content) que retorne el contenido como bytes:
fromtypingimportAnyimportorjsonfromfastapiimportFastAPI,Responseapp=FastAPI()classCustomORJSONResponse(Response):media_type="application/json"defrender(self,content:Any)->bytes:assertorjsonisnotNone,"orjson must be installed"returnorjson.dumps(content,option=orjson.OPT_INDENT_2)@app.get("/",response_class=CustomORJSONResponse)asyncdefmain():return{"message":"Hello World"}
Ahora en lugar de retornar:
{"message":"Hello World"}
...esta respuesta retornará:
{"message":"Hello World"}
Por supuesto, probablemente encontrarás formas mucho mejores de aprovechar esto que formatear JSON. 😉
Si lo que buscas es rendimiento, probablemente estés mejor usando un Response Model que una respuesta orjson.
Con un response model, FastAPI usará Pydantic para serializar los datos a JSON, sin usar pasos intermedios, como convertirlos con jsonable_encoder, lo cual ocurriría en cualquier otro caso.
Y por debajo, Pydantic usa los mismos mecanismos subyacentes de Rust que orjson para serializar a JSON, así que ya obtendrás el mejor rendimiento con un response model.
Al crear una instancia de la clase FastAPI o un APIRouter puedes especificar qué clase de respuesta usar por defecto.
El parámetro que define esto es default_response_class.
En el ejemplo de abajo, FastAPI usará HTMLResponse por defecto, en todas las path operations, en lugar de JSON.
fromfastapiimportFastAPIfromfastapi.responsesimportHTMLResponseapp=FastAPI(default_response_class=HTMLResponse)@app.get("/items/")asyncdefread_items():return"<h1>Items</h1><p>This is a list of items.</p>"
Consejo
Aún puedes sobrescribir response_class en las path operations como antes.