270 lines
9.1 KiB
Python
270 lines
9.1 KiB
Python
"""
|
|
flask_caching.backends.rediscache
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
The redis caching backend.
|
|
|
|
:copyright: (c) 2018 by Peter Justin.
|
|
:copyright: (c) 2010 by Thadeus Burgess.
|
|
:license: BSD, see LICENSE for more details.
|
|
"""
|
|
|
|
import pickle
|
|
|
|
from cachelib import RedisCache as CachelibRedisCache
|
|
|
|
from flask_caching.backends.base import BaseCache
|
|
|
|
|
|
class RedisCache(BaseCache, CachelibRedisCache):
|
|
"""Uses the Redis key-value store as a cache backend.
|
|
|
|
The first argument can be either a string denoting address of the Redis
|
|
server or an object resembling an instance of a redis.Redis class.
|
|
|
|
Note: Python Redis API already takes care of encoding unicode strings on
|
|
the fly.
|
|
|
|
:param host: address of the Redis server or an object which API is
|
|
compatible with the official Python Redis client (redis-py).
|
|
:param port: port number on which Redis server listens for connections.
|
|
:param password: password authentication for the Redis server.
|
|
:param db: db (zero-based numeric index) on Redis Server to connect.
|
|
:param default_timeout: the default timeout that is used if no timeout is
|
|
specified on :meth:`~BaseCache.set`. A timeout of
|
|
0 indicates that the cache never expires.
|
|
:param key_prefix: A prefix that should be added to all keys.
|
|
|
|
Any additional keyword arguments will be passed to ``redis.Redis``.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
host="localhost",
|
|
port=6379,
|
|
password=None,
|
|
db=0,
|
|
default_timeout=300,
|
|
key_prefix=None,
|
|
**kwargs
|
|
):
|
|
BaseCache.__init__(self, default_timeout=default_timeout)
|
|
CachelibRedisCache.__init__(
|
|
self,
|
|
host=host,
|
|
port=port,
|
|
password=password,
|
|
db=db,
|
|
default_timeout=default_timeout,
|
|
key_prefix=key_prefix,
|
|
**kwargs
|
|
)
|
|
|
|
@classmethod
|
|
def factory(cls, app, config, args, kwargs):
|
|
try:
|
|
from redis import from_url as redis_from_url
|
|
except ImportError as e:
|
|
raise RuntimeError("no redis module found") from e
|
|
|
|
kwargs.update(
|
|
dict(
|
|
host=config.get("CACHE_REDIS_HOST", "localhost"),
|
|
port=config.get("CACHE_REDIS_PORT", 6379),
|
|
db=config.get("CACHE_REDIS_DB", 0),
|
|
)
|
|
)
|
|
password = config.get("CACHE_REDIS_PASSWORD")
|
|
if password:
|
|
kwargs["password"] = password
|
|
|
|
key_prefix = config.get("CACHE_KEY_PREFIX")
|
|
if key_prefix:
|
|
kwargs["key_prefix"] = key_prefix
|
|
|
|
redis_url = config.get("CACHE_REDIS_URL")
|
|
if redis_url:
|
|
kwargs["host"] = redis_from_url(redis_url, db=kwargs.pop("db", None))
|
|
|
|
new_class = cls(*args, **kwargs)
|
|
|
|
return new_class
|
|
|
|
def dump_object(self, value):
|
|
"""Dumps an object into a string for redis. By default it serializes
|
|
integers as regular string and pickle dumps everything else.
|
|
"""
|
|
t = type(value)
|
|
if t == int:
|
|
return str(value).encode("ascii")
|
|
return b"!" + pickle.dumps(value)
|
|
|
|
def unlink(self, *keys):
|
|
"""when redis-py >= 3.0.0 and redis > 4, support this operation"""
|
|
if not keys:
|
|
return
|
|
if self.key_prefix:
|
|
keys = [self.key_prefix + key for key in keys]
|
|
|
|
unlink = getattr(self._write_client, "unlink", None)
|
|
if unlink is not None and callable(unlink):
|
|
return self._write_client.unlink(*keys)
|
|
return self._write_client.delete(*keys)
|
|
|
|
|
|
class RedisSentinelCache(RedisCache):
|
|
"""Uses the Redis key-value store as a cache backend.
|
|
|
|
The first argument can be either a string denoting address of the Redis
|
|
server or an object resembling an instance of a redis.Redis class.
|
|
|
|
Note: Python Redis API already takes care of encoding unicode strings on
|
|
the fly.
|
|
|
|
|
|
:param sentinels: A list or a tuple of Redis sentinel addresses.
|
|
:param master: The name of the master server in a sentinel configuration.
|
|
:param password: password authentication for the Redis server.
|
|
:param db: db (zero-based numeric index) on Redis Server to connect.
|
|
:param default_timeout: the default timeout that is used if no timeout is
|
|
specified on :meth:`~BaseCache.set`. A timeout of
|
|
0 indicates that the cache never expires.
|
|
:param key_prefix: A prefix that should be added to all keys.
|
|
|
|
Any additional keyword arguments will be passed to
|
|
``redis.sentinel.Sentinel``.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
sentinels=None,
|
|
master=None,
|
|
password=None,
|
|
db=0,
|
|
default_timeout=300,
|
|
key_prefix="",
|
|
**kwargs
|
|
):
|
|
super().__init__(key_prefix=key_prefix, default_timeout=default_timeout)
|
|
|
|
try:
|
|
import redis.sentinel
|
|
except ImportError as e:
|
|
raise RuntimeError("no redis module found") from e
|
|
|
|
if kwargs.get("decode_responses", None):
|
|
raise ValueError("decode_responses is not supported by RedisCache.")
|
|
|
|
sentinels = sentinels or [("127.0.0.1", 26379)]
|
|
sentinel_kwargs = {
|
|
key[9:]: value
|
|
for key, value in kwargs.items()
|
|
if key.startswith("sentinel_")
|
|
}
|
|
kwargs = {
|
|
key: value
|
|
for key, value in kwargs.items()
|
|
if not key.startswith("sentinel_")
|
|
}
|
|
|
|
sentinel = redis.sentinel.Sentinel(
|
|
sentinels=sentinels,
|
|
password=password,
|
|
db=db,
|
|
sentinel_kwargs=sentinel_kwargs,
|
|
**kwargs
|
|
)
|
|
|
|
self._write_client = sentinel.master_for(master)
|
|
self._read_client = sentinel.slave_for(master)
|
|
|
|
@classmethod
|
|
def factory(cls, app, config, args, kwargs):
|
|
kwargs.update(
|
|
dict(
|
|
sentinels=config.get("CACHE_REDIS_SENTINELS", [("127.0.0.1", 26379)]),
|
|
master=config.get("CACHE_REDIS_SENTINEL_MASTER", "mymaster"),
|
|
password=config.get("CACHE_REDIS_PASSWORD", None),
|
|
sentinel_password=config.get("CACHE_REDIS_SENTINEL_PASSWORD", None),
|
|
key_prefix=config.get("CACHE_KEY_PREFIX", None),
|
|
db=config.get("CACHE_REDIS_DB", 0),
|
|
)
|
|
)
|
|
|
|
return cls(*args, **kwargs)
|
|
|
|
|
|
class RedisClusterCache(RedisCache):
|
|
"""Uses the Redis key-value store as a cache backend.
|
|
|
|
The first argument can be either a string denoting address of the Redis
|
|
server or an object resembling an instance of a rediscluster.RedisCluster
|
|
class.
|
|
|
|
Note: Python Redis API already takes care of encoding unicode strings on
|
|
the fly.
|
|
|
|
|
|
:param cluster: The redis cluster nodes address separated by comma.
|
|
e.g. host1:port1,host2:port2,host3:port3 .
|
|
:param password: password authentication for the Redis server.
|
|
:param default_timeout: the default timeout that is used if no timeout is
|
|
specified on :meth:`~BaseCache.set`. A timeout of
|
|
0 indicates that the cache never expires.
|
|
:param key_prefix: A prefix that should be added to all keys.
|
|
|
|
Any additional keyword arguments will be passed to
|
|
``rediscluster.RedisCluster``.
|
|
"""
|
|
|
|
def __init__(
|
|
self, cluster="", password="", default_timeout=300, key_prefix="", **kwargs
|
|
):
|
|
super().__init__(key_prefix=key_prefix, default_timeout=default_timeout)
|
|
|
|
if kwargs.get("decode_responses", None):
|
|
raise ValueError("decode_responses is not supported by RedisCache.")
|
|
|
|
try:
|
|
from redis import RedisCluster
|
|
from redis.cluster import ClusterNode
|
|
except ImportError as e:
|
|
raise RuntimeError("no redis.cluster module found") from e
|
|
|
|
try:
|
|
nodes = [(node.split(":")) for node in cluster.split(",")]
|
|
startup_nodes = [
|
|
ClusterNode(node[0].strip(), node[1].strip()) for node in nodes
|
|
]
|
|
except IndexError as e:
|
|
raise ValueError(
|
|
"Please give the correct cluster argument "
|
|
"e.g. host1:port1,host2:port2,host3:port3"
|
|
) from e
|
|
|
|
# Skips the check of cluster-require-full-coverage config,
|
|
# useful for clusters without the CONFIG command (like aws)
|
|
skip_full_coverage_check = kwargs.pop("skip_full_coverage_check", True)
|
|
|
|
cluster = RedisCluster(
|
|
startup_nodes=startup_nodes,
|
|
password=password,
|
|
skip_full_coverage_check=skip_full_coverage_check,
|
|
**kwargs
|
|
)
|
|
|
|
self._write_client = cluster
|
|
self._read_client = cluster
|
|
|
|
@classmethod
|
|
def factory(cls, app, config, args, kwargs):
|
|
kwargs.update(
|
|
dict(
|
|
cluster=config.get("CACHE_REDIS_CLUSTER", ""),
|
|
password=config.get("CACHE_REDIS_PASSWORD", ""),
|
|
default_timeout=config.get("CACHE_DEFAULT_TIMEOUT", 300),
|
|
key_prefix=config.get("CACHE_KEY_PREFIX", ""),
|
|
)
|
|
)
|
|
return cls(*args, **kwargs)
|