4. Ejemplo Completo, Api de Peliculas
Ahora que ya hemos visto los conceptos básicos de FastAPI, vamos a crear un ejemplo sencillo sobre una API de películas. Lo primero que haremos es crear nuestra carpeta.
mkdir peliculas
cd peliculas
Ahora crearemos nuestro entorno virtual y activaremos nuestro entorno virtual.
python -m venv .venv
source .venv/bin/activate
Instalaremos fastapi.
pip install fastapi
Creamos nuestra carpeta app/ y dentro de ella nuestros archivos principales para nuestra api: main.py, init.py y schemas.py.
peliculas
├── .venv
└── app/
├── __init__.py
├── main.py
└── schemas.py
Con esto ya tendríamos las bases para comenzar a crear nuestra API.
A partir de aquí, imaginemos que nuestras películas pueden guardar la siguiente información:
- Título
- Director
- Año de lanzamiento
- Genero
- Rating
Creación de las Películas
Comenzaremos nuestra api creando el endpoint para registrar nuestras películas. Nos iremos al archivo schemas.py que habíamos creado anteriormente y vamos a definir le modelo de creación.
from pydantic import BaseModel
from typing import Optional
class CrearPelicula(BaseModel):
titulo: str
director: str
anio_lanzamiento: int
genero: str
rating: Optional[float] = None # Rating es opcional.
Ahora sentaremos las bases de nuestra API creando un archivo main.py. Aquí crearemos nuestra primera ruta y una lista en la cual guardaremos nuestras películas.
from fastapi import FastAPI, HTTPException, Body, status
from typing import List, Optional, Annotated
from app.schemas import CrearPelicula
app = FastAPI()
peliculas_db = [] # Vacia por el momento.
current_id = 1
# Ruta para crear película
@app.post(
"/peliculas/",
status_code=status.HTTP_201_CREATED,
description="Crea una nueva película en la base de datos.",
response_model=int,
)
async def crear_pelicula(
pelicula: Annotated[CrearPelicula, Body()]
):
pelicula_con_id = {
"id": current_id,
"titulo": pelicula.titulo,
"director": pelicula.director,
"anio_lanzamiento": pelicula.anio_lanzamiento,
"genero": pelicula.genero,
"rating": pelicula.rating,
}
peliculas_db.append(pelicula_con_id)
current_id += 1
return pelicula_con_id["id"]
Ejecutamos el servidor con el siguiente comando:
fastapi dev app/main.py
Y probamos en nuestra documentacion en http://127.0.0.1:8000/docs.
Como podemos ver, todo funciona y nuestras películas estan siendo creadas.
Leer las Películas.
Ahora crearemos el endpoint para leer las películas que hemos creado. Algo importante a destacar en este punto es que cada vez que el servidor sufra un cambio o se reinicie, todo el código se ejecuta nuevamente, por lo que el arreglo de películas se reinicia a un arreglo vacío [] como definimos en nuestro código.
A partir de aquí, agregaremos tres películas a nuestro arreglo para poder probar nuestras rutas sin tener que recrear las películas nuevamente cada vez que se borren.
# ...Resto del codigo...
peliculas_db = [
{
"id": 1,
"titulo": "Pulp Fiction",
"director": "Quentin Tarantino",
"anio_lanzamiento": 1994,
"genero": "Acción",
"rating": 8.9
},
{
"id": 2,
"titulo": "The Godfather",
"director": "Francis Ford Coppola",
"anio_lanzamiento": 1972,
"genero": "Drama",
"rating": 9.2
},
{
"id": 3,
"titulo": "The Dark Knight",
"director": "Christopher Nolan",
"anio_lanzamiento": 2008,
"genero": "Acción",
"rating": 9.0
}
]
current_id = 4 ## Actualizar para mantener la secuencia.
# ...Resto del codigo...
Habiendo hecho esto, procedemos a crear nuestras rutas para obtener nuestras películas. Para ello, en el archivo schema.py, vamos a crear un modelo para la información que queremos devolver.
Vamos a devolver todos los campos, excepto el id de la película y el director.
from pydantic import BaseModel
from typing import List, Optional
## ...Resto del codigo de nuestros modelos
class DevolverPeliculaRespuesta(BaseModel):
titulo: str
anio_lanzamiento: int
genero: str
rating: Optional[float] = None
Ahora vayamos a main.py y creemos nuestro endpoint con el método GET:
# Ruta para obtener todas las películas
@app.get(
"/peliculas/",
response_model=list[DeolverPeliculaRespuesta],
description="Obtiene una lista de todas las películas.",
)
async def obtener_peliculas():
return peliculas_db
Probamos en nuestra documentacion y vemos que no hay error
Sin embargo, este endpoint es muy simple. Hagamos que sea más completo agregando un parámetro para filtrar por género. Vamos a nuestro archivo schemas.py y agregaremos un modelo para definir los filtros:
from pydantic import BaseModel
from typing import List, Optional
## ...Resto del codigo de nuestros modelos
class FiltroPeliculaRequest(BaseModel):
genero: str | None = None
Como es un método get, o un endpoint de solo lectura, no podemos usar el body de la request disponible, por lo que debemos usar queryparams para obtener la información.
from fastapi import Query
# ...Resto del codigo...
# Ruta para obtener todas las películas
@app.get(
"/peliculas/",
response_model=list[DeolverPeliculaRespuesta],
description="Obtiene una lista de todas las películas.",
)
async def obtener_peliculas(
filtros: Annotated[FiltroPeliculaRequest, Query()],
):
peliculas_a_enviar = []
for pelicula in peliculas_db:
if filtros.genero:
if pelicula["genero"] == filtros.genero:
peliculas_a_enviar.append(pelicula)
else:
peliculas_a_enviar.append(pelicula)
return peliculas_a_enviar
Ahora probamos en nuestra documentacion en http://127.0.0.1:8000/docs,
y vemos como todo funciona.
Eliminar Película
Ahora crearemos el endpoint para eliminar una película. Este lo haremos más simple, ya que solo necesitamos enviar el ID al backend para eliminarla. Sin embargo, sí agregaremos el HTTPException para manejar el error en caso de que la película no exista, devolviendo un error 404.
Ahora crearemos el endpoint para eliminar una pelicula. Este lo haremos mas simple, ya que solo necesitamos enviar el id al backend para eliminarla. Sin embargo, si agregaremos el HTTPException para manejar el error en caso de que la pelicula no exista, devolviendo 404.
# ...Resto del codigo.
# Ruta para eliminar una película
@app.delete("/peliculas/{pelicula_id}", status_code=status.HTTP_204_NO_CONTENT)
async def eliminar_pelicula(pelicula_id: int):
for index, pelicula in enumerate(peliculas_db):
if pelicula["id"] == pelicula_id:
index_eliminado = peliculas_db.pop(index)
return index_eliminado
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Película con ID {pelicula_id} no encontrada"
)
Actualizar Película
Por último, crearemos el endpoint para actualizar una película. En este caso, haremos uso tanto del ID enviado en el endpoint como de un esquema de datos para indicar qué parámetros queremos actualizar.
Vamos a schemas.py y crearemos un esquema para la actualización de las películas.
from pydantic import BaseModel
from typing import List, Optional
## ...Resto del codigo de nuestros modelos
class ActualizarPeliculaRequest(BaseModel):
titulo: str | None = None
director: str | None = None
anio_lanzamiento: int | None = None
genero: str | None = None
rating: Optional[float] = None
from app.schemas import ActualizarPeliculaRequest
# ...Resto del codigo...
# Ruta para actualizar película
@app.put("/peliculas/{pelicula_id}")
async def actualizar_pelicula(
pelicula_id: int,
pelicula_actualizada: Annotated[ActualizarPeliculaRequest, Body()]
):
for index, pelicula in enumerate(peliculas_db):
if pelicula["id"] == pelicula_id:
nueva_pelicula = pelicula
if pelicula_actualizada.titulo:
nueva_pelicula["titulo"] = pelicula_actualizada.titulo
if pelicula_actualizada.director:
nueva_pelicula["director"] = pelicula_actualizada.director
if pelicula_actualizada.anio_lanzamiento:
nueva_pelicula["anio_lanzamiento"] = pelicula_actualizada.anio_lanzamiento
if pelicula_actualizada.genero:
nueva_pelicula["genero"] = pelicula_actualizada.genero
if pelicula_actualizada.rating:
nueva_pelicula["rating"] = pelicula_actualizada.rating
peliculas_db[index] = nueva_pelicula
return nueva_pelicula
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Película con ID {pelicula_id} no encontrada"
)
Probamos en nuestra documentacion en http://127.0.0.1:8000/docs,
y vemos como todo funciona.
Habiendo hecho esto, ya tendríamos las bases para crear nuestros primeros endpoints. Sin embargo, estamos omitiendo lo más importante: conectar nuestra API a una base de datos real para hacer persistentes nuestros datos.