aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Leon Sandøy <[email protected]>2020-05-16 23:40:21 +0200
committerGravatar Leon Sandøy <[email protected]>2020-05-16 23:40:21 +0200
commitee8386e67aa7d298b4761eef79b927c3066fe037 (patch)
tree071024a95f82832e48c73579eba5a432ad048aac
parentRefactor - no more mixins! (diff)
Add basic dict methods for RedisDict.
The rest of the features should be provided by the MutableMapping abc we're interfacing. Specifically, MutableMapping provides these: .pop, .popitem, .clear, .update, .setdefault, __contains__, .keys, .items, .values, .get, __eq__, and __ne__.
-rw-r--r--bot/utils/redis.py80
1 files changed, 45 insertions, 35 deletions
diff --git a/bot/utils/redis.py b/bot/utils/redis.py
index 8b33e8977..1e8c6422d 100644
--- a/bot/utils/redis.py
+++ b/bot/utils/redis.py
@@ -1,9 +1,12 @@
+import json
from collections.abc import MutableMapping
-from typing import Optional
+from enum import Enum
+from typing import Dict, List, Optional, Tuple, Union
import redis as redis_py
-redis = redis_py.Redis(host="redis")
+ValidRedisKey = Union[str, int, float]
+JSONSerializableType = Optional[Union[str, float, bool, Dict, List, Tuple, Enum]]
class RedisDict(MutableMapping):
@@ -16,45 +19,25 @@ class RedisDict(MutableMapping):
Redis is limited to simple types, so to allow you to store collections like lists
and dictionaries, we JSON deserialize every value. That means that it will not be possible
to store complex objects, only stuff like strings, numbers, and collections of strings and numbers.
-
- TODO: Implement these:
- __delitem__
- __getitem__
- __setitem__
- __iter__
- __len__
- clear (just use DEL and the hash goes)
- copy (convert to dict maybe?)
- pop
- popitem
- setdefault
- update
-
- TODO: TEST THESE
- .get
- .items
- .keys
- .values
- .__eg__
- .__ne__
"""
- namespaces = []
-
- def _set_namespace(self, namespace: str) -> None:
- """Try to set the namespace, but do not permit collisions."""
- while namespace in self.namespaces:
- namespace = namespace + "_"
-
- self.namespaces.append(namespace)
- self.namespace = namespace
+ _namespaces = []
+ _redis = redis_py.Redis(host="redis") # Can be overridden for testing
def __init__(self, namespace: Optional[str] = None) -> None:
"""Initialize the RedisDict with the right namespace."""
super().__init__()
- self.has_custom_namespace = namespace is not None
+ self._has_custom_namespace = namespace is not None
self._set_namespace(namespace)
+ def _set_namespace(self, namespace: str) -> None:
+ """Try to set the namespace, but do not permit collisions."""
+ while namespace in self._namespaces:
+ namespace = namespace + "_"
+
+ self._namespaces.append(namespace)
+ self._namespace = namespace
+
def __set_name__(self, owner: object, attribute_name: str) -> None:
"""
Set the namespace to Class.attribute_name.
@@ -62,9 +45,36 @@ class RedisDict(MutableMapping):
Called automatically when this class is constructed inside a class as an attribute, as long as
no custom namespace is provided to the constructor.
"""
- if not self.has_custom_namespace:
+ if not self._has_custom_namespace:
self._set_namespace(f"{owner.__name__}.{attribute_name}")
def __repr__(self) -> str:
"""Return a beautiful representation of this object instance."""
- return f"RedisDict(namespace={self.namespace!r})"
+ return f"RedisDict(namespace={self._namespace!r})"
+
+ def __setitem__(self, key: ValidRedisKey, value: JSONSerializableType):
+ """Store an item in the Redis cache."""
+ # JSON serialize the value before storing it.
+ json_value = json.dumps(value)
+ self._redis.hset(self._namespace, key, json_value)
+
+ def __getitem__(self, key: ValidRedisKey):
+ """Get an item from the Redis cache."""
+ value = self._redis.hget(self._namespace, key)
+ return json.loads(value)
+
+ def __delitem__(self, key: ValidRedisKey):
+ """Delete an item from the Redis cache."""
+ self._redis.hdel(self._namespace, key)
+
+ def __iter__(self):
+ """Iterate all the items in the Redis cache."""
+ return iter(self._redis.hkeys(self._namespace))
+
+ def __len__(self):
+ """Return the number of items in the Redis cache."""
+ return self._redis.hlen(self._namespace)
+
+ def copy(self) -> Dict:
+ """Convert to dict and return."""
+ return dict(self)