We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Cannot use ~ in module paths
Modules specified in the configuration with a path that includes the `~` character do not import. This character needs to be expanded.

--- 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 sys
6 import shutil
7 import subprocess
8 import importlib
9 import yaml
10 from opsdroid.const import (
11     DEFAULT_GIT_URL, MODULES_DIRECTORY, DEFAULT_MODULES_PATH,
12     DEFAULT_MODULE_BRANCH, DEFAULT_CONFIG_PATH, EXAMPLE_CONFIG_FILE,
13     DEFAULT_MODULE_DEPS_PATH)
14 
15 
16 _LOGGER = logging.getLogger(__name__)
17 
18 
19 class Loader:
20     """Class to load in config and modules."""
21 
22     def __init__(self, opsdroid):
23         """Create object with opsdroid instance."""
24         self.opsdroid = opsdroid
25         self.modules_directory = None
26         self.current_import_config = None
27         _LOGGER.debug("Loaded loader")
28 
29     @staticmethod
30     def import_module(config):
31         """Import module namespace as variable and return it."""
32         try:
33             module = importlib.import_module(
34                 config["module_path"] + "." + config["name"])
35             _LOGGER.debug("Loaded " + config["type"] + ": " +
36                           config["module_path"])
37             return module
38         except ImportError as error:
39             _LOGGER.debug("Failed to load " + config["type"] +
40                           " " + config["module_path"] + "." + config["name"])
41             _LOGGER.debug(error)
42 
43         try:
44             module = importlib.import_module(
45                 config["module_path"])
46             _LOGGER.debug("Loaded " + config["type"] + ": " +
47                           config["module_path"])
48             return module
49         except ImportError as error:
50             _LOGGER.debug("Failed to load " + config["type"] +
51                           " " + config["module_path"])
52             _LOGGER.debug(error)
53 
54         _LOGGER.error("Failed to load " + config["type"] +
55                       " " + config["module_path"])
56         return None
57 
58     @staticmethod
59     def check_cache(config):
60         """Remove module if 'no-cache' set in config."""
61         if "no-cache" in config \
62                 and config["no-cache"]:
63             _LOGGER.debug("'no-cache' set, removing " + config["install_path"])
64             if os.path.isdir(config["install_path"]):
65                 shutil.rmtree(config["install_path"])
66             if os.path.isfile(config["install_path"] + ".py"):
67                 os.remove(config["install_path"] + ".py")
68 
69     def build_module_path(self, path_type, config):
70         """Generate the module path from name and type."""
71         if path_type == "import":
72             return MODULES_DIRECTORY + "." + config["type"] + \
73                         "." + config["name"]
74         elif path_type == "install":
75             return self.modules_directory + "/" + config["type"] + \
76                         "/" + config["name"]
77 
78     @staticmethod
79     def git_clone(git_url, install_path, branch):
80         """Clone a git repo to a location and wait for finish."""
81         process = subprocess.Popen(["git", "clone", "-b", branch,
82                                     git_url, install_path], shell=False,
83                                    stdout=subprocess.PIPE,
84                                    stderr=subprocess.PIPE)
85         for output in process.communicate():
86             if output != "":
87                 for line in output.splitlines():
88                     _LOGGER.debug(str(line).strip())
89         process.wait()
90 
91     @staticmethod
92     def pip_install_deps(requirements_path):
93         """Pip install a requirements.txt file and wait for finish."""
94         process = subprocess.Popen(["pip", "install",
95                                     "--target={}".format(
96                                         DEFAULT_MODULE_DEPS_PATH),
97                                     "--ignore-installed",
98                                     "-r", requirements_path],
99                                    shell=False,
100                                    stdout=subprocess.PIPE,
101                                    stderr=subprocess.PIPE)
102         for output in process.communicate():
103             if output != "":
104                 for line in output.splitlines():
105                     _LOGGER.debug(str(line).strip())
106         process.wait()
107 
108     @staticmethod
109     def create_default_config(config_path):
110         """Create a default config file based on the included example."""
111         _LOGGER.info("Creating %s.", config_path)
112         config_dir, _ = os.path.split(config_path)
113         if not os.path.isdir(config_dir):
114             os.makedirs(config_dir)
115         shutil.copyfile(EXAMPLE_CONFIG_FILE, config_path)
116         return config_path
117 
118     @staticmethod
119     def _reload_modules(modules):
120         for module in modules:
121             importlib.reload(module["module"])
122 
123     def load_config_file(self, config_paths):
124         """Load a yaml config file from path."""
125         config_path = ""
126         for possible_path in config_paths:
127             if not os.path.isfile(possible_path):
128                 _LOGGER.debug("Config file " + possible_path +
129                               " not found")
130             else:
131                 config_path = possible_path
132                 break
133 
134         if not config_path:
135             _LOGGER.info("No configuration files found.")
136             config_path = self.create_default_config(DEFAULT_CONFIG_PATH)
137 
138         try:
139             with open(config_path, 'r') as stream:
140                 _LOGGER.info("Loaded config from %s", config_path)
141                 return yaml.load(stream)
142         except yaml.YAMLError as error:
143             self.opsdroid.critical(error, 1)
144         except FileNotFoundError as error:
145             self.opsdroid.critical(str(error), 1)
146 
147     def setup_modules_directory(self, config):
148         """Create and configure the modules directory."""
149         module_path = os.path.expanduser(
150             config.get("module-path", DEFAULT_MODULES_PATH))
151         sys.path.append(module_path)
152 
153         if not os.path.isdir(module_path):
154             os.makedirs(module_path, exist_ok=True)
155 
156         self.modules_directory = os.path.join(module_path, MODULES_DIRECTORY)
157 
158         # Create modules directory if doesn't exist
159         if not os.path.isdir(self.modules_directory):
160             os.makedirs(self.modules_directory)
161 
162     def load_modules_from_config(self, config):
163         """Load all module types based on config."""
164         _LOGGER.debug("Loading modules from config")
165 
166         self.setup_modules_directory(config)
167 
168         connectors, databases, skills = None, None, None
169 
170         if 'databases' in config.keys():
171             databases = self._load_modules('database', config['databases'])
172         else:
173             _LOGGER.warning("No databases in configuration")
174 
175         if 'skills' in config.keys():
176             skills = self._load_modules('skill', config['skills'])
177             self.opsdroid.skills = []
178             self._reload_modules(skills)
179         else:
180             self.opsdroid.critical(
181                 "No skills in configuration, at least 1 required", 1)
182 
183         if 'connectors' in config.keys():
184             connectors = self._load_modules('connector', config['connectors'])
185         else:
186             self.opsdroid.critical(
187                 "No connectors in configuration, at least 1 required", 1)
188 
189         return connectors, databases, skills
190 
191     def _load_modules(self, modules_type, modules):
192         """Install and load modules."""
193         _LOGGER.debug("Loading " + modules_type + " modules")
194         loaded_modules = []
195 
196         if not os.path.isdir(DEFAULT_MODULE_DEPS_PATH):
197             os.makedirs(DEFAULT_MODULE_DEPS_PATH)
198         sys.path.append(DEFAULT_MODULE_DEPS_PATH)
199 
200         for module in modules:
201 
202             # Set up module config
203             config = module
204             config = {} if config is None else config
205             config["name"] = module["name"]
206             config["type"] = modules_type
207             config["module_path"] = self.build_module_path("import", config)
208             config["install_path"] = self.build_module_path("install", config)
209             if "branch" not in config:
210                 config["branch"] = DEFAULT_MODULE_BRANCH
211 
212             # Remove module for reinstall if no-cache set
213             self.check_cache(config)
214 
215             # Install module
216             self._install_module(config)
217 
218             # Import module
219             self.current_import_config = config
220             module = self.import_module(config)
221             if module is not None:
222                 loaded_modules.append({
223                     "module": module,
224                     "config": config})
225             else:
226                 _LOGGER.error(
227                     "Module " + config["name"] + " failed to import")
228 
229         return loaded_modules
230 
231     def _install_module(self, config):
232         # pylint: disable=R0201
233         """Install a module."""
234         _LOGGER.debug("Installing " + config["name"])
235 
236         if os.path.isdir(config["install_path"]) or \
237                 os.path.isfile(config["install_path"] + ".py"):
238             # TODO Allow for updating or reinstalling of modules
239             _LOGGER.debug("Module " + config["name"] +
240                           " already installed, skipping")
241             return
242 
243         if "path" in config:
244             self._install_local_module(config)
245         else:
246             self._install_git_module(config)
247 
248         if os.path.isdir(config["install_path"]):
249             _LOGGER.debug("Installed " + config["name"] +
250                           " to " + config["install_path"])
251         else:
252             _LOGGER.debug("Install of " + config["name"] + " failed")
253 
254         # Install module dependancies
255         if os.path.isfile(config["install_path"] + "/requirements.txt"):
256             self.pip_install_deps(config["install_path"] +
257                                   "/requirements.txt")
258 
259     def _install_git_module(self, config):
260         """Install a module from a git repository."""
261         if config is not None and "repo" in config:
262             git_url = config["repo"]
263         else:
264             git_url = DEFAULT_GIT_URL + config["type"] + \
265                         "-" + config["name"] + ".git"
266 
267         if any(prefix in git_url for prefix in ["http", "https", "ssh"]):
268             # TODO Test if url or ssh path exists
269             # TODO Handle github authentication
270             _LOGGER.debug("Cloning from remote repository")
271             self.git_clone(git_url, config["install_path"],
272                            config["branch"])
273         else:
274             if os.path.isdir(git_url):
275                 _LOGGER.debug("Cloning from local repository")
276                 self.git_clone(git_url, config["install_path"],
277                                config["branch"])
278             else:
279                 _LOGGER.debug("Could not find local git repo " + git_url)
280 
281     @staticmethod
282     def _install_local_module(config):
283         """Install a module from a local path."""
284         installed = False
285 
286         installdir, _ = os.path.split(config["install_path"])
287         if not os.path.isdir(installdir):
288             os.makedirs(installdir, exist_ok=True)
289 
290         if os.path.isdir(config["path"]):
291             shutil.copytree(config["path"], config["install_path"])
292             installed = True
293 
294         if os.path.isfile(config["path"]):
295             os.makedirs(config["install_path"], exist_ok=True)
296             shutil.copyfile(config["path"], config["install_path"] +
297                             "/__init__.py")
298             installed = True
299 
300         if not installed:
301             _LOGGER.error("Failed to install from " + config["path"])
302
```
--- 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
@@ -282,6 +282,7 @@
     def _install_local_module(config):
         """Install a module from a local path."""
         installed = False
+        config["path"] = os.path.expanduser(config["path"])
 
         installdir, _ = os.path.split(config["install_path"])
         if not os.path.isdir(installdir):

{"golden_diff": "diff --git a/opsdroid/loader.py b/opsdroid/loader.py\n--- a/opsdroid/loader.py\n+++ b/opsdroid/loader.py\n@@ -282,6 +282,7 @@\n     def _install_local_module(config):\n         \"\"\"Install a module from a local path.\"\"\"\n         installed = False\n+        config[\"path\"] = os.path.expanduser(config[\"path\"])\n \n         installdir, _ = os.path.split(config[\"install_path\"])\n         if not os.path.isdir(installdir):\n", "issue": "Cannot use ~ in module paths\nModules specified in the configuration with a path that includes the `~` character do not import. This character needs to be expanded.\n", "before_files": [{"content": "\"\"\"Class for loading in modules to OpsDroid.\"\"\"\n\nimport logging\nimport os\nimport sys\nimport shutil\nimport subprocess\nimport importlib\nimport yaml\nfrom opsdroid.const import (\n    DEFAULT_GIT_URL, MODULES_DIRECTORY, DEFAULT_MODULES_PATH,\n    DEFAULT_MODULE_BRANCH, DEFAULT_CONFIG_PATH, EXAMPLE_CONFIG_FILE,\n    DEFAULT_MODULE_DEPS_PATH)\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\nclass Loader:\n    \"\"\"Class to load in config and modules.\"\"\"\n\n    def __init__(self, opsdroid):\n        \"\"\"Create object with opsdroid instance.\"\"\"\n        self.opsdroid = opsdroid\n        self.modules_directory = None\n        self.current_import_config = None\n        _LOGGER.debug(\"Loaded loader\")\n\n    @staticmethod\n    def import_module(config):\n        \"\"\"Import module namespace as variable and return it.\"\"\"\n        try:\n            module = importlib.import_module(\n                config[\"module_path\"] + \".\" + config[\"name\"])\n            _LOGGER.debug(\"Loaded \" + config[\"type\"] + \": \" +\n                          config[\"module_path\"])\n            return module\n        except ImportError as error:\n            _LOGGER.debug(\"Failed to load \" + config[\"type\"] +\n                          \" \" + config[\"module_path\"] + \".\" + config[\"name\"])\n            _LOGGER.debug(error)\n\n        try:\n            module = importlib.import_module(\n                config[\"module_path\"])\n            _LOGGER.debug(\"Loaded \" + config[\"type\"] + \": \" +\n                          config[\"module_path\"])\n            return module\n        except ImportError as error:\n            _LOGGER.debug(\"Failed to load \" + config[\"type\"] +\n                          \" \" + config[\"module_path\"])\n            _LOGGER.debug(error)\n\n        _LOGGER.error(\"Failed to load \" + config[\"type\"] +\n                      \" \" + config[\"module_path\"])\n        return None\n\n    @staticmethod\n    def check_cache(config):\n        \"\"\"Remove module if 'no-cache' set in config.\"\"\"\n        if \"no-cache\" in config \\\n                and config[\"no-cache\"]:\n            _LOGGER.debug(\"'no-cache' set, removing \" + config[\"install_path\"])\n            if os.path.isdir(config[\"install_path\"]):\n                shutil.rmtree(config[\"install_path\"])\n            if os.path.isfile(config[\"install_path\"] + \".py\"):\n                os.remove(config[\"install_path\"] + \".py\")\n\n    def build_module_path(self, path_type, config):\n        \"\"\"Generate the module path from name and type.\"\"\"\n        if path_type == \"import\":\n            return MODULES_DIRECTORY + \".\" + config[\"type\"] + \\\n                        \".\" + config[\"name\"]\n        elif path_type == \"install\":\n            return self.modules_directory + \"/\" + config[\"type\"] + \\\n                        \"/\" + config[\"name\"]\n\n    @staticmethod\n    def 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        for output in process.communicate():\n            if output != \"\":\n                for line in output.splitlines():\n                    _LOGGER.debug(str(line).strip())\n        process.wait()\n\n    @staticmethod\n    def pip_install_deps(requirements_path):\n        \"\"\"Pip install a requirements.txt file and wait for finish.\"\"\"\n        process = subprocess.Popen([\"pip\", \"install\",\n                                    \"--target={}\".format(\n                                        DEFAULT_MODULE_DEPS_PATH),\n                                    \"--ignore-installed\",\n                                    \"-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                    _LOGGER.debug(str(line).strip())\n        process.wait()\n\n    @staticmethod\n    def create_default_config(config_path):\n        \"\"\"Create a default config file based on the included example.\"\"\"\n        _LOGGER.info(\"Creating %s.\", config_path)\n        config_dir, _ = os.path.split(config_path)\n        if not os.path.isdir(config_dir):\n            os.makedirs(config_dir)\n        shutil.copyfile(EXAMPLE_CONFIG_FILE, config_path)\n        return config_path\n\n    @staticmethod\n    def _reload_modules(modules):\n        for module in modules:\n            importlib.reload(module[\"module\"])\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                _LOGGER.debug(\"Config file \" + possible_path +\n                              \" not found\")\n            else:\n                config_path = possible_path\n                break\n\n        if not config_path:\n            _LOGGER.info(\"No configuration files found.\")\n            config_path = self.create_default_config(DEFAULT_CONFIG_PATH)\n\n        try:\n            with open(config_path, 'r') as stream:\n                _LOGGER.info(\"Loaded config from %s\", config_path)\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 setup_modules_directory(self, config):\n        \"\"\"Create and configure the modules directory.\"\"\"\n        module_path = os.path.expanduser(\n            config.get(\"module-path\", DEFAULT_MODULES_PATH))\n        sys.path.append(module_path)\n\n        if not os.path.isdir(module_path):\n            os.makedirs(module_path, exist_ok=True)\n\n        self.modules_directory = os.path.join(module_path, MODULES_DIRECTORY)\n\n        # Create modules directory if doesn't exist\n        if not os.path.isdir(self.modules_directory):\n            os.makedirs(self.modules_directory)\n\n    def load_modules_from_config(self, config):\n        \"\"\"Load all module types based on config.\"\"\"\n        _LOGGER.debug(\"Loading modules from config\")\n\n        self.setup_modules_directory(config)\n\n        connectors, databases, skills = None, None, None\n\n        if 'databases' in config.keys():\n            databases = self._load_modules('database', config['databases'])\n        else:\n            _LOGGER.warning(\"No databases in configuration\")\n\n        if 'skills' in config.keys():\n            skills = self._load_modules('skill', config['skills'])\n            self.opsdroid.skills = []\n            self._reload_modules(skills)\n        else:\n            self.opsdroid.critical(\n                \"No skills in configuration, at least 1 required\", 1)\n\n        if 'connectors' in config.keys():\n            connectors = self._load_modules('connector', config['connectors'])\n        else:\n            self.opsdroid.critical(\n                \"No connectors in configuration, at least 1 required\", 1)\n\n        return connectors, databases, skills\n\n    def _load_modules(self, modules_type, modules):\n        \"\"\"Install and load modules.\"\"\"\n        _LOGGER.debug(\"Loading \" + modules_type + \" modules\")\n        loaded_modules = []\n\n        if not os.path.isdir(DEFAULT_MODULE_DEPS_PATH):\n            os.makedirs(DEFAULT_MODULE_DEPS_PATH)\n        sys.path.append(DEFAULT_MODULE_DEPS_PATH)\n\n        for module in modules:\n\n            # Set up module config\n            config = module\n            config = {} if config is None else config\n            config[\"name\"] = module[\"name\"]\n            config[\"type\"] = modules_type\n            config[\"module_path\"] = self.build_module_path(\"import\", config)\n            config[\"install_path\"] = self.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            self.check_cache(config)\n\n            # Install module\n            self._install_module(config)\n\n            # Import module\n            self.current_import_config = config\n            module = self.import_module(config)\n            if module is not None:\n                loaded_modules.append({\n                    \"module\": module,\n                    \"config\": config})\n            else:\n                _LOGGER.error(\n                    \"Module \" + config[\"name\"] + \" failed to import\")\n\n        return loaded_modules\n\n    def _install_module(self, config):\n        # pylint: disable=R0201\n        \"\"\"Install a module.\"\"\"\n        _LOGGER.debug(\"Installing \" + config[\"name\"])\n\n        if os.path.isdir(config[\"install_path\"]) or \\\n                os.path.isfile(config[\"install_path\"] + \".py\"):\n            # TODO Allow for updating or reinstalling of modules\n            _LOGGER.debug(\"Module \" + config[\"name\"] +\n                          \" already installed, skipping\")\n            return\n\n        if \"path\" in config:\n            self._install_local_module(config)\n        else:\n            self._install_git_module(config)\n\n        if os.path.isdir(config[\"install_path\"]):\n            _LOGGER.debug(\"Installed \" + config[\"name\"] +\n                          \" to \" + config[\"install_path\"])\n        else:\n            _LOGGER.debug(\"Install of \" + config[\"name\"] + \" failed\")\n\n        # Install module dependancies\n        if os.path.isfile(config[\"install_path\"] + \"/requirements.txt\"):\n            self.pip_install_deps(config[\"install_path\"] +\n                                  \"/requirements.txt\")\n\n    def _install_git_module(self, config):\n        \"\"\"Install a module from a git repository.\"\"\"\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            _LOGGER.debug(\"Cloning from remote repository\")\n            self.git_clone(git_url, config[\"install_path\"],\n                           config[\"branch\"])\n        else:\n            if os.path.isdir(git_url):\n                _LOGGER.debug(\"Cloning from local repository\")\n                self.git_clone(git_url, config[\"install_path\"],\n                               config[\"branch\"])\n            else:\n                _LOGGER.debug(\"Could not find local git repo \" + git_url)\n\n    @staticmethod\n    def _install_local_module(config):\n        \"\"\"Install a module from a local path.\"\"\"\n        installed = False\n\n        installdir, _ = os.path.split(config[\"install_path\"])\n        if not os.path.isdir(installdir):\n            os.makedirs(installdir, exist_ok=True)\n\n        if os.path.isdir(config[\"path\"]):\n            shutil.copytree(config[\"path\"], config[\"install_path\"])\n            installed = True\n\n        if os.path.isfile(config[\"path\"]):\n            os.makedirs(config[\"install_path\"], exist_ok=True)\n            shutil.copyfile(config[\"path\"], config[\"install_path\"] +\n                            \"/__init__.py\")\n            installed = True\n\n        if not installed:\n            _LOGGER.error(\"Failed to install from \" + config[\"path\"])\n", "path": "opsdroid/loader.py"}], "after_files": [{"content": "\"\"\"Class for loading in modules to OpsDroid.\"\"\"\n\nimport logging\nimport os\nimport sys\nimport shutil\nimport subprocess\nimport importlib\nimport yaml\nfrom opsdroid.const import (\n    DEFAULT_GIT_URL, MODULES_DIRECTORY, DEFAULT_MODULES_PATH,\n    DEFAULT_MODULE_BRANCH, DEFAULT_CONFIG_PATH, EXAMPLE_CONFIG_FILE,\n    DEFAULT_MODULE_DEPS_PATH)\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\nclass Loader:\n    \"\"\"Class to load in config and modules.\"\"\"\n\n    def __init__(self, opsdroid):\n        \"\"\"Create object with opsdroid instance.\"\"\"\n        self.opsdroid = opsdroid\n        self.modules_directory = None\n        self.current_import_config = None\n        _LOGGER.debug(\"Loaded loader\")\n\n    @staticmethod\n    def import_module(config):\n        \"\"\"Import module namespace as variable and return it.\"\"\"\n        try:\n            module = importlib.import_module(\n                config[\"module_path\"] + \".\" + config[\"name\"])\n            _LOGGER.debug(\"Loaded \" + config[\"type\"] + \": \" +\n                          config[\"module_path\"])\n            return module\n        except ImportError as error:\n            _LOGGER.debug(\"Failed to load \" + config[\"type\"] +\n                          \" \" + config[\"module_path\"] + \".\" + config[\"name\"])\n            _LOGGER.debug(error)\n\n        try:\n            module = importlib.import_module(\n                config[\"module_path\"])\n            _LOGGER.debug(\"Loaded \" + config[\"type\"] + \": \" +\n                          config[\"module_path\"])\n            return module\n        except ImportError as error:\n            _LOGGER.debug(\"Failed to load \" + config[\"type\"] +\n                          \" \" + config[\"module_path\"])\n            _LOGGER.debug(error)\n\n        _LOGGER.error(\"Failed to load \" + config[\"type\"] +\n                      \" \" + config[\"module_path\"])\n        return None\n\n    @staticmethod\n    def check_cache(config):\n        \"\"\"Remove module if 'no-cache' set in config.\"\"\"\n        if \"no-cache\" in config \\\n                and config[\"no-cache\"]:\n            _LOGGER.debug(\"'no-cache' set, removing \" + config[\"install_path\"])\n            if os.path.isdir(config[\"install_path\"]):\n                shutil.rmtree(config[\"install_path\"])\n            if os.path.isfile(config[\"install_path\"] + \".py\"):\n                os.remove(config[\"install_path\"] + \".py\")\n\n    def build_module_path(self, path_type, config):\n        \"\"\"Generate the module path from name and type.\"\"\"\n        if path_type == \"import\":\n            return MODULES_DIRECTORY + \".\" + config[\"type\"] + \\\n                        \".\" + config[\"name\"]\n        elif path_type == \"install\":\n            return self.modules_directory + \"/\" + config[\"type\"] + \\\n                        \"/\" + config[\"name\"]\n\n    @staticmethod\n    def 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        for output in process.communicate():\n            if output != \"\":\n                for line in output.splitlines():\n                    _LOGGER.debug(str(line).strip())\n        process.wait()\n\n    @staticmethod\n    def pip_install_deps(requirements_path):\n        \"\"\"Pip install a requirements.txt file and wait for finish.\"\"\"\n        process = subprocess.Popen([\"pip\", \"install\",\n                                    \"--target={}\".format(\n                                        DEFAULT_MODULE_DEPS_PATH),\n                                    \"--ignore-installed\",\n                                    \"-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                    _LOGGER.debug(str(line).strip())\n        process.wait()\n\n    @staticmethod\n    def create_default_config(config_path):\n        \"\"\"Create a default config file based on the included example.\"\"\"\n        _LOGGER.info(\"Creating %s.\", config_path)\n        config_dir, _ = os.path.split(config_path)\n        if not os.path.isdir(config_dir):\n            os.makedirs(config_dir)\n        shutil.copyfile(EXAMPLE_CONFIG_FILE, config_path)\n        return config_path\n\n    @staticmethod\n    def _reload_modules(modules):\n        for module in modules:\n            importlib.reload(module[\"module\"])\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                _LOGGER.debug(\"Config file \" + possible_path +\n                              \" not found\")\n            else:\n                config_path = possible_path\n                break\n\n        if not config_path:\n            _LOGGER.info(\"No configuration files found.\")\n            config_path = self.create_default_config(DEFAULT_CONFIG_PATH)\n\n        try:\n            with open(config_path, 'r') as stream:\n                _LOGGER.info(\"Loaded config from %s\", config_path)\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 setup_modules_directory(self, config):\n        \"\"\"Create and configure the modules directory.\"\"\"\n        module_path = os.path.expanduser(\n            config.get(\"module-path\", DEFAULT_MODULES_PATH))\n        sys.path.append(module_path)\n\n        if not os.path.isdir(module_path):\n            os.makedirs(module_path, exist_ok=True)\n\n        self.modules_directory = os.path.join(module_path, MODULES_DIRECTORY)\n\n        # Create modules directory if doesn't exist\n        if not os.path.isdir(self.modules_directory):\n            os.makedirs(self.modules_directory)\n\n    def load_modules_from_config(self, config):\n        \"\"\"Load all module types based on config.\"\"\"\n        _LOGGER.debug(\"Loading modules from config\")\n\n        self.setup_modules_directory(config)\n\n        connectors, databases, skills = None, None, None\n\n        if 'databases' in config.keys():\n            databases = self._load_modules('database', config['databases'])\n        else:\n            _LOGGER.warning(\"No databases in configuration\")\n\n        if 'skills' in config.keys():\n            skills = self._load_modules('skill', config['skills'])\n            self.opsdroid.skills = []\n            self._reload_modules(skills)\n        else:\n            self.opsdroid.critical(\n                \"No skills in configuration, at least 1 required\", 1)\n\n        if 'connectors' in config.keys():\n            connectors = self._load_modules('connector', config['connectors'])\n        else:\n            self.opsdroid.critical(\n                \"No connectors in configuration, at least 1 required\", 1)\n\n        return connectors, databases, skills\n\n    def _load_modules(self, modules_type, modules):\n        \"\"\"Install and load modules.\"\"\"\n        _LOGGER.debug(\"Loading \" + modules_type + \" modules\")\n        loaded_modules = []\n\n        if not os.path.isdir(DEFAULT_MODULE_DEPS_PATH):\n            os.makedirs(DEFAULT_MODULE_DEPS_PATH)\n        sys.path.append(DEFAULT_MODULE_DEPS_PATH)\n\n        for module in modules:\n\n            # Set up module config\n            config = module\n            config = {} if config is None else config\n            config[\"name\"] = module[\"name\"]\n            config[\"type\"] = modules_type\n            config[\"module_path\"] = self.build_module_path(\"import\", config)\n            config[\"install_path\"] = self.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            self.check_cache(config)\n\n            # Install module\n            self._install_module(config)\n\n            # Import module\n            self.current_import_config = config\n            module = self.import_module(config)\n            if module is not None:\n                loaded_modules.append({\n                    \"module\": module,\n                    \"config\": config})\n            else:\n                _LOGGER.error(\n                    \"Module \" + config[\"name\"] + \" failed to import\")\n\n        return loaded_modules\n\n    def _install_module(self, config):\n        # pylint: disable=R0201\n        \"\"\"Install a module.\"\"\"\n        _LOGGER.debug(\"Installing \" + config[\"name\"])\n\n        if os.path.isdir(config[\"install_path\"]) or \\\n                os.path.isfile(config[\"install_path\"] + \".py\"):\n            # TODO Allow for updating or reinstalling of modules\n            _LOGGER.debug(\"Module \" + config[\"name\"] +\n                          \" already installed, skipping\")\n            return\n\n        if \"path\" in config:\n            self._install_local_module(config)\n        else:\n            self._install_git_module(config)\n\n        if os.path.isdir(config[\"install_path\"]):\n            _LOGGER.debug(\"Installed \" + config[\"name\"] +\n                          \" to \" + config[\"install_path\"])\n        else:\n            _LOGGER.debug(\"Install of \" + config[\"name\"] + \" failed\")\n\n        # Install module dependancies\n        if os.path.isfile(config[\"install_path\"] + \"/requirements.txt\"):\n            self.pip_install_deps(config[\"install_path\"] +\n                                  \"/requirements.txt\")\n\n    def _install_git_module(self, config):\n        \"\"\"Install a module from a git repository.\"\"\"\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            _LOGGER.debug(\"Cloning from remote repository\")\n            self.git_clone(git_url, config[\"install_path\"],\n                           config[\"branch\"])\n        else:\n            if os.path.isdir(git_url):\n                _LOGGER.debug(\"Cloning from local repository\")\n                self.git_clone(git_url, config[\"install_path\"],\n                               config[\"branch\"])\n            else:\n                _LOGGER.debug(\"Could not find local git repo \" + git_url)\n\n    @staticmethod\n    def _install_local_module(config):\n        \"\"\"Install a module from a local path.\"\"\"\n        installed = False\n        config[\"path\"] = os.path.expanduser(config[\"path\"])\n\n        installdir, _ = os.path.split(config[\"install_path\"])\n        if not os.path.isdir(installdir):\n            os.makedirs(installdir, exist_ok=True)\n\n        if os.path.isdir(config[\"path\"]):\n            shutil.copytree(config[\"path\"], config[\"install_path\"])\n            installed = True\n\n        if os.path.isfile(config[\"path\"]):\n            os.makedirs(config[\"install_path\"], exist_ok=True)\n            shutil.copyfile(config[\"path\"], config[\"install_path\"] +\n                            \"/__init__.py\")\n            installed = True\n\n        if not installed:\n            _LOGGER.error(\"Failed to install from \" + config[\"path\"])\n", "path": "opsdroid/loader.py"}]}