Repository: bottlepy/bottle

Issue:
Ability to load config from module
Currently it is not possible to load config from a Python module (similar to the way Django does it).

I'd like to propose the following;

```py
class ConfigDict(dict):
    def load_module(self, module_path):
        """
        Load configuration from module path

        >>> load_module('settings.prod')
        """
        assert isinstance(module_path, str)
        config = importlib.import_module(module_path)
        obj = {key: getattr(config, key) for key in dir(config) if key.isupper()}
        self.update(obj)
```

Happy to put together a tested PR if this proposal is accepted

Discussion:

Patch:
diff --git a/bottle.py b/bottle.py
index e4b17d8..5287147 100644
--- a/bottle.py
+++ b/bottle.py
@@ -2176,6 +2176,22 @@ class ConfigDict(dict):
         self._meta = {}
         self._on_change = lambda name, value: None
 
+    def load_module(self, path, squash):
+        """ Load values from a Python module.
+            :param squash: Squash nested dicts into namespaces by using
+                           load_dict(), otherwise use update()
+            Example: load_config('my.app.settings', True)
+            Example: load_config('my.app.settings', False)
+        """
+        config_obj = __import__(path)
+        obj = dict([(key, getattr(config_obj, key))
+                for key in dir(config_obj) if key.isupper()])
+        if squash:
+            self.load_dict(obj)
+        else:
+            self.update(obj)
+        return self
+
     def load_config(self, filename):
         """ Load values from an ``*.ini`` style config file.
 


Test patch:
diff --git a/test/example_settings.py b/test/example_settings.py
new file mode 100644
index 0000000..58dd992
--- /dev/null
+++ b/test/example_settings.py
@@ -0,0 +1,5 @@
+A = {
+    "B": {
+        "C": 3
+    }
+}
\ No newline at end of file
diff --git a/test/test_config.py b/test/test_config.py
index a4cfd9f..a9ea4ab 100644
--- a/test/test_config.py
+++ b/test/test_config.py
@@ -1,3 +1,4 @@
+import sys
 import unittest
 from bottle import ConfigDict
 
@@ -69,6 +70,17 @@ class TestConfDict(unittest.TestCase):
         c = ConfigDict()
         c.load_dict({key: {'subkey': 'value'}})
         self.assertEqual('value', c[key + '.subkey'])
+
+    def test_load_module(self):
+        c = ConfigDict()
+        c.load_module('example_settings', True)
+        self.assertEqual(c['A.B.C'], 3)
+
+        c = ConfigDict()
+        c.load_module('example_settings', False)
+        self.assertEqual(c['A']['B']['C'], 3)
+
+
    
 if __name__ == '__main__': #pragma: no cover
     unittest.main()