We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
skill-seen broken with redis database?
I've been testing opsdroid with a redis database and the seen skill appears to be having problems serializing python datetime objects.

user: when did you last see user?
opsdroid: Whoops there has been an error.
opsdroid: Check the log for details.

this is the opsdroid log with DEBUG logging enabled:

```
notrexroof_1  | DEBUG opsdroid.memory: Putting seen to memory.
notrexroof_1  | DEBUG opsdroid.database.redis: Putting seen into Redis.
notrexroof_1  | ERROR opsdroid.core: Exception when running skill 'seen'.
notrexroof_1  | Traceback (most recent call last):
notrexroof_1  |   File "/usr/local/lib/python3.8/site-packages/opsdroid/core.py", line 427, in run_skill
notrexroof_1  |     return await skill(self, config, event)
notrexroof_1  |   File "/root/.local/share/opsdroid/opsdroid-modules/skill/seen/__init__.py", line 16, in last_seen
notrexroof_1  |     await message.respond("I last saw {} {}".format(name, human(seen[name], precision=1)))
notrexroof_1  |   File "/root/.local/share/opsdroid/site-packages/ago.py", line 55, in human
notrexroof_1  |     delta = get_delta_from_subject(subject)
notrexroof_1  |   File "/root/.local/share/opsdroid/site-packages/ago.py", line 16, in get_delta_from_subject
notrexroof_1  |     subject = float(subject)
notrexroof_1  | TypeError: float() argument must be a string or a number, not 'dict'
```

I know this hasn't been touched in a few years, but I'm wondering if there is a general issue with serializing objects into a redis database within opsdroid.



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `opsdroid/database/redis/__init__.py`
Content:
```
1 """Module for storing data within Redis."""
2 import json
3 import logging
4 
5 import aioredis
6 from aioredis import parser
7 from voluptuous import Any
8 
9 from opsdroid.database import Database
10 from opsdroid.helper import JSONEncoder, JSONDecoder
11 
12 _LOGGER = logging.getLogger(__name__)
13 CONFIG_SCHEMA = {"host": str, "port": Any(int, str), "database": int, "password": str}
14 
15 
16 class RedisDatabase(Database):
17     """Database class for storing data within a Redis instance."""
18 
19     def __init__(self, config, opsdroid=None):
20         """Initialise the redis database.
21 
22         Set basic properties of the database. Initialise properties like
23         name, connection arguments, database file, table name and config.
24 
25         Args:
26             config (dict): The configuration of the database which consists
27                            of `file` and `table` name of the sqlite database
28                            specified in `configuration.yaml` file.
29             opsdroid (OpsDroid): An instance of opsdroid.core.
30 
31         """
32         super().__init__(config, opsdroid=opsdroid)
33         self.config = config
34         self.client = None
35         self.host = self.config.get("host", "localhost")
36         self.port = self.config.get("port", 6379)
37         self.database = self.config.get("database", 0)
38         self.password = self.config.get("password", None)
39         _LOGGER.debug(_("Loaded Redis database connector."))
40 
41     async def connect(self):
42         """Connect to the database.
43 
44         This method will connect to a Redis database. By default it will
45         connect to Redis on localhost on port 6379
46 
47         """
48         try:
49             self.client = await aioredis.create_pool(
50                 address=(self.host, int(self.port)),
51                 db=self.database,
52                 password=self.password,
53                 parser=parser.PyReader,
54             )
55 
56             _LOGGER.info(
57                 _("Connected to Redis database %s from %s on port %s."),
58                 self.database,
59                 self.host,
60                 self.port,
61             )
62         except OSError:
63             _LOGGER.warning(
64                 _("Unable to connect to Redis database on address: %s port: %s."),
65                 self.host,
66                 self.port,
67             )
68 
69     async def put(self, key, data):
70         """Store the data object in Redis against the key.
71 
72         Args:
73             key (string): The key to store the data object under.
74             data (object): The data object to store.
75 
76         """
77         if self.client:
78             _LOGGER.debug(_("Putting %s into Redis."), key)
79             await self.client.execute("SET", key, json.dumps(data, cls=JSONEncoder))
80 
81     async def get(self, key):
82         """Get data from Redis for a given key.
83 
84         Args:
85             key (string): The key to lookup in the database.
86 
87         Returns:
88             object or None: The data object stored for that key, or None if no
89                             object found for that key.
90 
91         """
92         if self.client:
93             _LOGGER.debug(_("Getting %s from Redis."), key)
94             data = await self.client.execute("GET", key)
95 
96             if data:
97                 return json.loads(data, encoding=JSONDecoder)
98 
99             return None
100 
101     async def delete(self, key):
102         """Delete data from Redis for a given key.
103 
104         Args:
105             key (string): The key to delete in the database.
106 
107         """
108         if self.client:
109             _LOGGER.debug(_("Deleting %s from Redis."), key)
110             await self.client.execute("DEL", key)
111 
112     async def disconnect(self):
113         """Disconnect from the database."""
114         if self.client:
115             self.client.close()
116
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/opsdroid/database/redis/__init__.py b/opsdroid/database/redis/__init__.py
--- a/opsdroid/database/redis/__init__.py
+++ b/opsdroid/database/redis/__init__.py
@@ -94,7 +94,7 @@
             data = await self.client.execute("GET", key)
 
             if data:
-                return json.loads(data, encoding=JSONDecoder)
+                return json.loads(data, object_hook=JSONDecoder())
 
             return None

{"golden_diff": "diff --git a/opsdroid/database/redis/__init__.py b/opsdroid/database/redis/__init__.py\n--- a/opsdroid/database/redis/__init__.py\n+++ b/opsdroid/database/redis/__init__.py\n@@ -94,7 +94,7 @@\n             data = await self.client.execute(\"GET\", key)\n \n             if data:\n-                return json.loads(data, encoding=JSONDecoder)\n+                return json.loads(data, object_hook=JSONDecoder())\n \n             return None\n", "issue": "skill-seen broken with redis database?\nI've been testing opsdroid with a redis database and the seen skill appears to be having problems serializing python datetime objects.\r\n\r\nuser: when did you last see user?\r\nopsdroid: Whoops there has been an error.\r\nopsdroid: Check the log for details.\r\n\r\nthis is the opsdroid log with DEBUG logging enabled:\r\n\r\n```\r\nnotrexroof_1  | DEBUG opsdroid.memory: Putting seen to memory.\r\nnotrexroof_1  | DEBUG opsdroid.database.redis: Putting seen into Redis.\r\nnotrexroof_1  | ERROR opsdroid.core: Exception when running skill 'seen'.\r\nnotrexroof_1  | Traceback (most recent call last):\r\nnotrexroof_1  |   File \"/usr/local/lib/python3.8/site-packages/opsdroid/core.py\", line 427, in run_skill\r\nnotrexroof_1  |     return await skill(self, config, event)\r\nnotrexroof_1  |   File \"/root/.local/share/opsdroid/opsdroid-modules/skill/seen/__init__.py\", line 16, in last_seen\r\nnotrexroof_1  |     await message.respond(\"I last saw {} {}\".format(name, human(seen[name], precision=1)))\r\nnotrexroof_1  |   File \"/root/.local/share/opsdroid/site-packages/ago.py\", line 55, in human\r\nnotrexroof_1  |     delta = get_delta_from_subject(subject)\r\nnotrexroof_1  |   File \"/root/.local/share/opsdroid/site-packages/ago.py\", line 16, in get_delta_from_subject\r\nnotrexroof_1  |     subject = float(subject)\r\nnotrexroof_1  | TypeError: float() argument must be a string or a number, not 'dict'\r\n```\r\n\r\nI know this hasn't been touched in a few years, but I'm wondering if there is a general issue with serializing objects into a redis database within opsdroid.\r\n\r\n\n", "before_files": [{"content": "\"\"\"Module for storing data within Redis.\"\"\"\nimport json\nimport logging\n\nimport aioredis\nfrom aioredis import parser\nfrom voluptuous import Any\n\nfrom opsdroid.database import Database\nfrom opsdroid.helper import JSONEncoder, JSONDecoder\n\n_LOGGER = logging.getLogger(__name__)\nCONFIG_SCHEMA = {\"host\": str, \"port\": Any(int, str), \"database\": int, \"password\": str}\n\n\nclass RedisDatabase(Database):\n    \"\"\"Database class for storing data within a Redis instance.\"\"\"\n\n    def __init__(self, config, opsdroid=None):\n        \"\"\"Initialise the redis database.\n\n        Set basic properties of the database. Initialise properties like\n        name, connection arguments, database file, table name and config.\n\n        Args:\n            config (dict): The configuration of the database which consists\n                           of `file` and `table` name of the sqlite database\n                           specified in `configuration.yaml` file.\n            opsdroid (OpsDroid): An instance of opsdroid.core.\n\n        \"\"\"\n        super().__init__(config, opsdroid=opsdroid)\n        self.config = config\n        self.client = None\n        self.host = self.config.get(\"host\", \"localhost\")\n        self.port = self.config.get(\"port\", 6379)\n        self.database = self.config.get(\"database\", 0)\n        self.password = self.config.get(\"password\", None)\n        _LOGGER.debug(_(\"Loaded Redis database connector.\"))\n\n    async def connect(self):\n        \"\"\"Connect to the database.\n\n        This method will connect to a Redis database. By default it will\n        connect to Redis on localhost on port 6379\n\n        \"\"\"\n        try:\n            self.client = await aioredis.create_pool(\n                address=(self.host, int(self.port)),\n                db=self.database,\n                password=self.password,\n                parser=parser.PyReader,\n            )\n\n            _LOGGER.info(\n                _(\"Connected to Redis database %s from %s on port %s.\"),\n                self.database,\n                self.host,\n                self.port,\n            )\n        except OSError:\n            _LOGGER.warning(\n                _(\"Unable to connect to Redis database on address: %s port: %s.\"),\n                self.host,\n                self.port,\n            )\n\n    async def put(self, key, data):\n        \"\"\"Store the data object in Redis against the key.\n\n        Args:\n            key (string): The key to store the data object under.\n            data (object): The data object to store.\n\n        \"\"\"\n        if self.client:\n            _LOGGER.debug(_(\"Putting %s into Redis.\"), key)\n            await self.client.execute(\"SET\", key, json.dumps(data, cls=JSONEncoder))\n\n    async def get(self, key):\n        \"\"\"Get data from Redis for a given key.\n\n        Args:\n            key (string): The key to lookup in the database.\n\n        Returns:\n            object or None: The data object stored for that key, or None if no\n                            object found for that key.\n\n        \"\"\"\n        if self.client:\n            _LOGGER.debug(_(\"Getting %s from Redis.\"), key)\n            data = await self.client.execute(\"GET\", key)\n\n            if data:\n                return json.loads(data, encoding=JSONDecoder)\n\n            return None\n\n    async def delete(self, key):\n        \"\"\"Delete data from Redis for a given key.\n\n        Args:\n            key (string): The key to delete in the database.\n\n        \"\"\"\n        if self.client:\n            _LOGGER.debug(_(\"Deleting %s from Redis.\"), key)\n            await self.client.execute(\"DEL\", key)\n\n    async def disconnect(self):\n        \"\"\"Disconnect from the database.\"\"\"\n        if self.client:\n            self.client.close()\n", "path": "opsdroid/database/redis/__init__.py"}], "after_files": [{"content": "\"\"\"Module for storing data within Redis.\"\"\"\nimport json\nimport logging\n\nimport aioredis\nfrom aioredis import parser\nfrom voluptuous import Any\n\nfrom opsdroid.database import Database\nfrom opsdroid.helper import JSONEncoder, JSONDecoder\n\n_LOGGER = logging.getLogger(__name__)\nCONFIG_SCHEMA = {\"host\": str, \"port\": Any(int, str), \"database\": int, \"password\": str}\n\n\nclass RedisDatabase(Database):\n    \"\"\"Database class for storing data within a Redis instance.\"\"\"\n\n    def __init__(self, config, opsdroid=None):\n        \"\"\"Initialise the redis database.\n\n        Set basic properties of the database. Initialise properties like\n        name, connection arguments, database file, table name and config.\n\n        Args:\n            config (dict): The configuration of the database which consists\n                           of `file` and `table` name of the sqlite database\n                           specified in `configuration.yaml` file.\n            opsdroid (OpsDroid): An instance of opsdroid.core.\n\n        \"\"\"\n        super().__init__(config, opsdroid=opsdroid)\n        self.config = config\n        self.client = None\n        self.host = self.config.get(\"host\", \"localhost\")\n        self.port = self.config.get(\"port\", 6379)\n        self.database = self.config.get(\"database\", 0)\n        self.password = self.config.get(\"password\", None)\n        _LOGGER.debug(_(\"Loaded Redis database connector.\"))\n\n    async def connect(self):\n        \"\"\"Connect to the database.\n\n        This method will connect to a Redis database. By default it will\n        connect to Redis on localhost on port 6379\n\n        \"\"\"\n        try:\n            self.client = await aioredis.create_pool(\n                address=(self.host, int(self.port)),\n                db=self.database,\n                password=self.password,\n                parser=parser.PyReader,\n            )\n\n            _LOGGER.info(\n                _(\"Connected to Redis database %s from %s on port %s.\"),\n                self.database,\n                self.host,\n                self.port,\n            )\n        except OSError:\n            _LOGGER.warning(\n                _(\"Unable to connect to Redis database on address: %s port: %s.\"),\n                self.host,\n                self.port,\n            )\n\n    async def put(self, key, data):\n        \"\"\"Store the data object in Redis against the key.\n\n        Args:\n            key (string): The key to store the data object under.\n            data (object): The data object to store.\n\n        \"\"\"\n        if self.client:\n            _LOGGER.debug(_(\"Putting %s into Redis.\"), key)\n            await self.client.execute(\"SET\", key, json.dumps(data, cls=JSONEncoder))\n\n    async def get(self, key):\n        \"\"\"Get data from Redis for a given key.\n\n        Args:\n            key (string): The key to lookup in the database.\n\n        Returns:\n            object or None: The data object stored for that key, or None if no\n                            object found for that key.\n\n        \"\"\"\n        if self.client:\n            _LOGGER.debug(_(\"Getting %s from Redis.\"), key)\n            data = await self.client.execute(\"GET\", key)\n\n            if data:\n                return json.loads(data, object_hook=JSONDecoder())\n\n            return None\n\n    async def delete(self, key):\n        \"\"\"Delete data from Redis for a given key.\n\n        Args:\n            key (string): The key to delete in the database.\n\n        \"\"\"\n        if self.client:\n            _LOGGER.debug(_(\"Deleting %s from Redis.\"), key)\n            await self.client.execute(\"DEL\", key)\n\n    async def disconnect(self):\n        \"\"\"Disconnect from the database.\"\"\"\n        if self.client:\n            self.client.close()\n", "path": "opsdroid/database/redis/__init__.py"}]}