Coverage for jutil/redis_helpers.py: 78%
50 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-07 16:40 -0500
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-07 16:40 -0500
1import json
2import logging
3from functools import lru_cache
4from typing import Any, Optional
5from django.conf import settings
6from django.core.serializers.json import DjangoJSONEncoder
8try:
9 import redis # type: ignore
11 if int(redis.__version__.split(".", maxsplit=1)[0]) < 3: # type: ignore 11 ↛ 12line 11 didn't jump to line 12, because the condition on line 11 was never true
12 raise Exception("Invalid version")
13except Exception as err:
14 raise Exception("Using jutil.redis_helpers requires redis>=3.0.0 installed") from err
16logger = logging.getLogger(__name__)
19@lru_cache()
20def redis_connection_pool(connection_str: str) -> redis.ConnectionPool:
21 """
22 Returns cached Redis connection pool.
23 :param connection_str: str
24 :return: redis.ConnectionPool
25 """
26 return redis.ConnectionPool.from_url(connection_str)
29@lru_cache()
30def redis_instance(connection_str: str = "") -> redis.Redis:
31 """
32 Returns cached Redis instance using settings.REDIS_URL connection string.
33 If no connection string is provided, AND settings.REDIS_URL is empty or missing, then
34 localhost with default Redis settings (no password) is assumed.
35 :param connection_str: Default connection string is settings.REDIS_URL or 'redis://:@localhost:6379/1?max_connections=50'.
36 """
37 if not connection_str: 37 ↛ 39line 37 didn't jump to line 39, because the condition on line 37 was never false
38 connection_str = settings.REDIS_URL if hasattr(settings, "REDIS_URL") else "redis://:@localhost:6379/1?max_connections=50" # type: ignore
39 return redis.Redis(connection_pool=redis_connection_pool(connection_str))
42def redis_prefix_key(name: str) -> str:
43 """
44 Returns name prefixed with default DB name separated with dot.
45 For example, if Django default DB name is "my_db"
46 then "my_key" is returned as "my_db.my_key".
47 :param name: Key name without prefix
48 :return: Key name with prefix
49 """
50 return str(settings.DATABASES["default"]["NAME"]) + "." + name
53def redis_set_bytes(name: str, value: bytes, ex: Optional[int] = None):
54 """
55 Sets value of the key as bytes to Redis.
57 Uses default DB name as a key prefix. For example, if default DB name is "my_db"
58 then key "my_key" is stored in Redis as "my_db.my_key".
60 :param name: Key name without DB name prefix
61 :param value: bytes
62 :param ex: Optional expire time in seconds
63 """
64 return redis_instance().set(redis_prefix_key(name), value, ex=ex)
67def redis_get_bytes_or_none(name: str) -> Optional[bytes]:
68 """
69 Returns value of the key as bytes from Redis or None if value missing.
71 Uses default DB name as a key prefix. For example, if default DB name is "my_db"
72 then key "my_key" is stored in Redis as "my_db.my_key".
74 :param name: Key name without DB name prefix
75 :return: bytes or None if value missing
76 """
77 return redis_instance().get(redis_prefix_key(name))
80def redis_get_bytes(name: str) -> bytes:
81 """
82 Returns value of the key as bytes from Redis. Raise Exception if value is missing.
84 Uses default DB name as a key prefix. For example, if default DB name is "my_db"
85 then key "my_key" is stored in Redis as "my_db.my_key".
87 :param name: Key name without DB name prefix
88 :return: bytes
89 """
90 buf = redis_get_bytes_or_none(name)
91 if buf is None: 91 ↛ 92line 91 didn't jump to line 92, because the condition on line 91 was never true
92 raise Exception(f"{redis_prefix_key(name)} not in Redis")
93 return buf
96def redis_set_json(name: str, value: Any, ex: Optional[int] = None, cls=DjangoJSONEncoder):
97 """
98 Sets value of the key as JSON to Redis.
100 Uses default DB name as a key prefix. For example, if default DB name is "my_db"
101 then key "my_key" is stored in Redis as "my_db.my_key".
103 Stored value is assumed to be JSON and it is serialized before sending.
105 :param name: Key name without DB name prefix
106 :param value: Any value that will be serialized as JSON
107 :param ex: Optional expire time in seconds
108 :param cls: JSON encoder class (default is DjangoJSONEncoder from Django)
109 """
110 value_bytes = json.dumps(value, cls=cls).encode()
111 return redis_instance().set(redis_prefix_key(name), value_bytes, ex=ex)
114def redis_get_json_or_none(name: str) -> Any:
115 """
116 Returns value of the key as JSON from Redis or None if value missing.
118 Uses default DB name as a key prefix. For example, if default DB name is "my_db"
119 then key "my_key" is stored in Redis as "my_db.my_key".
121 Stored value is assumed to be JSON and it is deserialized before returning.
123 :param name: Key name without DB name prefix
124 :return: bytes deserialized as JSON or None
125 """
126 try:
127 buf = redis_instance().get(redis_prefix_key(name))
128 if buf is not None: 128 ↛ 129line 128 didn't jump to line 129, because the condition on line 128 was never true
129 return json.loads(buf)
130 except Exception as exc:
131 logger.error("redis_get_json(%s) failed: %s", name, exc)
132 return None
135def redis_get_json(name: str) -> Any:
136 """
137 Returns value of the key as JSON from Redis. Raise Exception if value is missing.
139 Uses default DB name as a key prefix. For example, if default DB name is "my_db"
140 then key "my_key" is stored in Redis as "my_db.my_key".
142 Stored value is assumed to be JSON and it is deserialized before returning.
144 :param name: Key name without DB name prefix
145 :return: bytes deserialized as JSON
146 """
147 buf = redis_instance().get(redis_prefix_key(name))
148 if buf is None: 148 ↛ 149line 148 didn't jump to line 149, because the condition on line 148 was never true
149 raise Exception(f"{redis_prefix_key(name)} not in Redis")
150 return json.loads(buf)
153def redis_delete(name: str):
154 """
155 Deletes key-value pair from from Redis.
157 Uses default DB name as a key prefix. For example, if default DB name is "my_db"
158 then key "my_key" is stored in Redis as "my_db.my_key".
160 :param name: Key name without DB name prefix
161 """
162 return redis_instance().delete(redis_prefix_key(name))