aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/mixins/__init__.py3
-rw-r--r--bot/mixins/redis.py56
-rw-r--r--bot/utils/__init__.py4
-rw-r--r--bot/utils/redis.py70
4 files changed, 74 insertions, 59 deletions
diff --git a/bot/mixins/__init__.py b/bot/mixins/__init__.py
deleted file mode 100644
index ff1f0c50d..000000000
--- a/bot/mixins/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from .redis import RedisCacheMixin
-
-__all__ = ['RedisCacheMixin']
diff --git a/bot/mixins/redis.py b/bot/mixins/redis.py
deleted file mode 100644
index f19108576..000000000
--- a/bot/mixins/redis.py
+++ /dev/null
@@ -1,56 +0,0 @@
-import redis as redis_py
-
-redis = redis_py.Redis(host="redis")
-
-
-class RedisDict(dict):
- """
- A dictionary interface for a Redis database.
-
- Objects created by this class should mostly behave like a normal dictionary,
- but will store all the data in our Redis database for persistence between restarts.
-
- There are, however, a few limitations to what kinds of data types can be
- stored on Redis, so this is a little bit more limited than a regular dict.
- """
-
- def __init__(self, namespace: str = "global"):
- """Initialize the RedisDict with the right namespace."""
- # TODO: Make namespace collision impossible!
- # Append a number or something if it exists already.
- self.namespace = namespace
-
- # redis.mset({"firedog": "donkeykong"})
- #
- # print(redis.get("firedog").decode("utf-8")
-
-
-class RedisCacheMixin:
- """
- A mixin which adds a cls.cache parameter which can be used for persistent caching.
-
- This adds a dictionary-like object called cache which can be treated like a regular dictionary,
- but which can only store simple data types like ints, strings, and floats.
-
- To use it, simply subclass it into your class like this:
-
- class MyCog(Cog, RedisCacheMixin):
- def some_command(self):
- # You can now do this!
- self.cache['some_data'] = some_data
-
- All the data stored in this cache will probably be available permanently, even if the bot restarts or
- is updated. However, Redis is not meant to be used for reliable, permanent storage. It may be cleared
- from time to time, so please only use it for caching data that you can afford to lose.
-
- If it's really important that your data should never disappear, please use our postgres database instead.
- """
-
- def __init_subclass__(cls, **kwargs):
- """
- Initialize the cache when subclass is created.
-
- When this mixin is subclassed, we create a cache using the subclass name as the namespace.
- This is to prevent collisions between subclasses.
- """
- cls.cache = RedisDict(cls.__name__)
diff --git a/bot/utils/__init__.py b/bot/utils/__init__.py
index 9b32e515d..7ae2db8fe 100644
--- a/bot/utils/__init__.py
+++ b/bot/utils/__init__.py
@@ -2,6 +2,10 @@ from abc import ABCMeta
from discord.ext.commands import CogMeta
+from bot.utils.redis import RedisDict
+
+__all__ = ['RedisDict', 'CogABCMeta']
+
class CogABCMeta(CogMeta, ABCMeta):
"""Metaclass for ABCs meant to be implemented as Cogs."""
diff --git a/bot/utils/redis.py b/bot/utils/redis.py
new file mode 100644
index 000000000..8b33e8977
--- /dev/null
+++ b/bot/utils/redis.py
@@ -0,0 +1,70 @@
+from collections.abc import MutableMapping
+from typing import Optional
+
+import redis as redis_py
+
+redis = redis_py.Redis(host="redis")
+
+
+class RedisDict(MutableMapping):
+ """
+ A dictionary interface for a Redis database.
+
+ Objects created by this class should mostly behave like a normal dictionary,
+ but will store all the data in our Redis database for persistence between restarts.
+
+ 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
+
+ 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._set_namespace(namespace)
+
+ def __set_name__(self, owner: object, attribute_name: str) -> None:
+ """
+ Set the namespace to Class.attribute_name.
+
+ 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:
+ 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})"