We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
SIGINT (Ctrl+C) should be handled by opsdroid
When executing opsdroid in Ubuntu or in a Docker container, the following message appears:
`(Press CTRL+C to quit)`

When hitting `Ctrl+C` though, the following traceback is produced:

```
^CERROR asyncio: Task exception was never retrieved
future: <Task finished coro=<ConnectorShell.listen() done, defined at /root/.opsdroid/modules/opsdroid-modules/connector/shell/__init__.py:57> exception=ConnectionResetError('Connection lost',)>
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/asyncio/tasks.py", line 240, in _step
    result = coro.send(None)
  File "/root/.opsdroid/modules/opsdroid-modules/connector/shell/__init__.py", line 63, in listen
    user_input = await async_input('', opsdroid.eventloop)
  File "/root/.opsdroid/modules/opsdroid-modules/connector/shell/__init__.py", line 37, in async_input
    await writer.drain()
  File "/usr/local/lib/python3.5/asyncio/streams.py", line 333, in drain
    yield from self._protocol._drain_helper()
  File "/usr/local/lib/python3.5/asyncio/streams.py", line 204, in _drain_helper
    raise ConnectionResetError('Connection lost')
ConnectionResetError: Connection lost
ERROR asyncio: Exception in default exception handler
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/asyncio/base_events.py", line 1284, in call_exception_handler
    self.default_exception_handler(context)
  File "/usr/local/lib/python3.5/asyncio/base_events.py", line 1259, in default_exception_handler
    logger.error('\n'.join(log_lines), exc_info=exc_info)
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1314, in error
    self._log(ERROR, msg, args, **kwargs)
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1421, in _log
    self.handle(record)
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1431, in handle
    self.callHandlers(record)
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1493, in callHandlers
    hdlr.handle(record)
  File "/usr/local/lib/python3.5/logging/__init__.py", line 861, in handle
    self.emit(record)
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1053, in emit
    self.stream = self._open()
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1043, in _open
    return open(self.baseFilename, self.mode, encoding=self.encoding)
NameError: name 'open' is not defined
Exception ignored in: <bound method Task.__del__ of <Task finished coro=<ConnectorShell.listen() done, defined at /root/.opsdroid/modules/opsdroid-modules/connector/shell/__init__.py:57> exception=ConnectionResetError('Connection lost',)>>
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/asyncio/tasks.py", line 93, in __del__
  File "/usr/local/lib/python3.5/asyncio/futures.py", line 234, in __del__
  File "/usr/local/lib/python3.5/asyncio/base_events.py", line 1290, in call_exception_handler
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1314, in error
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1421, in _log
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1431, in handle
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1493, in callHandlers
  File "/usr/local/lib/python3.5/logging/__init__.py", line 861, in handle
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1053, in emit
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1043, in _open
NameError: name 'open' is not defined
ERROR asyncio: Task was destroyed but it is pending!
task: <Task pending coro=<parse_crontab() running at /usr/src/app/opsdroid/parsers/crontab.py:20> wait_for=<Future pending cb=[Task._wakeup()]>>
ERROR asyncio: Exception in default exception handler
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/asyncio/base_events.py", line 1284, in call_exception_handler
    self.default_exception_handler(context)
  File "/usr/local/lib/python3.5/asyncio/base_events.py", line 1259, in default_exception_handler
    logger.error('\n'.join(log_lines), exc_info=exc_info)
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1314, in error
    self._log(ERROR, msg, args, **kwargs)
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1421, in _log
    self.handle(record)
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1431, in handle
    self.callHandlers(record)
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1493, in callHandlers
    hdlr.handle(record)
  File "/usr/local/lib/python3.5/logging/__init__.py", line 861, in handle
    self.emit(record)
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1053, in emit
    self.stream = self._open()
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1043, in _open
    return open(self.baseFilename, self.mode, encoding=self.encoding)
NameError: name 'open' is not defined
Exception ignored in: <bound method Task.__del__ of <Task pending coro=<parse_crontab() running at /usr/src/app/opsdroid/parsers/crontab.py:20> wait_for=<Future pending cb=[Task._wakeup()]>>>
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/asyncio/tasks.py", line 92, in __del__
  File "/usr/local/lib/python3.5/asyncio/base_events.py", line 1290, in call_exception_handler
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1314, in error
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1421, in _log
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1431, in handle
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1493, in callHandlers
  File "/usr/local/lib/python3.5/logging/__init__.py", line 861, in handle
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1053, in emit
  File "/usr/local/lib/python3.5/logging/__init__.py", line 1043, in _open
NameError: name 'open' is not defined
```

This should be handled, so that opsdroid can exit gracefully without producing any tracebacks.

--- 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/core.py`
Content:
```
1 """Core components of OpsDroid."""
2 
3 import copy
4 import logging
5 import sys
6 import weakref
7 import asyncio
8 
9 from opsdroid.memory import Memory
10 from opsdroid.connector import Connector
11 from opsdroid.database import Database
12 from opsdroid.loader import Loader
13 from opsdroid.parsers.regex import parse_regex
14 from opsdroid.parsers.apiai import parse_apiai
15 from opsdroid.parsers.crontab import parse_crontab
16 from opsdroid.const import DEFAULT_CONFIG_PATH
17 
18 
19 _LOGGER = logging.getLogger(__name__)
20 
21 
22 class OpsDroid():
23     """Root object for opsdroid."""
24 
25     # pylint: disable=too-many-instance-attributes
26     # All are reasonable in this case.
27 
28     instances = []
29 
30     def __init__(self):
31         """Start opsdroid."""
32         self.bot_name = 'opsdroid'
33         self.sys_status = 0
34         self.connectors = []
35         self.connector_tasks = []
36         self.eventloop = asyncio.get_event_loop()
37         self.skills = []
38         self.memory = Memory()
39         self.loader = Loader(self)
40         self.config = {}
41         self.stats = {
42             "messages_parsed": 0,
43             "webhooks_called": 0,
44             "total_response_time": 0,
45             "total_responses": 0,
46         }
47         self.web_server = None
48         self.should_restart = False
49         self.stored_path = []
50         _LOGGER.info("Created main opsdroid object")
51 
52     def __enter__(self):
53         """Add self to existing instances."""
54         self.stored_path = copy.copy(sys.path)
55         if not self.__class__.instances:
56             self.__class__.instances.append(weakref.proxy(self))
57         else:
58             self.critical("opsdroid has already been started", 1)
59         return self
60 
61     def __exit__(self, exc_type, exc_value, traceback):
62         """Remove self from existing instances."""
63         sys.path = self.stored_path
64         self.__class__.instances = []
65         asyncio.set_event_loop(asyncio.new_event_loop())
66 
67     @property
68     def default_connector(self):
69         """Return the default connector."""
70         default_connector = None
71         for connector in self.connectors:
72             if "default" in connector.config and connector.config["default"]:
73                 default_connector = connector
74                 break
75         if default_connector is None:
76             default_connector = self.connectors[0]
77         return default_connector
78 
79     def exit(self):
80         """Exit application."""
81         _LOGGER.info("Exiting application with return code " +
82                      str(self.sys_status))
83         sys.exit(self.sys_status)
84 
85     def critical(self, error, code):
86         """Exit due to unrecoverable error."""
87         self.sys_status = code
88         _LOGGER.critical(error)
89         print("Error: " + error)
90         self.exit()
91 
92     def restart(self):
93         """Restart opsdroid."""
94         self.should_restart = True
95         self.stop()
96 
97     def stop(self):
98         """Stop the event loop."""
99         pending = asyncio.Task.all_tasks()
100         for task in pending:
101             task.cancel()
102         self.eventloop.stop()
103 
104     def load(self):
105         """Load configuration."""
106         self.config = self.loader.load_config_file([
107             "./configuration.yaml",
108             DEFAULT_CONFIG_PATH,
109             "/etc/opsdroid/configuration.yaml"
110             ])
111 
112     def start_loop(self):
113         """Start the event loop."""
114         connectors, databases, skills = \
115             self.loader.load_modules_from_config(self.config)
116         _LOGGER.debug("Loaded %i skills", len(skills))
117         if databases is not None:
118             self.start_databases(databases)
119         self.setup_skills(skills)
120         self.start_connector_tasks(connectors)
121         self.eventloop.create_task(parse_crontab(self))
122         self.web_server.start()
123         try:
124             pending = asyncio.Task.all_tasks()
125             self.eventloop.run_until_complete(asyncio.gather(*pending))
126         except (KeyboardInterrupt, EOFError):
127             print('')  # Prints a character return for return to shell
128             self.stop()
129             _LOGGER.info("Keyboard interrupt, exiting.")
130         except RuntimeError as error:
131             if str(error) != 'Event loop is closed':
132                 raise error
133         finally:
134             self.eventloop.close()
135 
136     def setup_skills(self, skills):
137         """Call the setup function on the passed in skills."""
138         for skill in skills:
139             try:
140                 skill["module"].setup(self)
141             except AttributeError:
142                 pass
143 
144     def start_connector_tasks(self, connectors):
145         """Start the connectors."""
146         for connector_module in connectors:
147             for _, cls in connector_module["module"].__dict__.items():
148                 if isinstance(cls, type) and \
149                    issubclass(cls, Connector) and\
150                    cls is not Connector:
151                     connector = cls(connector_module["config"])
152                     self.connectors.append(connector)
153 
154         if connectors:
155             for connector in self.connectors:
156                 self.eventloop.run_until_complete(connector.connect(self))
157             for connector in self.connectors:
158                 task = self.eventloop.create_task(connector.listen(self))
159                 self.connector_tasks.append(task)
160         else:
161             self.critical("All connectors failed to load", 1)
162 
163     def start_databases(self, databases):
164         """Start the databases."""
165         if not databases:
166             _LOGGER.debug(databases)
167             _LOGGER.warning("All databases failed to load")
168         for database_module in databases:
169             for name, cls in database_module["module"].__dict__.items():
170                 if isinstance(cls, type) and \
171                    issubclass(cls, Database) and \
172                    cls is not Database:
173                     _LOGGER.debug("Adding database: " + name)
174                     database = cls(database_module["config"])
175                     self.memory.databases.append(database)
176                     self.eventloop.run_until_complete(database.connect(self))
177 
178     async def parse(self, message):
179         """Parse a string against all skills."""
180         self.stats["messages_parsed"] = self.stats["messages_parsed"] + 1
181         tasks = []
182         if message.text.strip() != "":
183             _LOGGER.debug("Parsing input: " + message.text)
184 
185             tasks.append(
186                 self.eventloop.create_task(parse_regex(self, message)))
187 
188             if "parsers" in self.config:
189                 _LOGGER.debug("Processing parsers")
190                 parsers = self.config["parsers"]
191 
192                 apiai = [p for p in parsers if p["name"] == "apiai"]
193                 _LOGGER.debug("Checking apiai")
194                 if len(apiai) == 1 and \
195                         ("enabled" not in apiai[0] or
196                          apiai[0]["enabled"] is not False):
197                     _LOGGER.debug("Parsing with apiai")
198                     tasks.append(
199                         self.eventloop.create_task(
200                             parse_apiai(self, message, apiai[0])))
201         return tasks
202
```
--- 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/core.py b/opsdroid/core.py
--- a/opsdroid/core.py
+++ b/opsdroid/core.py
@@ -2,6 +2,7 @@
 
 import copy
 import logging
+import signal
 import sys
 import weakref
 import asyncio
@@ -34,6 +35,8 @@
         self.connectors = []
         self.connector_tasks = []
         self.eventloop = asyncio.get_event_loop()
+        for sig in (signal.SIGINT, signal.SIGTERM):
+            self.eventloop.add_signal_handler(sig, self.stop)
         self.skills = []
         self.memory = Memory()
         self.loader = Loader(self)
@@ -100,6 +103,8 @@
         for task in pending:
             task.cancel()
         self.eventloop.stop()
+        print('')  # Prints a character return for return to shell
+        _LOGGER.info("Keyboard interrupt, exiting.")
 
     def load(self):
         """Load configuration."""
@@ -123,10 +128,6 @@
         try:
             pending = asyncio.Task.all_tasks()
             self.eventloop.run_until_complete(asyncio.gather(*pending))
-        except (KeyboardInterrupt, EOFError):
-            print('')  # Prints a character return for return to shell
-            self.stop()
-            _LOGGER.info("Keyboard interrupt, exiting.")
         except RuntimeError as error:
             if str(error) != 'Event loop is closed':
                 raise error

{"golden_diff": "diff --git a/opsdroid/core.py b/opsdroid/core.py\n--- a/opsdroid/core.py\n+++ b/opsdroid/core.py\n@@ -2,6 +2,7 @@\n \n import copy\n import logging\n+import signal\n import sys\n import weakref\n import asyncio\n@@ -34,6 +35,8 @@\n         self.connectors = []\n         self.connector_tasks = []\n         self.eventloop = asyncio.get_event_loop()\n+        for sig in (signal.SIGINT, signal.SIGTERM):\n+            self.eventloop.add_signal_handler(sig, self.stop)\n         self.skills = []\n         self.memory = Memory()\n         self.loader = Loader(self)\n@@ -100,6 +103,8 @@\n         for task in pending:\n             task.cancel()\n         self.eventloop.stop()\n+        print('')  # Prints a character return for return to shell\n+        _LOGGER.info(\"Keyboard interrupt, exiting.\")\n \n     def load(self):\n         \"\"\"Load configuration.\"\"\"\n@@ -123,10 +128,6 @@\n         try:\n             pending = asyncio.Task.all_tasks()\n             self.eventloop.run_until_complete(asyncio.gather(*pending))\n-        except (KeyboardInterrupt, EOFError):\n-            print('')  # Prints a character return for return to shell\n-            self.stop()\n-            _LOGGER.info(\"Keyboard interrupt, exiting.\")\n         except RuntimeError as error:\n             if str(error) != 'Event loop is closed':\n                 raise error\n", "issue": "SIGINT (Ctrl+C) should be handled by opsdroid\nWhen executing opsdroid in Ubuntu or in a Docker container, the following message appears:\r\n`(Press CTRL+C to quit)`\r\n\r\nWhen hitting `Ctrl+C` though, the following traceback is produced:\r\n\r\n```\r\n^CERROR asyncio: Task exception was never retrieved\r\nfuture: <Task finished coro=<ConnectorShell.listen() done, defined at /root/.opsdroid/modules/opsdroid-modules/connector/shell/__init__.py:57> exception=ConnectionResetError('Connection lost',)>\r\nTraceback (most recent call last):\r\n  File \"/usr/local/lib/python3.5/asyncio/tasks.py\", line 240, in _step\r\n    result = coro.send(None)\r\n  File \"/root/.opsdroid/modules/opsdroid-modules/connector/shell/__init__.py\", line 63, in listen\r\n    user_input = await async_input('', opsdroid.eventloop)\r\n  File \"/root/.opsdroid/modules/opsdroid-modules/connector/shell/__init__.py\", line 37, in async_input\r\n    await writer.drain()\r\n  File \"/usr/local/lib/python3.5/asyncio/streams.py\", line 333, in drain\r\n    yield from self._protocol._drain_helper()\r\n  File \"/usr/local/lib/python3.5/asyncio/streams.py\", line 204, in _drain_helper\r\n    raise ConnectionResetError('Connection lost')\r\nConnectionResetError: Connection lost\r\nERROR asyncio: Exception in default exception handler\r\nTraceback (most recent call last):\r\n  File \"/usr/local/lib/python3.5/asyncio/base_events.py\", line 1284, in call_exception_handler\r\n    self.default_exception_handler(context)\r\n  File \"/usr/local/lib/python3.5/asyncio/base_events.py\", line 1259, in default_exception_handler\r\n    logger.error('\\n'.join(log_lines), exc_info=exc_info)\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1314, in error\r\n    self._log(ERROR, msg, args, **kwargs)\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1421, in _log\r\n    self.handle(record)\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1431, in handle\r\n    self.callHandlers(record)\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1493, in callHandlers\r\n    hdlr.handle(record)\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 861, in handle\r\n    self.emit(record)\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1053, in emit\r\n    self.stream = self._open()\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1043, in _open\r\n    return open(self.baseFilename, self.mode, encoding=self.encoding)\r\nNameError: name 'open' is not defined\r\nException ignored in: <bound method Task.__del__ of <Task finished coro=<ConnectorShell.listen() done, defined at /root/.opsdroid/modules/opsdroid-modules/connector/shell/__init__.py:57> exception=ConnectionResetError('Connection lost',)>>\r\nTraceback (most recent call last):\r\n  File \"/usr/local/lib/python3.5/asyncio/tasks.py\", line 93, in __del__\r\n  File \"/usr/local/lib/python3.5/asyncio/futures.py\", line 234, in __del__\r\n  File \"/usr/local/lib/python3.5/asyncio/base_events.py\", line 1290, in call_exception_handler\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1314, in error\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1421, in _log\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1431, in handle\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1493, in callHandlers\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 861, in handle\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1053, in emit\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1043, in _open\r\nNameError: name 'open' is not defined\r\nERROR asyncio: Task was destroyed but it is pending!\r\ntask: <Task pending coro=<parse_crontab() running at /usr/src/app/opsdroid/parsers/crontab.py:20> wait_for=<Future pending cb=[Task._wakeup()]>>\r\nERROR asyncio: Exception in default exception handler\r\nTraceback (most recent call last):\r\n  File \"/usr/local/lib/python3.5/asyncio/base_events.py\", line 1284, in call_exception_handler\r\n    self.default_exception_handler(context)\r\n  File \"/usr/local/lib/python3.5/asyncio/base_events.py\", line 1259, in default_exception_handler\r\n    logger.error('\\n'.join(log_lines), exc_info=exc_info)\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1314, in error\r\n    self._log(ERROR, msg, args, **kwargs)\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1421, in _log\r\n    self.handle(record)\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1431, in handle\r\n    self.callHandlers(record)\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1493, in callHandlers\r\n    hdlr.handle(record)\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 861, in handle\r\n    self.emit(record)\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1053, in emit\r\n    self.stream = self._open()\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1043, in _open\r\n    return open(self.baseFilename, self.mode, encoding=self.encoding)\r\nNameError: name 'open' is not defined\r\nException ignored in: <bound method Task.__del__ of <Task pending coro=<parse_crontab() running at /usr/src/app/opsdroid/parsers/crontab.py:20> wait_for=<Future pending cb=[Task._wakeup()]>>>\r\nTraceback (most recent call last):\r\n  File \"/usr/local/lib/python3.5/asyncio/tasks.py\", line 92, in __del__\r\n  File \"/usr/local/lib/python3.5/asyncio/base_events.py\", line 1290, in call_exception_handler\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1314, in error\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1421, in _log\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1431, in handle\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1493, in callHandlers\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 861, in handle\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1053, in emit\r\n  File \"/usr/local/lib/python3.5/logging/__init__.py\", line 1043, in _open\r\nNameError: name 'open' is not defined\r\n```\r\n\r\nThis should be handled, so that opsdroid can exit gracefully without producing any tracebacks.\n", "before_files": [{"content": "\"\"\"Core components of OpsDroid.\"\"\"\n\nimport copy\nimport logging\nimport sys\nimport weakref\nimport asyncio\n\nfrom opsdroid.memory import Memory\nfrom opsdroid.connector import Connector\nfrom opsdroid.database import Database\nfrom opsdroid.loader import Loader\nfrom opsdroid.parsers.regex import parse_regex\nfrom opsdroid.parsers.apiai import parse_apiai\nfrom opsdroid.parsers.crontab import parse_crontab\nfrom opsdroid.const import DEFAULT_CONFIG_PATH\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\nclass OpsDroid():\n    \"\"\"Root object for opsdroid.\"\"\"\n\n    # pylint: disable=too-many-instance-attributes\n    # All are reasonable in this case.\n\n    instances = []\n\n    def __init__(self):\n        \"\"\"Start opsdroid.\"\"\"\n        self.bot_name = 'opsdroid'\n        self.sys_status = 0\n        self.connectors = []\n        self.connector_tasks = []\n        self.eventloop = asyncio.get_event_loop()\n        self.skills = []\n        self.memory = Memory()\n        self.loader = Loader(self)\n        self.config = {}\n        self.stats = {\n            \"messages_parsed\": 0,\n            \"webhooks_called\": 0,\n            \"total_response_time\": 0,\n            \"total_responses\": 0,\n        }\n        self.web_server = None\n        self.should_restart = False\n        self.stored_path = []\n        _LOGGER.info(\"Created main opsdroid object\")\n\n    def __enter__(self):\n        \"\"\"Add self to existing instances.\"\"\"\n        self.stored_path = copy.copy(sys.path)\n        if not self.__class__.instances:\n            self.__class__.instances.append(weakref.proxy(self))\n        else:\n            self.critical(\"opsdroid has already been started\", 1)\n        return self\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        \"\"\"Remove self from existing instances.\"\"\"\n        sys.path = self.stored_path\n        self.__class__.instances = []\n        asyncio.set_event_loop(asyncio.new_event_loop())\n\n    @property\n    def default_connector(self):\n        \"\"\"Return the default connector.\"\"\"\n        default_connector = None\n        for connector in self.connectors:\n            if \"default\" in connector.config and connector.config[\"default\"]:\n                default_connector = connector\n                break\n        if default_connector is None:\n            default_connector = self.connectors[0]\n        return default_connector\n\n    def exit(self):\n        \"\"\"Exit application.\"\"\"\n        _LOGGER.info(\"Exiting application with return code \" +\n                     str(self.sys_status))\n        sys.exit(self.sys_status)\n\n    def critical(self, error, code):\n        \"\"\"Exit due to unrecoverable error.\"\"\"\n        self.sys_status = code\n        _LOGGER.critical(error)\n        print(\"Error: \" + error)\n        self.exit()\n\n    def restart(self):\n        \"\"\"Restart opsdroid.\"\"\"\n        self.should_restart = True\n        self.stop()\n\n    def stop(self):\n        \"\"\"Stop the event loop.\"\"\"\n        pending = asyncio.Task.all_tasks()\n        for task in pending:\n            task.cancel()\n        self.eventloop.stop()\n\n    def load(self):\n        \"\"\"Load configuration.\"\"\"\n        self.config = self.loader.load_config_file([\n            \"./configuration.yaml\",\n            DEFAULT_CONFIG_PATH,\n            \"/etc/opsdroid/configuration.yaml\"\n            ])\n\n    def start_loop(self):\n        \"\"\"Start the event loop.\"\"\"\n        connectors, databases, skills = \\\n            self.loader.load_modules_from_config(self.config)\n        _LOGGER.debug(\"Loaded %i skills\", len(skills))\n        if databases is not None:\n            self.start_databases(databases)\n        self.setup_skills(skills)\n        self.start_connector_tasks(connectors)\n        self.eventloop.create_task(parse_crontab(self))\n        self.web_server.start()\n        try:\n            pending = asyncio.Task.all_tasks()\n            self.eventloop.run_until_complete(asyncio.gather(*pending))\n        except (KeyboardInterrupt, EOFError):\n            print('')  # Prints a character return for return to shell\n            self.stop()\n            _LOGGER.info(\"Keyboard interrupt, exiting.\")\n        except RuntimeError as error:\n            if str(error) != 'Event loop is closed':\n                raise error\n        finally:\n            self.eventloop.close()\n\n    def setup_skills(self, skills):\n        \"\"\"Call the setup function on the passed in skills.\"\"\"\n        for skill in skills:\n            try:\n                skill[\"module\"].setup(self)\n            except AttributeError:\n                pass\n\n    def start_connector_tasks(self, connectors):\n        \"\"\"Start the connectors.\"\"\"\n        for connector_module in connectors:\n            for _, cls in connector_module[\"module\"].__dict__.items():\n                if isinstance(cls, type) and \\\n                   issubclass(cls, Connector) and\\\n                   cls is not Connector:\n                    connector = cls(connector_module[\"config\"])\n                    self.connectors.append(connector)\n\n        if connectors:\n            for connector in self.connectors:\n                self.eventloop.run_until_complete(connector.connect(self))\n            for connector in self.connectors:\n                task = self.eventloop.create_task(connector.listen(self))\n                self.connector_tasks.append(task)\n        else:\n            self.critical(\"All connectors failed to load\", 1)\n\n    def start_databases(self, databases):\n        \"\"\"Start the databases.\"\"\"\n        if not databases:\n            _LOGGER.debug(databases)\n            _LOGGER.warning(\"All databases failed to load\")\n        for database_module in databases:\n            for name, cls in database_module[\"module\"].__dict__.items():\n                if isinstance(cls, type) and \\\n                   issubclass(cls, Database) and \\\n                   cls is not Database:\n                    _LOGGER.debug(\"Adding database: \" + name)\n                    database = cls(database_module[\"config\"])\n                    self.memory.databases.append(database)\n                    self.eventloop.run_until_complete(database.connect(self))\n\n    async def parse(self, message):\n        \"\"\"Parse a string against all skills.\"\"\"\n        self.stats[\"messages_parsed\"] = self.stats[\"messages_parsed\"] + 1\n        tasks = []\n        if message.text.strip() != \"\":\n            _LOGGER.debug(\"Parsing input: \" + message.text)\n\n            tasks.append(\n                self.eventloop.create_task(parse_regex(self, message)))\n\n            if \"parsers\" in self.config:\n                _LOGGER.debug(\"Processing parsers\")\n                parsers = self.config[\"parsers\"]\n\n                apiai = [p for p in parsers if p[\"name\"] == \"apiai\"]\n                _LOGGER.debug(\"Checking apiai\")\n                if len(apiai) == 1 and \\\n                        (\"enabled\" not in apiai[0] or\n                         apiai[0][\"enabled\"] is not False):\n                    _LOGGER.debug(\"Parsing with apiai\")\n                    tasks.append(\n                        self.eventloop.create_task(\n                            parse_apiai(self, message, apiai[0])))\n        return tasks\n", "path": "opsdroid/core.py"}], "after_files": [{"content": "\"\"\"Core components of OpsDroid.\"\"\"\n\nimport copy\nimport logging\nimport signal\nimport sys\nimport weakref\nimport asyncio\n\nfrom opsdroid.memory import Memory\nfrom opsdroid.connector import Connector\nfrom opsdroid.database import Database\nfrom opsdroid.loader import Loader\nfrom opsdroid.parsers.regex import parse_regex\nfrom opsdroid.parsers.apiai import parse_apiai\nfrom opsdroid.parsers.crontab import parse_crontab\nfrom opsdroid.const import DEFAULT_CONFIG_PATH\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\nclass OpsDroid():\n    \"\"\"Root object for opsdroid.\"\"\"\n\n    # pylint: disable=too-many-instance-attributes\n    # All are reasonable in this case.\n\n    instances = []\n\n    def __init__(self):\n        \"\"\"Start opsdroid.\"\"\"\n        self.bot_name = 'opsdroid'\n        self.sys_status = 0\n        self.connectors = []\n        self.connector_tasks = []\n        self.eventloop = asyncio.get_event_loop()\n        for sig in (signal.SIGINT, signal.SIGTERM):\n            self.eventloop.add_signal_handler(sig, self.stop)\n        self.skills = []\n        self.memory = Memory()\n        self.loader = Loader(self)\n        self.config = {}\n        self.stats = {\n            \"messages_parsed\": 0,\n            \"webhooks_called\": 0,\n            \"total_response_time\": 0,\n            \"total_responses\": 0,\n        }\n        self.web_server = None\n        self.should_restart = False\n        self.stored_path = []\n        _LOGGER.info(\"Created main opsdroid object\")\n\n    def __enter__(self):\n        \"\"\"Add self to existing instances.\"\"\"\n        self.stored_path = copy.copy(sys.path)\n        if not self.__class__.instances:\n            self.__class__.instances.append(weakref.proxy(self))\n        else:\n            self.critical(\"opsdroid has already been started\", 1)\n        return self\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        \"\"\"Remove self from existing instances.\"\"\"\n        sys.path = self.stored_path\n        self.__class__.instances = []\n        asyncio.set_event_loop(asyncio.new_event_loop())\n\n    @property\n    def default_connector(self):\n        \"\"\"Return the default connector.\"\"\"\n        default_connector = None\n        for connector in self.connectors:\n            if \"default\" in connector.config and connector.config[\"default\"]:\n                default_connector = connector\n                break\n        if default_connector is None:\n            default_connector = self.connectors[0]\n        return default_connector\n\n    def exit(self):\n        \"\"\"Exit application.\"\"\"\n        _LOGGER.info(\"Exiting application with return code \" +\n                     str(self.sys_status))\n        sys.exit(self.sys_status)\n\n    def critical(self, error, code):\n        \"\"\"Exit due to unrecoverable error.\"\"\"\n        self.sys_status = code\n        _LOGGER.critical(error)\n        print(\"Error: \" + error)\n        self.exit()\n\n    def restart(self):\n        \"\"\"Restart opsdroid.\"\"\"\n        self.should_restart = True\n        self.stop()\n\n    def stop(self):\n        \"\"\"Stop the event loop.\"\"\"\n        pending = asyncio.Task.all_tasks()\n        for task in pending:\n            task.cancel()\n        self.eventloop.stop()\n        print('')  # Prints a character return for return to shell\n        _LOGGER.info(\"Keyboard interrupt, exiting.\")\n\n    def load(self):\n        \"\"\"Load configuration.\"\"\"\n        self.config = self.loader.load_config_file([\n            \"./configuration.yaml\",\n            DEFAULT_CONFIG_PATH,\n            \"/etc/opsdroid/configuration.yaml\"\n            ])\n\n    def start_loop(self):\n        \"\"\"Start the event loop.\"\"\"\n        connectors, databases, skills = \\\n            self.loader.load_modules_from_config(self.config)\n        _LOGGER.debug(\"Loaded %i skills\", len(skills))\n        if databases is not None:\n            self.start_databases(databases)\n        self.setup_skills(skills)\n        self.start_connector_tasks(connectors)\n        self.eventloop.create_task(parse_crontab(self))\n        self.web_server.start()\n        try:\n            pending = asyncio.Task.all_tasks()\n            self.eventloop.run_until_complete(asyncio.gather(*pending))\n        except RuntimeError as error:\n            if str(error) != 'Event loop is closed':\n                raise error\n        finally:\n            self.eventloop.close()\n\n    def setup_skills(self, skills):\n        \"\"\"Call the setup function on the passed in skills.\"\"\"\n        for skill in skills:\n            try:\n                skill[\"module\"].setup(self)\n            except AttributeError:\n                pass\n\n    def start_connector_tasks(self, connectors):\n        \"\"\"Start the connectors.\"\"\"\n        for connector_module in connectors:\n            for _, cls in connector_module[\"module\"].__dict__.items():\n                if isinstance(cls, type) and \\\n                   issubclass(cls, Connector) and\\\n                   cls is not Connector:\n                    connector = cls(connector_module[\"config\"])\n                    self.connectors.append(connector)\n\n        if connectors:\n            for connector in self.connectors:\n                self.eventloop.run_until_complete(connector.connect(self))\n            for connector in self.connectors:\n                task = self.eventloop.create_task(connector.listen(self))\n                self.connector_tasks.append(task)\n        else:\n            self.critical(\"All connectors failed to load\", 1)\n\n    def start_databases(self, databases):\n        \"\"\"Start the databases.\"\"\"\n        if not databases:\n            _LOGGER.debug(databases)\n            _LOGGER.warning(\"All databases failed to load\")\n        for database_module in databases:\n            for name, cls in database_module[\"module\"].__dict__.items():\n                if isinstance(cls, type) and \\\n                   issubclass(cls, Database) and \\\n                   cls is not Database:\n                    _LOGGER.debug(\"Adding database: \" + name)\n                    database = cls(database_module[\"config\"])\n                    self.memory.databases.append(database)\n                    self.eventloop.run_until_complete(database.connect(self))\n\n    async def parse(self, message):\n        \"\"\"Parse a string against all skills.\"\"\"\n        self.stats[\"messages_parsed\"] = self.stats[\"messages_parsed\"] + 1\n        tasks = []\n        if message.text.strip() != \"\":\n            _LOGGER.debug(\"Parsing input: \" + message.text)\n\n            tasks.append(\n                self.eventloop.create_task(parse_regex(self, message)))\n\n            if \"parsers\" in self.config:\n                _LOGGER.debug(\"Processing parsers\")\n                parsers = self.config[\"parsers\"]\n\n                apiai = [p for p in parsers if p[\"name\"] == \"apiai\"]\n                _LOGGER.debug(\"Checking apiai\")\n                if len(apiai) == 1 and \\\n                        (\"enabled\" not in apiai[0] or\n                         apiai[0][\"enabled\"] is not False):\n                    _LOGGER.debug(\"Parsing with apiai\")\n                    tasks.append(\n                        self.eventloop.create_task(\n                            parse_apiai(self, message, apiai[0])))\n        return tasks\n", "path": "opsdroid/core.py"}]}