Python SQLAlchemy Pydantic en la práctica: el código y los comandos que realmente importan
Python SQLAlchemy Pydantic : lo esencial en un artículo — código real, esquemas y pasos concretos, extractos de un curso de 27 lecciones.
Sin teoría interminable aquí: abrimos la terminal y practicamos. Aquí lo esencial de Python SQLAlchemy Pydantic, extraído directamente de un curso completo de 27 lecciones — con código real que puedes copiar y pegar ahora.
- Introducción
- SQLAlchemy Core
- ORM Declarative
- Sesión y transacciones
- Consultas avanzadas
Factories con factory-boy
factory-boy y Faker, sin duplicar código en cada test.Objetivos pedagógicos
- Explicar por qué las fixtures escritas a mano se vuelven inmanejables
- Definir una
SQLAlchemyModelFactoryvinculada a una sesión - Usar
Faker,SequenceySubFactorypara datos realistas - Gestionar las relaciones (one-to-many) mediante
RelatedFactoryList - Conectar las factories a las fixtures de pytest del módulo anterior
La intuición: una fábrica en lugar de un formulario vacío
En un test necesitas un objeto User válido. Escrito a mano, esto da User(name="Test", email="a@b.c", age=30, is_active=True, ...). Repetido en 40 tests, es 40 veces la misma tarea, y el día que añadas una columna NOT NULL, los 40 tests fallan.
Una factory es una fábrica: describes una sola vez cómo fabricar un User típico, luego cada test pide UserFactory() y recibe un objeto completo y válido. Si un test necesita un caso particular, solo sobrecarga el campo en cuestión: UserFactory(age=17). Todo lo demás se rellena automáticamente.
Declarativo
Describes el modelo tipo, no cada instancia. El código de test sigue siendo corto y legible.
Realista
Faker genera nombres, emails y direcciones plausibles en lugar de "aaa".
Reproducible
Se puede fijar la semilla (seed) para obtener los mismos datos en cada ejecución.
Instalación y primera factory
Instalamos factory-boy (que incluye Faker) :
RelatedFactoryList con size=100 inserta 100 objetos uno por uno. Para grandes volúmenes de seed, prefiere create_batch o una inserción masiva.Conectar las factories a pytest
Reutilizamos la fixture db_session del módulo anterior (rollback automático). Inyectamos esta sesión en las factories y luego exponemos fixtures prácticas.
Consultas complejas y reporting
Objetivos pedagógicos
- Escribir una agregación multi-tabla (clientes, pedidos, líneas) con
GROUP BY - Clasificar productos con una función de ventana
rank() - Estructurar una consulta legible con una CTE
- Mapear un resultado heterogéneo (Row) a un esquema Pydantic de reporting
- Validar estas consultas mediante tests sobre datos de factory
La intuición: separar lo transaccional de lo analítico
Hasta ahora, el ERP crea pedidos (transaccional). El reporting responde a otras preguntas: ¿quiénes son mis mejores clientes? ¿qué producto se vende mejor este mes? Estas consultas no modifican nada; agregan y clasifican. Se aíslan en un módulo reporting/ para no mezclar responsabilidades.
Servicios transaccionales
Crean y modifican entidades (realizar pedido, decrementar stock). Una sesión, una transacción.
Consultas de reporting
Solo lectura, agregaciones pesadas. Devuelven filas (Row) en lugar de entidades ORM.
Cifra de negocio por cliente
Une Customer, Order y OrderLine, luego suma quantity * unit_price. Usa la API select() 2.0 y func para la agregación.
Proyecto ERP - Modelos
Proyecto final • Pliego de condiciones • modelos ORM
Especificaciones
- Gestionar clientes (B2B/B2C) con direcciones
- Catálogo de productos con stock multi-almacén
- Pedidos multilínea con estado workflow
- Facturación automática con IVA
- Movimientos de stock auditados
- Informes por mes / por cliente / por producto
Estructura del proyecto
erp/ ├── alembic.ini ├── migrations/ ├── app/ │ ├── core/ │ │ ├── config.py # Configuración │ │ └── database.py │ ├── models/ │ │ ├── customer.py │ │ ├── product.py │ │ ├── order.py │ │ └── invoice.py │ ├── schemas/ # Pydantic In/Out │ ├── services/ # Lógica de negocio │ ├── api/ # Rutas FastAPI │ └── main.py └── tests/
Modelo Customer
import enum from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy import ForeignKey from datetime import datetime class CustomerType(str, enum.Enum): B2B = "b2b" B2C = "b2c" class Customer(Base): __tablename__ = "customers" id: Mapped[int] = mapped_column(primary_key=True) type: Mapped[CustomerType] name: Mapped[str] email: Mapped[str] = mapped_column(unique=True) tax_id: Mapped[str | None] # para B2B created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) addresses: Mapped[list["Address"]] = relationship(back_populates="customer", cascade="all") orders: Mapped[list["Order"]] = relationship(back_populates="customer") class Address(Base): __tablename__ = "addresses" id: Mapped[int] = mapped_column(primary_key=True) customer_id: Mapped[int] = mapped_column(ForeignKey("customers.id")) street: Mapped[str] city: Mapped[str] zip_code: Mapped[str] country: Mapped[str] is_billing: Mapped[bool] = mapped_column(default=False) is_shipping: Mapped[bool] = mapped_column(default=True) customer: Mapped["Customer"] = relationship(back_populates="addresses")
Modelo Product y Stock
from decimal import Decimal class Product(Base): __tablename__ = "products" id: Mapped[int] = mapped_column(primary_key=True) sku: Mapped[str] = mapped_column(unique=True) name: Mapped[str] price: Mapped[Decimal] = mapped_column(Numeric(10, 2)) vat_rate: Mapped[Decimal] = mapped_column(Numeric(4, 2), default=Decimal("20.00")) active: Mapped[bool] = mapped_column(default=True) stocks: Mapped[list["Stock"]] = relationship(back_populates="product") class Warehouse(Base): __tablename__ = "warehouses" id: Mapped[int] = mapped_column(primary_key=True) code: Mapped[str] = mapped_column(unique=True) name: Mapped[str] class Stock(Base): __tablename__ = "stocks" id: Mapped[int] = mapped_column(primary_key=True) product_id: Mapped[int] = mapped_column(ForeignKey("products.id")) warehouse_id: Mapped[int] = mapped_column(ForeignKey("warehouses.id")) quantity: Mapped[int] = mapped_column(default=0) __table_args__ = ( UniqueConstraint("product_id", "warehouse_id"), CheckConstraint("quantity >= 0"), ) product: Mapped["Product"] = relationship(back_populates="stocks")
Modelo Order y OrderLine
class OrderStatus(str, enum.Enum): DRAFT = "draft" CONFIRMED = "confirmed" SHIPPED = "shipped" DELIVERED = "delivered" CANCELLED = "cancelled" class Order(Base): __tablename__ = "orders" id: Mapped[int] = mapped_column(primary_key=True) customer_id: Mapped[int] = mapped_column(ForeignKey("customers.id")) reference: Mapped[str] = mapped_column(unique=True) status: Mapped[OrderStatus] = mapped_column(default=OrderStatus.DRAFT) created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) customer: Mapped["Customer"] = relationship(back_populates="orders") lines: Mapped[list["OrderLine"]] = relationship(back_populates="order", cascade="all, delete-orphan") invoice: Mapped["Invoice"] = relationship(back_populates="order", uselist=False) class OrderLine(Base): __tablename__ = "order_lines" id: Mapped[int] = mapped_column(primary_key=True) order_id: Mapped[int] = mapped_column(ForeignKey("orders.id")) product_id: Mapped[int] = mapped_column(ForeignKey("products.id")) quantity: Mapped[int] = mapped_column() unit_price: Mapped[Decimal] = mapped_column(Numeric(10, 2)) order: Mapped["Order"] = relationship(back_populates="lines") product: Mapped["Product"] = relationship()
Modelo Invoice y StockMovement
class Invoice(Base): __tablename__ = "invoices" id: Mapped[int] = mapped_column(primary_key=True) order_id: Mapped[int] = mapped_column(ForeignKey("orders.id"), unique=True) number: Mapped[str] = mapped_column(unique=True) issued_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) subtotal: Mapped[Decimal] = mapped_column(Numeric(10, 2)) vat: Mapped[Decimal] = mapped_column(Numeric(10, 2)) total: Mapped[Decimal] = mapped_column(Numeric(10, 2)) paid: Mapped[bool] = mapped_column(default=False) order: Mapped["Order"] = relationship(back_populates="invoice") class StockMovement(Base): __tablename__ = "stock_movements" id: Mapped[int] = mapped_column(primary_key=True) product_id: Mapped[int] = mapped_column(ForeignKey("products.id")) warehouse_id: Mapped[int] = mapped_column(ForeignKey("warehouses.id")) quantity: Mapped[int] # negativo = salida reason: Mapped[str] # "order_123", "restock" created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
Resumen
- Modelos separados por dominio (customer, order, product)
- Decimal para el dinero (nunca float)
- Enum para los estados workflow
- cascade para las agregaciones (Order -> lines)
- StockMovement = audit log inmutable
Este artículo cubre los extractos más útiles — el curso completo Python SQLAlchemy Pydantic (9 capítulos, 27 lecciones, ejercicios corregidos y proyecto final) te lleva hasta el final.
./acceder-au-cours-complet cours gratuit : Vibe CodingFAQ
¿Cuánto tiempo se necesita para aprender Python SQLAlchemy Pydantic?
¿Se necesitan requisitos previos?
¿Por dónde empezar concretamente?
📬 ¿Quieres recibir este tipo de guía cada semana? Suscríbete gratis — código real, cero palabrería.