10. Conexión con base de datos
🗄️ 10.1 Introducción a bases de datos con FastAPI
Section titled “🗄️ 10.1 Introducción a bases de datos con FastAPI”Opciones de bases de datos
Section titled “Opciones de bases de datos”FastAPI puede conectarse a cualquier base de datos. Las opciones más comunes son:
| Base de datos | Tipo | Uso común |
|---|---|---|
| SQLite | SQL (archivo) | Desarrollo, prototipos |
| PostgreSQL | SQL | Producción |
| MySQL | SQL | Producción |
| MongoDB | NoSQL | Datos flexibles |
ORMs disponibles
Section titled “ORMs disponibles”| ORM | Descripción |
|---|---|
| SQLAlchemy | El más popular, maduro y completo |
| Tortoise ORM | Async nativo, similar a Django |
| SQLModel | Creado por el autor de FastAPI |
Dependencias necesarias
Section titled “Dependencias necesarias”# Para SQLite (incluido en Python)pip install sqlalchemy
# Para PostgreSQLpip install sqlalchemy psycopg2-binary
# Para MySQLpip install sqlalchemy pymysql🔌 10.2 Conexión con SQLite
Section titled “🔌 10.2 Conexión con SQLite”Configurar conexión
Section titled “Configurar conexión”# app/db/database.pyfrom sqlalchemy import create_enginefrom sqlalchemy.ext.declarative import declarative_basefrom sqlalchemy.orm import sessionmaker
# URL de conexión SQLite (archivo local)SQLALCHEMY_DATABASE_URL = "sqlite:///./app.db"
# Crear engineengine = create_engine( SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} # Solo para SQLite)
# Crear sesiónSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Base para los modelosBase = declarative_base()Crear dependencia de sesión
Section titled “Crear dependencia de sesión”# app/db/database.py (continuación)
def get_db(): """Dependencia que proporciona una sesión de base de datos.""" db = SessionLocal() try: yield db finally: db.close()
# Uso en endpoints:# @app.get("/usuarios")# def listar(db: Session = Depends(get_db)):# ...🐘 10.3 Conexión con PostgreSQL
Section titled “🐘 10.3 Conexión con PostgreSQL”Configurar conexión
Section titled “Configurar conexión”# app/db/database.pyfrom sqlalchemy import create_enginefrom sqlalchemy.ext.declarative import declarative_basefrom sqlalchemy.orm import sessionmaker
# URL de conexión PostgreSQL# Formato: postgresql://usuario:password@host:puerto/nombre_dbSQLALCHEMY_DATABASE_URL = "postgresql://postgres:password@localhost:5432/mi_api"
# Crear engine (sin connect_args para PostgreSQL)engine = create_engine(SQLALCHEMY_DATABASE_URL)
# Crear sesiónSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Base para los modelosBase = declarative_base()
def get_db(): db = SessionLocal() try: yield db finally: db.close()Usar variables de entorno
Section titled “Usar variables de entorno”# app/core/config.pyfrom pydantic import BaseSettings
class Settings(BaseSettings): database_url: str = "sqlite:///./app.db"
class Config: env_file = ".env"
settings = Settings()# .envDATABASE_URL=postgresql://postgres:password@localhost:5432/mi_api# app/db/database.pyfrom sqlalchemy import create_enginefrom sqlalchemy.ext.declarative import declarative_basefrom sqlalchemy.orm import sessionmakerfrom app.core.config import settings
engine = create_engine(settings.database_url)SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)Base = declarative_base()📊 10.4 Modelos SQLAlchemy
Section titled “📊 10.4 Modelos SQLAlchemy”Crear modelos de base de datos
Section titled “Crear modelos de base de datos”# app/db/models.pyfrom sqlalchemy import Column, Integer, String, Float, Boolean, DateTime, ForeignKeyfrom sqlalchemy.orm import relationshipfrom sqlalchemy.sql import funcfrom app.db.database import Base
class Usuario(Base): __tablename__ = "usuarios"
id = Column(Integer, primary_key=True, index=True) nombre = Column(String(100), nullable=False) email = Column(String(100), unique=True, index=True, nullable=False) password_hash = Column(String(255), nullable=False) activo = Column(Boolean, default=True) fecha_registro = Column(DateTime(timezone=True), server_default=func.now())
# Relación con pedidos pedidos = relationship("Pedido", back_populates="usuario")
class Producto(Base): __tablename__ = "productos"
id = Column(Integer, primary_key=True, index=True) nombre = Column(String(200), nullable=False) descripcion = Column(String(500)) precio = Column(Float, nullable=False) stock = Column(Integer, default=0) activo = Column(Boolean, default=True) fecha_creacion = Column(DateTime(timezone=True), server_default=func.now())
class Pedido(Base): __tablename__ = "pedidos"
id = Column(Integer, primary_key=True, index=True) usuario_id = Column(Integer, ForeignKey("usuarios.id"), nullable=False) total = Column(Float, nullable=False) estado = Column(String(50), default="pendiente") fecha_creacion = Column(DateTime(timezone=True), server_default=func.now())
# Relación con usuario usuario = relationship("Usuario", back_populates="pedidos")Crear las tablas
Section titled “Crear las tablas”# app/main.pyfrom fastapi import FastAPIfrom app.db.database import engine, Basefrom app.db import models
# Crear todas las tablasBase.metadata.create_all(bind=engine)
app = FastAPI(title="Mi API con BD")🔄 10.5 Operaciones CRUD
Section titled “🔄 10.5 Operaciones CRUD”Esquemas Pydantic
Section titled “Esquemas Pydantic”# app/schemas/usuario.pyfrom pydantic import BaseModel, EmailStrfrom typing import Optionalfrom datetime import datetime
class UsuarioBase(BaseModel): nombre: str email: EmailStr
class UsuarioCrear(UsuarioBase): password: str
class UsuarioActualizar(BaseModel): nombre: Optional[str] = None email: Optional[EmailStr] = None activo: Optional[bool] = None
class UsuarioRespuesta(UsuarioBase): id: int activo: bool fecha_registro: datetime
class Config: orm_mode = TrueFunciones CRUD
Section titled “Funciones CRUD”# app/crud/usuario.pyfrom sqlalchemy.orm import Sessionfrom app.db.models import Usuariofrom app.schemas.usuario import UsuarioCrear, UsuarioActualizarfrom passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def obtener_usuario(db: Session, usuario_id: int): """Obtiene un usuario por ID.""" return db.query(Usuario).filter(Usuario.id == usuario_id).first()
def obtener_usuario_por_email(db: Session, email: str): """Obtiene un usuario por email.""" return db.query(Usuario).filter(Usuario.email == email).first()
def listar_usuarios(db: Session, skip: int = 0, limit: int = 100): """Lista usuarios con paginación.""" return db.query(Usuario).offset(skip).limit(limit).all()
def crear_usuario(db: Session, usuario: UsuarioCrear): """Crea un nuevo usuario.""" password_hash = pwd_context.hash(usuario.password) db_usuario = Usuario( nombre=usuario.nombre, email=usuario.email, password_hash=password_hash ) db.add(db_usuario) db.commit() db.refresh(db_usuario) return db_usuario
def actualizar_usuario(db: Session, usuario_id: int, datos: UsuarioActualizar): """Actualiza un usuario existente.""" db_usuario = obtener_usuario(db, usuario_id) if db_usuario: datos_dict = datos.dict(exclude_unset=True) for key, value in datos_dict.items(): setattr(db_usuario, key, value) db.commit() db.refresh(db_usuario) return db_usuario
def eliminar_usuario(db: Session, usuario_id: int): """Elimina un usuario (soft delete).""" db_usuario = obtener_usuario(db, usuario_id) if db_usuario: db_usuario.activo = False db.commit() return db_usuarioEndpoints con base de datos
Section titled “Endpoints con base de datos”# app/routers/usuarios.pyfrom fastapi import APIRouter, Depends, HTTPExceptionfrom sqlalchemy.orm import Sessionfrom typing import List
from app.db.database import get_dbfrom app.schemas.usuario import UsuarioCrear, UsuarioActualizar, UsuarioRespuestafrom app.crud import usuario as crud_usuario
router = APIRouter(prefix="/usuarios", tags=["Usuarios"])
@router.get("/", response_model=List[UsuarioRespuesta])def listar_usuarios(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)): """Lista todos los usuarios.""" usuarios = crud_usuario.listar_usuarios(db, skip=skip, limit=limit) return usuarios
@router.get("/{usuario_id}", response_model=UsuarioRespuesta)def obtener_usuario(usuario_id: int, db: Session = Depends(get_db)): """Obtiene un usuario por ID.""" usuario = crud_usuario.obtener_usuario(db, usuario_id) if not usuario: raise HTTPException(status_code=404, detail="Usuario no encontrado") return usuario
@router.post("/", response_model=UsuarioRespuesta, status_code=201)def crear_usuario(usuario: UsuarioCrear, db: Session = Depends(get_db)): """Crea un nuevo usuario.""" # Verificar email único db_usuario = crud_usuario.obtener_usuario_por_email(db, usuario.email) if db_usuario: raise HTTPException(status_code=400, detail="Email ya registrado") return crud_usuario.crear_usuario(db, usuario)
@router.patch("/{usuario_id}", response_model=UsuarioRespuesta)def actualizar_usuario( usuario_id: int, datos: UsuarioActualizar, db: Session = Depends(get_db)): """Actualiza un usuario.""" usuario = crud_usuario.actualizar_usuario(db, usuario_id, datos) if not usuario: raise HTTPException(status_code=404, detail="Usuario no encontrado") return usuario
@router.delete("/{usuario_id}")def eliminar_usuario(usuario_id: int, db: Session = Depends(get_db)): """Desactiva un usuario.""" usuario = crud_usuario.eliminar_usuario(db, usuario_id) if not usuario: raise HTTPException(status_code=404, detail="Usuario no encontrado") return {"mensaje": "Usuario desactivado"}💡 10.6 Ejemplo completo de API con base de datos
Section titled “💡 10.6 Ejemplo completo de API con base de datos”Estructura del proyecto
Section titled “Estructura del proyecto”mi_api/├── app/│ ├── __init__.py│ ├── main.py│ ├── core/│ │ └── config.py│ ├── db/│ │ ├── __init__.py│ │ ├── database.py│ │ └── models.py│ ├── schemas/│ │ ├── __init__.py│ │ └── usuario.py│ ├── crud/│ │ ├── __init__.py│ │ └── usuario.py│ └── routers/│ ├── __init__.py│ └── usuarios.py├── .env└── requirements.txtmain.py completo
Section titled “main.py completo”# app/main.pyfrom fastapi import FastAPIfrom app.db.database import engine, Basefrom app.routers import usuarios
# Crear tablasBase.metadata.create_all(bind=engine)
app = FastAPI( title="API con Base de Datos", description="API CRUD completa con SQLAlchemy", version="1.0.0")
# Incluir routersapp.include_router(usuarios.router)
@app.get("/")def inicio(): return {"mensaje": "API funcionando"}
@app.get("/health")def health_check(): return {"status": "ok", "database": "connected"}requirements.txt
Section titled “requirements.txt”fastapi==0.104.1uvicorn==0.24.0sqlalchemy==2.0.23psycopg2-binary==2.9.9python-dotenv==1.0.0passlib[bcrypt]==1.7.4pydantic[email]==2.5.2
🐝