internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: opsdroid/opsdroid

Issue:
Config locations
Currently opsdroid looks for the `configuration.yaml` file in the current working directory. It should also look in `~/.opsdroid/configuration.yaml` and `/etc/opsdroid/configuration.yaml`.



Discussion:

Resolving patch:
diff --git a/opsdroid/__main__.py b/opsdroid/__main__.py
--- a/opsdroid/__main__.py
+++ b/opsdroid/__main__.py
@@ -15,7 +15,11 @@ def main():
     logging.info("Stated application")
     with OpsDroid() as opsdroid:
         loader = Loader(opsdroid)
-        opsdroid.config = loader.load_config_file("./configuration.yaml")
+        opsdroid.config = loader.load_config_file([
+            "./configuration.yaml",
+            "~/.opsdroid/configuration.yaml",
+            "/etc/opsdroid/configuration.yaml"
+            ])
         if "logging" in opsdroid.config:
             set_logging_level(opsdroid.config['logging'])
         loader.load_config(opsdroid.config)
diff --git a/opsdroid/loader.py b/opsdroid/loader.py
--- a/opsdroid/loader.py
+++ b/opsdroid/loader.py
@@ -71,11 +71,19 @@ def __init__(self, opsdroid):
         self.opsdroid = opsdroid
         logging.debug("Loaded loader")
 
-    def load_config_file(self, config_path):
+    def load_config_file(self, config_paths):
         """Load a yaml config file from path."""
-        if not os.path.isfile(config_path):
-            self.opsdroid.critical("Config file " + config_path +
-                                   " not found", 1)
+        config_path = ""
+        for possible_path in config_paths:
+            if not os.path.isfile(possible_path):
+                logging.warning("Config file " + possible_path +
+                                " not found", 1)
+            else:
+                config_path = possible_path
+                break
+
+        if not config_path:
+            self.opsdroid.critical("No configuration files found", 1)
 
         try:
             with open(config_path, 'r') as stream:


Test patch:
diff --git a/tests/test_loader.py b/tests/test_loader.py
--- a/tests/test_loader.py
+++ b/tests/test_loader.py
@@ -25,17 +25,17 @@ def reset_subprocess_mocks(self):
 
     def test_load_config_file(self):
         opsdroid, loader = self.setup()
-        config = loader.load_config_file("tests/configs/minimal.yaml")
+        config = loader.load_config_file(["tests/configs/minimal.yaml"])
         self.assertIsNotNone(config)
 
     def test_load_non_existant_config_file(self):
         opsdroid, loader = self.setup()
-        loader.load_config_file("file_which_does_not_exist")
+        loader.load_config_file(["file_which_does_not_exist"])
         self.assertEqual(len(opsdroid.mock_calls), 2)
 
     def test_load_broken_config_file(self):
         opsdroid, loader = self.setup()
-        loader.load_config_file("tests/configs/broken.yaml")
+        loader.load_config_file(["tests/configs/broken.yaml"])
         self.assertRaises(yaml.YAMLError)
 
     def test_setup_modules(self):
Repository: opsdroid/opsdroid

Issue:
Skill setup should be optional
You might not want to have a setup function in your skill. 



Discussion:

Resolving patch:
diff --git a/opsdroid/loader.py b/opsdroid/loader.py
--- a/opsdroid/loader.py
+++ b/opsdroid/loader.py
@@ -157,7 +157,10 @@ def _load_modules(self, modules_type, modules):
     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


Test patch:
diff --git a/tests/test_loader.py b/tests/test_loader.py
--- a/tests/test_loader.py
+++ b/tests/test_loader.py
@@ -42,6 +42,7 @@ def test_setup_modules(self):
         opsdroid, loader = self.setup()
         example_modules = []
         example_modules.append({"module": mock.MagicMock()})
+        example_modules.append({"module": {"name": "test"}})
         loader._setup_modules(example_modules)
         self.assertEqual(len(example_modules[0]["module"].mock_calls), 1)
Repository: opsdroid/opsdroid

Issue:
Regex case sensitive
The regex match is currently case insensitive. It shouldn't be.

https://github.com/opsdroid/opsdroid/blob/master/opsdroid/helper.py#L30



Discussion:

Resolving patch:
diff --git a/opsdroid/helper.py b/opsdroid/helper.py
--- a/opsdroid/helper.py
+++ b/opsdroid/helper.py
@@ -27,4 +27,4 @@ def set_logging_level(logging_level):
 
 def match(regex, message):
     """Regex match a string."""
-    return re.match(regex, message, re.M | re.I)
+    return re.match(regex, message)


Test patch:
diff --git a/tests/test_helper.py b/tests/test_helper.py
--- a/tests/test_helper.py
+++ b/tests/test_helper.py
@@ -33,3 +33,11 @@ def test_match(self):
 
         match = helper.match(r"hello (.*)", "hello world")
         self.assertEqual(match.group(1), "world")
+
+    def test_sensitive_match(self):
+        """Matches should be case sensitive"""
+        match = helper.match(r"hello", "hello")
+        self.assertTrue(match)
+
+        match = helper.match(r"hello", "HELLO")
+        self.assertFalse(match)
Repository: opsdroid/opsdroid

Issue:
Copy message on respond
When a message responds it updates it's `text` value and passes itself to the connector.  Due to pointers in Python the next rule to parse the message goes on to parse the response text.

The message respond method should create a shallow copy of itself to pass to the connector, instead of updating itself directly.



Discussion:

Resolving patch:
diff --git a/opsdroid/message.py b/opsdroid/message.py
--- a/opsdroid/message.py
+++ b/opsdroid/message.py
@@ -1,5 +1,7 @@
 """Class to encapsulate a message."""
 
+from copy import copy
+
 
 class Message:
     # pylint: disable=too-few-public-methods
@@ -15,5 +17,6 @@ def __init__(self, text, user, room, connector):
 
     def respond(self, text):
         """Respond to this message using the connector it was created by."""
-        self.text = text
-        self.connector.respond(self)
+        response = copy(self)
+        response.text = text
+        self.connector.respond(response)


Test patch:
diff --git a/tests/test_message.py b/tests/test_message.py
--- a/tests/test_message.py
+++ b/tests/test_message.py
@@ -19,4 +19,11 @@ def test_message(self):
         message.respond("Goodbye world")
 
         self.assertEqual(len(mock_connector.mock_calls), 1)
-        self.assertEqual(message.text, "Goodbye world")
+
+    def test_response_effects(self):
+        """Responding to a message shouldn't change the message."""
+        mock_connector = mock.MagicMock()
+        message_text = "Hello world"
+        message = Message(message_text, "user", "default", mock_connector)
+        message.respond("Goodbye world")
+        self.assertEqual(message_text, message.text)
Repository: opsdroid/opsdroid

Issue:
Generate default config
It should be possible to generate some basic config with a command line flag to opsdroid. It should cause opsdroid to print out the config so that is can be piped into a file.

e.g

```
opsdroid --gen-config > configuration.yaml
```



Discussion:

Resolving patch:
diff --git a/opsdroid/__main__.py b/opsdroid/__main__.py
--- a/opsdroid/__main__.py
+++ b/opsdroid/__main__.py
@@ -1,6 +1,9 @@
 """Starts opsdroid."""
 
+import sys
+import os
 import logging
+import argparse
 
 from opsdroid.loader import Loader
 from opsdroid.core import OpsDroid
@@ -8,11 +11,30 @@
 from opsdroid.const import LOG_FILENAME
 
 
+def parse_args(args):
+    """Parse command line arguments."""
+    parser = argparse.ArgumentParser(description='Run opsdroid.')
+    parser.add_argument('--gen-config', action="store_true",
+                        help='prints out an example configuration file')
+    return parser.parse_args(args)
+
+
 def main():
     """The main function."""
     logging.basicConfig(filename=LOG_FILENAME, level=logging.INFO)
     logging.info("="*40)
     logging.info("Stated application")
+
+    args = parse_args(sys.argv[1:])
+
+    if args.gen_config:
+        path = os.path.join(
+            os.path.dirname(os.path.abspath(__file__)),
+            "configuration/example_configuration.yaml")
+        with open(path, 'r') as conf:
+            print(conf.read())
+        sys.exit(0)
+
     with OpsDroid() as opsdroid:
         loader = Loader(opsdroid)
         opsdroid.config = loader.load_config_file([


Test patch:
diff --git a/tests/test_main.py b/tests/test_main.py
new file mode 100644
--- /dev/null
+++ b/tests/test_main.py
@@ -0,0 +1,21 @@
+
+import sys
+import unittest
+import unittest.mock as mock
+
+sys.modules['sys'].exit = mock.MagicMock()
+
+import opsdroid.__main__ as opsdroid  # noqa: E402
+
+
+class TestMain(unittest.TestCase):
+    """Test the main opsdroid module."""
+
+    def test_parse_args(self):
+        args = opsdroid.parse_args(["--gen-config"])
+        self.assertEqual(True, args.gen_config)
+
+    def text_gen_config(self):
+        sys.argv = ["--gen-config"]
+        opsdroid.main()
+        self.assertEqual(1, len(sys.modules['sys'].exit.mock_calls))
Repository: opsdroid/opsdroid

Issue:
Module specific site-packages
It could be good for modules to install their dependancies in a specific `site-packages` directory which is only added to the path when it is time to import the modules.

A good place could be `~/.opsdroid/site-packages`.


Discussion:

Resolving patch:
diff --git a/opsdroid/const.py b/opsdroid/const.py
--- a/opsdroid/const.py
+++ b/opsdroid/const.py
@@ -8,6 +8,7 @@
 MODULES_DIRECTORY = "opsdroid-modules"
 DEFAULT_ROOT_PATH = os.path.join(os.path.expanduser("~"), ".opsdroid")
 DEFAULT_MODULES_PATH = os.path.join(DEFAULT_ROOT_PATH, "modules")
+DEFAULT_MODULE_DEPS_PATH = os.path.join(DEFAULT_ROOT_PATH, "site-packages")
 DEFAULT_CONFIG_PATH = os.path.join(DEFAULT_ROOT_PATH, "configuration.yaml")
 DEFAULT_MODULE_BRANCH = "master"
 EXAMPLE_CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
diff --git a/opsdroid/loader.py b/opsdroid/loader.py
--- a/opsdroid/loader.py
+++ b/opsdroid/loader.py
@@ -9,7 +9,8 @@
 import yaml
 from opsdroid.const import (
     DEFAULT_GIT_URL, MODULES_DIRECTORY, DEFAULT_MODULES_PATH,
-    DEFAULT_MODULE_BRANCH, DEFAULT_CONFIG_PATH, EXAMPLE_CONFIG_FILE)
+    DEFAULT_MODULE_BRANCH, DEFAULT_CONFIG_PATH, EXAMPLE_CONFIG_FILE,
+    DEFAULT_MODULE_DEPS_PATH)
 
 
 _LOGGER = logging.getLogger(__name__)
@@ -90,7 +91,11 @@ def git_clone(git_url, install_path, branch):
     @staticmethod
     def pip_install_deps(requirements_path):
         """Pip install a requirements.txt file and wait for finish."""
-        process = subprocess.Popen(["pip", "install", "-r", requirements_path],
+        process = subprocess.Popen(["pip", "install",
+                                    "--target={}".format(
+                                        DEFAULT_MODULE_DEPS_PATH),
+                                    "--ignore-installed",
+                                    "-r", requirements_path],
                                    shell=False,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE)
@@ -181,6 +186,10 @@ def _load_modules(self, modules_type, modules):
         _LOGGER.debug("Loading " + modules_type + " modules")
         loaded_modules = []
 
+        if not os.path.isdir(DEFAULT_MODULE_DEPS_PATH):
+            os.makedirs(DEFAULT_MODULE_DEPS_PATH)
+        sys.path.append(DEFAULT_MODULE_DEPS_PATH)
+
         for module in modules:
 
             # Set up module config


Test patch:
Repository: opsdroid/opsdroid

Issue:
arrow dep missing
Fresh install of ubuntu 16.04

```
$ sudo apt update && sudo apt install python3-pip
...
$ pip3 install opsdroid
...
$ opsdroid
Traceback (most recent call last):
  File "/home/ubuntu/.local/bin/opsdroid", line 7, in <module>
    from opsdroid.__main__ import main
  File "/home/ubuntu/.local/lib/python3.5/site-packages/opsdroid/__main__.py", line 8, in <module>
    from opsdroid.core import OpsDroid
  File "/home/ubuntu/.local/lib/python3.5/site-packages/opsdroid/core.py", line 15, in <module>
    from opsdroid.parsers.crontab import parse_crontab
  File "/home/ubuntu/.local/lib/python3.5/site-packages/opsdroid/parsers/crontab.py", line 6, in <module>
    import arrow
ImportError: No module named 'arrow'
```


Discussion:

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -10,9 +10,10 @@
                                   'modules.*', 'docs', 'docs.*'])
 
 REQUIRES = [
-    'pyyaml>=3.11,<4',
-    'aiohttp>=1.2.0,<2',
-    'pycron>=0.40',
+    'arrow==0.10.0',
+    'aiohttp==2.1.0',
+    'pycron==0.40',
+    'pyyaml==3.12'
 ]
 
 setup(


Test patch:
Repository: opsdroid/opsdroid

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.


Discussion:

Resolving patch:
diff --git a/opsdroid/loader.py b/opsdroid/loader.py
--- a/opsdroid/loader.py
+++ b/opsdroid/loader.py
@@ -282,6 +282,7 @@ def _install_git_module(self, config):
     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):


Test patch:
Repository: opsdroid/opsdroid

Issue:
Change default log location
Logs by default are written to `./opsdroid.log`. So they end up being written wherever you run the command.

Logs should either be written to `/var/log/opsdroid.log` or as that may not be writeable by all users maybe `~/.opsdroid/opsdroid.log`.


Discussion:

Resolving patch:
diff --git a/opsdroid/__main__.py b/opsdroid/__main__.py
--- a/opsdroid/__main__.py
+++ b/opsdroid/__main__.py
@@ -1,11 +1,12 @@
 """Starts opsdroid."""
 
+import os
 import sys
 import logging
 import argparse
 
 from opsdroid.core import OpsDroid
-from opsdroid.const import LOG_FILENAME, EXAMPLE_CONFIG_FILE
+from opsdroid.const import DEFAULT_LOG_FILENAME, EXAMPLE_CONFIG_FILE
 from opsdroid.web import Web
 
 
@@ -19,9 +20,12 @@ def configure_logging(config):
         rootlogger.handlers.pop()
 
     try:
-        logfile_path = config["logging"]["path"]
+        if config["logging"]["path"]:
+            logfile_path = os.path.expanduser(config["logging"]["path"])
+        else:
+            logfile_path = config["logging"]["path"]
     except KeyError:
-        logfile_path = LOG_FILENAME
+        logfile_path = DEFAULT_LOG_FILENAME
 
     try:
         log_level = get_logging_level(
diff --git a/opsdroid/const.py b/opsdroid/const.py
--- a/opsdroid/const.py
+++ b/opsdroid/const.py
@@ -3,10 +3,10 @@
 
 __version__ = "0.8.1"
 
-LOG_FILENAME = 'output.log'
 DEFAULT_GIT_URL = "https://github.com/opsdroid/"
 MODULES_DIRECTORY = "opsdroid-modules"
-DEFAULT_ROOT_PATH = os.path.join(os.path.expanduser("~"), ".opsdroid")
+DEFAULT_ROOT_PATH = os.path.expanduser("~/.opsdroid")
+DEFAULT_LOG_FILENAME = os.path.join(DEFAULT_ROOT_PATH, 'output.log')
 DEFAULT_MODULES_PATH = os.path.join(DEFAULT_ROOT_PATH, "modules")
 DEFAULT_MODULE_DEPS_PATH = os.path.join(DEFAULT_ROOT_PATH, "site-packages")
 DEFAULT_CONFIG_PATH = os.path.join(DEFAULT_ROOT_PATH, "configuration.yaml")


Test patch:
Repository: opsdroid/opsdroid

Issue:
Allow usage of env vars in config
The configuration should be parsed for environment variables when loaded. This would allow for secrets like api keys to be kept outside of the opsdroid configuration.

#### Example

```yaml
connectors:
  - name: slack
    default-room: '#general'
    bot-name: "opsdroid"
    icon-emoji: ":robot:"
    api-token: "$SLACK_API_KEY"
```
In this example `$SLACK_API_KEY` would be replaced with the contents of the environment variable of the same name.


Discussion:

Resolving patch:
diff --git a/opsdroid/loader.py b/opsdroid/loader.py
--- a/opsdroid/loader.py
+++ b/opsdroid/loader.py
@@ -6,6 +6,7 @@
 import shutil
 import subprocess
 import importlib
+import re
 import yaml
 from opsdroid.const import (
     DEFAULT_GIT_URL, MODULES_DIRECTORY, DEFAULT_MODULES_PATH,
@@ -135,6 +136,17 @@ def load_config_file(self, config_paths):
             _LOGGER.info("No configuration files found.")
             config_path = self.create_default_config(DEFAULT_CONFIG_PATH)
 
+        env_var_pattern = re.compile(r'^\$([A-Z_]*)$')
+        yaml.add_implicit_resolver("!envvar", env_var_pattern)
+
+        def envvar_constructor(loader, node):
+            """Yaml parser for env vars."""
+            value = loader.construct_scalar(node)
+            [env_var] = env_var_pattern.match(value).groups()
+            return os.environ[env_var]
+
+        yaml.add_constructor('!envvar', envvar_constructor)
+
         try:
             with open(config_path, 'r') as stream:
                 _LOGGER.info("Loaded config from %s", config_path)


Test patch:
diff --git a/tests/configs/minimal_with_envs.yaml b/tests/configs/minimal_with_envs.yaml
new file mode 100644
--- /dev/null
+++ b/tests/configs/minimal_with_envs.yaml
@@ -0,0 +1 @@
+test: $ENVVAR
\ No newline at end of file
diff --git a/tests/test_loader.py b/tests/test_loader.py
--- a/tests/test_loader.py
+++ b/tests/test_loader.py
@@ -29,6 +29,13 @@ def test_load_config_file(self):
         config = loader.load_config_file(["tests/configs/minimal.yaml"])
         self.assertIsNotNone(config)
 
+    def test_load_config_file_with_env_vars(self):
+        opsdroid, loader = self.setup()
+        os.environ["ENVVAR"] = 'test'
+        config = loader.load_config_file(
+            ["tests/configs/minimal_with_envs.yaml"])
+        self.assertEqual(config["test"], os.environ["ENVVAR"])
+
     def test_create_default_config(self):
         test_config_path = "/tmp/test_config_path/configuration.yaml"
         opsdroid, loader = self.setup()
Repository: opsdroid/opsdroid

Issue:
Error if parsers is empty
# Description
If you leave the `parsers` section of the config as an empty dictionary an error is thrown and startup fails.

## Steps to Reproduce
Uncomment the parsers section of the config but with no actual parsers

```
parsers:

# nothing else
```

## Expected Functionality
The application  should start with the default parsers only. The same as if `parsers:` is not in the config.

## Experienced Functionality

```
DEBUG opsdroid.core: Parsing input: hi
DEBUG opsdroid.core: Processing parsers...
ERROR aiohttp.server: Error handling request
Traceback (most recent call last):
  File "/Users/jacob/.pyenv/versions/3.5.4/lib/python3.5/site-packages/aiohttp/web_protocol.py", line 416, in start
    resp = yield from self._request_handler(request)
  File "/Users/jacob/.pyenv/versions/3.5.4/lib/python3.5/site-packages/aiohttp/web.py", line 325, in _handle
    resp = yield from handler(request)
  File "/Users/jacob/.opsdroid/modules/opsdroid-modules/connector/websocket/__init__.py", line 77, in websocket_handler
    await self.opsdroid.parse(message)
  File "/Users/jacob/Projects/opsdroid/opsdroid/opsdroid/core.py", line 273, in parse
    ranked_skills = await self.get_ranked_skills(message)
  File "/Users/jacob/Projects/opsdroid/opsdroid/opsdroid/core.py", line 218, in get_ranked_skills
    dialogflow = [p for p in parsers if p["name"] == "dialogflow"
TypeError: 'NoneType' object is not iterable
```

## Versions
- **Opsdroid version:** 0.10.0
- **Python version:** Python 3.5.4
- **OS/Docker version:** macOS 10.13


Discussion:

Resolving patch:
diff --git a/opsdroid/core.py b/opsdroid/core.py
--- a/opsdroid/core.py
+++ b/opsdroid/core.py
@@ -213,7 +213,7 @@ async def get_ranked_skills(self, message):
 
         if "parsers" in self.config:
             _LOGGER.debug("Processing parsers...")
-            parsers = self.config["parsers"]
+            parsers = self.config["parsers"] or []
 
             dialogflow = [p for p in parsers if p["name"] == "dialogflow"
                           or p["name"] == "apiai"]


Test patch:
Repository: opsdroid/opsdroid

Issue:
Change logging level of opsdroid.memory
# Description
When running opsdroid from the command line, every time opsdroid replies to the user input a logging.warning is printed in the console.


## Steps to Reproduce

**1.** Don't have any database configured in `configuration.yaml`
**2.** Start opsdroid from the command line
**3.** Type hello


## Expected Functionality
database warning to be shown on first lines of opsdroid but then go silent  afterwards.


## Experienced Functionality
```
hi
Hey fabiorosado
opsdroid> WARNING opsdroid.memory: No databases configured, data will not persist
WARNING opsdroid.memory: No databases configured, data will not persist
```

## Additional Details
When opsdroid loads, the warning message `WARNING opsdroid.loader: No databases in configuration` is shown. This should be enough to warn the user that no databases are configured.

It seems that even if no databases are configured, with each message, opsdroid will try to invoke: `opsdroid.memory._get_from_database()` and `opsdroid.memory._put_to_database()`
Perhaps these loggings could be removed as they seem redundant since the user already knows that no databases are configured - it also might help with some "spam" in the log files.





Discussion:
Agreed these messages are overkill. Let's take them out.

Resolving patch:
diff --git a/opsdroid/loader.py b/opsdroid/loader.py
--- a/opsdroid/loader.py
+++ b/opsdroid/loader.py
@@ -216,7 +216,9 @@ def load_modules_from_config(self, config):
         if 'databases' in config.keys() and config['databases']:
             databases = self._load_modules('database', config['databases'])
         else:
-            _LOGGER.warning("No databases in configuration.")
+            _LOGGER.warning("No databases in configuration."
+                            "This will cause skills which store things in"
+                            "memory to lose data when opsdroid is restarted.")
 
         if 'skills' in config.keys() and config['skills']:
             skills = self._load_modules('skill', config['skills'])
diff --git a/opsdroid/memory.py b/opsdroid/memory.py
--- a/opsdroid/memory.py
+++ b/opsdroid/memory.py
@@ -34,7 +34,6 @@ async def put(self, key, data):
     async def _get_from_database(self, key):
         """Get updates from databases for a given key."""
         if not self.databases:
-            _LOGGER.warning("No databases configured, data will not persist.")
             return None
 
         results = []
@@ -45,8 +44,6 @@ async def _get_from_database(self, key):
 
     async def _put_to_database(self, key, data):
         """Put updates into databases for a given key."""
-        if not self.databases:
-            _LOGGER.warning("No databases configured, data will not persist.")
-        else:
+        if self.databases:
             for database in self.databases:
                 await database.put(key, data)


Test patch: