Repository: opsdroid/opsdroid

Issue:
Add get module convenience methods
Sometimes it is helpful to be able to grab a connector, database, etc from within your skill.

You may wish to call `connector.send` on a specific connector, or use the `database.client` to directly interact with the database (just don't mess with the `opsdroid` table).

Currently you can access the lists of modules from the attributes `opsdroid.connectors`, `opsdroid.databases`, etc. However to grab a specific connector or database you end up doing a list compehension.

```python
[slack] = [connector for connector in self.opsdroid.connectors if connector.name == "slack"]
await slack.send(Message("Hello world"))
```

It would be nice to have some convenience methods such as `get_connector` and `get_database` in the `opsdroid` object to make accessing these easier. For example

```python
slack = self.opsdroid.get_connector("slack")
await slack.send(Message("Hello world"))

sqlite = self.opsdroid.get_database("sqlite")
cur = await sqlite.client.cursor()
await cur.execute("INSERT INTO students (name) VALUES ('" + name + "');")  # Beware of Bobby Tables
await self.client.commit()
```




Discussion:
If it doesn't already work, we should allow the `connector=` kwarg to `Event` to be a string, which gets converted into the connector based on the name. This would let you do send easily by doing something like
```python
opsdroid.send(Message("hello", connector="slack"))
```
Ooh yes definitely!
This would be a really useful feature. However is there a potential problem with just using the connector name as the key? What if there are multiple connectors of the same type?

Maybe there needs to be some sort of `id` associated with the connector configuration? ie:
```
connectors:
    - name: matrix
      id: connector1
      mxid: '@user1:matrix.org'
      password: xxx
      rooms:
        'room1': '#room1:matrix.org'
    - name: matrix
      id: connector2
      mxid: '@user1:matrix.org'
      password: xxx
      rooms:
        'room1': '#room1:matrix.org'
```
It would then be used something like:
```
opsdroid.send(Message("hello", connector="connector1"))
```

However, if we were willing to make a backward incompatible change, then changing the existing `name` to `type` might be preferable. ie:
```
connectors:
    - name: connector1
      type: matrix
      ...

    - name: connector1
      type: matrix
      ...
```
There may be a way to fudge this a bit to provide some backwards compatibility (ie `type` defaults to the value of `name` if not provided), but for the moment I'm just putting some general thoughts out there.
You cannot set a duplicate name. Opsdroid tries to autodetect which module you want based on the name. Setting the name to `matrix` will set the module property to `opsdroid.connector.matrix`. So for multiple matrix connectors you should set something like this.

```
connectors:
  - name: matrix

  - name: othermatrix
    module: opsdroid.connector.matrix
```


@jacobtomlinson yeah, it think you can probably ignore my contribution. I think i wrote that before I realised the config syntax had changed.

Resolving patch:
diff --git a/opsdroid/core.py b/opsdroid/core.py
--- a/opsdroid/core.py
+++ b/opsdroid/core.py
@@ -486,6 +486,46 @@ async def get_ranked_skills(self, skills, message):
 
         return sorted(ranked_skills, key=lambda k: k["score"], reverse=True)
 
+    def get_connector(self, name):
+        """Get a connector object.
+
+        Get a specific connector by name from the list of active connectors.
+
+        Args:
+            name (string): Name of the connector we want to access.
+
+        Returns:
+            connector (opsdroid.connector.Connector): An opsdroid connector.
+
+        """
+        try:
+            [connector] = [
+                connector for connector in self.connectors if connector.name == name
+            ]
+            return connector
+        except ValueError:
+            return None
+
+    def get_database(self, name):
+        """Get a database object.
+
+        Get a specific database by name from the list of active databases.
+
+        Args:
+            name (string): Name of the database we want to access.
+
+        Returns:
+            database (opsdroid.database.Database): An opsdroid database.
+
+        """
+        try:
+            [database] = [
+                database for database in self.memory.databases if database.name == name
+            ]
+            return database
+        except ValueError:
+            return None
+
     async def _constrain_skills(self, skills, message):
         """Remove skills with contraints which prohibit them from running.
 
diff --git a/opsdroid/database/__init__.py b/opsdroid/database/__init__.py
--- a/opsdroid/database/__init__.py
+++ b/opsdroid/database/__init__.py
@@ -97,6 +97,7 @@ class InMemoryDatabase(Database):
     def __init__(self, config={}, opsdroid=None):  # noqa: D107
         super().__init__(config, opsdroid)
         self.memory = {}
+        self.name = "inmem"
 
     async def connect(self):  # noqa: D102
         pass  # pragma: nocover


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -582,3 +582,21 @@ async def modify_dir(opsdroid, directory):
                 )
 
             assert opsdroid.reload.called
+
+    async def test_get_connector_database(self):
+        skill_path = os.path.join(
+            os.path.dirname(os.path.abspath(__file__)),
+            "mockmodules/skills/skill/skilltest",
+        )
+        example_config = {
+            "connectors": {"websocket": {}},
+            "skills": {"test": {"path": skill_path}},
+        }
+        with OpsDroid(config=example_config) as opsdroid:
+            await opsdroid.load()
+
+            assert opsdroid.get_connector("websocket") is not None
+            assert opsdroid.get_connector("slack") is None
+
+            assert opsdroid.get_database("inmem") is not None
+            assert opsdroid.get_database("redis") is None