We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Skill setup should be optional
You might not want to have a setup function in your skill. 


--- 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/loader.py`
Content:
```
1 """Class for loading in modules to OpsDroid."""
2 
3 import logging
4 import os
5 import shutil
6 import subprocess
7 import importlib
8 import yaml
9 from opsdroid.const import (
10     DEFAULT_GIT_URL, MODULES_DIRECTORY, DEFAULT_MODULE_BRANCH)
11 
12 
13 def import_module(config):
14     """Import module namespace as variable and return it."""
15     try:
16         module = importlib.import_module(
17             config["path"] + "." + config["name"])
18         logging.debug("Loading " + config["type"] + ": " + config["name"])
19         return module
20     except ImportError as error:
21         logging.error("Failed to load " + config["type"] +
22                       " " + config["name"])
23         logging.error(error)
24         return None
25 
26 
27 def check_cache(config):
28     """Remove module if 'no-cache' set in config."""
29     if "no-cache" in config \
30             and config["no-cache"] \
31             and os.path.isdir(config["install_path"]):
32         logging.debug("'no-cache' set, removing " + config["install_path"])
33         shutil.rmtree(config["install_path"])
34 
35 
36 def build_module_path(path_type, config):
37     """Generate the module path from name and type."""
38     if path_type == "import":
39         return MODULES_DIRECTORY + "." + config["type"] + "." + config["name"]
40     elif path_type == "install":
41         return MODULES_DIRECTORY + "/" + config["type"] + "/" + config["name"]
42 
43 
44 def git_clone(git_url, install_path, branch):
45     """Clone a git repo to a location and wait for finish."""
46     process = subprocess.Popen(["git", "clone", "-b", branch,
47                                 git_url, install_path], shell=False,
48                                stdout=subprocess.PIPE,
49                                stderr=subprocess.PIPE)
50     process.wait()
51 
52 
53 def pip_install_deps(requirements_path):
54     """Pip install a requirements.txt file and wait for finish."""
55     process = subprocess.Popen(["pip", "install", "-r", requirements_path],
56                                shell=False,
57                                stdout=subprocess.PIPE,
58                                stderr=subprocess.PIPE)
59     for output in process.communicate():
60         if output != "":
61             for line in output.splitlines():
62                 logging.debug(str(line).strip())
63     process.wait()
64 
65 
66 class Loader:
67     """Class to load in config and modules."""
68 
69     def __init__(self, opsdroid):
70         """Setup object with opsdroid instance."""
71         self.opsdroid = opsdroid
72         logging.debug("Loaded loader")
73 
74     def load_config_file(self, config_paths):
75         """Load a yaml config file from path."""
76         config_path = ""
77         for possible_path in config_paths:
78             if not os.path.isfile(possible_path):
79                 logging.warning("Config file " + possible_path +
80                                 " not found", 1)
81             else:
82                 config_path = possible_path
83                 break
84 
85         if not config_path:
86             self.opsdroid.critical("No configuration files found", 1)
87 
88         try:
89             with open(config_path, 'r') as stream:
90                 return yaml.load(stream)
91         except yaml.YAMLError as error:
92             self.opsdroid.critical(error, 1)
93         except FileNotFoundError as error:
94             self.opsdroid.critical(str(error), 1)
95 
96     def load_config(self, config):
97         """Load all module types based on config."""
98         logging.debug("Loading modules from config")
99 
100         if 'databases' in config.keys():
101             self.opsdroid.start_databases(
102                 self._load_modules('database', config['databases']))
103         else:
104             logging.warning("No databases in configuration")
105 
106         if 'skills' in config.keys():
107             self._setup_modules(
108                 self._load_modules('skill', config['skills'])
109             )
110         else:
111             self.opsdroid.critical(
112                 "No skills in configuration, at least 1 required", 1)
113 
114         if 'connectors' in config.keys():
115             self.opsdroid.start_connectors(
116                 self._load_modules('connector', config['connectors']))
117         else:
118             self.opsdroid.critical(
119                 "No connectors in configuration, at least 1 required", 1)
120 
121     def _load_modules(self, modules_type, modules):
122         """Install and load modules."""
123         logging.debug("Loading " + modules_type + " modules")
124         loaded_modules = []
125 
126         # Create modules directory if doesn't exist
127         if not os.path.isdir(MODULES_DIRECTORY):
128             os.makedirs(MODULES_DIRECTORY)
129 
130         for module_name in modules.keys():
131 
132             # Set up module config
133             config = modules[module_name]
134             config = {} if config is None else config
135             config["name"] = module_name
136             config["type"] = modules_type
137             config["path"] = build_module_path("import", config)
138             config["install_path"] = build_module_path("install", config)
139             if "branch" not in config:
140                 config["branch"] = DEFAULT_MODULE_BRANCH
141 
142             # Remove module for reinstall if no-cache set
143             check_cache(config)
144 
145             # Install module
146             self._install_module(config)
147 
148             # Import module
149             module = import_module(config)
150             if module is not None:
151                 loaded_modules.append({
152                     "module": module,
153                     "config": config})
154 
155         return loaded_modules
156 
157     def _setup_modules(self, modules):
158         """Call the setup function on the passed in modules."""
159         for module in modules:
160             module["module"].setup(self.opsdroid)
161 
162     def _install_module(self, config):
163         # pylint: disable=R0201
164         """Install a module."""
165         logging.debug("Installing " + config["name"])
166 
167         if os.path.isdir(config["install_path"]):
168             # TODO Allow for updating or reinstalling of modules
169             logging.debug("Module " + config["name"] +
170                           " already installed, skipping")
171         else:
172             if config is not None and "repo" in config:
173                 git_url = config["repo"]
174             else:
175                 git_url = DEFAULT_GIT_URL + config["type"] + \
176                             "-" + config["name"] + ".git"
177 
178             if any(prefix in git_url for prefix in ["http", "https", "ssh"]):
179                 # TODO Test if url or ssh path exists
180                 # TODO Handle github authentication
181                 git_clone(git_url, config["install_path"], config["branch"])
182             else:
183                 if os.path.isdir(git_url):
184                     git_clone(git_url, config["install_path"],
185                               config["branch"])
186                 else:
187                     logging.debug("Could not find local git repo " + git_url)
188 
189             if os.path.isdir(config["install_path"]):
190                 logging.debug("Installed " + config["name"] +
191                               " to " + config["install_path"])
192             else:
193                 logging.debug("Install of " + config["name"] + " failed ")
194 
195             # Install module dependancies
196             if os.path.isfile(config["install_path"] + "/requirements.txt"):
197                 pip_install_deps(config["install_path"] + "/requirements.txt")
198
```
--- 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/loader.py b/opsdroid/loader.py
--- a/opsdroid/loader.py
+++ b/opsdroid/loader.py
@@ -157,7 +157,10 @@
     def _setup_modules(self, modules):
         """Call the setup function on the passed in modules."""
         for module in modules:
-            module["module"].setup(self.opsdroid)
+            try:
+                module["module"].setup(self.opsdroid)
+            except AttributeError:
+                pass
 
     def _install_module(self, config):
         # pylint: disable=R0201

{"golden_diff": "diff --git a/opsdroid/loader.py b/opsdroid/loader.py\n--- a/opsdroid/loader.py\n+++ b/opsdroid/loader.py\n@@ -157,7 +157,10 @@\n     def _setup_modules(self, modules):\n         \"\"\"Call the setup function on the passed in modules.\"\"\"\n         for module in modules:\n-            module[\"module\"].setup(self.opsdroid)\n+            try:\n+                module[\"module\"].setup(self.opsdroid)\n+            except AttributeError:\n+                pass\n \n     def _install_module(self, config):\n         # pylint: disable=R0201\n", "issue": "Skill setup should be optional\nYou might not want to have a setup function in your skill. \n\n", "before_files": [{"content": "\"\"\"Class for loading in modules to OpsDroid.\"\"\"\n\nimport logging\nimport os\nimport shutil\nimport subprocess\nimport importlib\nimport yaml\nfrom opsdroid.const import (\n    DEFAULT_GIT_URL, MODULES_DIRECTORY, DEFAULT_MODULE_BRANCH)\n\n\ndef import_module(config):\n    \"\"\"Import module namespace as variable and return it.\"\"\"\n    try:\n        module = importlib.import_module(\n            config[\"path\"] + \".\" + config[\"name\"])\n        logging.debug(\"Loading \" + config[\"type\"] + \": \" + config[\"name\"])\n        return module\n    except ImportError as error:\n        logging.error(\"Failed to load \" + config[\"type\"] +\n                      \" \" + config[\"name\"])\n        logging.error(error)\n        return None\n\n\ndef check_cache(config):\n    \"\"\"Remove module if 'no-cache' set in config.\"\"\"\n    if \"no-cache\" in config \\\n            and config[\"no-cache\"] \\\n            and os.path.isdir(config[\"install_path\"]):\n        logging.debug(\"'no-cache' set, removing \" + config[\"install_path\"])\n        shutil.rmtree(config[\"install_path\"])\n\n\ndef build_module_path(path_type, config):\n    \"\"\"Generate the module path from name and type.\"\"\"\n    if path_type == \"import\":\n        return MODULES_DIRECTORY + \".\" + config[\"type\"] + \".\" + config[\"name\"]\n    elif path_type == \"install\":\n        return MODULES_DIRECTORY + \"/\" + config[\"type\"] + \"/\" + config[\"name\"]\n\n\ndef git_clone(git_url, install_path, branch):\n    \"\"\"Clone a git repo to a location and wait for finish.\"\"\"\n    process = subprocess.Popen([\"git\", \"clone\", \"-b\", branch,\n                                git_url, install_path], shell=False,\n                               stdout=subprocess.PIPE,\n                               stderr=subprocess.PIPE)\n    process.wait()\n\n\ndef pip_install_deps(requirements_path):\n    \"\"\"Pip install a requirements.txt file and wait for finish.\"\"\"\n    process = subprocess.Popen([\"pip\", \"install\", \"-r\", requirements_path],\n                               shell=False,\n                               stdout=subprocess.PIPE,\n                               stderr=subprocess.PIPE)\n    for output in process.communicate():\n        if output != \"\":\n            for line in output.splitlines():\n                logging.debug(str(line).strip())\n    process.wait()\n\n\nclass Loader:\n    \"\"\"Class to load in config and modules.\"\"\"\n\n    def __init__(self, opsdroid):\n        \"\"\"Setup object with opsdroid instance.\"\"\"\n        self.opsdroid = opsdroid\n        logging.debug(\"Loaded loader\")\n\n    def load_config_file(self, config_paths):\n        \"\"\"Load a yaml config file from path.\"\"\"\n        config_path = \"\"\n        for possible_path in config_paths:\n            if not os.path.isfile(possible_path):\n                logging.warning(\"Config file \" + possible_path +\n                                \" not found\", 1)\n            else:\n                config_path = possible_path\n                break\n\n        if not config_path:\n            self.opsdroid.critical(\"No configuration files found\", 1)\n\n        try:\n            with open(config_path, 'r') as stream:\n                return yaml.load(stream)\n        except yaml.YAMLError as error:\n            self.opsdroid.critical(error, 1)\n        except FileNotFoundError as error:\n            self.opsdroid.critical(str(error), 1)\n\n    def load_config(self, config):\n        \"\"\"Load all module types based on config.\"\"\"\n        logging.debug(\"Loading modules from config\")\n\n        if 'databases' in config.keys():\n            self.opsdroid.start_databases(\n                self._load_modules('database', config['databases']))\n        else:\n            logging.warning(\"No databases in configuration\")\n\n        if 'skills' in config.keys():\n            self._setup_modules(\n                self._load_modules('skill', config['skills'])\n            )\n        else:\n            self.opsdroid.critical(\n                \"No skills in configuration, at least 1 required\", 1)\n\n        if 'connectors' in config.keys():\n            self.opsdroid.start_connectors(\n                self._load_modules('connector', config['connectors']))\n        else:\n            self.opsdroid.critical(\n                \"No connectors in configuration, at least 1 required\", 1)\n\n    def _load_modules(self, modules_type, modules):\n        \"\"\"Install and load modules.\"\"\"\n        logging.debug(\"Loading \" + modules_type + \" modules\")\n        loaded_modules = []\n\n        # Create modules directory if doesn't exist\n        if not os.path.isdir(MODULES_DIRECTORY):\n            os.makedirs(MODULES_DIRECTORY)\n\n        for module_name in modules.keys():\n\n            # Set up module config\n            config = modules[module_name]\n            config = {} if config is None else config\n            config[\"name\"] = module_name\n            config[\"type\"] = modules_type\n            config[\"path\"] = build_module_path(\"import\", config)\n            config[\"install_path\"] = build_module_path(\"install\", config)\n            if \"branch\" not in config:\n                config[\"branch\"] = DEFAULT_MODULE_BRANCH\n\n            # Remove module for reinstall if no-cache set\n            check_cache(config)\n\n            # Install module\n            self._install_module(config)\n\n            # Import module\n            module = import_module(config)\n            if module is not None:\n                loaded_modules.append({\n                    \"module\": module,\n                    \"config\": config})\n\n        return loaded_modules\n\n    def _setup_modules(self, modules):\n        \"\"\"Call the setup function on the passed in modules.\"\"\"\n        for module in modules:\n            module[\"module\"].setup(self.opsdroid)\n\n    def _install_module(self, config):\n        # pylint: disable=R0201\n        \"\"\"Install a module.\"\"\"\n        logging.debug(\"Installing \" + config[\"name\"])\n\n        if os.path.isdir(config[\"install_path\"]):\n            # TODO Allow for updating or reinstalling of modules\n            logging.debug(\"Module \" + config[\"name\"] +\n                          \" already installed, skipping\")\n        else:\n            if config is not None and \"repo\" in config:\n                git_url = config[\"repo\"]\n            else:\n                git_url = DEFAULT_GIT_URL + config[\"type\"] + \\\n                            \"-\" + config[\"name\"] + \".git\"\n\n            if any(prefix in git_url for prefix in [\"http\", \"https\", \"ssh\"]):\n                # TODO Test if url or ssh path exists\n                # TODO Handle github authentication\n                git_clone(git_url, config[\"install_path\"], config[\"branch\"])\n            else:\n                if os.path.isdir(git_url):\n                    git_clone(git_url, config[\"install_path\"],\n                              config[\"branch\"])\n                else:\n                    logging.debug(\"Could not find local git repo \" + git_url)\n\n            if os.path.isdir(config[\"install_path\"]):\n                logging.debug(\"Installed \" + config[\"name\"] +\n                              \" to \" + config[\"install_path\"])\n            else:\n                logging.debug(\"Install of \" + config[\"name\"] + \" failed \")\n\n            # Install module dependancies\n            if os.path.isfile(config[\"install_path\"] + \"/requirements.txt\"):\n                pip_install_deps(config[\"install_path\"] + \"/requirements.txt\")\n", "path": "opsdroid/loader.py"}], "after_files": [{"content": "\"\"\"Class for loading in modules to OpsDroid.\"\"\"\n\nimport logging\nimport os\nimport shutil\nimport subprocess\nimport importlib\nimport yaml\nfrom opsdroid.const import (\n    DEFAULT_GIT_URL, MODULES_DIRECTORY, DEFAULT_MODULE_BRANCH)\n\n\ndef import_module(config):\n    \"\"\"Import module namespace as variable and return it.\"\"\"\n    try:\n        module = importlib.import_module(\n            config[\"path\"] + \".\" + config[\"name\"])\n        logging.debug(\"Loading \" + config[\"type\"] + \": \" + config[\"name\"])\n        return module\n    except ImportError as error:\n        logging.error(\"Failed to load \" + config[\"type\"] +\n                      \" \" + config[\"name\"])\n        logging.error(error)\n        return None\n\n\ndef check_cache(config):\n    \"\"\"Remove module if 'no-cache' set in config.\"\"\"\n    if \"no-cache\" in config \\\n            and config[\"no-cache\"] \\\n            and os.path.isdir(config[\"install_path\"]):\n        logging.debug(\"'no-cache' set, removing \" + config[\"install_path\"])\n        shutil.rmtree(config[\"install_path\"])\n\n\ndef build_module_path(path_type, config):\n    \"\"\"Generate the module path from name and type.\"\"\"\n    if path_type == \"import\":\n        return MODULES_DIRECTORY + \".\" + config[\"type\"] + \".\" + config[\"name\"]\n    elif path_type == \"install\":\n        return MODULES_DIRECTORY + \"/\" + config[\"type\"] + \"/\" + config[\"name\"]\n\n\ndef git_clone(git_url, install_path, branch):\n    \"\"\"Clone a git repo to a location and wait for finish.\"\"\"\n    process = subprocess.Popen([\"git\", \"clone\", \"-b\", branch,\n                                git_url, install_path], shell=False,\n                               stdout=subprocess.PIPE,\n                               stderr=subprocess.PIPE)\n    process.wait()\n\n\ndef pip_install_deps(requirements_path):\n    \"\"\"Pip install a requirements.txt file and wait for finish.\"\"\"\n    process = subprocess.Popen([\"pip\", \"install\", \"-r\", requirements_path],\n                               shell=False,\n                               stdout=subprocess.PIPE,\n                               stderr=subprocess.PIPE)\n    for output in process.communicate():\n        if output != \"\":\n            for line in output.splitlines():\n                logging.debug(str(line).strip())\n    process.wait()\n\n\nclass Loader:\n    \"\"\"Class to load in config and modules.\"\"\"\n\n    def __init__(self, opsdroid):\n        \"\"\"Setup object with opsdroid instance.\"\"\"\n        self.opsdroid = opsdroid\n        logging.debug(\"Loaded loader\")\n\n    def load_config_file(self, config_paths):\n        \"\"\"Load a yaml config file from path.\"\"\"\n        config_path = \"\"\n        for possible_path in config_paths:\n            if not os.path.isfile(possible_path):\n                logging.warning(\"Config file \" + possible_path +\n                                \" not found\", 1)\n            else:\n                config_path = possible_path\n                break\n\n        if not config_path:\n            self.opsdroid.critical(\"No configuration files found\", 1)\n\n        try:\n            with open(config_path, 'r') as stream:\n                return yaml.load(stream)\n        except yaml.YAMLError as error:\n            self.opsdroid.critical(error, 1)\n        except FileNotFoundError as error:\n            self.opsdroid.critical(str(error), 1)\n\n    def load_config(self, config):\n        \"\"\"Load all module types based on config.\"\"\"\n        logging.debug(\"Loading modules from config\")\n\n        if 'databases' in config.keys():\n            self.opsdroid.start_databases(\n                self._load_modules('database', config['databases']))\n        else:\n            logging.warning(\"No databases in configuration\")\n\n        if 'skills' in config.keys():\n            self._setup_modules(\n                self._load_modules('skill', config['skills'])\n            )\n        else:\n            self.opsdroid.critical(\n                \"No skills in configuration, at least 1 required\", 1)\n\n        if 'connectors' in config.keys():\n            self.opsdroid.start_connectors(\n                self._load_modules('connector', config['connectors']))\n        else:\n            self.opsdroid.critical(\n                \"No connectors in configuration, at least 1 required\", 1)\n\n    def _load_modules(self, modules_type, modules):\n        \"\"\"Install and load modules.\"\"\"\n        logging.debug(\"Loading \" + modules_type + \" modules\")\n        loaded_modules = []\n\n        # Create modules directory if doesn't exist\n        if not os.path.isdir(MODULES_DIRECTORY):\n            os.makedirs(MODULES_DIRECTORY)\n\n        for module_name in modules.keys():\n\n            # Set up module config\n            config = modules[module_name]\n            config = {} if config is None else config\n            config[\"name\"] = module_name\n            config[\"type\"] = modules_type\n            config[\"path\"] = build_module_path(\"import\", config)\n            config[\"install_path\"] = build_module_path(\"install\", config)\n            if \"branch\" not in config:\n                config[\"branch\"] = DEFAULT_MODULE_BRANCH\n\n            # Remove module for reinstall if no-cache set\n            check_cache(config)\n\n            # Install module\n            self._install_module(config)\n\n            # Import module\n            module = import_module(config)\n            if module is not None:\n                loaded_modules.append({\n                    \"module\": module,\n                    \"config\": config})\n\n        return loaded_modules\n\n    def _setup_modules(self, modules):\n        \"\"\"Call the setup function on the passed in modules.\"\"\"\n        for module in modules:\n            try:\n                module[\"module\"].setup(self.opsdroid)\n            except AttributeError:\n                pass\n\n    def _install_module(self, config):\n        # pylint: disable=R0201\n        \"\"\"Install a module.\"\"\"\n        logging.debug(\"Installing \" + config[\"name\"])\n\n        if os.path.isdir(config[\"install_path\"]):\n            # TODO Allow for updating or reinstalling of modules\n            logging.debug(\"Module \" + config[\"name\"] +\n                          \" already installed, skipping\")\n        else:\n            if config is not None and \"repo\" in config:\n                git_url = config[\"repo\"]\n            else:\n                git_url = DEFAULT_GIT_URL + config[\"type\"] + \\\n                            \"-\" + config[\"name\"] + \".git\"\n\n            if any(prefix in git_url for prefix in [\"http\", \"https\", \"ssh\"]):\n                # TODO Test if url or ssh path exists\n                # TODO Handle github authentication\n                git_clone(git_url, config[\"install_path\"], config[\"branch\"])\n            else:\n                if os.path.isdir(git_url):\n                    git_clone(git_url, config[\"install_path\"],\n                              config[\"branch\"])\n                else:\n                    logging.debug(\"Could not find local git repo \" + git_url)\n\n            if os.path.isdir(config[\"install_path\"]):\n                logging.debug(\"Installed \" + config[\"name\"] +\n                              \" to \" + config[\"install_path\"])\n            else:\n                logging.debug(\"Install of \" + config[\"name\"] + \" failed \")\n\n            # Install module dependancies\n            if os.path.isfile(config[\"install_path\"] + \"/requirements.txt\"):\n                pip_install_deps(config[\"install_path\"] + \"/requirements.txt\")\n", "path": "opsdroid/loader.py"}]}