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 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)