ORM Integration Guide¶
This guide covers type-safe integration between LumiX and Object-Relational Mapping (ORM) libraries like SQLAlchemy, Django ORM, and Peewee.
Overview¶
LumiX’s ORM integration utilities provide a bridge between database models and optimization models, enabling:
Type-safe queries with full IDE autocomplete
Structural typing that works with any ORM
Seamless data flow from database to optimization model
No inheritance required - works via Protocol (PEP 544)
Key Components:
LXORMModel- Protocol for ORM modelsLXORMContext- Type-safe query wrapperLXTypedQuery- Fluent query builderLXNumeric- Protocol for numeric types
Why ORM Integration?¶
Traditional Approach¶
Without ORM integration, building models from database data requires manual queries and type-unsafe operations:
# Manual query (no type safety)
products = session.query(Product).all()
# No IDE autocomplete in lambda
production = LXVariable("production").from_data(products)
# Type errors not caught until runtime
model.maximize(
LXLinearExpression().add_term(production, lambda p: p.profi) # Typo!
)
LumiX Approach¶
With ORM integration, you get full type safety and IDE support:
from lumix.utils import LXORMContext
# Type-safe context
ctx = LXORMContext(session)
# IDE autocomplete for Product attributes
products = ctx.query(Product).filter(lambda p: p.active).all()
# Full type safety in lambdas
production = LXVariable[Product, float]("production").from_data(products)
# IDE catches typos
model.maximize(
LXLinearExpression().add_term(production, lambda p: p.profit) # ✓
)
Quick Start¶
SQLAlchemy Example¶
from sqlalchemy import Column, Integer, String, Float, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session
from lumix import LXModel, LXVariable, LXLinearExpression
from lumix.utils import LXORMContext
# Define ORM models
Base = declarative_base()
class Product(Base):
__tablename__ = 'products'
id = Column(Integer, primary_key=True)
name = Column(String)
profit = Column(Float)
cost = Column(Float)
available = Column(Integer)
# Create session
engine = create_engine('sqlite:///production.db')
session = Session(engine)
# Type-safe queries
ctx = LXORMContext(session)
products = ctx.query(Product).filter(lambda p: p.available > 0).all()
# Build optimization model
production = (
LXVariable[Product, float]("production")
.continuous()
.bounds(lower=0, upper=lambda p: p.available)
.from_data(products)
.indexed_by(lambda p: p.id)
)
model = (
LXModel("production")
.add_variable(production)
.maximize(
LXLinearExpression().add_term(production, lambda p: p.profit)
)
)
Django ORM Example¶
from django.db import models
from lumix import LXModel, LXVariable
from lumix.utils import LXORMContext
# Django model (automatically satisfies LXORMModel protocol)
class Product(models.Model):
name = models.CharField(max_length=100)
profit = models.FloatField()
cost = models.FloatField()
class Meta:
app_label = 'production'
# Query and optimize
products = Product.objects.filter(profit__gt=10)
production = (
LXVariable[Product, float]("production")
.from_data(list(products)) # Convert QuerySet to list
.indexed_by(lambda p: p.id)
)
Core Concepts¶
Structural Typing (Protocol)¶
LumiX uses Python’s Protocol (PEP 544) for structural typing. Any object with an
id attribute automatically satisfies LXORMModel:
from dataclasses import dataclass
from lumix.utils import LXORMModel
# Dataclass with id - automatically satisfies protocol
@dataclass
class Product:
id: int
name: str
profit: float
# No inheritance needed!
product = Product(1, "Widget", 10.5)
assert isinstance(product, LXORMModel) # True
Type-Safe Queries¶
LXTypedQuery provides type-safe filtering with lambdas:
from lumix.utils import LXORMContext
ctx = LXORMContext(session)
# IDE knows 'p' is a Product
expensive_products = (
ctx.query(Product)
.filter(lambda p: p.profit > 100) # ← IDE autocomplete here!
.filter(lambda p: p.available > 0)
.all()
)
Generic Type Parameters¶
Use type parameters for full IDE support:
# With type parameter
production = LXVariable[Product, float]("production")
# IDE knows p.profit, p.cost, etc. exist
expr.add_term(production, lambda p: p.profit) # ← Autocomplete!
# Without type parameter
production = LXVariable("production")
# IDE doesn't know what 'p' is
expr.add_term(production, lambda p: p.profit) # No autocomplete
Advanced Usage¶
Complex Filtering¶
Chain multiple filters for complex queries:
ctx = LXORMContext(session)
selected_products = (
ctx.query(Product)
.filter(lambda p: p.category == "Electronics")
.filter(lambda p: p.profit > 50)
.filter(lambda p: p.in_stock)
.all()
)
Multi-Model Optimization¶
Build models from multiple related tables:
# Define related models
class Product(Base):
__tablename__ = 'products'
id = Column(Integer, primary_key=True)
profit = Column(Float)
class Resource(Base):
__tablename__ = 'resources'
id = Column(Integer, primary_key=True)
capacity = Column(Float)
class ProductResource(Base):
__tablename__ = 'product_resources'
product_id = Column(Integer)
resource_id = Column(Integer)
usage = Column(Float)
# Query data
ctx = LXORMContext(session)
products = ctx.query(Product).all()
resources = ctx.query(Resource).all()
usages = session.query(ProductResource).all()
# Build usage dictionary
usage_dict = {
(u.product_id, u.resource_id): u.usage
for u in usages
}
# Create optimization model
production = (
LXVariable[Product, float]("production")
.continuous()
.bounds(lower=0)
.from_data(products)
.indexed_by(lambda p: p.id)
)
capacity = (
LXConstraint[Resource]("capacity")
.expression(
LXLinearExpression().add_term(
production,
lambda p, r: usage_dict.get((p.id, r.id), 0)
)
)
.le()
.rhs(lambda r: r.capacity)
.from_data(resources)
.indexed_by(lambda r: r.id)
)
model = (
LXModel("multi_table")
.add_variable(production)
.add_constraint(capacity)
.maximize(
LXLinearExpression().add_term(production, lambda p: p.profit)
)
)
Eager Loading¶
Use ORM eager loading to avoid N+1 queries:
from sqlalchemy.orm import joinedload
# SQLAlchemy eager loading
products = (
session.query(Product)
.options(joinedload(Product.category))
.all()
)
# Now can access category without additional queries
production = LXVariable[Product, float]("production").from_data(products)
Dynamic Model Building¶
Build models dynamically based on database state:
def build_production_model(session, scenario: str):
ctx = LXORMContext(session)
# Query data based on scenario
if scenario == "normal":
products = ctx.query(Product).all()
elif scenario == "high_demand":
products = ctx.query(Product).filter(lambda p: p.high_demand).all()
elif scenario == "low_cost":
products = ctx.query(Product).filter(lambda p: p.cost < 10).all()
# Build model from filtered data
production = (
LXVariable[Product, float]("production")
.from_data(products)
.indexed_by(lambda p: p.id)
)
model = (
LXModel(f"production_{scenario}")
.add_variable(production)
.maximize(
LXLinearExpression().add_term(production, lambda p: p.profit)
)
)
return model
Integration Patterns¶
Repository Pattern¶
Encapsulate data access in repositories:
from typing import List
from lumix.utils import LXORMContext
class ProductRepository:
def __init__(self, session):
self.ctx = LXORMContext(session)
def get_active_products(self) -> List[Product]:
return self.ctx.query(Product).filter(lambda p: p.active).all()
def get_profitable_products(self, min_profit: float) -> List[Product]:
return (
self.ctx.query(Product)
.filter(lambda p: p.profit >= min_profit)
.all()
)
# Usage
repo = ProductRepository(session)
products = repo.get_profitable_products(min_profit=50)
production = LXVariable[Product, float]("production").from_data(products)
Service Layer¶
Separate business logic from data access:
class OptimizationService:
def __init__(self, session):
self.session = session
self.ctx = LXORMContext(session)
def optimize_production(self, scenario: str):
# Fetch data
products = self._get_products(scenario)
resources = self._get_resources()
# Build model
model = self._build_model(products, resources)
# Solve
optimizer = LXOptimizer().use_solver("gurobi")
solution = optimizer.solve(model)
# Save results
self._save_solution(solution, products)
return solution
def _get_products(self, scenario: str):
# Data access logic
return self.ctx.query(Product).all()
def _build_model(self, products, resources):
# Model building logic
pass
def _save_solution(self, solution, products):
# Persist results back to database
pass
Best Practices¶
Filter at Database Level
Apply ORM-specific filters before using LXTypedQuery:
# Good: Filter at database level products = session.query(Product).filter(Product.active == True).all() ctx_products = LXORMContext(session).query(Product).all() # Avoid: Fetching all then filtering in Python all_products = session.query(Product).all() active = [p for p in all_products if p.active]
Use Type Parameters
Always specify type parameters for IDE support:
# Good production = LXVariable[Product, float]("production") # Avoid production = LXVariable("production")
Manage Sessions Properly
Use context managers for session handling:
from contextlib import contextmanager @contextmanager def get_session(): session = Session() try: yield session session.commit() except: session.rollback() raise finally: session.close() # Usage with get_session() as session: ctx = LXORMContext(session) products = ctx.query(Product).all()
Cache Query Results
For repeated model builds, cache database queries:
class CachedProductRepository: def __init__(self, session): self.ctx = LXORMContext(session) self._cache = {} def get_products(self, use_cache=True): if use_cache and 'products' in self._cache: return self._cache['products'] products = self.ctx.query(Product).all() self._cache['products'] = products return products
Performance Considerations¶
Query Optimization¶
LXTypedQuery filters in Python, not at database level
For large datasets, use ORM filters first
Use database indexes for frequently filtered columns
Consider eager loading for related objects
Memory Management¶
Fetch only needed data
Use pagination for very large result sets
Clear session periodically for long-running processes
Troubleshooting¶
IDE Not Showing Autocomplete¶
Ensure type parameters are specified:
# This should work
var = LXVariable[Product, float]("x")
expr.add_term(var, lambda p: p.profit) # ← Autocomplete works
Type Checker Errors¶
If mypy complains about Protocol compatibility, ensure your model has the required attributes:
# Model must have 'id' attribute
class Product(Base):
id = Column(Integer, primary_key=True) # Required
name = Column(String)
See Also¶
LXORMModel- ORM model protocolLXORMContext- Type-safe query contextLXTypedQuery- Query builderUtils Module API - Utils API reference
SQLAlchemy: https://www.sqlalchemy.org/
Django ORM: https://docs.djangoproject.com/en/stable/topics/db/