Repository: rochacbruno/dynaconf (python)

Issue:
[RFC] Allow dotted environment variables
**Is your feature request related to a problem? Please describe.**
Parameters defined in environment variables cannot be accessed with dotted key notation in the same way as parameters defined in settings files, which prevents overriding dotted key parameters with environment variables. 

**Example:** Environment variable `DYNACONF_SERVICE_PORT` cannot be accessed via `settings['SERVICE.PORT']`. Instead, it must be accessed via `settings['SERVICE_PORT']`.

**Describe the solution you'd like**
One solution would be to use a designated sequence of characters to symbolize a dot separator, possibly two consecutive underscores ( `__` ). This character sequence could be configurable (eg `ENVVAR_DOT_SEPARATOR_FOR_DYNACONF`).

**Example:** Environment variable `DYNACONF_SERVICE__PORT` could be accessed via `settings['SERVICE.PORT']`, and would override any `service.port` parameter defined in a settings file.

**Describe alternatives you've considered**
An alternative to this could be to simply match either dotted or non-dotted key values to the correlated environment variable. As a note, this is how Spring Framework handles this feature.

**Example:** Environment variable `DYNACONF_SERVICE_PORT` could be accessed via `settings['SERVICE.PORT']` or via `settings['SERVICE_PORT']`, and would override any `service.port` or `service_port` parameter defined in a settings file.

**Additional context**
This would be especially nice for:
- allowing natural grouping of related settings (eg `service.host` and `service.port`)
- allowing overriding of existing parameters defined in a settings file
- accessing environment variables via object dot notation (eg `settings.SERVICE.PORT`)


Pull request description:
Fix #197 add support for DOTTED__ENV__VARS
Fix #197 

## Merging new data to existing variables

### Nested keys in dictionaries via environment variables.

> **New in 2.0.5**
> 
> This is useful for `Django` settings.

Lets say you have a configuration like this:

`settings.py`

```py
DATABASES = {
    'default': {
        'NAME': 'db',
        'ENGINE': 'module.foo.engine',
        'ARGS': {'timeout': 30}
    }
}
```

And  now you want to change the values of `ENGINE` to `other.module`, via environment variables you can use the format `${ENVVAR_PREFIX}_${VARIABLE}__${NESTED_ITEM}__${NESTED_ITEM}`

Each `__` (dunder, a.k.a *double underline*) denotes access to nested elements in a dictionary.

So 

```py
DATABASES['default']['ENGINE'] = 'other.module'
```

Can be expressed as environment variables as:

```bash
export DYNACONF_DATABASES__default__ENGINE=other.module
```

This will result in

```py
DATABASES = {
    'default': {
        'NAME': 'db',
        'ENGINE': 'other.module',
        'ARGS': {'timeout': 30}
    }
}
```

> **IMPORTANT** lower case keys are respected only on *nix systems, unfortunately Windows environment variables are case insensitive and Python reads it as all upper cases, that means that if you are running on Windows the dictionary can have only upper case keys.

Now if you want to add a new item to `ARGS` key:

```bash
export DYNACONF_DATABASES__default__ARGS__retries=10
```

This will result in

```py
DATABASES = {
    'default': {
        'NAME': 'db',
        'ENGINE': 'other.module',
        'ARGS': {'timeout': 30, 'retries': 10}
    }
}
```

and you can also pass a `toml` like dictionary to be merged with existing `ARGS` key.

```bash
export DYNACONF_DATABASES__default__ARGS='{timeout=50, size=1}'
```

will result in

```py
DATABASES = {
    'default': {
        'NAME': 'db',
        'ENGINE': 'other.module',
        'ARGS': {'retries': 10, 'timeout': 50, 'size': 1}
    }
}
```

Now if you want to clean an existing nested attribute you can use the `@reset` token on exported env var.

```bash
export DYNACONF_DATABASES__default__ARGS='@reset {}'
```

This will result in

```py
DATABASES = {
    'default': {
        'NAME': 'db',
        'ENGINE': 'other.module',
        'ARGS': {}
    }
}
```

And also you can do a `@reset` followed by a re-assignment

> Dynaconf env vars are parsed using `toml` so the format for dictionaries is a bit different.

```bash
export DYNACONF_DATABASES__default__ARGS='@reset {timeout=90}'
```

This will result in

```py
DATABASES = {
    'default': {
        'NAME': 'db',
        'ENGINE': 'other.module',
        'ARGS': {'timeout': 90}
    }
}
```

And if in any case you need to completely remove that key from the dictionary:

```bash
export DYNACONF_DATABASES__default__ARGS='@del'
```

This will result in

```py
DATABASES = {
    'default': {
        'NAME': 'db',
        'ENGINE': 'other.module'
    }
}
```

Patch:
diff --git a/Makefile b/Makefile
index 6ed9753..10da243 100644
--- a/Makefile
+++ b/Makefile
@@ -13,6 +13,7 @@ test_examples:
 	cd example/;pwd;python full_example.py | grep -c full_example || exit 1
 	cd example/;pwd;python compat.py
 	cd example/app;pwd;python app.py | grep -c app || exit 1
+	cd example/dunder;pwd;python app.py || exit 1
 	cd example/app_with_dotenv;pwd;python app.py | grep -c app_with_dotenv || exit 1
 	cd example/merge_configs;pwd;python app.py | grep -c merge_configs || exit 1
 	cd example/dynaconf_merge;pwd;python app.py
diff --git a/docs/guides/environment_variables.md b/docs/guides/environment_variables.md
index 3de92b1..7107c9b 100644
--- a/docs/guides/environment_variables.md
+++ b/docs/guides/environment_variables.md
@@ -22,7 +22,7 @@ Environment variables take precedence over all other configuration sources: if t
 
 Variable values are parsed as if they were **TOML** syntax. As illustration, consider the following examples:
 
-```
+```ini
 # Numbers
 DYNACONF_INTEGER=42
 DYNACONF_FLOAT=3.14
@@ -46,7 +46,9 @@ DYNACONF_ARRAY=['a', 'b', 'c']
 
 # Dictionaries
 DYNACONF_DICT={key="abc",val=123}
+```
 
+```python
 # toml syntax does not allow `None/null` values so use @none
 DYNACONF_NONE='@none None'
 
@@ -56,7 +58,152 @@ DYNACONF_ARRAY='@json [42, 3.14, "hello", true, ["otherarray"], {"foo": "bar"}]'
 
 > **NOTE**: Older versions of Dynaconf used the `@casting` prefixes for env vars like `export DYNACONF_INTEGER='@int 123'` still works but this casting is deprecated in favor of using TOML syntax described above. To disable the `@casting` do `export AUTO_CAST_FOR_DYNACONF=false`
 
-### Merging exported variables with existing data
+## Merging new data to existing variables
+
+### Nested keys in dictionaries via environment variables.
+
+> **New in 2.0.5**
+> 
+> This is useful for `Django` settings.
+
+Lets say you have a configuration like this:
+
+`settings.py`
+
+```py
+DATABASES = {
+    'default': {
+        'NAME': 'db',
+        'ENGINE': 'module.foo.engine',
+        'ARGS': {'timeout': 30}
+    }
+}
+```
+
+And  now you want to change the values of `ENGINE` to `other.module`, via environment variables you can use the format `${ENVVAR_PREFIX}_${VARIABLE}__${NESTED_ITEM}__${NESTED_ITEM}`
+
+Each `__` (dunder, a.k.a *double underline*) denotes access to nested elements in a dictionary.
+
+So 
+
+```py
+DATABASES['default']['ENGINE'] = 'other.module'
+```
+
+Can be expressed as environment variables as:
+
+```bash
+export DYNACONF_DATABASES__default__ENGINE=other.module
+```
+
+> **NOTE**: if you are using Django extension then the prefix will be `DJANGO_` instead of `DYNACONF_` and the same if you are using `FLASK_` or a custom prefix if you have customized the `ENVVAR_PREFIX`.
+
+This will result in
+
+```py
+DATABASES = {
+    'default': {
+        'NAME': 'db',
+        'ENGINE': 'other.module',
+        'ARGS': {'timeout': 30}
+    }
+}
+```
+
+> **IMPORTANT** lower case keys are respected only on *nix systems, unfortunately Windows environment variables are case insensitive and Python reads it as all upper cases, that means that if you are running on Windows the dictionary can have only upper case keys.
+
+Now if you want to add a new item to `ARGS` key:
+
+```bash
+export DYNACONF_DATABASES__default__ARGS__retries=10
+```
+
+This will result in
+
+```py
+DATABASES = {
+    'default': {
+        'NAME': 'db',
+        'ENGINE': 'other.module',
+        'ARGS': {'timeout': 30, 'retries': 10}
+    }
+}
+```
+
+and you can also pass a `toml` like dictionary to be merged with existing `ARGS` key.
+
+```bash
+export DYNACONF_DATABASES__default__ARGS='{timeout=50, size=1}'
+```
+
+will result in
+
+```py
+DATABASES = {
+    'default': {
+        'NAME': 'db',
+        'ENGINE': 'other.module',
+        'ARGS': {'retries': 10, 'timeout': 50, 'size': 1}
+    }
+}
+```
+
+Now if you want to clean an existing nested attribute you can use the `@reset` token on exported env var.
+
+```bash
+export DYNACONF_DATABASES__default__ARGS='@reset {}'
+```
+
+This will result in
+
+```py
+DATABASES = {
+    'default': {
+        'NAME': 'db',
+        'ENGINE': 'other.module',
+        'ARGS': {}
+    }
+}
+```
+
+And also you can do a `@reset` followed by a re-assignment
+
+> Dynaconf env vars are parsed using `toml` so the format for dictionaries is a bit different.
+
+```bash
+export DYNACONF_DATABASES__default__ARGS='@reset {timeout=90}'
+```
+
+This will result in
+
+```py
+DATABASES = {
+    'default': {
+        'NAME': 'db',
+        'ENGINE': 'other.module',
+        'ARGS': {'timeout': 90}
+    }
+}
+```
+
+And if in any case you need to completely remove that key from the dictionary:
+
+```bash
+export DYNACONF_DATABASES__default__ARGS='@del'
+```
+
+This will result in
+
+```py
+DATABASES = {
+    'default': {
+        'NAME': 'db',
+        'ENGINE': 'other.module'
+    }
+}
+```
+
+### Using the `dynaconf_merge` mark on configuration files.
 
 > **New in 2.0.0**
 
@@ -64,7 +211,7 @@ To merge exported variables there is the **dynaconf_merge** tokens, example:
 
 Your main settings file (e.g `settings.toml`) has an existing `DATABASE` dict setting on `[default]` env.
 
-Now you want to contribute to the same `DATABASE` key by addind new keys, so you can use `dynaconf_merge` at the end of your dict:
+Now you want to contribute to the same `DATABASE` key by adding new keys, so you can use `dynaconf_merge` at the end of your dict:
 
 In specific `[envs]`
 
@@ -89,7 +236,7 @@ The end result will be on `[development]` env:
 settings.DATABASE == {'host': 'server.com', 'user': 'dev_user', 'password': 1234}
 ```
 
-Read more in [Getting Started Guide](usage.html)
+Read more in [Getting Started Guide](usage.html#merging-existing-values)
 
 ### The global prefix
 
diff --git a/docs/guides/usage.md b/docs/guides/usage.md
index 137373d..367bda5 100644
--- a/docs/guides/usage.md
+++ b/docs/guides/usage.md
@@ -391,7 +391,26 @@ In an environment variable:
 export DYNACONF_DATABASE='{password=1234, dynaconf_merge=true}'
 ```
 
-Or in an additional file (e.g `settings.yaml, .secrets.yaml, etc`):
+It is also possible to use nested  `dunder` traversal like:
+
+```bash
+export DYNACONF_DATABASE__password=1234
+export DYNACONF_DATABASE__user=admin
+export DYNACONF_DATABASE__ARGS__timeout=30
+export DYNACONF_DATABASE__ARGS__retries=5
+```
+
+Each `__` is parsed as a level traversing thought dict keys. read more in [environment variables](environment_variables.html#nested-keys-in-dictionaries-via-environment-variables)
+
+So the above will result in
+
+```py
+DATABASE = {'password': 1234, 'user': 'admin', 'ARGS': {'timeout': 30, 'retries': 5}}
+```
+
+> **IMPORTANT** lower case keys are respected only on *nix systems, unfortunately Windows environment variables are case insensitive and Python reads it as all upper cases, that means that if you are running on Windows the dictionary can have only upper case keys.
+
+Or in an additional file (e.g `settings.yaml, .secrets.yaml, etc`) by using `dynaconf_merge` token:
 
 ```yaml
 default:
diff --git a/docs/index.rst b/docs/index.rst
index 0da2845..a23b58b 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -88,11 +88,14 @@ Dynaconf - Easy and Powerful Settings Configuration for Python
 Who is using Dynaconf?
 ^^^^^^^^^^^^^^^^^^^^^^
 
-- Pulp Project - Django - (Red Hat)
-- Ansible Galaxy - Django - (Red Hat)
-- Insights QE (Red Hat)
+- Pulp Project - Django - (RedHat)
+- Ansible Galaxy - Django - (RedHat)
+- Insights QE (RedHat)
+- CloudForms QE (RedHat)
 - Seek AI & Catho Job boards - Flask - (on AI APIs)
 - Quokka CMS - Flask
+- iNNOVO Cloud GmbH
+- Teraki
 
 Are you using Dynaconf? Please `give feedback`_
 
diff --git a/dynaconf/base.py b/dynaconf/base.py
index 7465665..62bc861 100644
--- a/dynaconf/base.py
+++ b/dynaconf/base.py
@@ -7,6 +7,7 @@ from contextlib import suppress
 from dynaconf import default_settings
 from dynaconf.loaders import default_loader
 from dynaconf.loaders import enable_external_loaders
+from dynaconf.loaders import env_loader
 from dynaconf.loaders import py_loader
 from dynaconf.loaders import settings_loader
 from dynaconf.loaders import yaml_loader
@@ -537,38 +538,60 @@ class Settings(object):
         for key in list(self.store.keys()):
             self.unset(key)
 
-    def unset(self, key):
+    def unset(self, key, force=False):
         """Unset on all references
 
         :param key: The key to be unset
+        :param force: Bypass default checks and force unset
         """
         key = key.strip().upper()
-        if key not in dir(default_settings) and key not in self._defaults:
+        if (
+            key not in dir(default_settings)
+            and key not in self._defaults
+            or force
+        ):
             self.logger.debug("Unset %s", key)
             delattr(self, key)
             self.store.pop(key, None)
 
-    def unset_all(self, keys):  # pragma: no cover
+    def unset_all(self, keys, force=False):  # pragma: no cover
         """Unset based on a list of keys
 
         :param keys: a list of keys
+        :param force: Bypass default checks and force unset
         """
         for key in keys:
-            self.unset(key)
+            self.unset(key, force=force)
+
+    def _dotted_set(
+        self, dotted_key, value, tomlfy=False, merge=True, **kwargs
+    ):
+        """Sets dotted keys as nested dictionaries.
+
+        Arguments:
+            dotted_key {str} -- A traversal name e.g: foo.bar.zaz
+            value {Any} -- The value to set to the nested value.
 
-    def _dotted_set(self, dotted_key, value, tomlfy=False, **kwargs):
+        Keyword Arguments:
+            tomlfy {bool} -- Perform toml parsing (default: {False})
+            merge {bool} -- Merge existing dictionaries (default: {True})
+        """
 
-        data = DynaBox(default_box=True)
-        tree = data
         split_keys = dotted_key.split(".")
+        existing_data = self.get(split_keys[0], {}) if merge else {}
+        new_data = DynaBox(default_box=True)
 
+        tree = new_data
         for k in split_keys[:-1]:
             tree = tree.setdefault(k, {})
 
         value = parse_conf_data(value, tomlfy=tomlfy)
         tree[split_keys[-1]] = value
 
-        self.update(data=data, **kwargs)
+        if existing_data and merge:
+            object_merge({split_keys[0]: existing_data}, new_data)
+
+        self.update(data=new_data, tomlfy=tomlfy, **kwargs)
 
     def set(
         self,
@@ -578,6 +601,7 @@ class Settings(object):
         tomlfy=False,
         dotted_lookup=True,
         is_secret=False,
+        merge=True,
     ):
         """Set a value storing references for the loader
 
@@ -586,16 +610,35 @@ class Settings(object):
         :param loader_identifier: Optional loader name e.g: toml, yaml etc.
         :param tomlfy: Bool define if value is parsed by toml (defaults False)
         :param is_secret: Bool define if secret values is hidden on logs.
+        :param merge: Bool define if existing nested/dotted will be merged.
         """
 
+        nested_sep = self.get("NESTED_SEPARATOR_FOR_DYNACONF")
+        if nested_sep and nested_sep in key:
+            # turn FOO__bar__ZAZ in `FOO.bar.ZAZ`
+            key = key.replace(nested_sep, ".")
+
         if "." in key and dotted_lookup is True:
             return self._dotted_set(
-                key, value, loader_identifier=loader_identifier, tomlfy=tomlfy
+                key,
+                value,
+                loader_identifier=loader_identifier,
+                tomlfy=tomlfy,
+                merge=merge,
             )
 
         value = parse_conf_data(value, tomlfy=tomlfy)
         key = key.strip().upper()
         existing = getattr(self, key, None)
+
+        if getattr(value, "dynaconf_del", None):
+            self.unset(key, force=True)
+            return
+
+        if getattr(value, "dynaconf_reset", False):
+            # just in case someone use a `@reset` in a first level var.
+            value = value.value
+
         if existing is not None and existing != value:
             value = self._merge_before_set(key, existing, value, is_secret)
 
@@ -762,6 +805,10 @@ class Settings(object):
         if includes:
             self.logger.debug("Processing includes %s", includes)
             self.load_file(path=includes, env=env, silent=silent, key=key)
+            # ensure env vars are the last thing loaded after all includes
+            last_loader = self.loaders and self.loaders[-1]
+            if last_loader and last_loader == env_loader:
+                last_loader.load(self, env, silent, key)
 
     def load_file(self, path=None, env=None, silent=True, key=None):
         """Programmatically load files from ``path``.
diff --git a/dynaconf/default_settings.py b/dynaconf/default_settings.py
index 79ef548..98fd868 100644
--- a/dynaconf/default_settings.py
+++ b/dynaconf/default_settings.py
@@ -119,6 +119,13 @@ ENCODING_FOR_DYNACONF = get("ENCODING_FOR_DYNACONF", "utf-8")
 # Merge objects on load
 MERGE_ENABLED_FOR_DYNACONF = get("MERGE_ENABLED_FOR_DYNACONF", False)
 
+# BY default `__` is the separator for nested env vars
+# export `DYNACONF__DATABASE__server=server.com`
+# export `DYNACONF__DATABASE__PORT=6666`
+# Should result in settings.DATABASE == {'server': 'server.com', 'PORT': 6666}
+# To disable it one can set `NESTED_SEPARATOR_FOR_DYNACONF=false`
+NESTED_SEPARATOR_FOR_DYNACONF = get("NESTED_SEPARATOR_FOR_DYNACONF", "__")
+
 # The env var specifying settings module
 ENVVAR_FOR_DYNACONF = get("ENVVAR_FOR_DYNACONF", "SETTINGS_FILE_FOR_DYNACONF")
 
diff --git a/dynaconf/loaders/env_loader.py b/dynaconf/loaders/env_loader.py
index aee0b27..772a541 100644
--- a/dynaconf/loaders/env_loader.py
+++ b/dynaconf/loaders/env_loader.py
@@ -1,4 +1,4 @@
-import os
+from os import environ
 
 from dotenv import cli as dotenv_cli
 
@@ -27,7 +27,7 @@ def load_from_env(identifier, key, env, obj, silent):
         env_ = "{0}_".format(env)
     try:
         if key:
-            value = os.environ.get("{0}{1}".format(env_, key))
+            value = environ.get("{0}{1}".format(env_, key))
             if value:
                 obj.logger.debug(
                     "env_loader: loading by key: %s:%s (%s:%s)",
@@ -41,7 +41,7 @@ def load_from_env(identifier, key, env, obj, silent):
             trim_len = len(env_)
             data = {
                 key[trim_len:]: parse_conf_data(data, tomlfy=True)
-                for key, data in os.environ.items()
+                for key, data in environ.items()
                 if key.startswith(env_)
             }
             if data:
@@ -60,4 +60,4 @@ def load_from_env(identifier, key, env, obj, silent):
 def write(settings_path, settings_data, **kwargs):
     """Write data to .env file"""
     for key, value in settings_data.items():
-        dotenv_cli.set_key(str(settings_path), key.upper(), str(value))
+        dotenv_cli.set_key(str(settings_path), key, str(value))
diff --git a/dynaconf/loaders/py_loader.py b/dynaconf/loaders/py_loader.py
index 1405095..ea7eeaf 100644
--- a/dynaconf/loaders/py_loader.py
+++ b/dynaconf/loaders/py_loader.py
@@ -32,7 +32,8 @@ def load_from_python_object(
     obj, mod, settings_module, key=None, identifier=None
 ):
     for setting in dir(mod):
-        if setting.isupper():
+        # at least 3 first chars should be upper to be considered a setting var
+        if setting[:3].isupper():
             if key is None or key == setting:
                 setting_value = getattr(mod, setting)
                 obj.logger.debug(
diff --git a/dynaconf/utils/__init__.py b/dynaconf/utils/__init__.py
index 2d3c4b4..f5cb81e 100644
--- a/dynaconf/utils/__init__.py
+++ b/dynaconf/utils/__init__.py
@@ -23,9 +23,11 @@ def object_merge(old, new, unique=False):
 
     :param unique: When set to True existing list items are not set.
     """
+    if old == new:
+        # Nothing to merge
+        return
+
     if isinstance(old, list) and isinstance(new, list):
-        if old == new:
-            return
         for item in old[::-1]:
             if unique and item in new:
                 continue
@@ -37,6 +39,15 @@ def object_merge(old, new, unique=False):
             else:
                 object_merge(value, new[key])
 
+        # Cleanup of MetaValues on New dict
+        for key, value in new.items():
+            if getattr(new[key], "dynaconf_reset", False):
+                # new Reset triggers cleanup of existing data
+                new[key] = new[key].value
+            elif getattr(new[key], "dynaconf_del", False):
+                # new Del triggers deletion of existing data
+                new.pop(key, None)
+
 
 class DynaconfDict(dict):
     """A dict representing en empty Dynaconf object
diff --git a/dynaconf/utils/parse_conf.py b/dynaconf/utils/parse_conf.py
index bce36e1..b118c61 100644
--- a/dynaconf/utils/parse_conf.py
+++ b/dynaconf/utils/parse_conf.py
@@ -9,6 +9,33 @@ from dynaconf.utils.boxing import DynaBox
 true_values = ("t", "true", "enabled", "1", "on", "yes", "True")
 false_values = ("f", "false", "disabled", "0", "off", "no", "False", "")
 
+
+class MetaValue:
+    """A Marker to trigger specific actions on `set` and `object_merge`"""
+
+    meta_value = True
+
+    def __init__(self, value):
+        self.value = parse_conf_data(value, tomlfy=True)
+
+    def __repr__(self):
+        return "{_class}({value}) on {_id}".format(
+            _class=self.__class__.__name__, value=self.value, _id=id(self)
+        )
+
+
+class Reset(MetaValue):
+    """Triggers an existing key to be reset to its value"""
+
+    dynaconf_reset = True
+
+
+class Del(MetaValue):
+    """Triggers an existing key to be deleted"""
+
+    dynaconf_del = True
+
+
 converters = {
     "@int": int,
     "@float": float,
@@ -16,6 +43,9 @@ converters = {
         lambda value: True if str(value).lower() in true_values else False
     ),
     "@json": json.loads,
+    # Meta Values to trigger pre assignment actions
+    "@reset": lambda value: Reset(value),
+    "@del": lambda value: Del(value),
     # Special markers to be used as placeholders e.g: in prefilled forms
     # will always return None when evaluated
     "@note": lambda value: None,
diff --git a/example/dunder/.env b/example/dunder/.env
new file mode 100644
index 0000000..fb53415
--- /dev/null
+++ b/example/dunder/.env
@@ -0,0 +1,5 @@
+INCLUDES_FOR_DYNACONF="['other_settings.py']"
+DYNACONF_FILES__all="['env']"
+DYNACONF_FILES__last="@reset ['env']"
+DYNACONF_FILES__loaded__TYPES="{env=true}"
+DYNACONF_FILES__nothere="@del "
diff --git a/example/dunder/app.py b/example/dunder/app.py
new file mode 100644
index 0000000..737b276
--- /dev/null
+++ b/example/dunder/app.py
@@ -0,0 +1,17 @@
+from dynaconf import settings
+
+settings.FILES
+
+keys = ["default", "env", "py"]
+
+print(settings.FILES)
+for key in keys:
+    assert settings.FILES.loaded.TYPES[key] is True
+    assert settings.FILES.loaded["TYPES"][key] is True
+
+    assert key in settings.FILES.all
+    assert key in settings.FILES["all"]
+
+assert settings.FILES.last == ["env"]
+assert "nothere" not in settings.FILES
+print(settings.FILES)
diff --git a/example/dunder/other_settings.py b/example/dunder/other_settings.py
new file mode 100644
index 0000000..d6a4c62
--- /dev/null
+++ b/example/dunder/other_settings.py
@@ -0,0 +1,3 @@
+FILES__all = ["py"]
+FILES__last = "@reset ['py']"
+FILES__loaded__TYPES = {"py": True}
diff --git a/example/dunder/settings.py b/example/dunder/settings.py
new file mode 100644
index 0000000..6b3e7fe
--- /dev/null
+++ b/example/dunder/settings.py
@@ -0,0 +1,6 @@
+FILES = {
+    "all": ["default"],
+    "last": ["default"],
+    "loaded": {"TYPES": {"default": True}},
+    "nothere": "hello",
+}
diff --git a/tox.ini b/tox.ini
index d48f2b4..64364ef 100644
--- a/tox.ini
+++ b/tox.ini
@@ -9,5 +9,5 @@ whitelist_externals=
     cd
     python
 commands =
-    py.test -v --cov=dynaconf -l --tb=short --maxfail=1 {posargs}
+    py.test -sv --cov=dynaconf -l --tb=short --maxfail=1 {posargs}
     ; make test_examples


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 613e5f9..e09164c 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,3 +1,4 @@
+import copy
 import os
 import sys
 
@@ -55,3 +56,13 @@ def go_to_tmpdir(request):
     # Chdir only for the duration of the test.
     with tmpdir.as_cwd():
         yield
+
+
+@pytest.fixture(scope="module")
+def clean_env(request):
+    backup = copy.deepcopy(os.environ)
+    for key in os.environ.keys():
+        if key.startswith("DYNACONF_"):
+            del os.environ[key]
+    yield
+    os.environ.update(backup)
diff --git a/tests/test_base.py b/tests/test_base.py
index 44ed13e..7c1f7a0 100644
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -324,6 +324,7 @@ def test_dotted_traversal_access(settings):
 
 
 def test_dotted_set(settings):
+    settings.set("MERGE_ENABLED_FOR_DYNACONF", False)
 
     settings.set("nested_1.nested_2.nested_3.nested_4", "secret")
 
@@ -340,24 +341,86 @@ def test_dotted_set(settings):
     }
 
     with pytest.raises(KeyError):
-        settings.NESTED_1.NESTED_5
+        settings.NESTED_1.NESTED_2_0
 
+    settings.set("nested_1.nested_2_0", "Hello")
+    assert settings.NESTED_1.NESTED_2_0 == "Hello"
 
-def test_dotted_set_with_merge(settings):
-    settings.set("MERGE_ENABLED_FOR_DYNACONF", True)
+    settings.set("nested_1.nested_2.nested_3.nested_4", "Updated Secret")
+    assert settings.NESTED_1.NESTED_2.NESTED_3.NESTED_4 == "Updated Secret"
+    assert settings.NESTED_1.NESTED_2.NESTED_3.to_dict() == {
+        "nested_4": "Updated Secret"
+    }
+    assert settings.NESTED_1.NESTED_2.to_dict() == {
+        "nested_3": {"nested_4": "Updated Secret"}
+    }
 
+    assert settings.get("nested_1").to_dict() == {
+        "nested_2": {"nested_3": {"nested_4": "Updated Secret"}},
+        "nested_2_0": "Hello",
+    }
     settings.set(
-        "MERGE.KEY", {"items": [{"name": "item 1"}, {"name": "item 2"}]}
-    )
-    settings.set(
-        "MERGE.KEY", {"items": [{"name": "item 3"}, {"name": "item 4"}]}
+        "nested_1.nested_2.nested_3.nested_4", "New Stuff", merge=False
     )
+    assert settings.NESTED_1.NESTED_2.NESTED_3.NESTED_4 == "New Stuff"
+    assert settings.NESTED_1.NESTED_2.NESTED_3 == {"nested_4": "New Stuff"}
+    assert settings.NESTED_1.NESTED_2 == {
+        "nested_3": {"nested_4": "New Stuff"}
+    }
+    assert settings.NESTED_1 == {
+        "nested_2": {"nested_3": {"nested_4": "New Stuff"}}
+    }
+    assert settings.get("nested_1").to_dict() == {
+        "nested_2": {"nested_3": {"nested_4": "New Stuff"}}
+    }
 
-    assert settings.MERGE.KEY == {
-        "items": [
-            {"name": "item 1"},
-            {"name": "item 2"},
-            {"name": "item 3"},
-            {"name": "item 4"},
-        ]
+
+def test_dotted_set_with_merge(settings):
+    settings.set("MERGE_ENABLED_FOR_DYNACONF", False)
+
+    start_data = {
+        "default": {
+            "NAME": "testdb",
+            "ENGINE": "db.foo.bar",
+            "PORT": 6666,
+            "PARAMS": ["a", "b", "c"],
+            "ATTRS": {"a": 1, "b": 2},
+        }
     }
+    settings.set("DATABASES", start_data)
+
+    assert settings.DATABASES == start_data
+
+    # Change DB name
+    settings.set("DATABASES.default.NAME", "bladb")
+    assert settings.DATABASES != start_data
+    assert settings.DATABASES["default"].keys() == start_data["default"].keys()
+    settings.DATABASES.default.NAME == "bladb"
+
+    # Add new item to the list
+    assert settings.DATABASES.default.PARAMS == ["a", "b", "c"]
+    settings.set("DATABASES.default.PARAMS", ["d", "e"])
+    assert settings.DATABASES != start_data
+    assert settings.DATABASES["default"].keys() == start_data["default"].keys()
+    assert settings.DATABASES.default.PARAMS == ["a", "b", "c", "d", "e"]
+
+    # Add new item to the dict
+    assert settings.DATABASES.default.ATTRS == {"a": 1, "b": 2}
+    settings.set("DATABASES.default.ATTRS", {"c": 3})
+    assert settings.DATABASES != start_data
+    assert settings.DATABASES["default"].keys() == start_data["default"].keys()
+    assert settings.DATABASES.default.ATTRS == {"a": 1, "b": 2, "c": 3}
+
+    # Replace the entire list
+    settings.set(
+        "DATABASES.default.PARAMS", '@reset ["x", "y", "z"]', tomlfy=True
+    )
+    assert settings.DATABASES != start_data
+    assert settings.DATABASES["default"].keys() == start_data["default"].keys()
+    assert settings.DATABASES.default.PARAMS == ["x", "y", "z"]
+
+    # Replace the entire dict
+    settings.set("DATABASES.default.ATTRS", "@reset {x=26}", tomlfy=True)
+    assert settings.DATABASES != start_data
+    assert settings.DATABASES["default"].keys() == start_data["default"].keys()
+    assert settings.DATABASES.default.ATTRS == {"x": 26}
diff --git a/tests/test_env_loader.py b/tests/test_env_loader.py
index e71e65c..2775c98 100644
--- a/tests/test_env_loader.py
+++ b/tests/test_env_loader.py
@@ -1,4 +1,6 @@
 import os
+import sys
+from os import environ
 
 import pytest
 
@@ -7,17 +9,17 @@ from dynaconf.loaders.env_loader import load
 from dynaconf.loaders.env_loader import load_from_env
 
 # GLOBAL ENV VARS
-os.environ["DYNACONF_HOSTNAME"] = "host.com"
-os.environ["DYNACONF_PORT"] = "@int 5000"
-os.environ["DYNACONF_ALIST"] = '@json ["item1", "item2", "item3", 123]'
-os.environ["DYNACONF_ADICT"] = '@json {"key": "value", "int": 42}'
-os.environ["DYNACONF_DEBUG"] = "@bool true"
-os.environ["DYNACONF_MUSTBEFRESH"] = "first"
-os.environ["DYNACONF_MUSTBEALWAYSFRESH"] = "first"
-os.environ["DYNACONF_SHOULDBEFRESHINCONTEXT"] = "first"
-os.environ["DYNACONF_VALUE"] = "@float 42.1"
-
-# os.environ['FRESH_VARS_FOR_DYNACONF'] = '@json ["MUSTBEALWAYSFRESH"]'
+environ["DYNACONF_HOSTNAME"] = "host.com"
+environ["DYNACONF_PORT"] = "@int 5000"
+environ["DYNACONF_ALIST"] = '@json ["item1", "item2", "item3", 123]'
+environ["DYNACONF_ADICT"] = '@json {"key": "value", "int": 42}'
+environ["DYNACONF_DEBUG"] = "@bool true"
+environ["DYNACONF_MUSTBEFRESH"] = "first"
+environ["DYNACONF_MUSTBEALWAYSFRESH"] = "first"
+environ["DYNACONF_SHOULDBEFRESHINCONTEXT"] = "first"
+environ["DYNACONF_VALUE"] = "@float 42.1"
+
+# environ['FRESH_VARS_FOR_DYNACONF'] = '@json ["MUSTBEALWAYSFRESH"]'
 # settings.configure()
 settings.configure(
     FRESH_VARS_FOR_DYNACONF=["MUSTBEALWAYSFRESH"],
@@ -33,7 +35,7 @@ def test_env_loader():
 
 
 def test_single_key():
-    os.environ["DYNACONF_HOSTNAME"] = "changedhost.com"
+    environ["DYNACONF_HOSTNAME"] = "changedhost.com"
     load(settings, key="HOSTNAME")
     # hostname is reloaded
     assert settings.HOSTNAME == "changedhost.com"
@@ -50,14 +52,14 @@ def test_dotenv_loader():
 
 def test_get_fresh():
     assert settings.MUSTBEFRESH == "first"
-    os.environ["DYNACONF_MUSTBEFRESH"] = "second"
+    environ["DYNACONF_MUSTBEFRESH"] = "second"
     with pytest.raises(AssertionError):
         # fresh should now be second
         assert settings.exists("MUSTBEFRESH")
         assert settings.get_fresh("MUSTBEFRESH") == "first"
     assert settings.get_fresh("MUSTBEFRESH") == "second"
 
-    os.environ["DYNACONF_THISMUSTEXIST"] = "@int 1"
+    environ["DYNACONF_THISMUSTEXIST"] = "@int 1"
     # must tnot exist yet (not loaded)
     assert settings.exists("THISMUSTEXIST") is False
     # must exist because fresh will call loaders
@@ -65,8 +67,8 @@ def test_get_fresh():
     # loaders run only once
     assert settings.get("THISMUSTEXIST") == 1
 
-    os.environ["DYNACONF_THISMUSTEXIST"] = "@int 23"
-    del os.environ["DYNACONF_THISMUSTEXIST"]
+    environ["DYNACONF_THISMUSTEXIST"] = "@int 23"
+    del environ["DYNACONF_THISMUSTEXIST"]
     # this should error because envvar got cleaned
     # but it is not, so cleaners should be fixed
     assert settings.get_fresh("THISMUSTEXIST") is None
@@ -75,24 +77,24 @@ def test_get_fresh():
     with pytest.raises(KeyError):
         settings["THISMUSTEXIST"]
 
-    os.environ["DYNACONF_THISMUSTEXIST"] = "@int 23"
+    environ["DYNACONF_THISMUSTEXIST"] = "@int 23"
     load(settings)
     assert settings.get("THISMUSTEXIST") == 23
 
 
 def test_always_fresh():
-    # assert os.environ['FRESH_VARS_FOR_DYNACONF'] == '@json ["MUSTBEALWAYSFRESH"]'  # noqa
+    # assert environ['FRESH_VARS_FOR_DYNACONF'] == '@json ["MUSTBEALWAYSFRESH"]'  # noqa
     assert settings.FRESH_VARS_FOR_DYNACONF == ["MUSTBEALWAYSFRESH"]
     assert settings.MUSTBEALWAYSFRESH == "first"
-    os.environ["DYNACONF_MUSTBEALWAYSFRESH"] = "second"
+    environ["DYNACONF_MUSTBEALWAYSFRESH"] = "second"
     assert settings.MUSTBEALWAYSFRESH == "second"
-    os.environ["DYNACONF_MUSTBEALWAYSFRESH"] = "third"
+    environ["DYNACONF_MUSTBEALWAYSFRESH"] = "third"
     assert settings.MUSTBEALWAYSFRESH == "third"
 
 
 def test_fresh_context():
     assert settings.SHOULDBEFRESHINCONTEXT == "first"
-    os.environ["DYNACONF_SHOULDBEFRESHINCONTEXT"] = "second"
+    environ["DYNACONF_SHOULDBEFRESHINCONTEXT"] = "second"
     assert settings.SHOULDBEFRESHINCONTEXT == "first"
     with settings.fresh():
         assert settings.get("DOTENV_INT") == 1
@@ -106,7 +108,7 @@ def test_cleaner():
 
 
 def test_empty_string_prefix():
-    os.environ["_VALUE"] = "underscored"
+    environ["_VALUE"] = "underscored"
     load_from_env(
         identifier="env_global", key=None, env="", obj=settings, silent=True
     )
@@ -114,7 +116,7 @@ def test_empty_string_prefix():
 
 
 def test_no_prefix():
-    os.environ["VALUE"] = "no_prefix"
+    environ["VALUE"] = "no_prefix"
     load_from_env(
         identifier="env_global", key=None, env=False, obj=settings, silent=True
     )
@@ -122,7 +124,7 @@ def test_no_prefix():
 
 
 def test_none_as_string_prefix():
-    os.environ["NONE_VALUE"] = "none as prefix"
+    environ["NONE_VALUE"] = "none as prefix"
     load_from_env(
         identifier="env_global",
         key=None,
@@ -131,3 +133,226 @@ def test_none_as_string_prefix():
         silent=True,
     )
     assert settings.VALUE == "none as prefix"
+
+
+def clean_environ(prefix):
+    keys = [k for k in environ if k.startswith(prefix)]
+    for key in keys:
+        environ.pop(key)
+
+
+@pytest.mark.skipif(
+    sys.platform.startswith("win"),
+    reason="Windows env vars are case insensitive",
+)
+def test_load_dunder(clean_env):
+    """Test load and merge with dunder settings"""
+    clean_environ("DYNACONF_DATABASES")
+    settings.set(
+        "DATABASES",
+        {
+            "default": {
+                "NAME": "db",
+                "ENGINE": "module.foo.engine",
+                "ARGS": {"timeout": 30},
+                "PORTS": [123, 456],
+            }
+        },
+    )
+    # change engine
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES__default__ENGINE"] = "other.module"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert settings.DATABASES.default.ENGINE == "other.module"
+
+    # change timeout directly
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES__default__ARGS__timeout"] = "99"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert settings.DATABASES.default.ARGS.timeout == 99
+
+    # add to ARGS
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES__default__ARGS"] = "{retries=10}"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert settings.DATABASES.default.ARGS.retries == 10
+    assert settings.DATABASES.default.ARGS.timeout == 99
+
+    # Ensure dictionary keeps its format
+    assert settings.DATABASES == {
+        "default": {
+            "NAME": "db",
+            "ENGINE": "other.module",
+            "ARGS": {"timeout": 99, "retries": 10},
+            "PORTS": [123, 456],
+        }
+    }
+    assert "default" in settings["DATABASES"].keys()
+    assert "DEFAULT" not in settings["DATABASES"].keys()
+    assert "NAME" in settings["DATABASES"]["default"].keys()
+    assert "name" not in settings["DATABASES"]["default"].keys()
+
+    # Clean args
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES__default__ARGS"] = "@reset {timeout=8}"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert settings.DATABASES.default.ARGS == {"timeout": 8}
+
+    # Make args empty
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES__default__ARGS"] = "@reset {}"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert settings.DATABASES.default.ARGS == {}
+
+    # Remove ARGS key
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES__default__ARGS"] = "@del"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert "ARGS" not in settings.DATABASES.default.keys()
+
+    # add to existing PORTS
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES__default__PORTS"] = "[789, 101112]"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert "ARGS" not in settings.DATABASES.default.keys()
+    assert settings.DATABASES.default.PORTS == [123, 456, 789, 101112]
+
+    # reset PORTS
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES__default__PORTS"] = "@reset [789, 101112]"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert "ARGS" not in settings.DATABASES.default.keys()
+    assert settings.DATABASES.default.PORTS == [789, 101112]
+
+    # delete PORTS
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES__default__PORTS"] = "@del"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert "ARGS" not in settings.DATABASES.default.keys()
+    assert "PORTS" not in settings.DATABASES.default.keys()
+
+    # reset default key
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES__default"] = "@reset {}"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert settings.DATABASES.default == {}
+
+    # remove default
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES__default"] = "@del"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert settings.DATABASES == {}
+
+    # set value to databases
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES__foo"] = "bar"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert settings.DATABASES == {"foo": "bar"}
+
+    # reset databases
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES"] = "{hello='world'}"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert settings.DATABASES == {"hello": "world"}
+
+    # also reset databases
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES"] = "@reset {yes='no'}"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert settings.DATABASES == {"yes": "no"}
+
+    # remove databases
+    clean_environ("DYNACONF_DATABASES")
+    environ["DYNACONF_DATABASES"] = "@del"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="dynaconf",
+        obj=settings,
+        silent=True,
+    )
+    assert "DATABASES" not in settings
diff --git a/tests/test_ini_loader.py b/tests/test_ini_loader.py
index df42164..290cdff 100644
--- a/tests/test_ini_loader.py
+++ b/tests/test_ini_loader.py
@@ -163,3 +163,16 @@ def test_using_env(tmpdir):
     with settings.using_env("DEVELOPMENT", filename=str(tmpfile)):
         assert settings.HOST == "devserver.com"
     assert settings.HOST == "prodserver.com"
+
+
+def test_load_dunder():
+    """Test load with dunder settings"""
+    ini = """
+    a = "a,b"
+    [foo]
+    colors__white__code = '#FFFFFF'
+    COLORS__white__name = 'white'
+    """
+    load(settings, filename=ini, env="FOO")
+    assert settings.COLORS.white.code == "#FFFFFF"
+    assert settings.COLORS.white.name == "white"
diff --git a/tests/test_json_loader.py b/tests/test_json_loader.py
index 32c91eb..7188bda 100644
--- a/tests/test_json_loader.py
+++ b/tests/test_json_loader.py
@@ -163,3 +163,18 @@ def test_using_env(tmpdir):
     with settings.using_env("DEVELOPMENT", filename=str(tmpfile)):
         assert settings.HOST == "devserver.com"
     assert settings.HOST == "prodserver.com"
+
+
+def test_load_dunder():
+    """Test loading with dunder settings"""
+    _JSON = """
+    {
+      "foo": {
+        "colors__yellow__code": "#FFCC00",
+        "COLORS__yellow__name": "Yellow"
+      }
+    }
+    """
+    load(settings, filename=_JSON, env="FOO")
+    assert settings.COLORS.yellow.code == "#FFCC00"
+    assert settings.COLORS.yellow.name == "Yellow"
diff --git a/tests/test_nested_loading.py b/tests/test_nested_loading.py
index acb93c8..2ffcb33 100644
--- a/tests/test_nested_loading.py
+++ b/tests/test_nested_loading.py
@@ -344,7 +344,7 @@ def test_include_via_python_module_name(tmpdir):
     assert settings.FOO == "164110"
 
 
-def test_include_via_python_module_name_and_otjers(tmpdir):
+def test_include_via_python_module_name_and_others(tmpdir):
     """Check if an include can be a Python module name plus others"""
     settings_file = tmpdir.join("settings.toml")
     settings_file.write(
diff --git a/tests/test_py_loader.py b/tests/test_py_loader.py
index 2411354..17f5450 100644
--- a/tests/test_py_loader.py
+++ b/tests/test_py_loader.py
@@ -4,12 +4,13 @@ import os
 import pytest
 
 from dynaconf import default_settings
+from dynaconf import LazySettings
 from dynaconf.loaders.py_loader import load
 from dynaconf.loaders.py_loader import try_to_load_from_py_module_name
 from dynaconf.utils import DynaconfDict
 
 
-def test_py_loader_form_file(tmpdir):
+def test_py_loader_from_file(tmpdir):
 
     settings = DynaconfDict()
     dummy_path = tmpdir.join("dummy_module.py")
@@ -61,3 +62,36 @@ def test_silently_try_to_load_from_py_module_name(tmpdir):
     try_to_load_from_py_module_name(settings, "foo.bar.dummy", silent=True)
 
     assert settings.exists("FOO") is False
+
+
+def test_py_loader_from_file_dunder(clean_env, tmpdir):
+    """Test load with dunder settings"""
+
+    settings = LazySettings(
+        DATABASES={
+            "default": {
+                "NAME": "db",
+                "ENGINE": "module.foo.engine",
+                "ARGS": {"timeout": 30},
+                "PORTS": [123, 456],
+            }
+        }
+    )
+    dummy_path = tmpdir.join("dummy_module.py")
+    with io.open(
+        str(dummy_path), "w", encoding=default_settings.ENCODING_FOR_DYNACONF
+    ) as f:
+        f.write('F = "bar"')
+        f.write("\n")
+        f.write('COLORS__white__code = "#FFFFFF"')
+        f.write("\n")
+        f.write('DATABASES__default__ENGINE = "other.module"')
+
+    load(settings, "dummy_module.py")
+    os.remove("dummy_module.py")
+    load(settings, "dummy_module.py")  # will be ignored not found
+
+    assert settings.get("F") == "bar"
+    assert settings.COLORS == {"white": {"code": "#FFFFFF"}}
+    assert settings.DATABASES.default.NAME == "db"
+    assert settings.DATABASES.default.ENGINE == "other.module"
diff --git a/tests/test_toml_loader.py b/tests/test_toml_loader.py
index b25ce12..6694798 100644
--- a/tests/test_toml_loader.py
+++ b/tests/test_toml_loader.py
@@ -166,3 +166,16 @@ def test_using_env(tmpdir):
     with settings.using_env("DEVELOPMENT", filename=str(tmpfile)):
         assert settings.HOST == "devserver.com"
     assert settings.HOST == "prodserver.com"
+
+
+def test_load_dunder():
+    """Test load with dunder settings"""
+    toml = """
+    a = "a,b"
+    [foo]
+    colors__gray__code = '#CCCCCC'
+    COLORS__gray__name = 'Gray'
+    """
+    load(settings, filename=toml, env="FOO")
+    assert settings.COLORS.gray.code == "#CCCCCC"
+    assert settings.COLORS.gray.name == "Gray"
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 0a11069..b53d1a7 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -121,6 +121,18 @@ def test_missing_sentinel():
     assert str(missing) == "<dynaconf.missing>"
 
 
+def test_meta_values():
+    reset = parse_conf_data("@reset [1, 2]", tomlfy=True)
+    assert reset.value == [1, 2]
+    assert reset.dynaconf_reset is True
+    assert "Reset([1, 2])" in repr(reset)
+
+    _del = parse_conf_data("@del", tomlfy=True)
+    assert _del.value == ""
+    assert _del.dynaconf_del is True
+    assert "Del()" in repr(_del)
+
+
 def test_merge_existing_list():
     existing = ["bruno", "karla"]
     object_merge(existing, existing)
diff --git a/tests/test_yaml_loader.py b/tests/test_yaml_loader.py
index 4900872..2ea5a92 100644
--- a/tests/test_yaml_loader.py
+++ b/tests/test_yaml_loader.py
@@ -188,3 +188,17 @@ def test_using_env(tmpdir):
     with settings.using_env("DEVELOPMENT", filename=str(tmpfile)):
         assert settings.HOST == "devserver.com"
     assert settings.HOST == "prodserver.com"
+
+
+def test_load_dunder():
+    """Test load with dunder settings"""
+    yaml = """
+    foo:
+       bar: blaz
+       zaz: naz
+       colors__black__code: '#000000'
+       COLORS__black__name: Black
+    """
+    load(settings, filename=yaml, env="FOO")
+    assert settings.COLORS.black.code == "#000000"
+    assert settings.COLORS.black.name == "Black"