Repository: rochacbruno/dynaconf
Pull request #316 (2020-03-10 15:51:18)

Better exception handling on env_loader

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

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

```diff
@@            Coverage Diff            @@
##            master      #316   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           18        18           
  Lines          924       925    +1     
=========================================
+ Hits           924       925    +1     
```


| [Impacted Files](https://codecov.io/gh/rochacbruno/dynaconf/pull/316?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [dynaconf/loaders/env\_loader.py](https://codecov.io/gh/rochacbruno/dynaconf/pull/316/diff?src=pr&el=tree#diff-ZHluYWNvbmYvbG9hZGVycy9lbnZfbG9hZGVyLnB5) | `100.00% <100.00%> (ø)` | |

------

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


Patch:
diff --git a/.gitignore b/.gitignore
index 173e8bc..88635fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,8 @@ __pycache__/
 *.sqlite3
 # C extensions
 *.so
+.mypy_cache/
+*.mypy_cache/
 
 # Distribution / packaging
 .Python
diff --git a/dynaconf/loaders/env_loader.py b/dynaconf/loaders/env_loader.py
index 0b8084b..067c9df 100644
--- a/dynaconf/loaders/env_loader.py
+++ b/dynaconf/loaders/env_loader.py
@@ -2,8 +2,11 @@ from os import environ
 
 from dotenv import cli as dotenv_cli
 
+from dynaconf.utils import get_logger
+from dynaconf.utils import upperfy
 from dynaconf.utils.parse_conf import parse_conf_data
 
+
 IDENTIFIER = "env"
 
 
@@ -12,47 +15,59 @@ def load(obj, env=None, silent=True, key=None):
 
     `DYNACONF_` (default global) or `$(ENVVAR_PREFIX_FOR_DYNACONF)_`
     """
-    global_env = obj.get("ENVVAR_PREFIX_FOR_DYNACONF")
-    if global_env is False or global_env.upper() != "DYNACONF":
-        load_from_env(IDENTIFIER + "_global", key, "DYNACONF", obj, silent)
+    global_prefix = obj.get("ENVVAR_PREFIX_FOR_DYNACONF")
+    if global_prefix is False or global_prefix.upper() != "DYNACONF":
+        load_from_env(obj, "DYNACONF", key, silent, IDENTIFIER + "_global")
 
     # Load the global env if exists and overwrite everything
-    load_from_env(IDENTIFIER + "_global", key, global_env, obj, silent)
+    load_from_env(obj, global_prefix, key, silent, IDENTIFIER + "_global")
+
 
+def load_from_env(
+    obj,
+    prefix=False,
+    key=None,
+    silent=False,
+    identifier=IDENTIFIER,
+    env=False,  # backwards compatibility bc renamed param
+):
+    if prefix is False and env is not False:
+        prefix = env
 
-def load_from_env(identifier, key, env, obj, silent):
     env_ = ""
-    if env is not False:
-        env = env.upper()
-        env_ = f"{env}_"
-    try:
-        if key:
-            value = environ.get(f"{env_}{key}")
-            if value:
-                obj.logger.debug(
-                    f"env_loader: loading by key: {key}:{value} "
-                    f"({identifier}:{env})"
-                )
+    if prefix is not False:
+        if not isinstance(prefix, str):
+            raise TypeError("`prefix/env` must be str or False")
+
+        prefix = prefix.upper()
+        env_ = f"{prefix}_"
+
+    logger = get_logger(obj)
+
+    if key:
+        key = upperfy(key)
+        value = environ.get(f"{env_}{key}")
+        if value:
+            logger.debug(
+                f"env_loader: loading by key: "
+                f"{key}:{value} ({identifier}:{prefix})"
+            )
+            try:  # obj is a Settings
                 obj.set(key, value, loader_identifier=identifier, tomlfy=True)
-        else:
-            trim_len = len(env_)
-            data = {
-                key[trim_len:]: parse_conf_data(data, tomlfy=True)
-                for key, data in environ.items()
-                if key.startswith(env_)
-            }
-            if data:
-                obj.logger.debug(
-                    f"env_loader: loading: {data} ({identifier}:{env})"
-                )
-                obj.update(data, loader_identifier=identifier)
-    # box.exceptions.BoxKeyError
-    except Exception as e:  # pragma: no cover
-        e.message = f"env_loader: Error ({str(e)})"
-        if silent:
-            obj.logger.error(str(e))
-        else:
-            raise
+            except AttributeError:  # obj is a dict
+                obj[key] = parse_conf_data(value, tomlfy=True)
+    else:
+        trim_len = len(env_)
+        data = {
+            key[trim_len:]: parse_conf_data(data, tomlfy=True)
+            for key, data in environ.items()
+            if key.startswith(env_)
+        }
+        if data:
+            logger.debug(
+                f"env_loader: loading: {data} ({identifier}:{prefix})"
+            )
+            obj.update(data, loader_identifier=identifier)
 
 
 def write(settings_path, settings_data, **kwargs):
diff --git a/dynaconf/loaders/redis_loader.py b/dynaconf/loaders/redis_loader.py
index 287c0a3..7b1183d 100644
--- a/dynaconf/loaders/redis_loader.py
+++ b/dynaconf/loaders/redis_loader.py
@@ -1,4 +1,5 @@
 from dynaconf.utils import build_env_list
+from dynaconf.utils import get_logger
 from dynaconf.utils import upperfy
 from dynaconf.utils.parse_conf import parse_conf_data
 from dynaconf.utils.parse_conf import unparse_conf_data
diff --git a/dynaconf/utils/__init__.py b/dynaconf/utils/__init__.py
index c11568e..98a979d 100644
--- a/dynaconf/utils/__init__.py
+++ b/dynaconf/utils/__init__.py
@@ -118,6 +118,13 @@ def raw_logger(level=None):
     return _logger(level)
 
 
+def get_logger(obj):
+    try:
+        return obj.logger
+    except AttributeError:
+        return raw_logger()
+
+
 RENAMED_VARS = {
     # old: new
     "DYNACONF_NAMESPACE": "ENV_FOR_DYNACONF",
@@ -271,6 +278,7 @@ def upperfy(key):
     Returns:
         The key as upper case but keeping the nested elements.
     """
+    key = str(key)
     if "__" in key:
         parts = key.split("__")
         return "__".join([parts[0].upper()] + parts[1:])


Test patch:
diff --git a/tests/test_env_loader.py b/tests/test_env_loader.py
index 7924b08..344e07d 100644
--- a/tests/test_env_loader.py
+++ b/tests/test_env_loader.py
@@ -163,7 +163,7 @@ def test_cleaner():
 def test_empty_string_prefix():
     environ["_VALUE"] = "underscored"
     load_from_env(
-        identifier="env_global", key=None, env="", obj=settings, silent=True
+        identifier="env_global", key=None, prefix="", obj=settings, silent=True
     )
     assert settings.VALUE == "underscored"
 
@@ -171,7 +171,11 @@ def test_empty_string_prefix():
 def test_no_prefix():
     environ["VALUE"] = "no_prefix"
     load_from_env(
-        identifier="env_global", key=None, env=False, obj=settings, silent=True
+        identifier="env_global",
+        key=None,
+        prefix=False,
+        obj=settings,
+        silent=True,
     )
     assert settings.VALUE == "no_prefix"
 
@@ -181,13 +185,39 @@ def test_none_as_string_prefix():
     load_from_env(
         identifier="env_global",
         key=None,
-        env="none",
+        prefix="none",
         obj=settings,
         silent=True,
     )
     assert settings.VALUE == "none as prefix"
 
 
+def test_backwards_compat_using_env_argument():
+    environ["BLARG_VALUE"] = "BLARG as prefix"
+    load_from_env(
+        identifier="env_global",
+        key=None,
+        env="BLARG",  # renamed to `prefix` on 3.0.0
+        obj=settings,
+        silent=True,
+    )
+    assert settings.VALUE == "BLARG as prefix"
+
+
+def test_env_is_not_str_raises():
+    with pytest.raises(TypeError):
+        load_from_env(settings, prefix=int)
+    with pytest.raises(TypeError):
+        load_from_env(settings, prefix=True)
+
+
+def test_can_load_in_to_dict():
+    os.environ["LOADTODICT"] = "true"
+    sets = {}
+    load_from_env(sets, prefix=False, key="LOADTODICT")
+    assert sets["LOADTODICT"] is True
+
+
 def clean_environ(prefix):
     keys = [k for k in environ if k.startswith(prefix)]
     for key in keys:
@@ -218,7 +248,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )
@@ -230,7 +260,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )
@@ -242,7 +272,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )
@@ -269,7 +299,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )
@@ -281,7 +311,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )
@@ -293,7 +323,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )
@@ -305,7 +335,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )
@@ -318,7 +348,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )
@@ -331,7 +361,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )
@@ -344,7 +374,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )
@@ -356,7 +386,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )
@@ -368,7 +398,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )
@@ -380,7 +410,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )
@@ -392,7 +422,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )
@@ -404,7 +434,7 @@ def test_load_dunder(clean_env):
     load_from_env(
         identifier="env_global",
         key=None,
-        env="dynaconf",
+        prefix="dynaconf",
         obj=settings,
         silent=True,
     )