Caching#
Keywords: cache decorator, file cache, memory cache, invalidate cache, redis cache, cache lifespan
Asok comes with a built-in caching system. It can speed up your application by caching HTTP responses, database queries, and template fragments.
1. Template Fragment Caching#
The template engine supports the {% cache "key" ttl %} tag to cache computationally expensive blocks of HTML. This is especially useful for rendering complex loops or menus.
{% cache "sidebar_categories" 3600 %}
<!-- This loop and any queries inside it are only executed once per hour! -->
<ul>
{% for cat in get_categories() %}
<li>{{ cat.name }}</li>
{% endfor %}
</ul>
{% endcache %}
2. Caching HTTP Pages#
The easiest way to cache a full page is to use the @cache_page decorator. It intercepts GET requests and serves the cached response without executing your view logic.
from asok.cache import cache_page
import time
@cache_page(ttl=60) # Caches the response for 60 seconds
def render(request):
time.sleep(2) # Simulating heavy computation
return request.html("dashboard.asok")
2. Caching Database Queries#
You can chain the .cache(ttl) method directly onto any ORM query builder. Asok will compute a unique hash based on the exact SQL statement and parameters, completely bypassing the database if the result is already in memory.
from models.post import Post
def get_popular_posts():
# Only hits the SQLite database once every 5 minutes
posts = Post.query().order_by('-views').limit(10).cache(ttl=300).get()
return [p.to_dict() for p in posts]
Note:
.cache()is part of the Query Builder. You must call it before execution methods like.get()or.first(). You cannot chain it after.all()since.all()returns a list immediately.
3. Global Configuration#
By default, Asok uses a blazing fast in-memory cache (backend="memory").
Production Recommendations#
In a production environment (especially when using Gunicorn with multiple workers), the memory backend is isolated to each process. This means a cache set by Worker A will not be visible to Worker B.
To ensure cache consistency across all worker processes, you should use either the file or the redis backend.
Option A: Redis backend (Recommended)#
Redis is ideal for high-performance production sites. It is fast, handles TTL natively, and shares cache values across all processes/servers.
Asok automatically configures a connection and socket timeout of 5.0 seconds (socket_timeout=5.0, socket_connect_timeout=5.0) when communicating with Redis to prevent the application from blocking if the Redis server goes down or becomes slow.
Install the optional package:
pip install "asok[redis]"
Configure your .env file:
ASOK_CACHE_BACKEND=redis
REDIS_URL=redis://localhost:6379/0
Option B: File backend#
Stores cached entries as JSON files in a local directory:
ASOK_CACHE_BACKEND=file
ASOK_CACHE_PATH=.asok/cache
Backend Comparison#
| Backend | Speed | Persistence | Multi-process Safe |
|---|---|---|---|
memory | ⚡ Instant | No (Lost on restart) | No |
file | 🚀 Fast | Yes | Yes |
redis | ⚡ Instant | Yes | Yes (Shared & Distributed) |
Advanced Usage (Manual Caching)#
If you need fine-grained control over caching specific variables or logic, you can use the underlying Cache class directly.
from asok.cache import Cache
# You can instantiate your own custom cache instance
cache = Cache(backend='file', path='.my_custom_cache')
cache.set('key', 'value', ttl=300)
cache.get('key', default='missing')
cache.has('key') # True/False
cache.forget('key')
cache.flush() # Clear all
The remember pattern#
A very common and elegant pattern to cache variables dynamically:
# If 'api_data' exists, return it. Otherwise, execute fetch_slow_api(), cache it for 60s, and return it.
data = cache.remember('api_data', ttl=60, fn=fetch_slow_api)
Was this page helpful?