Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support dynamic cache timeouts from flask responses #296

Merged
merged 4 commits into from
Apr 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Unreleased
- Add support for Flask 2.0 async. PR `#282 <https://github.com/pallets-eco/flask-caching/pull/282>`_.
- Cachelib is now used as backend. PR `#308 <https://github.com/pallets-eco/flask-caching/pull/308>`_.
- Drop support for python 3.6. PR `#332 <https://github.com/pallets-eco/flask-caching/pull/332>`_.
- Add support for dynamic cache timeouts `#296 <https://github.com/pallets-eco/flask-caching/pull/296>`_.


Version 1.10.1
Expand Down
11 changes: 11 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ The cached decorator has another optional argument called ``unless``. This
argument accepts a callable that returns True or False. If ``unless`` returns
``True`` then it will bypass the caching mechanism entirely.

To dynamically determine the timeout within the view, you can return `CachedResponse`,
a subclass of `flask.Response`.

@app.route("/")
@cache.cached()
def index():
return CachedResponse(
response=make_response(render_template('index.html')),
timeout=50,
)

.. warning::

When using ``cached`` on a view, take care to put it between Flask's
Expand Down
19 changes: 18 additions & 1 deletion src/flask_caching/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from flask import current_app
from flask import Flask
from flask import request
from flask import Response
from flask import url_for
from markupsafe import Markup
from werkzeug.utils import import_string
Expand Down Expand Up @@ -51,6 +52,19 @@
]


class CachedResponse(Response):
"""
views wraped by @cached can return this (which inherits from flask.Response)
to override the cache TTL dynamically
"""

timeout = None

def __init__(self, response, timeout):
self.__dict__ = response.__dict__
self.timeout = timeout


class Cache:
"""This class is used to control the cache objects."""

Expand Down Expand Up @@ -388,11 +402,14 @@ def decorated_function(*args, **kwargs):
rv = [val for val in rv]

if response_filter is None or response_filter(rv):
cache_timeout = decorated_function.cache_timeout
if isinstance(rv, CachedResponse):
cache_timeout = rv.timeout or cache_timeout
try:
self.cache.set(
cache_key,
rv,
timeout=decorated_function.cache_timeout,
timeout=cache_timeout,
)
except Exception:
if self.app.debug:
Expand Down
25 changes: 24 additions & 1 deletion tests/test_view.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import hashlib
import pytest
import time

import pytest
from flask import make_response
from flask import request

from flask_caching import CachedResponse


def test_cached_view(app, cache):
@app.route("/")
Expand Down Expand Up @@ -253,6 +256,26 @@ def cached_view2(foo, bar):
assert time2 != tc.get("/a/b").data.decode("utf-8")


def test_cache_timeout_dynamic(app, cache):
@app.route("/")
@cache.cached(timeout=1)
def cached_view():
# This should override the timeout to be 2 seconds
return CachedResponse(response=make_response(str(time.time())), timeout=2)

tc = app.test_client()

rv1 = tc.get("/")
time1 = rv1.data.decode("utf-8")
time.sleep(1)

# it's been 1 second, cache is still active
assert time1 == tc.get("/").data.decode("utf-8")
time.sleep(1)
# it's been >2 seconds, cache is not still active
assert time1 != tc.get("/").data.decode("utf-8")


def test_generate_cache_key_from_query_string(app, cache):
"""Test the _make_cache_key_query_string() cache key maker.

Expand Down