Redis based automatic idempotent and concurrency lock support for sqlalchemy based flask applications.
It caches responses in redis by request_id
, requests in a short time with the same request_id
would get the same response. But a cached response will be expired if any its affected db resources are last changed by other requests.
from flask_idempotent2 import Idempotent
idempotent = Idempotent(app, redis_client, DBSession) # DBSession: SQLAlchemy Session
@app.route('/api', methods=['PUT'])
@idempotent.cache(15) # Cache result for 15s
def api():
pass
@app.route('/api', methods=['PUT'])
@idempotent.lock(3) # Lock concurrency requests for 3s
def api():
pass
pip install flask_idempotent2
- Responses are cached into redis (with expiration).
- Return the response in redis if it exists.
- Clear the cache if any related db changes are made.
-
Get
request_id
by preconfiguredkey_func
. -
Get cached response from redis by
request_id
. -
Return the cached response if it exists and its related db resources are still not changed.
-
Otherwise call
view_function
and cache its response into redis withrequest_id
as key and preconfiguredtimeout
as an expiration. Then return this response. -
All db changes during the
view_function
call will be recorded in redis, in format ofresource-instance
torequest-id
.A cached response is valid only if its affected resource instances are all last affected by the samerequest-id
. If any other requests affects these db resources, therequest-id
will be reset, thus the cached response expires.Key Value app-name:api-name:request-id serialized-response, affected-resource-instances affected-resource:id e.g. "User:1"
request-id affected-resource:id... ...
Requests are distinguished by preconfigured keyfunc
, which is a function with no arguments and should return the request_id
.
from flask_idempotent2 import Idempotent, gen_keyfunc
keyfunc = gen_keyfunc(path=True, methods=True, query_string=True, data=True)
idempotent = Idempotent(app, redis_client, DBSession, keyfunc)
flask.g
can't be accessed outside flask request context, so I suggest you to use a threading local object instead:
import threading
# If your service is gevent based (one request one gevent thread).
# 1. Use gevent threading local instead.
# 2. Or just make sure builtin threading is patched by gevent.
g_ = threading.local()
idempotent = Idempotent(app, redis_client, DBSession, g_=threading.local())
BSD.