Repository: dynaconf/dynaconf

Issue:
Missing an isolated namespace [RFC]
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

The Settings object should have a way of accessing and traversing the keys loaded from the settings files without having all of the Dynaconf options in there. When I call `config.keys()` I'm getting `['SETTINGS_MODULE', 'PROJECT_ROOT_FOR_DYNACONF', 'SETTINGS_MODULE_FOR_DYNACONF', ...`

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

Ideally when you call `keys()` and similar methods, it should only return values in the namespaces loaded by the file. Even if it involved doing this `config.current_env.keys()` it would be fine.

Also, having methods of exporting the current (possibly modified) settings would be nice. Basically I would like to be able to do this:

```python 
with open(output_file, 'w') as f:
    yaml.dump(config.environments['development'].as_dict(), f)
```

And have that write out a flattened version of the development namespace.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.



Discussion:
The same thing has been implemented in #188 for `list` command, the same solution (filtering out any key that are present in `dynaconf.default_settings`)

We should add a new method `as_dict` on settings so we keep `.keys()` working in the way it is now without breaking compatibility.

Thanks for suggestion.

Resolving patch:
diff --git a/dynaconf/base.py b/dynaconf/base.py
--- a/dynaconf/base.py
+++ b/dynaconf/base.py
@@ -2,6 +2,7 @@
 import importlib
 import os
 from contextlib import contextmanager
+from contextlib import suppress
 
 from dynaconf import default_settings
 from dynaconf.loaders import default_loader
@@ -233,6 +234,21 @@ def values(self):
         """Redirects to store object"""
         return self.store.values()
 
+    def as_dict(self, env=None, internal=False):
+        """Returns a dictionary with set key and values.
+
+        :param env: Str env name, default self.current_env `DEVELOPMENT`
+        :param internal: bool - should include dynaconf internal vars?
+        """
+        ctx_mgr = suppress() if env is None else self.using_env(env)
+        with ctx_mgr:
+            data = self.store.copy()
+            # if not internal remove internal settings
+            if not internal:
+                for name in dir(default_settings):
+                    data.pop(name, None)
+            return data
+
     def _dotted_get(self, dotted_key, default=None, **kwargs):
         """
         Perform dotted key lookups and keep track of where we are.
diff --git a/dynaconf/cli.py b/dynaconf/cli.py
--- a/dynaconf/cli.py
+++ b/dynaconf/cli.py
@@ -1,5 +1,6 @@
 import importlib
 import io
+import json
 import os
 import pprint
 import sys
@@ -372,7 +373,14 @@ def init(fileformat, path, env, _vars, _secrets, wg, y, django):
     is_flag=True,
     help="show dynaconf internal settings?",
 )
-def _list(env, key, more, loader, _all=False):
+@click.option(
+    "--output",
+    "-o",
+    type=click.Path(writable=True, dir_okay=False),
+    default=None,
+    help="Filepath to write the listed values as json",
+)
+def _list(env, key, more, loader, _all=False, output=None):
     """Lists all user defined config values
     and if `--all` is passed it also shows dynaconf internal variables.
     """
@@ -398,7 +406,7 @@ def _list(env, key, more, loader, _all=False):
     )
 
     if not loader:
-        data = settings.store
+        data = settings.as_dict(env=env, internal=_all)
     else:
         identifier = "{}_{}".format(loader, cur_env)
         data = settings._loaded_by_loaders.get(identifier, {})
@@ -407,11 +415,6 @@ def _list(env, key, more, loader, _all=False):
     # remove to avoid displaying twice
     data.pop("SETTINGS_MODULE", None)
 
-    # if not --all remove internal settings
-    if not _all:
-        for name in dir(default_settings):
-            data.pop(name, None)
-
     def color(_k):
         if _k in dir(default_settings):
             return "blue"
@@ -424,6 +427,9 @@ def color(_k):
             for k, v in data.items()
         )
         (click.echo_via_pager if more else click.echo)(datalines)
+        if output:
+            with open(output, "w") as output_file:
+                json.dump({cur_env: data}, output_file)
     else:
         key = key.upper()
         value = data.get(key)
@@ -437,6 +443,9 @@ def color(_k):
                 pprint.pformat(value),
             )
         )
+        if output:
+            with open(output, "w") as output_file:
+                json.dump({cur_env: {key.upper(): value}}, output_file)
 
     if env:
         settings.setenv()


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,10 +1,8 @@
-import io
-import os
+import json
 from pathlib import Path
 
 import pytest
 from click.testing import CliRunner
-from dotenv import cli as dotenv_cli
 
 from dynaconf import default_settings
 from dynaconf import LazySettings
@@ -90,6 +88,22 @@ def test_list(testdir):
     assert "GLOBAL_ENV_FOR_DYNACONF: 'DYNACONF'" not in result
 
 
+def test_list_export_json(testdir):
+    result = run(
+        ["list", "-o", "sets.json"], env={"ROOT_PATH_FOR_DYNACONF": testdir}
+    )
+    assert "DOTENV_STR: 'hello'" in result
+    assert "DOTENV_STR" in read_file("sets.json")
+    assert (
+        json.loads(read_file("sets.json"))["development"]["DOTENV_STR"]
+        == "hello"
+    )
+    assert (
+        json.loads(read_file("sets.json"))["development"]["DOTENV_FLOAT"]
+        == 4.2
+    )
+
+
 def test_list_with_all(testdir):
     """Test list command with --all includes interval vars"""
     result = run(["list", "-a"], env={"ROOT_PATH_FOR_DYNACONF": testdir})
@@ -144,6 +158,18 @@ def test_list_with_key():
     assert "DOTENV_STR: 'hello'" in result
 
 
+def test_list_with_key_export_json(tmpdir):
+    result = run(["list", "-k", "DOTENV_STR", "-o", "sets.json"])
+    assert "DOTENV_STR: 'hello'" in result
+    assert "DOTENV_STR" in read_file("sets.json")
+    assert (
+        json.loads(read_file("sets.json"))["development"]["DOTENV_STR"]
+        == "hello"
+    )
+    with pytest.raises(KeyError):
+        json.loads(read_file("sets.json"))["development"]["DOTENV_FLOAT"]
+
+
 def test_list_with_missing_key():
     result = run(["list", "-k", "NOTEXISTS"])
     assert "Key not found" in result