Repository: mercadona/rele
Pull request #166 (2020-06-11 13:20:27)

Add settings path to the cli
### :tophat: What?

Add the `--settings` path argument to the cli

### :thinking: Why?

Ive noticed different ways of configuring a project. Some with the settings in the root of the project and some projects with a settings file one level deep. This way, we can specify it anywhere and also avoid the possibility of 2 settings files in a project. 

### :link: Related issue

#160 

```
$ rele-cli run --help                  
usage: Relé run [-h] [--settings SETTINGS]

optional arguments:
  -h, --help            show this help message and exit
  --settings SETTINGS, -s SETTINGS
                        Settings file dot path. Ex. project.settings. If none
                        is supplied, Relé will attempt to autodiscover in the
                        root path.

```

```
$ rele-cli run --settings test.settings
 * Discovered subs module: 'test.subs'
 * Discovered settings: 'test.settings'
Configuring worker with 1 subscription(s)...
  test-foo-created - foo_sub
```




Patch:
diff --git a/docs/guides/basics.rst b/docs/guides/basics.rst
index 1a69afa..3e96521 100644
--- a/docs/guides/basics.rst
+++ b/docs/guides/basics.rst
@@ -123,6 +123,15 @@ with Google Cloud and will begin to pull the messages from the topic.
 
     rele-cli run
 
+
+In addition, if the ``settings.py`` module is not in the current directory, we can specify the
+path.
+
+.. code:: bash
+
+    rele-cli run --settings app.settings
+
+
 .. note:: Autodiscovery of subscribers with ``rele-cli`` follows a strict directory structure.
 
     | ├──settings.py
diff --git a/rele/__main__.py b/rele/__main__.py
index 8f0a109..743879f 100644
--- a/rele/__main__.py
+++ b/rele/__main__.py
@@ -26,11 +26,18 @@ def main():
         "and settings modules. If no settings module is discovered, "
         "defaults will be used.",
     )
-
+    run_parser.add_argument(
+        "--settings",
+        "-s",
+        default=None,
+        required=False,
+        help="Settings file dot path. Ex. project.settings. "
+        "If none is supplied, Relé will attempt to autodiscover in the root path.",
+    )
     args = parser.parse_args()
 
     if args.command == "run":
-        settings, module_paths = discover.sub_modules()
+        settings, module_paths = discover.sub_modules(args.settings)
         configuration = config.setup(settings.RELE if settings else None)
         subs = config.load_subscriptions_from_paths(
             module_paths, configuration.sub_prefix, configuration.filter_by
diff --git a/rele/discover.py b/rele/discover.py
index c5abf08..ed00231 100644
--- a/rele/discover.py
+++ b/rele/discover.py
@@ -1,3 +1,4 @@
+import importlib
 import logging
 import pkgutil
 from importlib.util import find_spec as importlib_find
@@ -23,7 +24,13 @@ def module_has_submodule(package, module_name):
         return False
 
 
-def sub_modules():
+def _import_settings_from_path(path):
+    if path is not None:
+        print(" * Discovered settings: %r" % path)
+        return importlib.import_module(path)
+
+
+def sub_modules(settings_path=None):
     """
     In the current PYTHONPATH, we can traverse all modules and determine if they
     have a settings.py or directory with a subs.py module. If either one of
@@ -34,13 +41,13 @@ def sub_modules():
 
     :return: (settings module, List[string: subs module paths])
     """
-    settings = None
     module_paths = []
     for f, package, is_package in pkgutil.iter_modules(path=["."]):
         if package == "settings":
-            settings = __import__(package)
+            settings_path = package
         if is_package and module_has_submodule(package, "subs"):
             module = package + ".subs"
             module_paths.append(module)
-            logger.info(" * Discovered subs module: %r" % module)
+            print(" * Discovered subs module: %r" % module)
+    settings = _import_settings_from_path(settings_path)
     return settings, module_paths


Test patch:
diff --git a/tests/subs.py b/tests/subs.py
new file mode 100644
index 0000000..5df0388
--- /dev/null
+++ b/tests/subs.py
@@ -0,0 +1,6 @@
+from rele import sub
+
+
+@sub(topic="some-cool-topic", prefix="rele")
+def sub_stub(data, **kwargs):
+    return data["id"]
diff --git a/tests/test_discover.py b/tests/test_discover.py
new file mode 100644
index 0000000..bb988a1
--- /dev/null
+++ b/tests/test_discover.py
@@ -0,0 +1,24 @@
+import pytest
+from tests import settings
+
+from rele import discover
+
+
+class TestDiscoverSubModules:
+    def test_returns_settings_and_paths_when_settings_found(self):
+        discovered_settings, paths = discover.sub_modules("tests.settings")
+
+        assert discovered_settings is settings
+        assert discovered_settings.RELE == settings.RELE
+        assert paths == ["tests.subs"]
+
+    def test_returns_empty_settings_when_no_settings_module_found(self):
+        discovered_settings, paths = discover.sub_modules()
+
+        assert discovered_settings is None
+        assert paths == ["tests.subs"]
+
+    def test_raises_when_incorrect_path(self):
+        incorrect_path = "tests.foo"
+        with pytest.raises(ModuleNotFoundError):
+            discover.sub_modules(incorrect_path)