Skip to main content

Flask: Flask-Cache for Caching

Flask: Using Flask-Cache for Caching

Caching is a powerful technique to improve the performance of web applications by storing frequently accessed data, reducing server load and response times. Built on Flask’s lightweight core and leveraging Jinja2 Templating and Werkzeug WSGI, the Flask-Cache extension (now maintained as Flask-Caching) provides seamless caching integration. This tutorial explores Flask using Flask-Cache for caching, covering setup, caching strategies, and practical applications for optimizing web applications.


01. Why Use Flask-Cache?

Flask-Cache enhances application performance by storing the results of expensive computations or database queries, serving them quickly for subsequent requests. It supports multiple backends (e.g., in-memory, Redis, Memcached) and integrates with Flask’s ecosystem, allowing developers to cache views, functions, or templates. By leveraging Jinja2 Templating and Werkzeug WSGI, Flask-Cache ensures flexibility and scalability for high-traffic applications.

Example: Basic Flask-Cache Setup

from flask import Flask
from flask_caching import Cache

app = Flask(__name__)

# Flask-Cache configuration
app.config['CACHE_TYPE'] = 'SimpleCache'
app.config['CACHE_DEFAULT_TIMEOUT'] = 300

cache = Cache(app)

@app.route('/')
def index():
    return "Flask-Cache configured!"

if __name__ == '__main__':
    app.run(debug=True)

Output:

 * Running on http://127.0.0.1:5000
Flask-Cache configured!

Explanation:

  • Flask-Caching - Provides caching functionality for Flask.
  • CACHE_TYPE - Specifies the caching backend (e.g., SimpleCache for in-memory).
  • CACHE_DEFAULT_TIMEOUT - Sets the default cache duration in seconds.

02. Key Flask-Cache Techniques

Flask-Cache offers versatile methods to cache views, functions, and data, with support for various backends. The table below summarizes key techniques and their applications:

Technique Description Use Case
View Caching @cache.cached Cache entire view responses
Function Caching @cache.memoize Cache function results based on arguments
Manual Caching cache.set(), cache.get() Manually store and retrieve data
Cache Invalidation cache.delete(), cache.clear() Remove outdated cache entries
Backend Configuration CACHE_TYPE (e.g., Redis, Memcached) Scale caching for production


2.1 Caching Views

Example: Caching a View Response

from flask import Flask
from flask_caching import Cache
import time

app = Flask(__name__)
app.config['CACHE_TYPE'] = 'SimpleCache'
cache = Cache(app)

@app.route('/slow')
@cache.cached(timeout=60)
def slow_view():
    time.sleep(2)  # Simulate expensive operation
    return f"Rendered at {time.ctime()}"

if __name__ == '__main__':
    app.run(debug=True)

Output (visiting /slow multiple times):

Rendered at Sun May 11 12:00:00 2025  # First request (takes 2 seconds)
Rendered at Sun May 11 12:00:00 2025  # Subsequent requests (cached, instant)

Explanation:

  • @cache.cached - Caches the view’s response for the specified timeout.
  • Subsequent requests retrieve the cached response, bypassing the slow operation.

2.2 Caching Functions with Memoization

Example: Memoizing a Function

from flask import Flask
from flask_caching import Cache
import time

app = Flask(__name__)
app.config['CACHE_TYPE'] = 'SimpleCache'
cache = Cache(app)

@cache.memoize(timeout=50)
def expensive_computation(n):
    time.sleep(2)  # Simulate expensive computation
    return n * n

@app.route('/compute/<int:n>')
def compute(n):
    result = expensive_computation(n)
    return f"Result: {result}"

if __name__ == '__main__':
    app.run(debug=True)

Output (visiting /compute/5 multiple times):

Result: 25  # First request (takes 2 seconds)
Result: 25  # Subsequent requests (cached, instant)

Explanation:

  • @cache.memoize - Caches function results based on input arguments.
  • Ideal for expensive computations with consistent inputs.

2.3 Manual Caching

Example: Manually Storing and Retrieving Data

from flask import Flask
from flask_caching import Cache
import time

app = Flask(__name__)
app.config['CACHE_TYPE'] = 'SimpleCache'
cache = Cache(app)

@app.route('/store')
def store_data():
    cache.set('my_data', {'value': time.ctime()}, timeout=30)
    return "Data stored!"

@app.route('/retrieve')
def retrieve_data():
    data = cache.get('my_data')
    return f"Cached data: {data or 'Not found'}"

if __name__ == '__main__':
    app.run(debug=True)

Output (visiting /store then /retrieve):

Data stored!
Cached data: {'value': 'Sun May 11 12:00:00 2025'}

Explanation:

  • cache.set() - Stores data with a key and timeout.
  • cache.get() - Retrieves cached data by key.

2.4 Cache Invalidation

Example: Deleting Cached Data

from flask import Flask
from flask_caching import Cache

app = Flask(__name__)
app.config['CACHE_TYPE'] = 'SimpleCache'
cache = Cache(app)

@app.route('/store')
def store_data():
    cache.set('my_data', 'Hello, World!', timeout=30)
    return "Data stored!"

@app.route('/clear')
def clear_cache():
    cache.delete('my_data')
    return "Cache cleared!"

@app.route('/retrieve')
def retrieve_data():
    data = cache.get('my_data')
    return f"Cached data: {data or 'Not found'}"

if __name__ == '__main__':
    app.run(debug=True)

Output (visiting /store, /retrieve, /clear, /retrieve):

Data stored!
Cached data: Hello, World!
Cache cleared!
Cached data: Not found

Explanation:

  • cache.delete() - Removes a specific cache entry.
  • cache.clear() - Clears all cache entries (use cautiously).

2.5 Using Redis as a Cache Backend

Example: Configuring Redis Cache

from flask import Flask
from flask_caching import Cache

app = Flask(__name__)
app.config['CACHE_TYPE'] = 'RedisCache'
app.config['CACHE_REDIS_HOST'] = 'localhost'
app.config['CACHE_REDIS_PORT'] = 6379
app.config['CACHE_REDIS_DB'] = 0
app.config['CACHE_REDIS_URL'] = 'redis://localhost:6379/0'

cache = Cache(app)

@app.route('/')
@cache.cached(timeout=60)
def index():
    return "Redis cache configured!"

if __name__ == '__main__':
    app.run(debug=True)

Output:

Redis cache configured!

Explanation:

  • RedisCache - Uses Redis as a scalable caching backend.
  • Requires a running Redis server and proper configuration.

2.6 Incorrect Configuration

Example: Missing Redis Server

from flask import Flask
from flask_caching import Cache

app = Flask(__name__)
app.config['CACHE_TYPE'] = 'RedisCache'
app.config['CACHE_REDIS_URL'] = 'redis://localhost:6379/0'

cache = Cache(app)

@app.route('/')
@cache.cached(timeout=60)
def index():
    return "This will fail!"

if __name__ == '__main__':
    app.run(debug=True)

Output:

ConnectionError: Error connecting to Redis server

Explanation:

  • Missing or unreachable Redis server causes connection errors.
  • Solution: Ensure Redis is running and configuration is correct.

03. Effective Usage

3.1 Recommended Practices

  • Use @cache.cached for static or semi-static views.

Example: Comprehensive Caching Strategy

from flask import Flask, render_template
from flask_caching import Cache
import time

app = Flask(__name__)
app.config['CACHE_TYPE'] = 'SimpleCache'
app.config['CACHE_DEFAULT_TIMEOUT'] = 300
cache = Cache(app)

@cache.memoize(timeout=50)
def fetch_data(user_id):
    time.sleep(2)  # Simulate database query
    return f"Data for user {user_id}: {time.ctime()}"

@app.route('/user/<user_id>')
@cache.cached(timeout=60)
def user_profile(user_id):
    data = fetch_data(user_id)
    return render_template('profile.html', data=data)

@app.route('/clear/<user_id>')
def clear_user_cache(user_id):
    cache.delete_memoized(fetch_data, user_id)
    cache.delete(f"view-/user/{user_id}")
    return f"Cache cleared for {user_id}"

if __name__ == '__main__':
    app.run(debug=True)

Template (profile.html):

<!-- templates/profile.html -->
<h1>User Profile</h1>
<p>{{ data }}</p>
  • Combine view and function caching for optimal performance.
  • Implement cache invalidation for dynamic data updates.

3.2 Practices to Avoid

  • Avoid caching highly dynamic or user-specific data without invalidation.

Example: Caching Dynamic Data

from flask import Flask
from flask_caching import Cache
import time

app = Flask(__name__)
app.config['CACHE_TYPE'] = 'SimpleCache'
cache = Cache(app)

@app.route('/dynamic')
@cache.cached(timeout=60)
def dynamic_view():
    return f"Current time: {time.ctime()}"  # Incorrect: Caches dynamic content

if __name__ == '__main__':
    app.run(debug=True)

Output:

Current time: Sun May 11 12:00:00 2025  # Stale time persists for 60 seconds
  • Caching dynamic content leads to stale responses.
  • Solution: Avoid caching or use short timeouts with invalidation.

04. Common Use Cases

4.1 Caching API Responses

Cache responses from expensive API endpoints to reduce latency.

Example: Caching an API Endpoint

from flask import Flask, jsonify
from flask_caching import Cache
import time

app = Flask(__name__)
app.config['CACHE_TYPE'] = 'SimpleCache'
cache = Cache(app)

@app.route('/api/data')
@cache.cached(timeout=30)
def get_data():
    time.sleep(2)  # Simulate API call
    return jsonify({'data': 'Sample data', 'timestamp': time.ctime()})

if __name__ == '__main__':
    app.run(debug=True)

Output (visiting /api/data):

{
  "data": "Sample data",
  "timestamp": "Sun May 11 12:00:00 2025"
}

Explanation:

  • Caches JSON responses to improve API performance.
  • Reduces load on backend services.

4.2 Caching Database Queries

Cache results of expensive database queries for faster access.

Example: Caching Query Results

from flask import Flask, render_template
from flask_caching import Cache
from flask_sqlalchemy import SQLAlchemy
import time

app = Flask(__name__)
app.config['CACHE_TYPE'] = 'SimpleCache'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
cache = Cache(app)
db = SQLAlchemy(app)

class Item(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)

@cache.memoize(timeout=60)
def get_items():
    time.sleep(2)  # Simulate slow query
    return [item.name for item in Item.query.all()]

@app.route('/items')
def list_items():
    items = get_items()
    return render_template('items.html', items=items)

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)

Template (items.html):

<!-- templates/items.html -->
<ul style="padding: 0px 0px 0px 20px; margin-top: 0px;">
{% for item in items %}
    <li>{{ item }}</li>
{% endfor %}
</ul>

Explanation:

  • @cache.memoize - Caches query results to avoid repeated database hits.
  • Improves performance for read-heavy operations.

Conclusion

Flask-Cache, integrated with Jinja2 Templating and Werkzeug WSGI, provides a robust solution for optimizing Flask applications through caching. Key takeaways:

  • Use @cache.cached and @cache.memoize for views and functions.
  • Leverage manual caching with cache.set() and cache.get().
  • Configure scalable backends like Redis for production.
  • Avoid caching dynamic data without proper invalidation.

With Flask-Cache, you can significantly enhance the performance of your Flask applications, delivering faster responses and better user experiences!

Comments