rediscache.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. """
  2. flask_caching.backends.rediscache
  3. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4. The redis caching backend.
  5. :copyright: (c) 2018 by Peter Justin.
  6. :copyright: (c) 2010 by Thadeus Burgess.
  7. :license: BSD, see LICENSE for more details.
  8. """
  9. import pickle
  10. from cachelib import RedisCache as CachelibRedisCache
  11. from flask_caching.backends.base import BaseCache
  12. class RedisCache(BaseCache, CachelibRedisCache):
  13. """Uses the Redis key-value store as a cache backend.
  14. The first argument can be either a string denoting address of the Redis
  15. server or an object resembling an instance of a redis.Redis class.
  16. Note: Python Redis API already takes care of encoding unicode strings on
  17. the fly.
  18. :param host: address of the Redis server or an object which API is
  19. compatible with the official Python Redis client (redis-py).
  20. :param port: port number on which Redis server listens for connections.
  21. :param password: password authentication for the Redis server.
  22. :param db: db (zero-based numeric index) on Redis Server to connect.
  23. :param default_timeout: the default timeout that is used if no timeout is
  24. specified on :meth:`~BaseCache.set`. A timeout of
  25. 0 indicates that the cache never expires.
  26. :param key_prefix: A prefix that should be added to all keys.
  27. Any additional keyword arguments will be passed to ``redis.Redis``.
  28. """
  29. def __init__(
  30. self,
  31. host="localhost",
  32. port=6379,
  33. password=None,
  34. db=0,
  35. default_timeout=300,
  36. key_prefix=None,
  37. **kwargs
  38. ):
  39. BaseCache.__init__(self, default_timeout=default_timeout)
  40. CachelibRedisCache.__init__(
  41. self,
  42. host=host,
  43. port=port,
  44. password=password,
  45. db=db,
  46. default_timeout=default_timeout,
  47. key_prefix=key_prefix,
  48. **kwargs
  49. )
  50. @classmethod
  51. def factory(cls, app, config, args, kwargs):
  52. try:
  53. from redis import from_url as redis_from_url
  54. except ImportError as e:
  55. raise RuntimeError("no redis module found") from e
  56. kwargs.update(
  57. dict(
  58. host=config.get("CACHE_REDIS_HOST", "localhost"),
  59. port=config.get("CACHE_REDIS_PORT", 6379),
  60. db=config.get("CACHE_REDIS_DB", 0),
  61. )
  62. )
  63. password = config.get("CACHE_REDIS_PASSWORD")
  64. if password:
  65. kwargs["password"] = password
  66. key_prefix = config.get("CACHE_KEY_PREFIX")
  67. if key_prefix:
  68. kwargs["key_prefix"] = key_prefix
  69. redis_url = config.get("CACHE_REDIS_URL")
  70. if redis_url:
  71. kwargs["host"] = redis_from_url(redis_url, db=kwargs.pop("db", None))
  72. new_class = cls(*args, **kwargs)
  73. return new_class
  74. def dump_object(self, value):
  75. """Dumps an object into a string for redis. By default it serializes
  76. integers as regular string and pickle dumps everything else.
  77. """
  78. t = type(value)
  79. if t == int:
  80. return str(value).encode("ascii")
  81. return b"!" + pickle.dumps(value)
  82. def unlink(self, *keys):
  83. """when redis-py >= 3.0.0 and redis > 4, support this operation"""
  84. if not keys:
  85. return
  86. if self.key_prefix:
  87. keys = [self.key_prefix + key for key in keys]
  88. unlink = getattr(self._write_client, "unlink", None)
  89. if unlink is not None and callable(unlink):
  90. return self._write_client.unlink(*keys)
  91. return self._write_client.delete(*keys)
  92. class RedisSentinelCache(RedisCache):
  93. """Uses the Redis key-value store as a cache backend.
  94. The first argument can be either a string denoting address of the Redis
  95. server or an object resembling an instance of a redis.Redis class.
  96. Note: Python Redis API already takes care of encoding unicode strings on
  97. the fly.
  98. :param sentinels: A list or a tuple of Redis sentinel addresses.
  99. :param master: The name of the master server in a sentinel configuration.
  100. :param password: password authentication for the Redis server.
  101. :param db: db (zero-based numeric index) on Redis Server to connect.
  102. :param default_timeout: the default timeout that is used if no timeout is
  103. specified on :meth:`~BaseCache.set`. A timeout of
  104. 0 indicates that the cache never expires.
  105. :param key_prefix: A prefix that should be added to all keys.
  106. Any additional keyword arguments will be passed to
  107. ``redis.sentinel.Sentinel``.
  108. """
  109. def __init__(
  110. self,
  111. sentinels=None,
  112. master=None,
  113. password=None,
  114. db=0,
  115. default_timeout=300,
  116. key_prefix="",
  117. **kwargs
  118. ):
  119. super().__init__(key_prefix=key_prefix, default_timeout=default_timeout)
  120. try:
  121. import redis.sentinel
  122. except ImportError as e:
  123. raise RuntimeError("no redis module found") from e
  124. if kwargs.get("decode_responses", None):
  125. raise ValueError("decode_responses is not supported by RedisCache.")
  126. sentinels = sentinels or [("127.0.0.1", 26379)]
  127. sentinel_kwargs = {
  128. key[9:]: value
  129. for key, value in kwargs.items()
  130. if key.startswith("sentinel_")
  131. }
  132. kwargs = {
  133. key: value
  134. for key, value in kwargs.items()
  135. if not key.startswith("sentinel_")
  136. }
  137. sentinel = redis.sentinel.Sentinel(
  138. sentinels=sentinels,
  139. password=password,
  140. db=db,
  141. sentinel_kwargs=sentinel_kwargs,
  142. **kwargs
  143. )
  144. self._write_client = sentinel.master_for(master)
  145. self._read_client = sentinel.slave_for(master)
  146. @classmethod
  147. def factory(cls, app, config, args, kwargs):
  148. kwargs.update(
  149. dict(
  150. sentinels=config.get("CACHE_REDIS_SENTINELS", [("127.0.0.1", 26379)]),
  151. master=config.get("CACHE_REDIS_SENTINEL_MASTER", "mymaster"),
  152. password=config.get("CACHE_REDIS_PASSWORD", None),
  153. sentinel_password=config.get("CACHE_REDIS_SENTINEL_PASSWORD", None),
  154. key_prefix=config.get("CACHE_KEY_PREFIX", None),
  155. db=config.get("CACHE_REDIS_DB", 0),
  156. )
  157. )
  158. return cls(*args, **kwargs)
  159. class RedisClusterCache(RedisCache):
  160. """Uses the Redis key-value store as a cache backend.
  161. The first argument can be either a string denoting address of the Redis
  162. server or an object resembling an instance of a rediscluster.RedisCluster
  163. class.
  164. Note: Python Redis API already takes care of encoding unicode strings on
  165. the fly.
  166. :param cluster: The redis cluster nodes address separated by comma.
  167. e.g. host1:port1,host2:port2,host3:port3 .
  168. :param password: password authentication for the Redis server.
  169. :param default_timeout: the default timeout that is used if no timeout is
  170. specified on :meth:`~BaseCache.set`. A timeout of
  171. 0 indicates that the cache never expires.
  172. :param key_prefix: A prefix that should be added to all keys.
  173. Any additional keyword arguments will be passed to
  174. ``rediscluster.RedisCluster``.
  175. """
  176. def __init__(
  177. self, cluster="", password="", default_timeout=300, key_prefix="", **kwargs
  178. ):
  179. super().__init__(key_prefix=key_prefix, default_timeout=default_timeout)
  180. if kwargs.get("decode_responses", None):
  181. raise ValueError("decode_responses is not supported by RedisCache.")
  182. try:
  183. from redis import RedisCluster
  184. from redis.cluster import ClusterNode
  185. except ImportError as e:
  186. raise RuntimeError("no redis.cluster module found") from e
  187. try:
  188. nodes = [(node.split(":")) for node in cluster.split(",")]
  189. startup_nodes = [
  190. ClusterNode(node[0].strip(), node[1].strip()) for node in nodes
  191. ]
  192. except IndexError as e:
  193. raise ValueError(
  194. "Please give the correct cluster argument "
  195. "e.g. host1:port1,host2:port2,host3:port3"
  196. ) from e
  197. # Skips the check of cluster-require-full-coverage config,
  198. # useful for clusters without the CONFIG command (like aws)
  199. skip_full_coverage_check = kwargs.pop("skip_full_coverage_check", True)
  200. cluster = RedisCluster(
  201. startup_nodes=startup_nodes,
  202. password=password,
  203. skip_full_coverage_check=skip_full_coverage_check,
  204. **kwargs
  205. )
  206. self._write_client = cluster
  207. self._read_client = cluster
  208. @classmethod
  209. def factory(cls, app, config, args, kwargs):
  210. kwargs.update(
  211. dict(
  212. cluster=config.get("CACHE_REDIS_CLUSTER", ""),
  213. password=config.get("CACHE_REDIS_PASSWORD", ""),
  214. default_timeout=config.get("CACHE_DEFAULT_TIMEOUT", 300),
  215. key_prefix=config.get("CACHE_KEY_PREFIX", ""),
  216. )
  217. )
  218. return cls(*args, **kwargs)