Repository: opsdroid/opsdroid

Issue:
Upgrade redis database backend to use aioredis v2
Looks like v2 of `aioredis` is out and has breaking changes which affect us. In #1809 I've pinned us to v1 for now but we should upgrade things to work with v2.

Specifically importing the parser fails

https://github.com/opsdroid/opsdroid/blob/a45490d1bdceca39b49880e20262b55ea0be101d/opsdroid/database/redis/__init__.py#L6

```python-traceback
   ImportError while importing test module '/home/runner/work/opsdroid/opsdroid/opsdroid/database/redis/tests/test_redis.py'.
  Hint: make sure your test modules/packages have valid Python names.
  Traceback:
  /opt/hostedtoolcache/Python/3.7.11/x64/lib/python3.7/importlib/__init__.py:127: in import_module
      return _bootstrap._gcd_import(name[level:], package, level)
  opsdroid/database/redis/tests/test_redis.py:8: in <module>
      from opsdroid.database.redis import RedisDatabase
  opsdroid/database/redis/__init__.py:6: in <module>
      from aioredis import parser
  E   ImportError: cannot import name 'parser' from 'aioredis' (/home/runner/work/opsdroid/opsdroid/.tox/py37-e2e/lib/python3.7/site-packages/aioredis/__init__.py)
```


Discussion:
Hello! I would like to take it up. Hacktoberfest is coming and it's becoming my good tradition to visit opsdroid and help a bit (: 
One question before starting:
https://github.com/opsdroid/opsdroid/blob/a45490d1bdceca39b49880e20262b55ea0be101d/opsdroid/database/redis/__init__.py#L49-L54
Is the pure python parser setting intentional (it is slower)? Aioredis 2.0 uses hiredis parser if hiredis is installed, pure-python fallback otherwise
Another question: I guess it's safe to remove `disconnect` method as [the docs](https://aioredis.readthedocs.io/en/latest/migration/#cleaning-up) say it should be cleaned up automatically?

Oh actually, hactoberfest has already started: let's go! Opening the PR


Resolving patch:
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
@@ -2,8 +2,7 @@
 import json
 import logging
 
-import aioredis
-from aioredis import parser
+from aioredis import Redis
 from voluptuous import Any
 
 from opsdroid.database import Database
@@ -46,12 +45,13 @@ async def connect(self):
 
         """
         try:
-            self.client = await aioredis.create_pool(
-                address=(self.host, int(self.port)),
+            self.client = Redis(
+                host=self.host,
+                port=int(self.port),
                 db=self.database,
                 password=self.password,
-                parser=parser.PyReader,
             )
+            await self.client.ping()  # to actually initiate a connection
 
             _LOGGER.info(
                 _("Connected to Redis database %s from %s on port %s."),
@@ -76,7 +76,9 @@ async def put(self, key, data):
         """
         if self.client:
             _LOGGER.debug(_("Putting %s into Redis."), key)
-            await self.client.execute("SET", key, json.dumps(data, cls=JSONEncoder))
+            await self.client.execute_command(
+                "SET", key, json.dumps(data, cls=JSONEncoder)
+            )
 
     async def get(self, key):
         """Get data from Redis for a given key.
@@ -91,7 +93,7 @@ async def get(self, key):
         """
         if self.client:
             _LOGGER.debug(_("Getting %s from Redis."), key)
-            data = await self.client.execute("GET", key)
+            data = await self.client.execute_command("GET", key)
 
             if data:
                 return json.loads(data, object_hook=JSONDecoder())
@@ -107,9 +109,9 @@ async def delete(self, key):
         """
         if self.client:
             _LOGGER.debug(_("Deleting %s from Redis."), key)
-            await self.client.execute("DEL", key)
+            await self.client.execute_command("DEL", key)
 
     async def disconnect(self):
         """Disconnect from the database."""
         if self.client:
-            self.client.close()
+            await self.client.close()


Test patch:
diff --git a/opsdroid/database/redis/tests/test_redis.py b/opsdroid/database/redis/tests/test_redis.py
--- a/opsdroid/database/redis/tests/test_redis.py
+++ b/opsdroid/database/redis/tests/test_redis.py
@@ -31,7 +31,9 @@ def test_init(caplog):
 async def test_connect(mocker, caplog):
     caplog.set_level(logging.DEBUG)
     database = RedisDatabase({})
-    mocked_connection = mocker.patch("aioredis.create_pool")
+    mocked_connection = mocker.patch(
+        "aioredis.Redis.ping", return_value=return_async_value(True)
+    )
 
     await database.connect()
 
@@ -43,7 +45,7 @@ async def test_connect(mocker, caplog):
 async def test_connect_failure(mocker, caplog):
     caplog.set_level(logging.DEBUG)
     database = RedisDatabase({})
-    mocked_connection = mocker.patch("aioredis.create_pool", side_effect=OSError)
+    mocked_connection = mocker.patch("aioredis.Redis.ping", side_effect=OSError)
 
     with suppress(OSError):
         await database.connect()
@@ -57,13 +59,15 @@ async def test_get(mocker, caplog):
     caplog.set_level(logging.DEBUG)
     database = RedisDatabase({})
     database.client = mocker.Mock()
-    attrs = {"execute.return_value": return_async_value('{"data_key":"data_value"}')}
+    attrs = {
+        "execute_command.return_value": return_async_value('{"data_key":"data_value"}')
+    }
     database.client.configure_mock(**attrs)
 
     result = await database.get("key")
 
     assert result == dict(data_key="data_value")
-    database.client.execute.assert_called_with("GET", "key")
+    database.client.execute_command.assert_called_with("GET", "key")
     assert "Getting" in caplog.text
 
 
@@ -72,7 +76,7 @@ async def test_get_return_none(mocker, caplog):
     caplog.set_level(logging.DEBUG)
     database = RedisDatabase({})
     database.client = mocker.Mock()
-    attrs = {"execute.return_value": return_async_value(None)}
+    attrs = {"execute_command.return_value": return_async_value(None)}
     database.client.configure_mock(**attrs)
 
     result = await database.get("key")
@@ -86,12 +90,12 @@ async def test_put(mocker, caplog):
     caplog.set_level(logging.DEBUG)
     database = RedisDatabase({})
     database.client = mocker.Mock()
-    attrs = {"execute.return_value": return_async_value("None")}
+    attrs = {"execute_command.return_value": return_async_value("None")}
     database.client.configure_mock(**attrs)
 
     await database.put("key", dict(data_key="data_value"))
 
-    database.client.execute.assert_called_with(
+    database.client.execute_command.assert_called_with(
         "SET", "key", json.dumps(dict(data_key="data_value"), cls=JSONEncoder)
     )
     assert "Putting" in caplog.text
@@ -102,12 +106,12 @@ async def test_delete(mocker, caplog):
     caplog.set_level(logging.DEBUG)
     database = RedisDatabase({})
     database.client = mocker.Mock()
-    attrs = {"execute.return_value": return_async_value("None")}
+    attrs = {"execute_command.return_value": return_async_value("None")}
     database.client.configure_mock(**attrs)
 
     await database.delete("key")
 
-    database.client.execute.assert_called_with("DEL", "key")
+    database.client.execute_command.assert_called_with("DEL", "key")
     assert "Deleting" in caplog.text