Repository: dynaconf/dynaconf

Issue:
Question: How do you use autocomplete with dynaconf?
Hello!
First of all, great project, love the feel, idea and execution!

To the question-
How do you use autocomplete with IDEs (PyCharm in my case) when using dynaconf?
`from dynaconf import settings` gets its attributes on execution and I get that, but it takes away so much with it.. The autocomplete plus the IDE not being able to tell if there's a variable like that at all.

Or am I just missing something?

Because I already had a (quite long) settings.py file before introducing dynaconf and I wanted just to take out some vars and tuck them into separate files, I just set their values as:
`VAR_1 = dynaconf_settings.VAR_1` 


Discussion:

Resolving patch:
diff --git a/dynaconf/base.py b/dynaconf/base.py
--- a/dynaconf/base.py
+++ b/dynaconf/base.py
@@ -124,7 +124,7 @@ def __getattr__(self, name):
                 f"Attribute {name} was deleted, " "or belongs to different env"
             )
 
-        if name not in self.__reserved_attrs:
+        if name not in RESERVED_ATTRS:
             lowercase_mode = self._kwargs.get(
                 "LOWERCASE_READ_FOR_DYNACONF",
                 default_settings.LOWERCASE_READ_FOR_DYNACONF,
@@ -184,38 +184,6 @@ def configured(self):
         """If wrapped is configured"""
         return self._wrapped is not empty
 
-    @property
-    def __reserved_attrs(self):
-        return (
-            [
-                item[0]
-                for item in inspect.getmembers(LazySettings)
-                if not item[0].startswith("__")
-            ]
-            + [
-                item[0]
-                for item in inspect.getmembers(Settings)
-                if not item[0].startswith("__")
-            ]
-            + [
-                "_kwargs",
-                "_logger",
-                "_fresh",
-                "_loaded_envs",
-                "_loaded_files",
-                "_deleted",
-                "_store",
-                "_env_cache",
-                "_loaded_by_loaders",
-                "_loaders",
-                "_defaults",
-                "environ",
-                "SETTINGS_MODULE",
-                "_not_installed_warnings",
-                "_memoized",
-            ]
-        )
-
 
 class Settings(object):
     """
@@ -296,6 +264,14 @@ def store(self):
         """Gets internal storage"""
         return self._store
 
+    def __dir__(self):
+        """Enable auto-complete for code editors"""
+        return (
+            RESERVED_ATTRS
+            + [k.lower() for k in self.keys()]
+            + list(self.keys())
+        )
+
     def keys(self):
         """Redirects to store object"""
         return self.store.keys()
@@ -1134,3 +1110,35 @@ def dynaconf(self):  # pragma: no cover
         internal methods and attrs.
         """
         return  # TOBE IMPLEMENTED
+
+
+"""Attributes created on Settings before 3.0.0"""
+RESERVED_ATTRS = (
+    [
+        item[0]
+        for item in inspect.getmembers(LazySettings)
+        if not item[0].startswith("__")
+    ]
+    + [
+        item[0]
+        for item in inspect.getmembers(Settings)
+        if not item[0].startswith("__")
+    ]
+    + [
+        "_kwargs",
+        "_logger",
+        "_fresh",
+        "_loaded_envs",
+        "_loaded_files",
+        "_deleted",
+        "_store",
+        "_env_cache",
+        "_loaded_by_loaders",
+        "_loaders",
+        "_defaults",
+        "environ",
+        "SETTINGS_MODULE",
+        "_not_installed_warnings",
+        "_memoized",
+    ]
+)
diff --git a/dynaconf/utils/boxing.py b/dynaconf/utils/boxing.py
--- a/dynaconf/utils/boxing.py
+++ b/dynaconf/utils/boxing.py
@@ -1,3 +1,5 @@
+import inspect
+
 from box import Box
 
 from dynaconf.utils import upperfy
@@ -31,3 +33,17 @@ def get(self, item, default=None, *args, **kwargs):
         if item not in self:  # toggle case
             item = item.lower() if item.isupper() else upperfy(item)
         return super(DynaBox, self).get(item, default, *args, **kwargs)
+
+    def __dir__(self):
+        keys = list(self.keys())
+        reserved = [
+            item[0]
+            for item in inspect.getmembers(DynaBox)
+            if not item[0].startswith("__")
+        ]
+        return (
+            keys
+            + [k.lower() for k in keys]
+            + [k.upper() for k in keys]
+            + reserved
+        )


Test patch:
diff --git a/tests/test_base.py b/tests/test_base.py
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -811,7 +811,16 @@ def test_lowercase_read_mode(tmpdir):
     assert settings.DEFAULT == 1
     assert settings.default == 1
     assert settings.DATABASES.default.port == 8080
-    assert settings.Databases.default.port == 8080
+    assert settings.databases.default.port == 8080
 
     assert "foo" in settings
     assert "FOO" in settings
+
+    # test __dir__
+    results = dir(settings)
+    assert "foo" in results
+    assert "FOO" in results
+
+    results = dir(settings.databases)
+    assert "default" in results
+    assert "DEFAULT" in results