Repository: rochacbruno/dynaconf
Pull request #183 (2019-04-28 10:57:45)

Add example and test for issue #182
To switch files and keep a main default it is needed to always keep the same `SETTINGS_FILE` and swtich only the `INCLUDES`

codecov[bot]: # [Codecov](https://codecov.io/gh/rochacbruno/dynaconf/pull/183?src=pr&el=h1) Report
> Merging [#183](https://codecov.io/gh/rochacbruno/dynaconf/pull/183?src=pr&el=desc) into [master](https://codecov.io/gh/rochacbruno/dynaconf/commit/88a76ab2448c3303f2f6d4606193cf70fa354ca6?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/183/graphs/tree.svg?width=650&token=GXszVzJcqW&height=150&src=pr)](https://codecov.io/gh/rochacbruno/dynaconf/pull/183?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##           master   #183   +/-   ##
=====================================
  Coverage     100%   100%           
=====================================
  Files          18     18           
  Lines         705    705           
=====================================
  Hits          705    705
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/rochacbruno/dynaconf/pull/183?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/183?src=pr&el=footer). Last update [88a76ab...7167915](https://codecov.io/gh/rochacbruno/dynaconf/pull/183?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/Makefile b/Makefile
index 0b10cab..23fef4f 100644
--- a/Makefile
+++ b/Makefile
@@ -78,6 +78,7 @@ test_examples:
 	cd example/issues/160_path_traversal_fixed;pwd;./test.sh
 	cd example/issues/166_renamed_global_env;pwd;python app.py
 	cd example/issues/169_renamed_settings_module;pwd;python app.py
+	cd example/issues/182_multiple_locations;pwd;python app.py
 
 test_vault:
 	# @cd example/vault;pwd;python write.py
diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md
index 9055747..4af5d3d 100644
--- a/docs/guides/configuration.md
+++ b/docs/guides/configuration.md
@@ -39,10 +39,10 @@ It can also be passed as parameters to extensions like `FlaskDynaconf` or set in
 +---------------------+---------+--------------------------------------------------+--------------------------------------------------+--------------------------------------------------------------+
 | ENVVAR              | str     | The envvar which holds the list of settings files| 'SETTINGS_FILE_FOR_DYNACONF'                     | ENVVAR_FOR_DYNACONF=MYPROGRAM_SETTINGS                       |
 +---------------------+---------+--------------------------------------------------+--------------------------------------------------+--------------------------------------------------------------+
-| SETTINGS_FILE       | list    | List of files to load                            | | List of all supportes files:                   || SETTINGS_FILE_FOR_DYNACONF="conf.toml,settings.yaml"        |
+| SETTINGS_FILE       | list    | List of files to load                            | | List of all supportes files:                   || SETTINGS_FILE_FOR_DYNACONF="myconfig.toml"                  |
 |                     | str     |                                                  | | `settings.{py,toml,yaml,ini,conf,json}`        || SETTINGS_FILE_FOR_DYNACONF="['conf.toml','settings.yaml']"  |
-|                     |         |                                                  | | `.secrets.{py,toml,yaml,ini,conf,json}`        ||                                                             |
-|                     |         |                                                  | |                                                ||                                                             |
+|                     |         |                                                  | | `.secrets.{py,toml,yaml,ini,conf,json}`        || SETTINGS_FILE_FOR_DYNACONF="conf.toml,settings.yaml"        |
+|                     |         |                                                  | |                                                || SETTINGS_FILE_FOR_DYNACONF="conf.toml;settings.yaml"        |
 |                     |         |                                                  | | This var name can be replaced by:              ||                                                             |
 |                     |         |                                                  | | `ENVVAR_FOR_DYNACONF=MYPROGRAM_SETTINGS`       || MYPROGRAM_SETTINGS="conf.toml,settings.yaml"                |
 +---------------------+---------+--------------------------------------------------+--------------------------------------------------+--------------------------------------------------------------+
@@ -108,7 +108,11 @@ It can also be passed as parameters to extensions like `FlaskDynaconf` or set in
 +---------------------+---------+--------------------------------------------------+--------------------------------------------------+--------------------------------------------------------------+
 | SECRETS             | str     | Path to aditional secrets file to be loaded      | None                                             | SECRETS_FOR_DYNACONF=/var/jenkins/settings_ci.toml           |
 +---------------------+---------+--------------------------------------------------+--------------------------------------------------+--------------------------------------------------------------+
-| INCLUDES            | list    | A list of paths or a glob to load                | []                                               | INCLUDES_FOR_DYNACONF="['path1.ext', 'folder/*']"            |
+| INCLUDES            | list    || A list of paths or a glob to load               | []                                               | INCLUDES_FOR_DYNACONF="['path1.ext', 'folder/*']"            |
+|                     |         || can be a toml-like list, or sep by , or ;       |                                                  | INCLUDES_FOR_DYNACONF="path1.toml;path2.toml"                |
+|                     |         ||                                                 |                                                  | INCLUDES_FOR_DYNACONF="path1.toml,path2.toml"                |
+|                     |         ||                                                 |                                                  | INCLUDES_FOR_DYNACONF="single_path.toml"                     |
+|                     |         ||                                                 |                                                  | INCLUDES_FOR_DYNACONF="single_path/glob/*.toml"              |
 +---------------------+---------+--------------------------------------------------+--------------------------------------------------+--------------------------------------------------------------+
 ```
 
diff --git a/docs/guides/examples.md b/docs/guides/examples.md
index 6305c9a..3a5842d 100644
--- a/docs/guides/examples.md
+++ b/docs/guides/examples.md
@@ -119,6 +119,49 @@ MESSAGE="Dynaconf works like a charm with Flask and .env"
 TEST_RULE='/flask_with_ini'
 ```
 
+## Using a default main config file plus variable settings file
+
+On the `.env`
+
+```bash
+export SETTINGS_FILE_FOR_DYNACONF="default.toml"
+```
+
+The default file
+
+```ini
+[default]
+variable1 = 'value1'
+```
+
+Now having specific settings per environment
+
+Use cases:
+
+- plugin based apps
+- user specific settings
+- dev specific settings
+
+On the **user1** environment
+
+```bash
+export INCLUDES_FOR_DYNACONF='/path/to/user1_specific_settings.toml'
+```
+
+On the **user2** environment
+
+```bash
+export INCLUDES_FOR_DYNACONF='/path/to/user2_specific_settings.toml'
+```
+
+It can be a glob
+
+```bash
+export INCLUDES_FOR_DYNACONF='/path/to/config/*.toml'
+```
+
+And also supports having a `;` or `,` separated list of paths or globs.
+
 ## More examples
 
 Take a look at [example/](https://github.com/rochacbruno/dynaconf/tree/master/example) for more examples.
diff --git a/docs/guides/usage.md b/docs/guides/usage.md
index 9c010f5..9acc672 100644
--- a/docs/guides/usage.md
+++ b/docs/guides/usage.md
@@ -70,7 +70,7 @@ ENV_FOR_DYNACONF=staging python yourapp.py
 > NOTE: The settings files are optional. If it is not present, only the values from **environment variables** are used (**.env** file is also supported).
 
 Dynaconf will search for the settings files defined in `SETTINGS_FILE_FOR_DYNACONF` which by default is a list containing combinations of **settings.{py|toml|json|ini|yaml}** and **.secrets.{py|toml|json|ini|yaml}**
-and dynaconf will try to find each one of those combinations, optionally it is possible to configure it to a different set of files e.g: `export SETTINGS_FILE_FOR_DYNACONF='["myfilename.toml", "another.json"]'`, this value contains a list of relative or absolute paths, can be a toml-like list or a comma separated string and can be exported to `envvars`, write to `.env` file or passed directly to Dynaconf instance.
+and dynaconf will try to find each one of those combinations, optionally it is possible to configure it to a different set of files e.g: `export SETTINGS_FILE_FOR_DYNACONF='["myfilename.toml", "another.json"]'`, this value contains a list of relative or absolute paths, can be a toml-like list or a comma/semicolon separated string and can be exported to `envvars`, write to `.env` file or passed directly to Dynaconf instance.
 
 > IMPORTANT: Dynaconf by default reads settings files using `utf-8` encoding, if you have settings files written in other encoding please set `ENCODING_FOR_DYNACONF` environment variable.
 
@@ -80,7 +80,7 @@ See more details in [configuration](configuration.html)
 
 To find the files defined in `SETTINGS_FILE_FOR_DYNACONF` the search will start at the path defined in `ROOT_PATH_FOR_DYNACONF` (if defined), then will recursively walk to its root and then will try the **folder where the called program is located** and then it will recursivelly try its parent directories **until the root parent is reached which can be File System `/` or the current working dir** then finally will try the **current working directory** as the last option.
 
-Some people prefer to put settings in a subfolder so for each of the paths it will also search in a relative folder called `config/`.
+Some people prefer to put settings in a sub-folder so for each of the paths it will also search in a relative folder called `config/`.
 
 Dynaconf will stop searching on the first match and if no file is found it will **fail silently** unless `SILENT_ERRORS_FOR_DYNACONF=false` is exported.
 
@@ -343,9 +343,23 @@ A settings file can include a `dynaconf_include` stanza, whose exact
 It is also possible to setup includes using environment variable.
 
 ```bash
-export INCLUDES_FOR_DYNACONF='["/etc/myprogram/conf.d/*.toml"]'
+# A glob pattern
+export INCLUDES_FOR_DYNACONF='/etc/myprogram/conf.d/*.toml'
+# a single path
+export INCLUDES_FOR_DYNACONF='/path/to/file.yaml'
+# multiple files
+export INCLUDES_FOR_DYNACONF='/path/to/file.yaml;/other/path/to/file.toml'
 ```
 
+## Programmatically loading a settings file
+
+```python
+from dynaconf import settings
+settings.load_file(path="/path/to/file.toml")  # list or `;/,` separated allowed
+```
+
+> **NOTE**: programmatically loaded file is not persisted, once `env` is changed via `setenv|ugin_env`, or a `reload` or `configure` is invoked it will be cleaned, to persist it needs to go to `INCLUDES_FOR_DYNACONF` variable or you need to load it programmatically again.
+
 ## Merging existing values
 
 If your settings has existing variables of types `list` ot `dict` and you want to `merge` instead of `override` then 
@@ -397,6 +411,7 @@ settings.DATABASE == {'host': 'server.com', 'user': 'dev_user', 'password': 1234
 The same can be applied to **lists**:
 
 `settings.toml`
+
 ```cfg
 [default]
 plugins = ["core"]
diff --git a/dynaconf/base.py b/dynaconf/base.py
index 22848a2..ac0a4b4 100644
--- a/dynaconf/base.py
+++ b/dynaconf/base.py
@@ -11,6 +11,7 @@ from dynaconf.loaders import yaml_loader
 from dynaconf.utils import BANNER
 from dynaconf.utils import compat_kwargs
 from dynaconf.utils import deduplicate
+from dynaconf.utils import ensure_a_list
 from dynaconf.utils import missing
 from dynaconf.utils import object_merge
 from dynaconf.utils import raw_logger
@@ -720,7 +721,7 @@ class Settings(object):
         :param env: The environment to load
         :param silent: If loading erros is silenced
         :param key: if provided load a single key
-        :param filename: optinal custom filename to load
+        :param filename: optional custom filename to load
         """
         if key is None:
             default_loader(self, self._defaults)
@@ -740,16 +741,30 @@ class Settings(object):
     def load_includes(self, env, silent, key):
         """Do we have any nested includes we need to process?"""
         includes = self.get("DYNACONF_INCLUDE", [])
-        includes.extend(self.get("INCLUDES_FOR_DYNACONF", []))
+        includes.extend(ensure_a_list(self.get("INCLUDES_FOR_DYNACONF")))
         if includes:
-            self.logger.debug("Found %s includes to process", includes)
+            self.logger.debug("Processing includes %s", includes)
+            self.load_file(path=includes, env=env, silent=silent, key=key)
+
+    def load_file(self, path=None, env=None, silent=True, key=None):
+        """Programmatically load files from ``path``.
+
+        :param path: A single filename or a file list
+        :param env: Which env to load from file (default current_env)
+        :param silent: Should raise errors?
+        :param key: Load a single key?
+        """
+        env = (env or self.current_env).upper()
+        files = ensure_a_list(path)
+        if files:
+            self.logger.debug("Got %s files to process", files)
             already_loaded = set()
-            for include in includes:
-                self.logger.debug("Processing include %s", include)
-                included_filename = os.path.join(self._root_path, include)
-                self.logger.debug("Include path is %s", included_filename)
+            for _filename in files:
+                self.logger.debug("Processing file %s", _filename)
+                filepath = os.path.join(self._root_path, _filename)
+                self.logger.debug("File path is %s", filepath)
                 # Handle possible *.globs sorted alphanumeric
-                for path in sorted(glob.glob(included_filename)):
+                for path in sorted(glob.glob(filepath)):
                     self.logger.debug("Loading %s", path)
                     if path in already_loaded:  # pragma: no cover
                         self.logger.debug("Skipping %s, already loaded", path)
@@ -764,7 +779,7 @@ class Settings(object):
                     already_loaded.add(path)
             if not already_loaded:
                 self.logger.warning(
-                    "Not able to locate the files %s to include", includes
+                    "Not able to locate the files %s to load", files
                 )
 
     @property
diff --git a/dynaconf/loaders/__init__.py b/dynaconf/loaders/__init__.py
index 8f3d811..6932805 100644
--- a/dynaconf/loaders/__init__.py
+++ b/dynaconf/loaders/__init__.py
@@ -8,6 +8,7 @@ from dynaconf.loaders import py_loader
 from dynaconf.loaders import toml_loader
 from dynaconf.loaders import yaml_loader
 from dynaconf.utils import deduplicate
+from dynaconf.utils import ensure_a_list
 from dynaconf.utils.parse_conf import false_values
 
 
@@ -71,20 +72,11 @@ def settings_loader(
         settings_module = settings_module or obj.settings_module
         if not settings_module:  # pragma: no cover
             return
-
-        if isinstance(settings_module, (list, tuple)):
-            files = list(settings_module)
-        else:
-            files = settings_module.split(",")
+        files = ensure_a_list(settings_module)
     else:
-        files = [filename]
+        files = ensure_a_list(filename)
 
-    secrets_file = obj.get("SECRETS_FOR_DYNACONF", None)
-    if secrets_file is not None:
-        if isinstance(secrets_file, (list, tuple)):
-            files.extend(secrets_file)
-        else:
-            files.extend(secrets_file.split(","))
+    files.extend(ensure_a_list(obj.get("SECRETS_FOR_DYNACONF", None)))
 
     found_files = []
     modules_names = []
diff --git a/dynaconf/loaders/base.py b/dynaconf/loaders/base.py
index 375fefe..73b1800 100644
--- a/dynaconf/loaders/base.py
+++ b/dynaconf/loaders/base.py
@@ -1,6 +1,7 @@
 import io
 import os
 
+from dynaconf.utils import ensure_a_list
 from dynaconf.utils import raw_logger
 
 
@@ -53,10 +54,10 @@ class BaseLoader(object):
             return
 
         if not isinstance(filename, (list, tuple)):
-            split_files = filename.split(",")
+            split_files = ensure_a_list(filename)
             if all([f.endswith(self.extensions) for f in split_files]):  # noqa
-                files = split_files  # it is a ['file.ini', ...]
-            else:  # it is a single ini string
+                files = split_files  # it is a ['file.ext', ...]
+            else:  # it is a single config as string
                 files = [filename]
         else:  # it is already a list/tuple
             files = filename
diff --git a/dynaconf/utils/__init__.py b/dynaconf/utils/__init__.py
index 4ca2143..6de2262 100644
--- a/dynaconf/utils/__init__.py
+++ b/dynaconf/utils/__init__.py
@@ -166,3 +166,25 @@ def warn_deprecations(data):
                 "replace it with %s" % (old, new),
                 DeprecationWarning,
             )
+
+
+def trimmed_split(s, seps=(";", ",")):
+    """Given a string s, split is by one of one of the seps."""
+    for sep in seps:
+        if sep not in s:
+            continue
+        data = [item.strip() for item in s.strip().split(sep)]
+        return data
+    return [s]  # raw un-splitted
+
+
+def ensure_a_list(data):
+    """Ensure data is a list or wrap it in a list"""
+    if not data:
+        return []
+    if isinstance(data, (list, tuple, set)):
+        return list(data)
+    if isinstance(data, str):
+        data = trimmed_split(data)  # settings.toml,other.yaml
+        return data
+    return [data]
diff --git a/example/issues/182_multiple_locations/.env b/example/issues/182_multiple_locations/.env
new file mode 100644
index 0000000..b22c23e
--- /dev/null
+++ b/example/issues/182_multiple_locations/.env
@@ -0,0 +1,2 @@
+# Being set on python app.py os.environ[...]
+# SETTINGS_FILE_FOR_DYNACONF="default.toml"
diff --git a/example/issues/182_multiple_locations/app.py b/example/issues/182_multiple_locations/app.py
new file mode 100644
index 0000000..4b04aa7
--- /dev/null
+++ b/example/issues/182_multiple_locations/app.py
@@ -0,0 +1,51 @@
+import os
+
+from dynaconf import settings
+
+# This is the main settings file
+os.environ["SETTINGS_FILE_FOR_DYNACONF"] = "default.toml"
+
+
+# To make dynaconf read more file we need to include it
+print("using config 1")
+os.environ["INCLUDES_FOR_DYNACONF"] = "./cfg/settings.toml"
+
+print("development")
+settings.setenv("development")
+print(settings.VAR_DFT)
+print(settings.VAR1)
+assert settings.VAR_DFT == "default_dev"
+assert settings.VAR1 == "config1_dev"
+
+print("changing to production")
+settings.setenv("production")
+print(settings.VAR_DFT)
+print(settings.VAR1)
+assert settings.VAR_DFT == "default_prod"
+assert settings.VAR1 == "config1_prod"
+
+print("----")
+
+
+# To make dynaconf a diferent file we need to include it
+# includes can be a toml-like list
+print("using config 2")
+os.environ["INCLUDES_FOR_DYNACONF"] = "['./cfg2/settings.toml']"
+# if it is done during a running process we need to reload to read the new
+# envvar
+settings.reload()
+
+
+print("development")
+settings.setenv("development")
+print(settings.VAR_DFT)
+print(settings.VAR1)
+assert settings.VAR_DFT == "default_dev"
+assert settings.VAR1 == "config2_dev"
+
+print("changing to production")
+settings.setenv("production")
+print(settings.VAR_DFT)
+print(settings.VAR1)
+assert settings.VAR_DFT == "default_prod"
+assert settings.VAR1 == "config2_prod"
diff --git a/example/issues/182_multiple_locations/cfg/settings.toml b/example/issues/182_multiple_locations/cfg/settings.toml
new file mode 100644
index 0000000..bb24b7e
--- /dev/null
+++ b/example/issues/182_multiple_locations/cfg/settings.toml
@@ -0,0 +1,5 @@
+[development]
+VAR1 = 'config1_dev'
+
+[production]
+VAR1 = 'config1_prod'
diff --git a/example/issues/182_multiple_locations/cfg2/settings.toml b/example/issues/182_multiple_locations/cfg2/settings.toml
new file mode 100644
index 0000000..69477e4
--- /dev/null
+++ b/example/issues/182_multiple_locations/cfg2/settings.toml
@@ -0,0 +1,5 @@
+[development]
+VAR1 = 'config2_dev'
+
+[production]
+VAR1 = 'config2_prod'
diff --git a/example/issues/182_multiple_locations/default.toml b/example/issues/182_multiple_locations/default.toml
new file mode 100644
index 0000000..22d3f39
--- /dev/null
+++ b/example/issues/182_multiple_locations/default.toml
@@ -0,0 +1,5 @@
+[development]
+VAR_DFT = "default_dev"
+
+[production]
+VAR_DFT = "default_prod"
diff --git a/example/multiple_folders/.env b/example/multiple_folders/.env
index adaad88..a55bc66 100644
--- a/example/multiple_folders/.env
+++ b/example/multiple_folders/.env
@@ -1,1 +1,1 @@
-SETTINGS_FILE_FOR_DYNACONF="config_1/settings.toml,config_2/settings.toml"
+SETTINGS_FILE_FOR_DYNACONF="config_1/settings.toml;config_2/settings.toml"


Test patch:
diff --git a/tests/test_nested_loading.py b/tests/test_nested_loading.py
index d2edb6c..4555e9c 100644
--- a/tests/test_nested_loading.py
+++ b/tests/test_nested_loading.py
@@ -286,3 +286,36 @@ def test_load_nested_different_types_with_merge(tmpdir):
     assert settings.NESTED_1.nested_2.nested_3.nested_4.base == 4
     for ext in ["toml", "json", "yaml", "ini", "py"]:
         assert settings.NESTED_1.nested_2.nested_3.nested_4[ext] == 5
+
+
+def test_programmatically_file_load(tmpdir):
+    """Check file can be included programmatically"""
+    settings_file = tmpdir.join("settings.toml")
+    settings_file.write(
+        """
+       [default]
+       default_var = 'default'
+    """
+    )
+
+    settings = LazySettings(SETTINGS_FILE_FOR_DYNACONF=str(settings_file))
+    assert settings.DEFAULT_VAR == "default"
+
+    toml_plugin_file = tmpdir.join("plugin1.toml")
+    toml_plugin_file.write(
+        """
+        [development]
+        plugin_value = 'plugin'
+    """
+    )
+
+    settings.load_file(path=str(toml_plugin_file))
+    assert settings.PLUGIN_VALUE == "plugin"
+
+    settings.setenv("production")
+    assert settings.DEFAULT_VAR == "default"
+    # programmatically loaded file is not persisted
+    # once `env` is changed, or a `reload` it will be missed
+    # to persist it needs to go to `INCLUDES_FOR_DYNACONF` variable
+    with pytest.raises(AttributeError):
+        assert settings.PLUGIN_VALUE == "plugin"
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 0f8cd90..e5c0bf5 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -2,9 +2,11 @@ import io
 import os
 
 from dynaconf import default_settings
+from dynaconf.utils import ensure_a_list
 from dynaconf.utils import Missing
 from dynaconf.utils import missing
 from dynaconf.utils import object_merge
+from dynaconf.utils import trimmed_split
 from dynaconf.utils.files import find_file
 from dynaconf.utils.parse_conf import parse_conf_data
 from dynaconf.utils.parse_conf import unparse_conf_data
@@ -140,3 +142,55 @@ def test_merge_existing_dict():
 
     object_merge(existing, new)
     assert new == {"host": "localhost", "port": 666, "user": "admin"}
+
+
+def test_trimmed_split():
+    # No sep
+    assert trimmed_split("hello") == ["hello"]
+
+    # a comma sep string
+    assert trimmed_split("ab.toml,cd.yaml") == ["ab.toml", "cd.yaml"]
+    # spaces are trimmed
+    assert trimmed_split(" ab.toml , cd.yaml ") == ["ab.toml", "cd.yaml"]
+
+    # a semicollon sep string
+    assert trimmed_split("ab.toml;cd.yaml") == ["ab.toml", "cd.yaml"]
+    # semicollon are trimmed
+    assert trimmed_split(" ab.toml ; cd.yaml ") == ["ab.toml", "cd.yaml"]
+
+    # has comma and also semicollon (semicollon has precedence)
+    assert trimmed_split("ab.toml,cd.yaml;ef.ini") == [
+        "ab.toml,cd.yaml",
+        "ef.ini",
+    ]
+
+    # has comma and also semicollon (changing precedence)
+    assert trimmed_split("ab.toml,cd.yaml;ef.ini", seps=(",", ";")) == [
+        "ab.toml",
+        "cd.yaml;ef.ini",
+    ]
+
+    # using different separator
+    assert trimmed_split("ab.toml|cd.yaml", seps=("|")) == [
+        "ab.toml",
+        "cd.yaml",
+    ]
+
+
+def test_ensure_a_list():
+
+    # No data is empty list
+    assert ensure_a_list(None) == []
+
+    # Sequence types is only converted
+    assert ensure_a_list([1, 2]) == [1, 2]
+    assert ensure_a_list((1, 2)) == [1, 2]
+    assert ensure_a_list({1, 2}) == [1, 2]
+
+    # A string is trimmed_splitted
+    assert ensure_a_list("ab.toml") == ["ab.toml"]
+    assert ensure_a_list("ab.toml,cd.toml") == ["ab.toml", "cd.toml"]
+    assert ensure_a_list("ab.toml;cd.toml") == ["ab.toml", "cd.toml"]
+
+    # other types get wrapped in a list
+    assert ensure_a_list(1) == [1]