Creando una API con Python y FastAPI

Hola amigos en este post les dejo una guía rápida para implementar una API.

Instalar Python

Crear una carpeta del proyecto (Entorno virtual)

Usaremos visual studio code e instalamos la extensión de python

Ejecutamos el siguiente comando en terminal:

python -m venv venv

Luego:

venv\Scripts\actívate


Deberá mostrarse algo similar:

Ejecutamos en la terminal:

pip install fastapi uvicorn

Configurar el intérprete de Python en VS Code

  • Presiona Ctrl + Shift + P y busca «Seleccionar intérprete de Python».
  • Selecciona el entorno virtual (venv).

Creamos un documento main.py con el siguiente contenido:

from fastapi import FastAPI


app = FastAPI()

@app.get("/")
def home() :
    return "Hola Apolo"

Ejecutamos en terminal con el parámetro reload que detectará cambios automáticamente y no tener que ejecutar el servidor cada rato:

uvicorn main:app --reload

y se mostrará un log con la url y puerto, en mi caso sería:

http://127.0.0.1:8000/

Para cambiar el puerto y hacer accesible desde cualquier disposito de la red podemos hacer lo siguiente con el apoyo de parámetro host y port:

uvicorn main:app --host 0.0.0.0 --port 8080 --reload

La ip será la ip asignada al equipo donde se está ejecutando.

Documentación

Si queremos ver la documentación, esta se genera automáticamente con openapi, ingresando a la siguiente url:

http://127.0.0.1:7777/docs

Y se verá algo así:

Filtrar por parámetros URL

Para cambiar el nombre del texto que default y agrupar por módulos o funcionalidades, añadimos el parámetro tag, y anexamos un ejemplo para regresar un listado de objetos en formato json, el código debería verse así:

personas = [
    {
        "id" : 1,
        "nombre" : "Apolo Miranda"
    },
      {
        "id" : 2,
        "nombre" : "Artemisa Miranda"
    }
]

from fastapi import FastAPI


app = FastAPI()

 


@app.get("/", tags=["Home"])
def home() :
    return "Hola Apolo bebé"


@app.get("/Personas", tags=["Home"])
def Personas() :
    return personas

El resultado:

Para crear un endpoint que reciba un parámetro y busque en nuestro listado:


@app.get("/Personas/{id}", tags=["Home"])
def obtener_persona(id: int):
    for persona in personas:
        if persona["id"] == id:
            return persona
    return {"error": "Persona no encontrada"}

y se debería ver algo así:

Filtrar por query string

Añadimos el nuevo método

@app.get("/Personas/", tags=["Home"])
def obtener_persona_por_sexo(sexo: str = None):
    if sexo:
        filtradas = [persona for persona in personas if persona["sexo"].lower() == sexo.lower()]
        if filtradas:
            return filtradas
        return {"error": f"No se encontraron personas con sexo '{sexo}'"}
    
    return personas  # Devuelve toda la lista si no se pasa `sexo`

Y se vería algo asi:

Método post para crear una persona

Aquí mostraré 2 formas de hacerlo, un basándonos en el tipo BaseModel y otro por parámetros y la función Body(), ya que el modelo forza a recibir todas las propiedas y tal vez no queramos recibir el ID ya que este sería generado en el backend

Nuestros imports deben estar así hasta este punto:

from fastapi import FastAPI
from pydantic import BaseModel
from fastapi.params import Body
import random

Definiendo nuestra modelo:

class Persona(BaseModel):
    id: int
    nombre: str
    sexo: str

Definiendo nuestras funciones y algunas validaciones:

@app.post("/Personas", tags=["Home"])
def agregar_persona(persona: Persona):
    # Verificar si el ID ya existe
    for p in personas:
        if p["id"] == persona.id:
            raise HTTPException(status_code=400, detail="El ID ya existe")

    # Agregar la nueva persona a la lista
    
   ## personas.append( persona.dict())  Forma Pydantic v1
    personas.append( persona.model_dump())  ## Forma Pydantic v2

    return {"mensaje": "Persona agregada con éxito", "persona": nueva_persona}


@app.post("/v2/Personas", tags=["Home"])
def agregar_persona(
    nombre: str = Body(),
    sexo: str = Body()
):
     # Generar un ID único aleatorio
    nuevo_id = random.randint(1000, 9999)  # ID entre 1000 y 9999
    
    # Verificar que el ID generado no esté duplicado (repetir hasta encontrar uno único)
    while any(p["id"] == nuevo_id for p in personas):
        nuevo_id = random.randint(1000, 9999)

    # Agregar nueva persona
    nueva_persona = {"id": nuevo_id, "nombre": nombre, "sexo": sexo}
    personas.append(nueva_persona)

    return {"mensaje": "Persona agregada con éxito", "persona": nueva_persona}

Ejecutando la primer función con Modelo

Ejecutando la segunda versión con Id autogenerado:

Parámetros obligatorio y opcionales

En los parámetros de la función puedes añadir … en medio de los paréntesis para hacerlo obligatorio, si es opcional solo pon (None)

 sexo: str = Body(...) 

 sexo: str = Body(None)

Parámetros opcionales en modelos

Usando None

class Persona(BaseModel):
    id: int | None = None
    nombre: str
    sexo: str

Usando pydantic

from typing import Optional

class Persona(BaseModel):
    id: Optional[int] = None
    nombre: str
    sexo: str

Lo ideal es crear dos modelos/esquemas, uno para crear, actualizar y otro para mostrar ya que en uno no es requerido el id y en otro si debemos mostrarlo

Método PUT para agregar personas

Agregamos una nueva librería para el manejo de excepciones:

from fastapi import FastAPI, HTTPException

Agregamos el siguiente endpoint:


@app.put("/Personas/{id}", tags=["Personas"])
def actualizar_persona(id: int, nombre: str = Body(...), sexo: str = Body(...)):
    for persona in personas:
        if persona["id"] == id:
            persona["nombre"] = nombre
            persona["sexo"] = sexo
            return {"mensaje": "Persona actualizada con éxito", "persona": persona}
    
    raise HTTPException(status_code=404, detail="Persona no encontrada")

Método DELETE para eliminar personas

Añadí un ejemplo manipulando la lista y otro usando índices

@app.delete("/Personas/{id}", tags=["Personas"])
def eliminar_persona(id: int):
    for persona in personas[:]:  # Se usa una copia con `[:]` para evitar problemas al eliminar elementos
        if persona["id"] == id:
            personas.remove(persona)
            return {"mensaje": "Persona eliminada con éxito"}
    
    raise HTTPException(status_code=404, detail="Persona no encontrada")


@app.delete("/v2/Personas/{id}", tags=["Personas"])
def eliminar_persona(id: int):
    for index, persona in enumerate(personas):
        if persona["id"] == id:
            personas.pop(index)
            return {"mensaje": "Persona eliminada con éxito"}
    
    raise HTTPException(status_code=404, detail="Persona no encontrada")

Probando los endpoints eliminado 2 y actualizando 1 y posteriormente consultar dichos cambios con el get:

Validaciones de entrada de datos

Nuestras importaciones deberían verse así:

 
from fastapi import FastAPI, HTTPException, Body
from pydantic import BaseModel, Field
from datetime import datetime
import random

Nuestro modelo añadiendo unas validaciones y añadiendo comentarios de su significado:

class Persona(BaseModel):
    id: int | None = None
    nombre: str = Field(..., min_length=10, max_length=50)  # Validación de nombre
    sexo: str
    anio_nacimiento: int = Field(None, gt=1900, lt=datetime.now().year)  # Año opcional con restricciones

  #  gt=1900, 'gt' (greater than) significa que debe ser **mayor** a 1900
  #      ge=1920,   'ge' (greater or equal) significa que debe ser **mayor o igual** a 1920
  #      lt=datetime.now().year,  'lt' (less than) significa que debe ser **menor** al año actual
  #       le=datetime.now().year - 1  'le' (less or equal) significa que debe ser **menor o igual** al año anterior

Explicando nuestras referencias

# Importación de FastAPI para crear la aplicación web
from fastapi import FastAPI, HTTPException, Body

# Importaciones de Pydantic para definir modelos de datos y validaciones
from pydantic import BaseModel, Field

# Importación de datetime para trabajar con fechas y obtener el año actual
from datetime import datetime
 

# Importación de random para generar valores aleatorios, por ejemplo, IDs únicos
import random
Scroll al inicio