Saltar al contenido

Respuesta Personalizada - HTML, Stream, File, otras

Por defecto, FastAPI retornará respuestas JSON.

Puedes sobrescribirlo retornando un Response directamente como se vio en Retornar un Response directamente.

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.

Respuestas JSON

Por defecto FastAPI retorna respuestas JSON.

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.

Rendimiento de JSON

En resumen, si quieres el máximo rendimiento, usa un Response Model y no declares un response_class en el decorador de la path operation.

# Code above omitted 👆

@app.post("/items/")
async def create_item(item: Item) -> Item:
    return item

# Code below omitted 👇
👀 Vista previa del archivo completo
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list[str] = []


@app.post("/items/")
async def create_item(item: Item) -> Item:
    return item


@app.get("/items/")
async def read_items() -> list[Item]:
    return [
        Item(name="Portal Gun", price=42.0),
        Item(name="Plumbus", price=32.0),
    ]

Respuesta HTML

Para retornar una respuesta con HTML directamente desde FastAPI, usa HTMLResponse.

  • Importa HTMLResponse.
  • Pasa HTMLResponse como el parámetro response_class de tu decorador de la path operation.
from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.get("/items/", response_class=HTMLResponse)
async def read_items():
    return """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """

Nota

El parámetro response_class también se usará para definir el "media type" de la respuesta.

En este caso, el encabezado HTTP Content-Type se establecerá como text/html.

Y será documentado como tal en OpenAPI.

Retornar un Response

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í:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.get("/items/")
async def read_items():
    html_content = """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """
    return HTMLResponse(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.

Documentar en OpenAPI y sobrescribir el Response

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.

Retornar un HTMLResponse directamente

Por ejemplo, podría ser algo como:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


def generate_html_response():
    html_content = """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """
    return HTMLResponse(content=html_content, status_code=200)


@app.get("/items/", response_class=HTMLResponse)
async def read_items():
    return generate_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:

Respuestas disponibles

Aquí hay algunas de las respuestas disponibles.

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.

Response

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.

from fastapi import FastAPI, Response

app = FastAPI()


@app.get("/legacy/")
def get_legacy_data():
    data = """<?xml version="1.0"?>
    <shampoo>
    <Header>
        Apply shampoo here.
    </Header>
    <Body>
        You'll have to use soap here.
    </Body>
    </shampoo>
    """
    return Response(content=data, media_type="application/xml")

HTMLResponse

Toma algo de texto o bytes y retorna una respuesta HTML, como leíste arriba.

PlainTextResponse

Toma algo de texto o bytes y retorna una respuesta de texto plano.

from fastapi import FastAPI
from fastapi.responses import PlainTextResponse

app = FastAPI()


@app.get("/", response_class=PlainTextResponse)
async def main():
    return "Hello World"

JSONResponse

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.

RedirectResponse

Retorna una redirección HTTP. Usa un código de estado 307 (Temporary Redirect) por defecto.

Puedes retornar un RedirectResponse directamente:

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/typer")
async def redirect_typer():
    return RedirectResponse("https://typer.tiangolo.com")

O puedes usarlo en el parámetro response_class:

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/fastapi", response_class=RedirectResponse)
async def redirect_fastapi():
    return "https://fastapi.tiangolo.com"

Si haces eso, entonces puedes retornar la URL directamente desde tu función path operation.

En este caso, el status_code usado será el por defecto para RedirectResponse, que es 307.


También puedes usar el parámetro status_code combinado con el parámetro response_class:

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/pydantic", response_class=RedirectResponse, status_code=302)
async def redirect_pydantic():
    return "https://docs.pydantic.dev/"

StreamingResponse

Toma un generador async o un generador/iterador normal (una función con yield) y transmite el cuerpo de la respuesta.

import anyio
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()


async def fake_video_streamer():
    for i in range(10):
        yield b"some fake video bytes"
        await anyio.sleep(0)


@app.get("/")
async def main():
    return StreamingResponse(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.

FileResponse

Transmite un archivo asíncronamente como respuesta.

Toma un conjunto diferente de argumentos para instanciar que los otros tipos de respuesta:

  • path - La ruta del archivo a transmitir.
  • headers - Cualquier encabezado personalizado a incluir, como un diccionario.
  • media_type - Un string que indica el media type. Si no se establece, el nombre del archivo o la ruta se usarán para inferir un media type.
  • filename - Si se establece, se incluirá en el Content-Disposition de la respuesta.

Las respuestas de archivo incluirán los encabezados apropiados Content-Length, Last-Modified y ETag.

from fastapi import FastAPI
from fastapi.responses import FileResponse

some_file_path = "large-video-file.mp4"
app = FastAPI()


@app.get("/")
async def main():
    return FileResponse(some_file_path)

También puedes usar el parámetro response_class:

from fastapi import FastAPI
from fastapi.responses import FileResponse

some_file_path = "large-video-file.mp4"
app = FastAPI()


@app.get("/", response_class=FileResponse)
async def main():
    return some_file_path

En este caso, puedes retornar la ruta del archivo directamente desde tu función path operation.

Clase de respuesta personalizada

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:

from typing import Any

import orjson
from fastapi import FastAPI, Response

app = FastAPI()


class CustomORJSONResponse(Response):
    media_type = "application/json"

    def render(self, content: Any) -> bytes:
        assert orjson is not None, "orjson must be installed"
        return orjson.dumps(content, option=orjson.OPT_INDENT_2)


@app.get("/", response_class=CustomORJSONResponse)
async def main():
    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. 😉

orjson o Response Model

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.

Clase de respuesta por defecto

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.

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI(default_response_class=HTMLResponse)


@app.get("/items/")
async def read_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.

Documentación adicional

También puedes declarar el media type y muchos otros detalles en OpenAPI usando responses: Additional Responses in OpenAPI.