Repository: dynaconf/dynaconf (python)

Issue:
[bug] Setting auto_cast in instance options is ignored
**Affected version:** 3.1.9

**Describe the bug**
Setting **auto_cast** option inside `config.py` to **False** is ignored, even though the docs say otherwise.

**To Reproduce**
Add **auto_cast** to Dynaconf initialization.
```
from dynaconf import Dynaconf

settings = Dynaconf(
    settings_files=["settings.toml", ".secrets.toml"],
    auto_cast=False,
    **more_options
)
```
Add the following to the `settings.toml`:
```
foo="@int 32"
```
Check value of **foo** in your `program.py`:
```
from config import settings

foo = settings.get('foo', False)
print(f"{foo=}", type(foo))
```
Executing `program.py` will return:
```
foo=32 <class 'int'>
```
when it should return:
```
foo='@int 32' <class 'str'>
```
Running `program.py` with **AUTO_CAST_FOR_DYNACONF=false** works as expected.

Pull request description:
AUTO_CAST can be enabled on instance
Fix #772

Patch:
diff --git a/dynaconf/default_settings.py b/dynaconf/default_settings.py
index 3c96c55..d4ca108 100644
--- a/dynaconf/default_settings.py
+++ b/dynaconf/default_settings.py
@@ -125,6 +125,8 @@ IGNORE_UNKNOWN_ENVVARS_FOR_DYNACONF = get(
     "IGNORE_UNKNOWN_ENVVARS_FOR_DYNACONF", False
 )
 
+AUTO_CAST_FOR_DYNACONF = get("AUTO_CAST_FOR_DYNACONF", True)
+
 # The default encoding to open settings files
 ENCODING_FOR_DYNACONF = get("ENCODING_FOR_DYNACONF", "utf-8")
 
diff --git a/dynaconf/utils/parse_conf.py b/dynaconf/utils/parse_conf.py
index 2fcf723..ac3262d 100644
--- a/dynaconf/utils/parse_conf.py
+++ b/dynaconf/utils/parse_conf.py
@@ -311,8 +311,12 @@ def _parse_conf_data(data, tomlfy=False, box_settings=None):
     # not enforced to not break backwards compatibility with custom loaders
     box_settings = box_settings or {}
 
-    cast_toggler = os.environ.get("AUTO_CAST_FOR_DYNACONF", "true").lower()
-    castenabled = cast_toggler not in false_values
+    castenabled = box_settings.get("AUTO_CAST_FOR_DYNACONF", empty)
+    if castenabled is empty:
+        castenabled = (
+            os.environ.get("AUTO_CAST_FOR_DYNACONF", "true").lower()
+            not in false_values
+        )
 
     if (
         castenabled


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 4e3d3d6..f664a9b 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -8,6 +8,7 @@ from collections import namedtuple
 import pytest
 
 from dynaconf import default_settings
+from dynaconf import Dynaconf
 from dynaconf.loaders.json_loader import DynaconfEncoder
 from dynaconf.utils import build_env_list
 from dynaconf.utils import ensure_a_list
@@ -193,6 +194,13 @@ def test_disable_cast(monkeypatch):
         assert parse_conf_data("@int 42", box_settings={}) == "@int 42"
 
 
+def test_disable_cast_on_instance():
+    settings = Dynaconf(auto_cast=False, environments=True)
+    assert settings.auto_cast_for_dynaconf is False
+    settings.set("SIMPLE_INT", "@int 42")
+    assert settings.get("SIMPLE_INT") == "@int 42"
+
+
 def test_tomlfy(settings):
     assert parse_conf_data("1", tomlfy=True, box_settings=settings) == 1
     assert parse_conf_data("true", tomlfy=True, box_settings=settings) is True
@@ -380,13 +388,16 @@ def test_lazy_format_class():
 def test_evaluate_lazy_format_decorator(settings):
     class Settings:
         FOO = "foo"
+        AUTO_CAST_FOR_DYNACONF = True
 
         @evaluate_lazy_format
-        def get(self):
+        def get(self, key, default=None):
+            if key.endswith("_FOR_DYNACONF"):
+                return getattr(self, key)
             return parse_conf_data("@format {this.FOO}/bar", box_settings=self)
 
     settings = Settings()
-    assert settings.get() == "foo/bar"
+    assert settings.get("foo") == "foo/bar"
 
 
 def test_lazy_format_on_settings(settings):
@@ -408,14 +419,18 @@ def test_evaluate_lazy_format_decorator_jinja(settings):
     class Settings:
         FOO = "foo"
 
+        AUTO_CAST_FOR_DYNACONF = True
+
         @evaluate_lazy_format
-        def get(self):
+        def get(self, key, default=None):
+            if key.endswith("_FOR_DYNACONF"):
+                return getattr(self, key)
             return parse_conf_data(
                 "@jinja {{this.FOO}}/bar", box_settings=settings
             )
 
     settings = Settings()
-    assert settings.get() == "foo/bar"
+    assert settings.get("foo") == "foo/bar"
 
 
 def test_lazy_format_on_settings_jinja(settings):