Extensions

Cache

Our event query happily counts models on every call. That is fine until the table grows or the query gets expensive. The Cache battery gives you one small key-value API that swaps its backend behind the scenes, so you can keep hot values around without committing to a specific store.

Two backends, one API

The extension ships two implementations. Redis is the production backend and the default the framework falls back to. Legacy is a simple in-process dictionary that evicts entries with the help of the scheduler — great for development and single-process setups, but it requires EXT_SCHEDULING to be enabled. You pick one with CACHE_TYPE:

app_configs/app.ini ini
[general]
SECRET_KEY = supersecret

[data]
REDIS_URI = redis://localhost:6379

[extensions]
EXT_SCHEDULING = 1
EXT_CACHE = 1

[cache]
CACHE_TYPE = legacy
 

If you do not set CACHE_TYPE at all, the framework default resolves to redis and connects to REDIS_URI database 2. The legacy backend shown above keeps everything in memory, which is why it needs the scheduler to expire keys for you.

The config values

  • CACHE_TYPElegacy or redis.
  • CACHE_DEFAULT_TIMEOUT — TTL in seconds when you don't pass one. Default 300.
  • CACHE_REDIS_URI — the redis url for the redis backend.

Using the cache

Like every battery, the instance lives in the shared registry, and every method has an async twin. Let's make our model count from the previous chapter cheap to ask for:

fluid/events/models.py python
from webfluid.core.ext import db, cache, events
from sqlalchemy import select, func

from fluid.models import MyModel


@events.query("model:count")
async def count_models(_):
    cached = await cache.aget("model:count")
    if cached is not None:
        return int(cached)

    async with db.async_executor(model=MyModel) as e:
        result = await e.exec(select(func.count(MyModel.id)), scalars=False)
        total = result.scalar()

    # Keep it warm for a minute.
    await cache.aset("model:count", total, timeout=60)
    return total


# Whenever a model is created, drop the stale count:
@events.event("model:created", internal=False)
async def bust_count(_):
    await cache.adelete("model:count")

The synchronous side mirrors this one to one with cache.set, cache.get and cache.delete, plus clear / aclear to wipe everything. Values are stored as-is by the legacy backend and as strings by redis, so decode on the way out when you need a specific type.

Continue reading

From here you can continue straight with JWTManager.