[英]How to use Flask-Cache with Flask-Restful
How do I use Flask-Cache @cache.cached() decorator with Flask-Restful?如何将 Flask-Cache @cache.cached() 装饰器与 Flask-Restful 一起使用? For example, I have a class Foo inherited from Resource, and Foo has get, post, put, and delete methods.比如我有一个class的Foo继承自Resource,Foo有get、post、put、delete方法。
How can I can invalidate cached results after a POST
?如何在POST
后使缓存的结果无效?
@api.resource('/whatever')
class Foo(Resource):
@cache.cached(timeout=10)
def get(self):
return expensive_db_operation()
def post(self):
update_db_here()
## How do I invalidate the value cached in get()?
return something_useful()
Yes, you can use like that. 是的,你可以这样使用。
Maybe you will still need to read: flask-cache memoize URL query string parameters as well 也许您仍然需要阅读: flask-cache memoize URL查询字符串参数
As Flask-Cache
implementation doesn't give you access to the underlying cache
object, you'll have to explicitly instantiate a Redis
client and use it's keys
method (list all cache keys). 由于Flask-Cache
实现不允许您访问底层cache
对象,因此您必须显式实例化Redis
客户端并使用它的keys
方法(列出所有缓存键)。
cache_key
method is used to override the default key generation in your cache.cached
decorator. cache_key
方法用于覆盖cache.cached
装饰器中的默认密钥生成。 clear_cache
method will clear only the portion of the cache corresponding to the current resource. clear_cache
方法将仅清除与当前资源相对应的缓存部分。 This is a solution that was tested only for Redis
and the implementation will probably differ a little when using a different cache engine. 这是一个仅针对Redis
进行测试的解决方案,使用不同的缓存引擎时,实现可能略有不同。
from app import cache # The Flask-Cache object
from config import CACHE_REDIS_HOST, CACHE_REDIS_PORT # The Flask-Cache config
from redis import Redis
from flask import request
import urllib
redis_client = Redis(CACHE_REDIS_HOST, CACHE_REDIS_PORT)
def cache_key():
args = request.args
key = request.path + '?' + urllib.urlencode([
(k, v) for k in sorted(args) for v in sorted(args.getlist(k))
])
return key
@api.resource('/whatever')
class Foo(Resource):
@cache.cached(timeout=10, key_prefix=cache_key)
def get(self):
return expensive_db_operation()
def post(self):
update_db_here()
self.clear_cache()
return something_useful()
def clear_cache(self):
# Note: we have to use the Redis client to delete key by prefix,
# so we can't use the 'cache' Flask extension for this one.
key_prefix = request.path
keys = [key for key in redis_client.keys() if key.startswith(key_prefix)]
nkeys = len(keys)
for key in keys:
redis_client.delete(key)
if nkeys > 0:
log.info("Cleared %s cache keys" % nkeys)
log.info(keys)
You can invalidate cache using cache.clear()
method. 您可以使用cache.clear()
方法使缓存无效。
For more detials see: https://pythonhosted.org/Flask-Cache/#flask.ext.cache.Cache.clear and Clearing Cache section in https://pythonhosted.org/Flask-Cache/ 欲了解更多detials看到: https://pythonhosted.org/Flask-Cache/#flask.ext.cache.Cache.clear和清除缓存部分https://pythonhosted.org/Flask-Cache/
Answer from @JahMyst didn't work for me. @JahMyst 的回答对我不起作用。 Flask-Cache doesn't work with Flask restful framework. Flask-Cache 不适用于 Flask restful 框架。 @cache.Cached & @cache.memoize can't handle mutable objects per their documentation. @cache.Cached 和@cache.memoize 无法根据其文档处理可变对象。
Using mutable objects (classes, etc) as part of the cache key can become tricky. It is suggested to not pass in an object instance into a memoized function. However, the memoize does perform a repr() on the passed in arguments so that if the object has a __repr__ function that returns a uniquely identifying string for that object, that will be used as part of the cache key.
Had to come-up with my own implementation.不得不想出我自己的实现。 Leaving this code snippet incase someone else gets stuck with the same issue.留下这个代码片段以防其他人遇到同样的问题。
cache_key
function converts the user req into hash. cache_res_pickled
function is being used to pickle or unpickle the data cache_key
function 将用户请求转换为 hash。cache_res_pickled cache_res_pickled
用于 pickle 或 unpickle 数据
|-flask-app
|-app.py
|-resource
|--some_resource.py
import json
import logging
import pickle
import time
import urllib
from flask import Response, abort, request
from redis import Redis
redis_client = Redis("127.0.0.1", "6379")
exp_setting_s = 1500
def json_serial(obj):
"""
JSON serializer for objects not serializable by default json code"
Args:
obj: JSON serialized object for dates
Returns:
serialized JSON data
"""
if isinstance(obj, datetime.datetime):
return obj.__str__()
def cache_key():
""" ""
Returns: Hashed string of request made by the user.
"""
args = request.args
key = (
request.path
+ "?"
+ urllib.parse.urlencode(
[(k, v) for k in sorted(args) for v in sorted(args.getlist(k))]
)
)
key_hashed = hashlib.sha256(key.encode())
return key_hashed.hexdigest()
def cache_res_pickled(data, encode):
"""
Args:
data (dict): Data in dict format
encode (Boolean): Encode (true) or decode (false) the data
Returns: Result after pickling
"""
if encode:
return pickle.dumps(data)
else:
data = pickle.loads(data)
return data
class SomeResource(Resource):
@auth.login_required
def get(self):
# Get the key for request in hashed format SHA256
key = cache_key()
result = redis_client.get(key)
def generate():
"""
A lagging generator to stream JSON so we don't have to hold everything in memory
This is a little tricky, as we need to omit the last comma to make valid JSON,
thus we use a lagging generator, similar to http://stackoverflow.com/questions/1630320/
"""
releases = res.__iter__()
try:
prev_release = next(releases) # get first result
# We have some releases. First, yield the opening json
yield '{"data": ['
# Iterate over the releases
for release in releases:
yield json.dumps(prev_release, default=json_serial) + ", "
prev_release = release
logging.info(f"For {key} # records returned = {len(res)}")
# Now yield the last iteration without comma but with the closing brackets
yield json.dumps(prev_release, default=json_serial) + "]}"
except StopIteration:
# StopIteration here means the length was zero, so yield a valid releases doc and stop
logging.info(f"For {key} # records returned = {len(res)}")
yield '{"data": []}'
if result is None:
# Secure a key on Redis server.
redis_client.set(key, cache_res_pickled({}, True), ex=exp_setting_s)
try:
# Do the querying to the DB or math here to get res. It should be in dict format as shown below
res = {"A": 1, "B": 2, "C": 2}
# Update the key on Redis server with the latest data
redis_client.set(key, cache_res_pickled(res, True), ex=exp_setting_s)
return Response(generate(), content_type="application/json")
except Exception as e:
logging.exception(e)
abort(505, description="Resource not found. error - {}".format(e))
else:
res = cache_res_pickled(result, False)
if res:
logging.info(
f"The data already exists!😊 loading the data form Redis cache for Key - {key} "
)
return Response(generate(), content_type="application/json")
else:
logging.info(
f"There is already a request for this key. But there is no data in it. Key: {key}."
)
s = time.time()
counter = 0
# loops aimlessly till the data is available on the Redis
while not any(res):
result = redis_client.get(key)
res = cache_res_pickled(result, False)
counter += 1
logging.info(
f"The data was available after {time.time() - s} seconds. Had to loop {counter} times.🤦"
)
return Response(generate(), content_type="application/json")
##create a decarator
from werkzeug.contrib.cache import SimpleCache
CACHE_TIMEOUT = 300
cache = SimpleCache()
class cached(object):
def __init__(self, timeout=None):
self.timeout = timeout or CACHE_TIMEOUT
def __call__(self, f):
def decorator(*args, **kwargs):
response = cache.get(request.path)
if response is None:
response = f(*args, **kwargs)
cache.set(request.path, response, self.timeout)
return response
return decorator
#add this decarator to your views like below
@app.route('/buildingTotal',endpoint='buildingTotal')
@cached()
def eventAlert():
return 'something'
@app.route('/buildingTenants',endpoint='buildingTenants')
@cached()
def buildingTenants():
return 'something'
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.