3. Códigos de Estado HTTP y Manejo de Errores en FastAPI
Anteriormente ya habíamos creado nuestros modelos de Pydantic para definir cómo debe llegar la información a nuestros endpoints y cómo debe ser enviada. Ahora mejoraremos la calidad de nuestras respuestas y veremos qué tipos de respuestas podemos devolver en FastAPI.
Tipos de Respuesta en FastAPI
Los endpoints que hemos visto anteriormente son los más comunes en el desarrollo de servicios API REST, que devuelven respuestas en formato JSON. Como vimos, para enviar este tipo de respuesta basta con retornar un diccionario de Python o directamente un modelo de Pydantic, como podemos ver en los siguientes ejemplos:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# Diccionario de python
@app.get("/")
async def home():
return {"message": "Bienvenido a mi API"} # Se convierte automáticamente a JSON
# Modelos de Pydantic
class User(BaseModel):
id: int
name: str
@app.get("/user/")
async def get_user():
return User(id=1, name="Juan") # FastAPI convierte el modelo a JSON
Sin embargo, como en todo framework de backend, este no es el único tipo de dato que puede devolver un endpoint, ni tampoco es la única forma de devolver una respuesta en formato JSON. A continuación, repasaremos los tipos de respuesta que podemos retornar en FastAPI.
JSONResponse
Esta clase indica explícitamente que la respuesta será un JSON y permite un mayor control sobre el contenido de la respuesta. Es más recomendable que enviar directamente un diccionario de Python, ya que ofrece más flexibilidad.
from fastapi.responses import JSONResponse
@app.get("/custom/")
async def custom_response():
return JSONResponse(
content={"status": "success", "data": {"id": 1}},
status_code=201,
headers={"X-Custom-Header": "valor"}
)
Response
Si necesitamos enviar contenido que no sea JSON, como una página HTML, podemos usar la clase Response
. Debemos pasar el contenido del archivo y definir explícitamente el tipo de medio (media type) que estamos enviando.
from fastapi import Response
@app.get("/html/")
async def html_response():
html_content = "<h1>Hola Mundo</h1>"
return Response(content=html_content, media_type="text/html")
El media_type es el tipo de contenido, y se conocen como MIME types, cada tipo de archivo dependiendo de su extension tiene un MIME type. Ver documentacion
Archivos (FileResponse)
El FileResponse lo usamos si queremos enviar archivos binarios o blobs, como serían imágenes, vídeos, archivos pdf, etc.
from fastapi.responses import FileResponse
@app.get("/download/")
async def download_file():
return FileResponse(
path="archivo.pdf",
filename="documento.pdf",
media_type="application/pdf"
)
Redirecciones (RedirectResponse)
Este tipo de respuestas lo usamos cuando queremos redirigir a un usuario a otra URL. Por ejemplo, si un usuario intenta acceder a una URL que no existe, podemos redirigirlo a la URL de inicio. O si una ruta esta progegida y no tiene acceso, podemos redirigirlo al login.
from fastapi.responses import RedirectResponse
@app.get("/old/")
async def old_endpoint():
return RedirectResponse(url="/new", status_code=301)
Streaming (StreamingResponse)
El streaming es una forma de enviar datos en chunks, es decir, en partes, en lugar de enviar todo el contenido de una vez. Por ejemplo, un video. Estos no se suelen enviar en su totalidad, sino que se envia el contenido por partes hasta que se termina.
from fastapi.responses import StreamingResponse
import io
@app.get("/stream/")
async def stream_data():
def generate_data():
for i in range(5):
yield f"Linea {i}\n".encode("utf-8")
return StreamingResponse(
content=generate_data(),
media_type="text/plain"
)
El endpoint de arriba hace uso de una funcion generadora para enviar datos en trozos. Una función generadora es una funcion, que al retornar su valor con yield, queda en pausa. Cuando se ejecuta de nuevo, sigue en donde se quedo anteriormente.
De este modo, enviaria algo como lo siguiente:
- Primer envío: línea 1
- Segundo envío: línea 2
- Tercer envío: línea 3
- Cuarto envío: línea 4
- Quinto envío: línea 5
No hace falta que entiendas a fondo como funciona el streaming, ya que no lo usaremos en el curso. Pero si quieres entender mejor este endpoint, te recomiendo que revises que son funciones generadoras.
Códigos de Estado
Ahora que conocemos los diferentes tipos de respuestas en FastAPI, vamos a explorar los códigos de estado HTTP que podemos utilizar al enviar una respuesta.
En el estándar HTTP, los códigos de estado van desde 100 hasta 599 y se utilizan para indicar el resultado de una solicitud. Estos códigos se dividen en cinco categorías principales:
- 1xx (Informativo): La solicitud fue recibida y el proceso continúa.
- 2xx (Éxito): La solicitud se completó exitosamente (códigos 200-299).
- 3xx (Redirección): Se requiere acción adicional para completar la solicitud.
- 4xx (Error del Cliente): La solicitud contiene sintaxis incorrecta o no puede cumplirse (códigos 400-499).
- 5xx (Error del Servidor): El servidor falló al completar una solicitud aparentemente válida (códigos 500-599).
Código | Tipo | Ejemplo Común | Uso Típico |
---|---|---|---|
1xx | Informativo | 100 Continue | Solicitud recibida, proceso continúa |
2xx | Éxito | 200 OK | Operación exitosa |
3xx | Redirección | 301 Moved Permanently | Recurso movido permanentemente |
4xx | Error del Cliente | 404 Not Found | Recurso no existe |
5xx | Error del Servidor | 500 Internal Server Error | Fallo en el servidor |
Para indicar el estado de nuestra respuesta, simplemente usamos el parámetro status_code
en nuestros endpoints de la siguiente manera:
# Forma 1: Usando el código de estado directamente
@app.post("/items/", status_code=201)
async def create_item():
return {"id": 1, "name": "Item nuevo"}
# Forma 2: Usando las constantes de status (recomendado)
from fastapi import status
@app.post("/items/", status_code=status.HTTP_201_CREATED)
async def create_item():
return {"id": 1, "name": "Item nuevo"}
Explicación de las formas:
-
Forma 1: Usamos el código de estado directamente como un número entero en el parámetro status_code.
-
Forma 2 (Recomendada): Importamos el objeto status de FastAPI, que contiene constantes para todos los códigos de estado HTTP. Esto mejora la legibilidad del código.
Manejo de Errores
En fast api, si queremos indicar que un endpoint ha fallado, podemos usar la excepción HTTPException y retornarla en el endpoint de la siguiente manera.
from fastapi import HTTPException, status
@app.get("/items/{id}")
async def read_item(id: int):
if id > 100:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="El item solicitado no existe",
)
return {"id": id, "nombre": "Item " + str(id)}
Esto indicará una respuesta con el codigo de estado 404, y el detalle del error. Lo cual puede ser útil en endpoints donde se solicita un registro por id, y este no se encuentra en la base de datos