Repository: mercadona/rele
Pull request #167 (2020-06-12 15:20:41)

Traverse all packages for subs.py files
### :tophat: What?

Traversal of all packages in a Python path and get the path of the `subs.py` file. 

### :thinking: Why?

This way we can have a `subs.py` n-levels deep, and autodiscover it. 

### :link: Related issue

Enhancement of #160 

Here it is working on a local project I have set up.

```
$ rele-cli run --settings test.settings
 * Discovered subs module: 'test.subs'
 * Discovered subs module: 'test.foo.bar.subs'
 * Discovered settings: 'test.settings'
Configuring worker with 2 subscription(s)...
  test-delivery-driver-created - some_sub
  test-another-topic - some_sub
Cannot subscribe to a topic that does not exist.
```




Patch:
diff --git a/docs/guides/basics.rst b/docs/guides/basics.rst
index 3e96521..5458166 100644
--- a/docs/guides/basics.rst
+++ b/docs/guides/basics.rst
@@ -132,7 +132,8 @@ path.
     rele-cli run --settings app.settings
 
 
-.. note:: Autodiscovery of subscribers with ``rele-cli`` follows a strict directory structure.
+.. note:: Autodiscovery of subscribers with ``rele-cli`` is automatic.
+    Any ``subs.py`` module you have in your current path, will be imported, and all subsequent decorated objects will be registered.
 
     | ├──settings.py
     | ├──app # This can be called whatever you like
diff --git a/rele/discover.py b/rele/discover.py
index ed00231..6dd6919 100644
--- a/rele/discover.py
+++ b/rele/discover.py
@@ -11,11 +11,10 @@ def module_has_submodule(package, module_name):
     See if 'module' is in 'package'.
     Taken from https://github.com/django/django/blob/master/django/utils/module_loading.py#L63
     """
-    package = __import__(package)
-    package_name = package.__name__
-    package_path = package.__path__
+    imported_package = importlib.import_module(package)
+    package_name = imported_package.__name__
+    package_path = imported_package.__path__
     full_module_name = package_name + "." + module_name
-
     try:
         return importlib_find(full_module_name, package_path) is not None
     except (ModuleNotFoundError, AttributeError):
@@ -42,12 +41,13 @@ def sub_modules(settings_path=None):
     :return: (settings module, List[string: subs module paths])
     """
     module_paths = []
-    for f, package, is_package in pkgutil.iter_modules(path=["."]):
+    for f, package, is_package in pkgutil.walk_packages(path=["."]):
         if package == "settings":
             settings_path = package
         if is_package and module_has_submodule(package, "subs"):
             module = package + ".subs"
             module_paths.append(module)
             print(" * Discovered subs module: %r" % module)
+
     settings = _import_settings_from_path(settings_path)
     return settings, module_paths


Test patch:
diff --git a/tests/more_subs/__init__.py b/tests/more_subs/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/more_subs/subs.py b/tests/more_subs/subs.py
new file mode 100644
index 0000000..f32ede5
--- /dev/null
+++ b/tests/more_subs/subs.py
@@ -0,0 +1,6 @@
+from rele import sub
+
+
+@sub(topic="another-cool-topic", prefix="rele")
+def another_sub_stub(data, **kwargs):
+    return data["id"]
diff --git a/tests/test_discover.py b/tests/test_discover.py
index bb988a1..7e4f2d8 100644
--- a/tests/test_discover.py
+++ b/tests/test_discover.py
@@ -10,13 +10,13 @@ class TestDiscoverSubModules:
 
         assert discovered_settings is settings
         assert discovered_settings.RELE == settings.RELE
-        assert paths == ["tests.subs"]
+        assert paths == ["tests.subs", "tests.more_subs.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"]
+        assert paths == ["tests.subs", "tests.more_subs.subs"]
 
     def test_raises_when_incorrect_path(self):
         incorrect_path = "tests.foo"