Repository: rochacbruno/dynaconf
Pull request #138 (2019-03-27 15:05:23)

Add dynaconf_include examples

codecov[bot]: # [Codecov](https://codecov.io/gh/rochacbruno/dynaconf/pull/138?src=pr&el=h1) Report
> Merging [#138](https://codecov.io/gh/rochacbruno/dynaconf/pull/138?src=pr&el=desc) into [master](https://codecov.io/gh/rochacbruno/dynaconf/commit/d08284fa050d71647db55c56f6c9a218494f90bb?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/rochacbruno/dynaconf/pull/138/graphs/tree.svg?width=650&token=GXszVzJcqW&height=150&src=pr)](https://codecov.io/gh/rochacbruno/dynaconf/pull/138?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##           master   #138   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files          17     17           
  Lines         617    617           
=====================================
  Hits          617    617
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/rochacbruno/dynaconf/pull/138?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/rochacbruno/dynaconf/pull/138?src=pr&el=footer). Last update [d08284f...5580aa1](https://codecov.io/gh/rochacbruno/dynaconf/pull/138?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/Makefile b/Makefile
index 9d669e4..ff0bf3a 100644
--- a/Makefile
+++ b/Makefile
@@ -25,6 +25,7 @@ test_examples:
 	@cd example/envs;pwd;python app.py
 	@cd example/custom_loader;pwd;python app.py
 	@cd example/get_fresh;pwd;python app.py
+	@cd example/includes;pwd;python app.py
 	@cd example/jenkins_secrets_file;pwd;python app.py
 	@cd example/specific_settings_modules;pwd;python app.py
 	@cd example/django_example/;pwd;python manage.py test
diff --git a/dynaconf/base.py b/dynaconf/base.py
index 7423577..43b97d8 100644
--- a/dynaconf/base.py
+++ b/dynaconf/base.py
@@ -3,7 +3,6 @@ import os
 import glob
 import importlib
 from contextlib import contextmanager
-from collections import OrderedDict
 
 from six import string_types
 
@@ -634,35 +633,38 @@ class Settings(object):
         for loader in self.loaders:
             self.logger.debug('Dynaconf executing: %s', loader.__name__)
             loader.load(self, env, silent=silent, key=key)
+        self.load_includes(env, silent=silent, key=key)
 
-        # Do we have any nested includes we need to process? If so,
-        # Remove any duplicate paths (while preserving definition order)
-        # by converting to an ordered dict, and then converting back
-        # to a list.
-        includes = list(OrderedDict.fromkeys(self.get('DYNACONF_INCLUDE', [])))
+    def load_includes(self, env, silent, key):
+        """Do we have any nested includes we need to process?"""
+        includes = self.get('DYNACONF_INCLUDE', [])
         if includes:
+            self.logger.debug('Found %s includes to process', includes)
             already_loaded = set()
-            basepath = os.path.split(self.settings_module)[0]
-
             for include in includes:
-                # Handle absolute and relative paths in the configuration.
-                if os.path.isabs(include):  # pragma: no cover
-                    included_filename = include
-                else:
-                    included_filename = os.path.join(basepath, include)
-
-                # Handle possible globs
-                for path in glob.glob(included_filename):
+                self.logger.debug('Processing include %s', include)
+                included_filename = os.path.join(
+                    self.PROJECT_ROOT_FOR_DYNACONF, include
+                )
+                self.logger.debug('Include path is %s', included_filename)
+                # Handle possible *.globs sorted alphanumeric
+                for path in sorted(glob.glob(included_filename)):
+                    self.logger.debug('Loading %s', path)
                     if path in already_loaded:  # pragma: no cover
+                        self.logger.debug('Skipping %s, already loaded', path)
                         continue
-
-                    settings_loader(obj=self,
-                                    env=env,
-                                    silent=silent,
-                                    key=key,
-                                    filename=path)
-
+                    settings_loader(
+                        obj=self,
+                        env=env,
+                        silent=silent,
+                        key=key,
+                        filename=path
+                    )
                     already_loaded.add(path)
+            if not already_loaded:
+                self.logger.error(
+                    'Not able to locate the files %s to include', includes
+                )
 
     def load_extra_yaml(self, env, silent, key):
         """This is deprecated, kept for compat
@@ -690,6 +692,11 @@ class Settings(object):
             return os.path.join(*args)
         return os.path.join(self.PROJECT_ROOT_FOR_DYNACONF, *args)
 
+    @property
+    def abs_project_root(self):
+        """Absolute path for PROJECT_ROOT_FOR_DYNACONF"""
+        return os.path.abspath(self.PROJECT_ROOT_FOR_DYNACONF)
+
     @property
     def validators(self):
         """Gets or creates validator wrapper"""
diff --git a/example/includes/.env b/example/includes/.env
new file mode 100644
index 0000000..4fb6b85
--- /dev/null
+++ b/example/includes/.env
@@ -0,0 +1,1 @@
+MERGE_ENABLED_FOR_DYNACONF=true
diff --git a/example/includes/app.py b/example/includes/app.py
new file mode 100644
index 0000000..7a8c497
--- /dev/null
+++ b/example/includes/app.py
@@ -0,0 +1,16 @@
+from dynaconf import settings
+
+assert settings.DYNACONF_INCLUDE == ['configs/*', 'this_is_not_loaded.toml']
+assert settings.SETTINGS_VAR is True
+assert settings.LASTFILE == 'configs/plugin4.py'
+assert settings.MERGEABLE == {
+    'plugin1': True,
+    'plugin2': True,
+    'plugin3': True,
+    'plugin4': True,
+    'settings': True
+}
+assert settings.PLUGIN1_VAR is True
+assert settings.PLUGIN2_VAR is True
+assert settings.PLUGIN3_VAR is True
+assert settings.PLUGIN4_VAR is True
diff --git a/example/includes/configs/plugin1.toml b/example/includes/configs/plugin1.toml
new file mode 100644
index 0000000..1a231fb
--- /dev/null
+++ b/example/includes/configs/plugin1.toml
@@ -0,0 +1,7 @@
+[default]
+plugin1_var = true
+lastfile = 'configs/plugin1.toml'
+mergeable = {plugin1=true}
+
+# includes works only for the main file, will not work inside an included file
+dynaconf_include = ['this_is_not_loaded.toml']
diff --git a/example/includes/configs/plugin2.yaml b/example/includes/configs/plugin2.yaml
new file mode 100644
index 0000000..ad8cd9f
--- /dev/null
+++ b/example/includes/configs/plugin2.yaml
@@ -0,0 +1,5 @@
+default:
+  plugin2_var: true
+  lastfile: configs/plugin2.yaml
+  mergeable:
+    plugin2: on
diff --git a/example/includes/configs/plugin3.json b/example/includes/configs/plugin3.json
new file mode 100644
index 0000000..4df3ddc
--- /dev/null
+++ b/example/includes/configs/plugin3.json
@@ -0,0 +1,9 @@
+{
+    "default": {
+        "plugin3_var": true,
+        "lastfile": "configs/plugin3.json",
+        "mergeable": {
+            "plugin3": true
+        }
+    }
+}
diff --git a/example/includes/configs/plugin4.py b/example/includes/configs/plugin4.py
new file mode 100644
index 0000000..c477a9b
--- /dev/null
+++ b/example/includes/configs/plugin4.py
@@ -0,0 +1,3 @@
+PLUGIN4_VAR = True
+LASTFILE = "configs/plugin4.py"
+MERGEABLE = {"plugin4": True}
diff --git a/example/includes/settings.toml b/example/includes/settings.toml
new file mode 100644
index 0000000..8a1f09c
--- /dev/null
+++ b/example/includes/settings.toml
@@ -0,0 +1,5 @@
+[default]
+dynaconf_include = ['configs/*']
+settings_var = true
+lastfile = 'settings.toml'
+mergeable = {settings=true}
diff --git a/example/includes/this_is_not_loaded.toml b/example/includes/this_is_not_loaded.toml
new file mode 100644
index 0000000..7251fd3
--- /dev/null
+++ b/example/includes/this_is_not_loaded.toml
@@ -0,0 +1,2 @@
+[default]
+settings_var = false


Test patch:
diff --git a/tests/test_base.py b/tests/test_base.py
index 5f54326..37f266e 100644
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -306,3 +306,7 @@ def test_dotted_set_with_merge(settings):
             {'name': 'item 4'},
         ]
     }
+
+
+def test_abs_project_root(settings):
+    assert settings.abs_project_root in os.path.abspath(__file__)
diff --git a/tests/test_nested_loading.py b/tests/test_nested_loading.py
index a65e35d..933c92c 100644
--- a/tests/test_nested_loading.py
+++ b/tests/test_nested_loading.py
@@ -168,6 +168,23 @@ PLUGIN_TEXT = {
 }
 
 
+def test_invalid_include_path(tmpdir):
+    """Ensure non existing paths are not loaded."""
+
+    settings_file = tmpdir.join("settings.toml")
+    settings_file.write(TOML)
+
+    settings = LazySettings(
+        ENV_FOR_DYNACONF="DEFAULT",
+        silent=False,
+        LOADERS_FOR_DYNACONF=False,
+        SETTINGS_MODULE_FOR_DYNACONF=str(settings_file))
+
+    # Ensure overrides not happened
+    assert settings.SERVER == "base.example.com"
+    assert settings.DEBUG is False
+
+
 def test_load_nested_toml(tmpdir):
     """Load a TOML file that includes other TOML files."""
 
@@ -184,6 +201,7 @@ def test_load_nested_toml(tmpdir):
         ENV_FOR_DYNACONF="DEFAULT",
         silent=False,
         LOADERS_FOR_DYNACONF=False,
+        PROJECT_ROOT_FOR_DYNACONF=str(tmpdir),
         SETTINGS_MODULE_FOR_DYNACONF=str(settings_file))
 
     # Ensure overrides that happen via TOML plugin config load.
@@ -213,7 +231,9 @@ def test_load_nested_different_types(ext, tmpdir):
         ENV_FOR_DYNACONF="DEFAULT",
         silent=False,
         LOADERS_FOR_DYNACONF=False,
-        SETTINGS_MODULE_FOR_DYNACONF=str(settings_file))
+        PROJECT_ROOT_FOR_DYNACONF=str(tmpdir),
+        SETTINGS_MODULE_FOR_DYNACONF=str(settings_file)
+    )
 
     assert settings.DEBUG is False
     assert settings.DATABASE_URI == "{0}.example.com".format(ext)
@@ -239,6 +259,7 @@ def test_load_nested_different_types_with_merge(tmpdir):
         ENV_FOR_DYNACONF="custom",
         silent=False,
         LOADERS_FOR_DYNACONF=False,
+        PROJECT_ROOT_FOR_DYNACONF=str(tmpdir),
         SETTINGS_MODULE_FOR_DYNACONF=str(settings_file),
         MERGE_ENABLED_FOR_DYNACONF=True)