TechLead
Lección 24 de 25
5 min de lectura
Python

Python y bases de datos

Aprende a trabajar con bases de datos SQL y NoSQL usando SQLAlchemy, sqlite3 y buenas practicas de bases de datos

Acceso a bases de datos en Python

Python ofrece excelente soporte de bases de datos a traves de su modulo integrado sqlite3 para SQLite, el poderoso ORM SQLAlchemy para bases de datos SQL, y drivers dedicados para PostgreSQL (psycopg), MySQL (mysqlclient), y bases de datos NoSQL como MongoDB (pymongo) y Redis (redis-py).

SQLite con sqlite3

import sqlite3
from contextlib import closing

# Connect (creates file if not exists)
conn = sqlite3.connect("app.db")
# conn = sqlite3.connect(":memory:")  # In-memory database

# Row factory for dict-like access
conn.row_factory = sqlite3.Row

# Create tables
conn.execute("""
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL,
        age INTEGER,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
""")

# Insert data (always use parameterized queries!)
conn.execute(
    "INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
    ("Alice", "alice@example.com", 30)
)

# Insert many
users = [
    ("Bob", "bob@example.com", 25),
    ("Charlie", "charlie@example.com", 35),
    ("Diana", "diana@example.com", 28),
]
conn.executemany(
    "INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
    users
)
conn.commit()

# Query data
cursor = conn.execute("SELECT * FROM users WHERE age > ?", (27,))
for row in cursor:
    print(f"{row['name']} ({row['email']}): age {row['age']}")

# Fetch methods
cursor = conn.execute("SELECT * FROM users")
first = cursor.fetchone()    # Single row
rest = cursor.fetchall()     # All remaining rows

# Context manager for automatic commit/rollback
with conn:
    conn.execute("UPDATE users SET age = ? WHERE name = ?", (31, "Alice"))

conn.close()

# Best practice: use closing() context manager
with closing(sqlite3.connect("app.db")) as conn:
    conn.row_factory = sqlite3.Row
    with conn:
        rows = conn.execute("SELECT * FROM users").fetchall()

SQLAlchemy ORM

# pip install sqlalchemy
from sqlalchemy import create_engine, Column, Integer, String, Float, ForeignKey, select
from sqlalchemy.orm import DeclarativeBase, Session, relationship, sessionmaker

# Define the base class
class Base(DeclarativeBase):
    pass

# Define models
class Author(Base):
    __tablename__ = "authors"

    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    email = Column(String(200), unique=True)

    books = relationship("Book", back_populates="author", cascade="all, delete-orphan")

    def __repr__(self):
        return f"Author(id={self.id}, name='{self.name}')"

class Book(Base):
    __tablename__ = "books"

    id = Column(Integer, primary_key=True)
    title = Column(String(200), nullable=False)
    price = Column(Float, nullable=False)
    author_id = Column(Integer, ForeignKey("authors.id"), nullable=False)

    author = relationship("Author", back_populates="books")

    def __repr__(self):
        return f"Book(id={self.id}, title='{self.title}')"

# Create engine and tables
engine = create_engine("sqlite:///bookstore.db", echo=False)
Base.metadata.create_all(engine)
SessionLocal = sessionmaker(bind=engine)

# CRUD operations
with SessionLocal() as session:
    # Create
    author = Author(name="J.K. Rowling", email="jk@example.com")
    author.books.append(Book(title="Harry Potter 1", price=12.99))
    author.books.append(Book(title="Harry Potter 2", price=14.99))
    session.add(author)
    session.commit()

    # Read
    authors = session.execute(select(Author)).scalars().all()
    for a in authors:
        print(f"{a.name}: {[b.title for b in a.books]}")

    # Query with filters
    expensive = session.execute(
        select(Book).where(Book.price > 13.00)
    ).scalars().all()

    # Join query
    results = session.execute(
        select(Book, Author)
        .join(Author)
        .where(Author.name == "J.K. Rowling")
    ).all()

    # Update
    book = session.execute(
        select(Book).where(Book.title == "Harry Potter 1")
    ).scalar_one()
    book.price = 15.99
    session.commit()

    # Delete
    session.delete(book)
    session.commit()

Acceso asincrono a bases de datos

# pip install sqlalchemy[asyncio] aiosqlite
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy import select

# Async engine
async_engine = create_async_engine("sqlite+aiosqlite:///async_app.db")
AsyncSessionLocal = async_sessionmaker(async_engine, class_=AsyncSession)

async def get_users():
    async with AsyncSessionLocal() as session:
        result = await session.execute(select(User))
        users = result.scalars().all()
        return users

# With FastAPI
from fastapi import Depends

async def get_db():
    async with AsyncSessionLocal() as session:
        yield session

@app.get("/users")
async def list_users(db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(User))
    return result.scalars().all()

Migraciones de base de datos con Alembic

# pip install alembic

# Initialize Alembic
# alembic init migrations

# Generate a migration from model changes
# alembic revision --autogenerate -m "add users table"

# Apply migrations
# alembic upgrade head

# Rollback
# alembic downgrade -1

# Example migration file
# def upgrade():
#     op.create_table(
#         'users',
#         sa.Column('id', sa.Integer(), primary_key=True),
#         sa.Column('name', sa.String(100), nullable=False),
#         sa.Column('email', sa.String(200), unique=True),
#     )
#
# def downgrade():
#     op.drop_table('users')

Puntos clave

  • Consultas parametrizadas: Nunca uses f-strings o formato de cadenas en consultas SQL
  • SQLAlchemy para produccion: Usa el ORM para aplicaciones complejas
  • Async para rendimiento: Usa sesiones async en frameworks web asincronos
  • Alembic para migraciones: Rastrea cambios de esquema en control de versiones

Continuar Aprendiendo