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():
passpip 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_idby 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_functionand cache its response into redis withrequest_idas key and preconfiguredtimeoutas an expiration. Then return this response. -
All db changes during the
view_functioncall will be recorded in redis, in format ofresource-instancetorequest-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-idwill 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.