Tutorial: SQLAlchemy Integration

YokedCache works well with SQLAlchemy in a few different patterns depending on how your app is structured.

Setup

pip install "yokedcache[full]" sqlalchemy
docker run -d --name redis -p 6379:6379 redis:7

Pattern 1: Function-level caching

The simplest approach—cache individual query functions with @cached:

from yokedcache import YokedCache, cached
from yokedcache.config import CacheConfig

cache = YokedCache(CacheConfig(redis_url="redis://localhost:6379/0"))

@cached(ttl=600, tags=["users"])
async def get_user_by_id(user_id: int):
    with SessionLocal() as session:
        user = session.query(User).filter(User.id == user_id).first()
        # Return a dict, not the ORM object, so it serializes cleanly
        return {"id": user.id, "name": user.username, "email": user.email} if user else None

@cached(ttl=180, tags=["posts"])
async def get_published_posts(limit: int = 10):
    with SessionLocal() as session:
        posts = session.query(Post).filter(Post.published == True).limit(limit).all()
        return [{"id": p.id, "title": p.title} for p in posts]

When you write, invalidate the related tags:

async def create_user(user_data: dict):
    with SessionLocal() as session:
        user = User(**user_data)
        session.add(user)
        session.commit()
        await cache.invalidate_tags(["users"])
        return user.id

Pattern 2: Session-level caching (FastAPI)

Use cached_dependency to cache at the session/dependency level. When the session commits a write, the cache is invalidated automatically:

from fastapi import Depends
from yokedcache import cached_dependency

cached_get_session = cached_dependency(
    get_session,
    cache=cache,
    ttl=300,
    table_name="users",
)

@app.get("/users/{user_id}")
async def get_user(user_id: int, db=Depends(cached_get_session)):
    return db.query(User).filter(User.id == user_id).first()

@app.post("/users")
async def create_user(data: UserCreate, db=Depends(cached_get_session)):
    user = User(**data.dict())
    db.add(user)
    await db.commit()  # triggers invalidation of "table:users"
    return user

See the FastAPI tutorial for a more complete example.

Pattern 3: Repository pattern

Cache at the repository level for clean separation:

class UserRepository:
    def __init__(self, session: Session):
        self.session = session

    @cached(ttl=600, tags=["users"])
    async def get_by_id(self, user_id: int):
        return self.session.query(User).filter(User.id == user_id).first()

    @cached(ttl=300, tags=["users"])
    async def get_active(self, limit: int = 50):
        return self.session.query(User).filter(User.is_active == True).limit(limit).all()

    async def create(self, data: dict):
        user = User(**data)
        self.session.add(user)
        await self.session.commit()
        await cache.invalidate_tags(["users"])
        return user

Cache warming

Pre-populate the cache before traffic hits. Handy after a deploy or cold start:

import asyncio

async def warm_cache():
    # Concurrently warm the most-accessed entries
    top_user_ids = [1, 2, 3, 4, 5]
    await asyncio.gather(*[get_user_by_id(uid) for uid in top_user_ids])
    await get_published_posts(limit=20)

Or use the CLI with a warming config file:

yokedcache warm --config-file warming.yaml

Serialization note

ORM objects aren't JSON-serializable. Convert them to dicts (or use Pydantic) before caching, or use SerializationMethod.PICKLE if you need the full ORM object—though pickle has security implications (see Security).

Search documentation

Type to search. Fuzzy matching handles typos.