195 lines
4.7 KiB
Python
195 lines
4.7 KiB
Python
import logging
|
|
from contextlib import contextmanager
|
|
from functools import wraps
|
|
from typing import Any, Dict, Mapping, Union
|
|
|
|
try:
|
|
import hiredis # noqa
|
|
|
|
# Only support Hiredis >= 3.0:
|
|
HIREDIS_AVAILABLE = int(hiredis.__version__.split(".")[0]) >= 3
|
|
if not HIREDIS_AVAILABLE:
|
|
raise ImportError("hiredis package should be >= 3.0.0")
|
|
except ImportError:
|
|
HIREDIS_AVAILABLE = False
|
|
|
|
try:
|
|
import ssl # noqa
|
|
|
|
SSL_AVAILABLE = True
|
|
except ImportError:
|
|
SSL_AVAILABLE = False
|
|
|
|
try:
|
|
import cryptography # noqa
|
|
|
|
CRYPTOGRAPHY_AVAILABLE = True
|
|
except ImportError:
|
|
CRYPTOGRAPHY_AVAILABLE = False
|
|
|
|
from importlib import metadata
|
|
|
|
|
|
def from_url(url, **kwargs):
|
|
"""
|
|
Returns an active Redis client generated from the given database URL.
|
|
|
|
Will attempt to extract the database id from the path url fragment, if
|
|
none is provided.
|
|
"""
|
|
from redis.client import Redis
|
|
|
|
return Redis.from_url(url, **kwargs)
|
|
|
|
|
|
@contextmanager
|
|
def pipeline(redis_obj):
|
|
p = redis_obj.pipeline()
|
|
yield p
|
|
p.execute()
|
|
|
|
|
|
def str_if_bytes(value: Union[str, bytes]) -> str:
|
|
return (
|
|
value.decode("utf-8", errors="replace") if isinstance(value, bytes) else value
|
|
)
|
|
|
|
|
|
def safe_str(value):
|
|
return str(str_if_bytes(value))
|
|
|
|
|
|
def dict_merge(*dicts: Mapping[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Merge all provided dicts into 1 dict.
|
|
*dicts : `dict`
|
|
dictionaries to merge
|
|
"""
|
|
merged = {}
|
|
|
|
for d in dicts:
|
|
merged.update(d)
|
|
|
|
return merged
|
|
|
|
|
|
def list_keys_to_dict(key_list, callback):
|
|
return dict.fromkeys(key_list, callback)
|
|
|
|
|
|
def merge_result(command, res):
|
|
"""
|
|
Merge all items in `res` into a list.
|
|
|
|
This command is used when sending a command to multiple nodes
|
|
and the result from each node should be merged into a single list.
|
|
|
|
res : 'dict'
|
|
"""
|
|
result = set()
|
|
|
|
for v in res.values():
|
|
for value in v:
|
|
result.add(value)
|
|
|
|
return list(result)
|
|
|
|
|
|
def warn_deprecated(name, reason="", version="", stacklevel=2):
|
|
import warnings
|
|
|
|
msg = f"Call to deprecated {name}."
|
|
if reason:
|
|
msg += f" ({reason})"
|
|
if version:
|
|
msg += f" -- Deprecated since version {version}."
|
|
warnings.warn(msg, category=DeprecationWarning, stacklevel=stacklevel)
|
|
|
|
|
|
def deprecated_function(reason="", version="", name=None):
|
|
"""
|
|
Decorator to mark a function as deprecated.
|
|
"""
|
|
|
|
def decorator(func):
|
|
@wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
warn_deprecated(name or func.__name__, reason, version, stacklevel=3)
|
|
return func(*args, **kwargs)
|
|
|
|
return wrapper
|
|
|
|
return decorator
|
|
|
|
|
|
def _set_info_logger():
|
|
"""
|
|
Set up a logger that log info logs to stdout.
|
|
(This is used by the default push response handler)
|
|
"""
|
|
if "push_response" not in logging.root.manager.loggerDict.keys():
|
|
logger = logging.getLogger("push_response")
|
|
logger.setLevel(logging.INFO)
|
|
handler = logging.StreamHandler()
|
|
handler.setLevel(logging.INFO)
|
|
logger.addHandler(handler)
|
|
|
|
|
|
def get_lib_version():
|
|
try:
|
|
libver = metadata.version("redis")
|
|
except metadata.PackageNotFoundError:
|
|
libver = "99.99.99"
|
|
return libver
|
|
|
|
|
|
def format_error_message(host_error: str, exception: BaseException) -> str:
|
|
if not exception.args:
|
|
return f"Error connecting to {host_error}."
|
|
elif len(exception.args) == 1:
|
|
return f"Error {exception.args[0]} connecting to {host_error}."
|
|
else:
|
|
return (
|
|
f"Error {exception.args[0]} connecting to {host_error}. "
|
|
f"{exception.args[1]}."
|
|
)
|
|
|
|
|
|
def compare_versions(version1: str, version2: str) -> int:
|
|
"""
|
|
Compare two versions.
|
|
|
|
:return: -1 if version1 > version2
|
|
0 if both versions are equal
|
|
1 if version1 < version2
|
|
"""
|
|
|
|
num_versions1 = list(map(int, version1.split(".")))
|
|
num_versions2 = list(map(int, version2.split(".")))
|
|
|
|
if len(num_versions1) > len(num_versions2):
|
|
diff = len(num_versions1) - len(num_versions2)
|
|
for _ in range(diff):
|
|
num_versions2.append(0)
|
|
elif len(num_versions1) < len(num_versions2):
|
|
diff = len(num_versions2) - len(num_versions1)
|
|
for _ in range(diff):
|
|
num_versions1.append(0)
|
|
|
|
for i, ver in enumerate(num_versions1):
|
|
if num_versions1[i] > num_versions2[i]:
|
|
return -1
|
|
elif num_versions1[i] < num_versions2[i]:
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
def ensure_string(key):
|
|
if isinstance(key, bytes):
|
|
return key.decode("utf-8")
|
|
elif isinstance(key, str):
|
|
return key
|
|
else:
|
|
raise TypeError("Key must be either a string or bytes")
|