diff options
| author | 2020-05-16 23:40:21 +0200 | |
|---|---|---|
| committer | 2020-05-16 23:40:21 +0200 | |
| commit | ee8386e67aa7d298b4761eef79b927c3066fe037 (patch) | |
| tree | 071024a95f82832e48c73579eba5a432ad048aac | |
| parent | Refactor - 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.py | 80 |
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) |