lunes, 26 de enero de 2026

Aprendiendo Python de 0 a experto - Desarrollo de APIs

Introducción al Desarrollo de APIs en Python
Python // Chapter 14

Introducción al Desarrollo de APIs

Construye servicios web modernos con FastAPI, Flask y las mejores prácticas REST

Las APIs (Application Programming Interfaces) son el corazón de la arquitectura de software moderna. Permiten que diferentes aplicaciones se comuniquen entre sí, intercambien datos y expongan funcionalidades de manera estandarizada. En este capítulo exploraremos cómo construir APIs robustas en Python usando frameworks como FastAPI y Flask.

Desde los fundamentos del protocolo HTTP hasta la implementación de operaciones CRUD completas, pasando por validación de datos con Pydantic, documentación automática y manejo de errores, este módulo te proporcionará las herramientas necesarias para crear servicios web profesionales.

MÓDULO_01

Fundamentos de HTTP y REST

HTTP (HyperText Transfer Protocol) es el protocolo fundamental de la web. Cada vez que navegas por internet o consumes una API, estás utilizando HTTP. REST (REpresentational State Transfer) es un estilo arquitectónico que aprovecha HTTP para crear servicios web escalables y mantenibles.

Métodos HTTP Principales

Método Operación CRUD Descripción
GET Read Obtener recursos del servidor
POST Create Crear nuevos recursos
PUT Update Actualizar recursos existentes
DELETE Delete Eliminar recursos

Arquitectura Cliente-Servidor

Flujo de una Petición HTTP
Cliente
— HTTP Request →
API Server
— JSON Response →
Cliente

Realizando Peticiones con requests

http_requests.py
import requests

# Petición GET - Obtener datos
url = "https://httpbin.org/get"
resp = requests.get(url, params={"name": "Python"})
print(resp.json())

# Petición POST - Enviar datos
url = "https://httpbin.org/post"
data = {"title": "Learn Python", "author": "Fabrizio"}
resp = requests.post(url, json=data)
print(resp.status_code)  # 200
print(resp.json())
python http_requests.py
{'args': {'name': 'Python'}, 'headers': {...}, 'url': '...'}
200
{'json': {'title': 'Learn Python', 'author': 'Fabrizio'}, ...}
La librería requests simplifica enormemente las peticiones HTTP. El método json() del objeto response decodifica automáticamente la respuesta JSON a un diccionario Python.
MÓDULO_02

FastAPI: Framework Moderno

FastAPI es un framework web moderno y de alto rendimiento para construir APIs con Python. Ofrece validación automática de datos, documentación interactiva, y soporte nativo para operaciones asíncronas.

Características Principales

RENDIMIENTO
Comparable a Node.js y Go gracias a Starlette y Pydantic
DOCUMENTACIÓN
Swagger UI y ReDoc generados automáticamente
TYPE HINTS
Validación automática basada en anotaciones de tipo

Tu Primera API con FastAPI

main.py
from fastapi import FastAPI

# Crear instancia de la aplicación
app = FastAPI(
    title="Mi Primera API",
    description="API de ejemplo con FastAPI",
    version="1.0.0"
)

@app.get("/")
def root():
    return {"message": "¡Hola, API!"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
    return {"item_id": item_id, "query": q}
uvicorn main:app --reload
INFO: Uvicorn running on http://127.0.0.1:8000
INFO: Application startup complete.
Arquitectura FastAPI
FastAPI Application
Starlette (ASGI)
Pydantic (Validación)
Uvicorn Server
Ejecuta uvicorn main:app --reload para iniciar el servidor. La documentación interactiva estará disponible en /docs (Swagger) y /redoc (ReDoc).
MÓDULO_03

Validación con Pydantic

Pydantic es una librería de validación de datos que utiliza type hints de Python para definir esquemas. FastAPI la integra nativamente para validar automáticamente los datos de entrada y salida.

Definiendo Modelos

models.py
from pydantic import BaseModel, Field, EmailStr
from typing import Optional
from datetime import datetime

class UserBase(BaseModel):
    """Esquema base para usuarios."""
    email: EmailStr
    name: str = Field(..., min_length=1, max_length=100)
    age: int = Field(..., ge=18, le=120)

class UserCreate(UserBase):
    """Esquema para crear usuarios."""
    password: str = Field(..., min_length=8)

class UserResponse(UserBase):
    """Esquema de respuesta (sin password)."""
    id: int
    created_at: datetime
    is_active: bool = True
    
    class Config:
        from_attributes = True  # Permite usar objetos ORM

Validación Automática en Endpoints

routes.py
from fastapi import FastAPI, HTTPException
from models import UserCreate, UserResponse

app = FastAPI()
users_db = {}  # Simulación de base de datos

@app.post("/users/", response_model=UserResponse)
def create_user(user: UserCreate):
    # Pydantic valida automáticamente los datos
    user_id = len(users_db) + 1
    
    # Simular guardado en DB
    user_data = user.model_dump()
    user_data["id"] = user_id
    user_data["created_at"] = datetime.now()
    
    users_db[user_id] = user_data
    return user_data

@app.get("/users/{user_id}", response_model=UserResponse)
def get_user(user_id: int):
    if user_id not in users_db:
        raise HTTPException(
            status_code=404,
            detail="Usuario no encontrado"
        )
    return users_db[user_id]
Si los datos no cumplen con el esquema definido, FastAPI retornará automáticamente un error 422 (Unprocessable Entity) con detalles de la validación fallida.
MÓDULO_04

Operaciones CRUD Completas

CRUD representa las cuatro operaciones básicas de persistencia: Create, Read, Update y Delete. Implementar estas operaciones de manera correcta es fundamental para cualquier API RESTful.

Flujo CRUD
POST /items
GET /items
PUT /items/{id}
DELETE /items/{id}

Implementación Completa

crud_api.py
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

# Modelos
class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    in_stock: bool = True

class ItemUpdate(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None
    price: Optional[float] = None
    in_stock: Optional[bool] = None

# Base de datos simulada
items_db: dict[int, dict] = {}
counter = 0

# CREATE
@app.post("/items/", status_code=status.HTTP_201_CREATED)
def create_item(item: Item):
    global counter
    counter += 1
    items_db[counter] = {"id": counter, **item.model_dump()}
    return items_db[counter]

# READ (todos)
@app.get("/items/")
def read_items(skip: int = 0, limit: int = 10):
    items = list(items_db.values())
    return items[skip : skip + limit]

# READ (uno)
@app.get("/items/{item_id}")
def read_item(item_id: int):
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    return items_db[item_id]

# UPDATE
@app.put("/items/{item_id}")
def update_item(item_id: int, item: ItemUpdate):
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    
    stored_item = items_db[item_id]
    update_data = item.model_dump(exclude_unset=True)
    
    for key, value in update_data.items():
        stored_item[key] = value
    
    return stored_item

# DELETE
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_item(item_id: int):
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    del items_db[item_id]
    return None
El parámetro exclude_unset=True en model_dump() permite actualizaciones parciales, incluyendo solo los campos que el cliente envió explícitamente.
MÓDULO_05

Flask: Framework Minimalista

Flask es un microframework ligero y flexible. Es ideal para APIs pequeñas o cuando necesitas control total sobre la arquitectura de tu aplicación.

API Básica con Flask

flask_api.py
from flask import Flask, jsonify, request, abort

app = Flask(__name__)

# Base de datos en memoria
books = [
    {"id": 1, "title": "Clean Code", "author": "Robert Martin"},
    {"id": 2, "title": "Design Patterns", "author": "Gang of Four"},
]

@app.route("/api/books", methods=["GET"])
def get_books():
    return jsonify(books)

@app.route("/api/books/<int:book_id>", methods=["GET"])
def get_book(book_id):
    book = next((b for b in books if b["id"] == book_id), None)
    if book is None:
        abort(404)
    return jsonify(book)

@app.route("/api/books", methods=["POST"])
def create_book():
    if not request.json:
        abort(400)
    
    new_book = {
        "id": books[-1]["id"] + 1 if books else 1,
        "title": request.json.get("title"),
        "author": request.json.get("author"),
    }
    books.append(new_book)
    return jsonify(new_book), 201

if __name__ == "__main__":
    app.run(debug=True)
python flask_api.py
* Running on http://127.0.0.1:5000
* Debug mode: on
MÓDULO_06

Códigos de Estado HTTP

Los códigos de estado HTTP comunican el resultado de una petición. Usar los códigos correctos hace tu API más intuitiva y fácil de debuggear.

Códigos Más Comunes

Código Nombre Uso
200 OK Petición exitosa (GET, PUT)
201 Created Recurso creado exitosamente (POST)
204 No Content Éxito sin contenido de respuesta (DELETE)
400 Bad Request Datos de entrada inválidos
401 Unauthorized Autenticación requerida
404 Not Found Recurso no encontrado
422 Unprocessable Entity Error de validación
500 Internal Server Error Error del servidor

Manejo de Excepciones Personalizado

exceptions.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

# Excepción personalizada
class ItemNotFoundException(Exception):
    def __init__(self, item_id: int):
        self.item_id = item_id

# Handler personalizado
@app.exception_handler(ItemNotFoundException)
async def item_not_found_handler(
    request: Request,
    exc: ItemNotFoundException
):
    return JSONResponse(
        status_code=404,
        content={
            "error": "not_found",
            "message": f"Item con ID {exc.item_id} no existe",
            "item_id": exc.item_id
        }
    )

@app.get("/items/{item_id}")
def get_item(item_id: int):
    if item_id > 100:
        raise ItemNotFoundException(item_id)
    return {"item_id": item_id}
MÓDULO_07

Dependencias e Inyección

FastAPI tiene un poderoso sistema de inyección de dependencias que permite reutilizar lógica común, gestionar conexiones a bases de datos, autenticación y más.

Sistema de Dependencias

dependencies.py
from fastapi import FastAPI, Depends, HTTPException, Header

app = FastAPI()

# Dependencia: verificar API key
async def verify_api_key(
    x_api_key: str = Header(...)
):
    if x_api_key != "secret-api-key-123":
        raise HTTPException(
            status_code=401,
            detail="API Key inválida"
        )
    return x_api_key

# Dependencia: conexión a BD simulada
def get_db():
    db = {"connected": True}  # Simular conexión
    try:
        yield db
    finally:
        db["connected"] = False  # Cerrar conexión

# Endpoint protegido con dependencias
@app.get("/secure-data/")
async def get_secure_data(
    api_key: str = Depends(verify_api_key),
    db: dict = Depends(get_db)
):
    return {
        "data": "Información confidencial",
        "db_status": db["connected"]
    }
Cadena de Dependencias
Request
verify_api_key
get_db
Endpoint
Las dependencias con yield permiten ejecutar código de limpieza después de que la respuesta sea enviada, ideal para cerrar conexiones de base de datos.
MÓDULO_08

Variables de Entorno y Configuración

Las credenciales, claves de API y configuraciones sensibles nunca deben estar hardcodeadas. Usa variables de entorno y archivos .env para gestionar la configuración.

Configuración con Pydantic Settings

.env
# Archivo de configuración local
DATABASE_URL=postgresql://user:pass@localhost/mydb
API_SECRET_KEY=super-secret-key-12345
DEBUG=true
ALLOWED_HOSTS=localhost,127.0.0.1
config.py
from pydantic_settings import BaseSettings
from functools import lru_cache

class Settings(BaseSettings):
    """Configuración de la aplicación."""
    database_url: str
    api_secret_key: str
    debug: bool = False
    allowed_hosts: list[str] = ["localhost"]
    
    class Config:
        env_file = ".env"

@lru_cache()
def get_settings():
    return Settings()

# Uso en la aplicación
from fastapi import FastAPI, Depends

app = FastAPI()

@app.get("/info")
def get_info(settings: Settings = Depends(get_settings)):
    return {
        "debug_mode": settings.debug,
        "hosts": settings.allowed_hosts
    }
Nunca incluyas archivos .env en el control de versiones. Añade .env a tu .gitignore y proporciona un archivo .env.example como plantilla.
MÓDULO_09

Rate Limiting y Throttling

Protege tu API del abuso limitando la cantidad de peticiones que un cliente puede realizar en un período de tiempo determinado.

Implementación con Flask-Limiter

throttling.py
from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)

# Configurar el limitador
limiter = Limiter(
    get_remote_address,
    app=app,
    default_limits=["100 per day", "10 per hour"],
    storage_uri="memory://",
)

# Endpoint con límite por defecto
@app.route("/api/standard")
def standard_api():
    return {"message": "Endpoint estándar"}

# Endpoint con límite personalizado
@app.route("/api/premium")
@limiter.limit("2 per minute")
def premium_api():
    return {"message": "Endpoint premium (muy limitado)"}

# Endpoint sin límite
@app.route("/api/health")
@limiter.exempt
def health_check():
    return {"status": "healthy"}

if __name__ == "__main__":
    app.run(debug=True)
Exceder el límite
HTTP/1.1 429 TOO MANY REQUESTS
{"error": "ratelimit exceeded", "message": "2 per 1 minute"}
MÓDULO_10

Testing de APIs

Las pruebas automatizadas son esenciales para mantener la calidad de tu API. FastAPI incluye un cliente de pruebas integrado basado en httpx.

Tests con Pytest

test_api.py
import pytest
from fastapi.testclient import TestClient
from main import app

# Crear cliente de pruebas
client = TestClient(app)

def test_read_root():
    """Probar endpoint raíz."""
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "¡Hola, API!"}

def test_create_item():
    """Probar creación de item."""
    item_data = {
        "name": "Test Item",
        "price": 29.99,
        "in_stock": True
    }
    response = client.post("/items/", json=item_data)
    assert response.status_code == 201
    assert response.json()["name"] == "Test Item"

def test_item_not_found():
    """Probar error 404."""
    response = client.get("/items/99999")
    assert response.status_code == 404

def test_validation_error():
    """Probar validación fallida."""
    invalid_data = {"name": ""}  # Falta price
    response = client.post("/items/", json=invalid_data)
    assert response.status_code == 422
pytest test_api.py -v
test_api.py::test_read_root PASSED
test_api.py::test_create_item PASSED
test_api.py::test_item_not_found PASSED
test_api.py::test_validation_error PASSED

======= 4 passed in 0.15s =======
MÓDULO_11

Documentación Automática

FastAPI genera documentación interactiva automáticamente basándose en tus modelos Pydantic y decoradores de ruta.

Enriquecer la Documentación

documented_api.py
from fastapi import FastAPI, Query, Path
from pydantic import BaseModel, Field

app = FastAPI(
    title="Bookstore API",
    description="API para gestionar una librería virtual",
    version="2.0.0",
    contact={
        "name": "Soporte",
        "email": "api@bookstore.com"
    }
)

class Book(BaseModel):
    """Modelo de libro con documentación."""
    title: str = Field(
        ...,
        example="Clean Code",
        description="Título del libro"
    )
    author: str = Field(
        ...,
        example="Robert C. Martin"
    )
    isbn: str = Field(
        ...,
        pattern=r"^\d{13}$",
        example="9780132350884"
    )

@app.get(
    "/books/{book_id}",
    response_model=Book,
    summary="Obtener libro por ID",
    description="Retorna los detalles completos de un libro.",
    responses={
        404: {"description": "Libro no encontrado"}
    }
)
def get_book(
    book_id: int = Path(
        ...,
        ge=1,
        description="ID único del libro"
    )
):
    """
    Busca un libro por su identificador único.
    
    - **book_id**: ID numérico del libro (≥ 1)
    """
    return {"title": "Example", "author": "Author", "isbn": "1234567890123"}
Accede a /docs para Swagger UI interactivo o /redoc para documentación estilo ReDoc. Ambas se generan automáticamente desde tu código.

Resumen del Capítulo

01 // HTTP y REST

Los métodos GET, POST, PUT y DELETE forman la base de las operaciones CRUD en APIs RESTful.

02 // FastAPI

Framework moderno con validación automática, documentación integrada y alto rendimiento.

03 // Pydantic

Define esquemas de datos con type hints para validación y serialización automática.

04 // Flask

Microframework minimalista ideal para APIs pequeñas o control arquitectónico total.

05 // Códigos HTTP

Usa códigos de estado apropiados: 2xx éxito, 4xx errores del cliente, 5xx errores del servidor.

06 // Seguridad

Implementa autenticación, rate limiting y nunca expongas credenciales en el código.

Basado en "Learn Python Programming, 4th Edition" // Fabrizio Romano & Heinrich Kruger

Chapter 14: Introduction to API Development