You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
3.7 KiB
114 lines
3.7 KiB
|
3 years ago
|
import urllib.parse
|
||
|
|
from typing import TYPE_CHECKING
|
||
|
|
|
||
|
|
from packaging.version import Version
|
||
|
|
|
||
|
|
from limits.errors import ConfigurationError
|
||
|
|
from limits.storage.redis import RedisStorage
|
||
|
|
from limits.typing import Dict, Optional, Union
|
||
|
|
|
||
|
|
if TYPE_CHECKING:
|
||
|
|
import redis.sentinel
|
||
|
|
|
||
|
|
|
||
|
|
class RedisSentinelStorage(RedisStorage):
|
||
|
|
"""
|
||
|
|
Rate limit storage with redis sentinel as backend
|
||
|
|
|
||
|
|
Depends on :pypi:`redis` package
|
||
|
|
"""
|
||
|
|
|
||
|
|
STORAGE_SCHEME = ["redis+sentinel"]
|
||
|
|
"""The storage scheme for redis accessed via a redis sentinel installation"""
|
||
|
|
|
||
|
|
DEFAULT_OPTIONS: Dict[str, Union[float, str, bool]] = {
|
||
|
|
"socket_timeout": 0.2,
|
||
|
|
}
|
||
|
|
"Default options passed to :class:`~redis.sentinel.Sentinel`"
|
||
|
|
|
||
|
|
DEPENDENCIES = {"redis.sentinel": Version("3.0")}
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
uri: str,
|
||
|
|
service_name: Optional[str] = None,
|
||
|
|
use_replicas: bool = True,
|
||
|
|
sentinel_kwargs: Optional[Dict[str, Union[float, str, bool]]] = None,
|
||
|
|
**options: Union[float, str, bool]
|
||
|
|
) -> None:
|
||
|
|
"""
|
||
|
|
:param uri: url of the form
|
||
|
|
``redis+sentinel://host:port,host:port/service_name``
|
||
|
|
:param service_name: sentinel service name
|
||
|
|
(if not provided in :attr:`uri`)
|
||
|
|
:param use_replicas: Whether to use replicas for read only operations
|
||
|
|
:param sentinel_kwargs: kwargs to pass as
|
||
|
|
:attr:`sentinel_kwargs` to :class:`redis.sentinel.Sentinel`
|
||
|
|
:param options: all remaining keyword arguments are passed
|
||
|
|
directly to the constructor of :class:`redis.sentinel.Sentinel`
|
||
|
|
:raise ConfigurationError: when the redis library is not available
|
||
|
|
or if the redis master host cannot be pinged.
|
||
|
|
"""
|
||
|
|
|
||
|
|
super(RedisStorage, self).__init__(uri, **options)
|
||
|
|
|
||
|
|
parsed = urllib.parse.urlparse(uri)
|
||
|
|
sentinel_configuration = []
|
||
|
|
sentinel_options = sentinel_kwargs.copy() if sentinel_kwargs else {}
|
||
|
|
|
||
|
|
parsed_auth: Dict[str, Union[float, str, bool]] = {}
|
||
|
|
|
||
|
|
if parsed.username:
|
||
|
|
parsed_auth["username"] = parsed.username
|
||
|
|
if parsed.password:
|
||
|
|
parsed_auth["password"] = parsed.password
|
||
|
|
|
||
|
|
sep = parsed.netloc.find("@") + 1
|
||
|
|
|
||
|
|
for loc in parsed.netloc[sep:].split(","):
|
||
|
|
host, port = loc.split(":")
|
||
|
|
sentinel_configuration.append((host, int(port)))
|
||
|
|
self.service_name = (
|
||
|
|
parsed.path.replace("/", "") if parsed.path else service_name
|
||
|
|
)
|
||
|
|
|
||
|
|
if self.service_name is None:
|
||
|
|
raise ConfigurationError("'service_name' not provided")
|
||
|
|
|
||
|
|
sentinel_dep = self.dependencies["redis.sentinel"].module
|
||
|
|
self.sentinel: "redis.sentinel.Sentinel" = sentinel_dep.Sentinel(
|
||
|
|
sentinel_configuration,
|
||
|
|
sentinel_kwargs={**parsed_auth, **sentinel_options},
|
||
|
|
**{**self.DEFAULT_OPTIONS, **parsed_auth, **options}
|
||
|
|
)
|
||
|
|
self.storage = self.sentinel.master_for(self.service_name)
|
||
|
|
self.storage_slave = self.sentinel.slave_for(self.service_name)
|
||
|
|
self.use_replicas = use_replicas
|
||
|
|
self.initialize_storage(uri)
|
||
|
|
|
||
|
|
def get(self, key: str) -> int:
|
||
|
|
"""
|
||
|
|
:param key: the key to get the counter value for
|
||
|
|
"""
|
||
|
|
|
||
|
|
return super()._get(
|
||
|
|
key, self.storage_slave if self.use_replicas else self.storage
|
||
|
|
)
|
||
|
|
|
||
|
|
def get_expiry(self, key: str) -> int:
|
||
|
|
"""
|
||
|
|
:param key: the key to get the expiry for
|
||
|
|
"""
|
||
|
|
|
||
|
|
return super()._get_expiry(
|
||
|
|
key, self.storage_slave if self.use_replicas else self.storage
|
||
|
|
)
|
||
|
|
|
||
|
|
def check(self) -> bool:
|
||
|
|
"""
|
||
|
|
Check if storage is healthy by calling :class:`aredis.StrictRedis.ping`
|
||
|
|
on the slave.
|
||
|
|
"""
|
||
|
|
|
||
|
|
return super()._check(self.storage_slave if self.use_replicas else self.storage)
|