Repository: rochacbruno/dynaconf (python)

Issue:
Variable reference 
Is it currently possible to have variable referencing?
Just like here > https://github.com/vlucas/phpdotenv#nesting-variables

```php
BASE_DIR="/var/webroot/project-root"
CACHE_DIR="${BASE_DIR}/cache"
TMP_DIR="${BASE_DIR}/tmp"
```

I have item A and B.  Item A is very long and shares more than 90% of its configuration with B.
I would like to do something like:
```
default:
  item_a:
    name: 'A'
    ... many other fields
    ...
  item_b:  [extend item_a] 
    name: 'B'
    ... same other fields as A
```


Pull request:
fix #359 lazy templates on nested keys
fix #359 lazy templates on nested keys

Patch:
diff --git a/Makefile b/Makefile
index be80325..355e8e7 100644
--- a/Makefile
+++ b/Makefile
@@ -98,6 +98,7 @@ test_examples:
 	cd example/issues/266_envvar_from_env_override;pwd;python app.py
 	cd example/issues/288_null_values;pwd;python app.py
 	cd example/issues/306_merge_replace;pwd;python app.py
+	cd example/issues/359_variable_reference;pwd;python app.py
 test_vault:
 	# @cd example/vault;pwd;python write.py
 	docker run --rm --name dynaconf_with_vault -d -e 'VAULT_DEV_ROOT_TOKEN_ID=myroot' -p 8200:8200 vault
diff --git a/dynaconf/base.py b/dynaconf/base.py
index ef304f3..b5bde7c 100644
--- a/dynaconf/base.py
+++ b/dynaconf/base.py
@@ -26,6 +26,7 @@ from dynaconf.utils.functional import empty
 from dynaconf.utils.functional import LazyObject
 from dynaconf.utils.parse_conf import converters
 from dynaconf.utils.parse_conf import evaluate_lazy_format
+from dynaconf.utils.parse_conf import get_converter
 from dynaconf.utils.parse_conf import parse_conf_data
 from dynaconf.utils.parse_conf import true_values
 from dynaconf.validator import ValidatorList
@@ -229,7 +230,7 @@ class Settings(object):
         self._loaded_envs = []
         self._loaded_files = []
         self._deleted = set()
-        self._store = DynaBox()
+        self._store = DynaBox(box_settings=self)
         self._env_cache = {}
         self._loaded_by_loaders = {}
         self._loaders = []
@@ -396,7 +397,7 @@ class Settings(object):
 
         data = (parent or self.store).get(key, default)
         if cast:
-            data = converters.get(cast)(data)
+            data = get_converter(cast, data, box_settings=self)
         return data
 
     def exists(self, key, fresh=False):
@@ -435,9 +436,9 @@ class Settings(object):
         data = self.environ.get(key, default)
         if data:
             if cast in converters:
-                data = converters.get(cast)(data)
+                data = get_converter(cast, data, box_settings=self)
             if cast is True:
-                data = parse_conf_data(data, tomlfy=True)
+                data = parse_conf_data(data, tomlfy=True, box_settings=self)
         return data
 
     def exists_in_environ(self, key):
@@ -628,6 +629,7 @@ class Settings(object):
                 self.ENVVAR_FOR_DYNACONF, self.SETTINGS_FILE_FOR_DYNACONF
             ),
             tomlfy=True,
+            box_settings=self,
         )
         if settings_module != getattr(self, "SETTINGS_MODULE", None):
             self.set("SETTINGS_MODULE", settings_module)
@@ -728,13 +730,13 @@ class Settings(object):
 
         split_keys = dotted_key.split(".")
         existing_data = self.get(split_keys[0], {})
-        new_data = DynaBox()
+        new_data = DynaBox(box_settings=self)
 
         tree = new_data
         for k in split_keys[:-1]:
             tree = tree.setdefault(k, {})
 
-        value = parse_conf_data(value, tomlfy=tomlfy)
+        value = parse_conf_data(value, tomlfy=tomlfy, box_settings=self)
         tree[split_keys[-1]] = value
 
         if existing_data:
@@ -775,7 +777,7 @@ class Settings(object):
                 key, value, loader_identifier=loader_identifier, tomlfy=tomlfy
             )
 
-        value = parse_conf_data(value, tomlfy=tomlfy)
+        value = parse_conf_data(value, tomlfy=tomlfy, box_settings=self)
         key = upperfy(key.strip())
         existing = getattr(self, key, None)
 
@@ -804,7 +806,7 @@ class Settings(object):
                 value = self._merge_before_set(key, existing, value, is_secret)
 
         if isinstance(value, dict):
-            value = DynaBox(value)
+            value = DynaBox(value, box_settings=self)
 
         setattr(self, key, value)
         self.store[key] = value
diff --git a/dynaconf/cli.py b/dynaconf/cli.py
index 6b7e3f5..a9671d3 100644
--- a/dynaconf/cli.py
+++ b/dynaconf/cli.py
@@ -119,7 +119,9 @@ def split_vars(_vars):
     """Splits values like foo=bar=zaz in {'foo': 'bar=zaz'}"""
     return (
         {
-            upperfy(k.strip()): parse_conf_data(v.strip(), tomlfy=True)
+            upperfy(k.strip()): parse_conf_data(
+                v.strip(), tomlfy=True, box_settings=settings
+            )
             for k, _, v in [item.partition("=") for item in _vars]
         }
         if _vars
diff --git a/dynaconf/default_settings.py b/dynaconf/default_settings.py
index 157996e..9e092f6 100644
--- a/dynaconf/default_settings.py
+++ b/dynaconf/default_settings.py
@@ -35,7 +35,9 @@ def get(key, default=None):
         value = try_renamed(key, value, old, new)
 
     return (
-        parse_conf_data(value, tomlfy=True) if value is not None else default
+        parse_conf_data(value, tomlfy=True, box_settings={})
+        if value is not None
+        else default
     )
 
 
diff --git a/dynaconf/loaders/__init__.py b/dynaconf/loaders/__init__.py
index 1d30c6b..30f1d42 100644
--- a/dynaconf/loaders/__init__.py
+++ b/dynaconf/loaders/__init__.py
@@ -191,7 +191,7 @@ def write(filename, data, env=None):
     if not loader:
         raise IOError(f"{loader_name} cannot be found.")
 
-    data = DynaBox(data).to_dict()
+    data = DynaBox(data, box_settings={}).to_dict()
     if loader is not py_loader and env and env not in data:
         data = {env: data}
 
diff --git a/dynaconf/loaders/env_loader.py b/dynaconf/loaders/env_loader.py
index 2044793..918d035 100644
--- a/dynaconf/loaders/env_loader.py
+++ b/dynaconf/loaders/env_loader.py
@@ -48,11 +48,15 @@ def load_from_env(
             try:  # obj is a Settings
                 obj.set(key, value, loader_identifier=identifier, tomlfy=True)
             except AttributeError:  # obj is a dict
-                obj[key] = parse_conf_data(value, tomlfy=True)
+                obj[key] = parse_conf_data(
+                    value, tomlfy=True, box_settings=obj
+                )
     else:
         trim_len = len(env_)
         data = {
-            key[trim_len:]: parse_conf_data(data, tomlfy=True)
+            key[trim_len:]: parse_conf_data(
+                data, tomlfy=True, box_settings=obj
+            )
             for key, data in environ.items()
             if key.startswith(env_)
         }
diff --git a/dynaconf/loaders/redis_loader.py b/dynaconf/loaders/redis_loader.py
index 3715235..ba5e71b 100644
--- a/dynaconf/loaders/redis_loader.py
+++ b/dynaconf/loaders/redis_loader.py
@@ -34,12 +34,14 @@ def load(obj, env=None, silent=True, key=None):
             if key:
                 value = redis.hget(holder.upper(), key)
                 if value:
-                    parsed_value = parse_conf_data(value, tomlfy=True)
+                    parsed_value = parse_conf_data(
+                        value, tomlfy=True, box_settings=obj
+                    )
                     if parsed_value:
                         obj.set(key, parsed_value)
             else:
                 data = {
-                    key: parse_conf_data(value, tomlfy=True)
+                    key: parse_conf_data(value, tomlfy=True, box_settings=obj)
                     for key, value in redis.hgetall(holder.upper()).items()
                 }
                 if data:
diff --git a/dynaconf/loaders/vault_loader.py b/dynaconf/loaders/vault_loader.py
index 312a2f7..e27150d 100644
--- a/dynaconf/loaders/vault_loader.py
+++ b/dynaconf/loaders/vault_loader.py
@@ -65,7 +65,9 @@ def load(obj, env=None, silent=None, key=None):
             data = data.get("data", {}).get("data", {})
         try:
             if data and key:
-                value = parse_conf_data(data.get(key), tomlfy=True)
+                value = parse_conf_data(
+                    data.get(key), tomlfy=True, box_settings=obj
+                )
                 if value:
                     obj.set(key, value)
             elif data:
diff --git a/dynaconf/utils/boxing.py b/dynaconf/utils/boxing.py
index 3f00e1b..0e103d5 100644
--- a/dynaconf/utils/boxing.py
+++ b/dynaconf/utils/boxing.py
@@ -1,14 +1,33 @@
 import inspect
+from functools import wraps
 
-from box import Box
-
+import dynaconf
 from dynaconf.utils import upperfy
+from dynaconf.vendor.box import Box
+
+
+def evaluate_lazy_format(f):
+    """Marks a method on Dynabox instance to
+    lazily evaluate LazyFormat objects upon access."""
+
+    @wraps(f)
+    def evaluate(dynabox, item, *args, **kwargs):
+        value = f(dynabox, item, *args, **kwargs)
+        if getattr(value, "_dynaconf_lazy_format", None):
+            dynabox._box_config[
+                f"raw_{item.lower()}"
+            ] = f"@{value.formatter.token} {value.value}"
+            return value(dynabox._box_config["box_settings"])
+        return value
+
+    return evaluate
 
 
 class DynaBox(Box):
     """Specialized Box for dynaconf
     it allows items/attrs to be found both in upper or lower case"""
 
+    @evaluate_lazy_format
     def __getattr__(self, item, *args, **kwargs):
         try:
             return super(DynaBox, self).__getattr__(item, *args, **kwargs)
@@ -16,6 +35,7 @@ class DynaBox(Box):
             n_item = item.lower() if item.isupper() else upperfy(item)
             return super(DynaBox, self).__getattr__(n_item, *args, **kwargs)
 
+    @evaluate_lazy_format
     def __getitem__(self, item, *args, **kwargs):
         try:
             return super(DynaBox, self).__getitem__(item, *args, **kwargs)
@@ -24,11 +44,18 @@ class DynaBox(Box):
             return super(DynaBox, self).__getitem__(n_item, *args, **kwargs)
 
     def __copy__(self):
-        return self.__class__(super(Box, self).copy())
+        return self.__class__(
+            super(Box, self).copy(),
+            box_settings=self._box_config.get("box_settings"),
+        )
 
     def copy(self):
-        return self.__class__(super(Box, self).copy())
+        return self.__class__(
+            super(Box, self).copy(),
+            box_settings=self._box_config.get("box_settings"),
+        )
 
+    @evaluate_lazy_format
     def get(self, item, default=None, *args, **kwargs):
         if item not in self:  # toggle case
             item = item.lower() if item.isupper() else upperfy(item)
diff --git a/dynaconf/utils/parse_conf.py b/dynaconf/utils/parse_conf.py
index 872f1cb..3436c5f 100644
--- a/dynaconf/utils/parse_conf.py
+++ b/dynaconf/utils/parse_conf.py
@@ -3,6 +3,7 @@ import os
 import re
 import warnings
 from functools import wraps
+from typing import Type
 
 import toml
 from box import BoxKeyError
@@ -10,6 +11,7 @@ from box import BoxKeyError
 from dynaconf.utils import extract_json_objects
 from dynaconf.utils import multi_replace
 from dynaconf.utils.boxing import DynaBox
+from dynaconf.vendor.box import box
 
 try:
     from jinja2 import Environment
@@ -37,8 +39,11 @@ class MetaValue:
 
     _meta_value = True
 
-    def __init__(self, value):
-        self.value = parse_conf_data(value, tomlfy=True)
+    def __init__(self, value, box_settings):
+        self.box_settings = box_settings
+        self.value = parse_conf_data(
+            value, tomlfy=True, box_settings=box_settings
+        )
 
     def __repr__(self):
         return f"{self.__class__.__name__}({self.value}) on {id(self)}"
@@ -54,8 +59,11 @@ class Reset(MetaValue):
 
     _dynaconf_reset = True
 
-    def __init__(self, value):
-        self.value = parse_conf_data(value, tomlfy=True)
+    def __init__(self, value, box_settings):
+        self.box_settings = box_settings
+        self.value = parse_conf_data(
+            value, tomlfy=True, box_settings=self.box_settings
+        )
         warnings.warn(f"{self.value} does not need `@reset` anymore.")
 
 
@@ -73,9 +81,12 @@ class Merge(MetaValue):
 
     _dynaconf_merge = True
 
-    def __init__(self, value, unique=False):
+    def __init__(self, value, box_settings, unique=False):
+        self.box_settings = box_settings
 
-        self.value = parse_conf_data(value, tomlfy=True)
+        self.value = parse_conf_data(
+            value, tomlfy=True, box_settings=box_settings
+        )
 
         if isinstance(self.value, (int, float, bool)):
             # @merge 1, @merge 1.1, @merge False
@@ -104,7 +115,9 @@ class Merge(MetaValue):
                 # a=b, c=d
                 if matches:
                     self.value = {
-                        k.strip(): parse_conf_data(v, tomlfy=True)
+                        k.strip(): parse_conf_data(
+                            v, tomlfy=True, box_settings=box_settings
+                        )
                         for k, v in (
                             match.strip().split("=") for match in matches
                         )
@@ -208,10 +221,14 @@ converters = {
     "@format": lambda value: Lazy(value),
     "@jinja": lambda value: Lazy(value, formatter=Formatters.jinja_formatter),
     # Meta Values to trigger pre assignment actions
-    "@reset": lambda value: Reset(value),  # @reset is DEPRECATED on v3.0.0
-    "@del": lambda value: Del(value),
-    "@merge": lambda value: Merge(value),
-    "@merge_unique": lambda value: Merge(value, unique=True),
+    "@reset": lambda value, box_settings: Reset(
+        value, box_settings
+    ),  # @reset is DEPRECATED on v3.0.0
+    "@del": lambda value, box_settings: Del(value, box_settings),
+    "@merge": lambda value, box_settings: Merge(value, box_settings),
+    "@merge_unique": lambda value, box_settings: Merge(
+        value, box_settings, unique=True
+    ),
     # Special markers to be used as placeholders e.g: in prefilled forms
     # will always return None when evaluated
     "@note": lambda value: None,
@@ -221,15 +238,24 @@ converters = {
 }
 
 
+def get_converter(converter_key, value, box_settings):
+    converter = converters[converter_key]
+    try:
+        converted_value = converter(value, box_settings=box_settings)
+    except TypeError:
+        converted_value = converter(value)
+    return converted_value
+
+
 def parse_with_toml(data):
     """Uses TOML syntax to parse data"""
     try:
-        return toml.loads(f"key={data}", DynaBox).key
-    except (toml.TomlDecodeError, BoxKeyError):
+        return toml.loads(f"key={data}")["key"]
+    except (toml.TomlDecodeError, KeyError):
         return data
 
 
-def _parse_conf_data(data, tomlfy=False):
+def _parse_conf_data(data, tomlfy=False, box_settings=None):
     """
     @int @bool @float @json (for lists and dicts)
     strings does not need converters
@@ -241,6 +267,9 @@ def _parse_conf_data(data, tomlfy=False):
     export DYNACONF_MONGODB_SETTINGS='@json {"DB": "quokka_db"}'
     export DYNACONF_ALLOWED_EXTENSIONS='@json ["jpg", "png"]'
     """
+    # 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
 
@@ -253,24 +282,36 @@ def _parse_conf_data(data, tomlfy=False):
         parts = data.partition(" ")
         converter_key = parts[0]
         value = parts[-1]
-        return converters.get(converter_key)(value)
+        return get_converter(converter_key, value, box_settings)
+
+    value = parse_with_toml(data) if tomlfy else data
+    if isinstance(value, dict):
+        value = DynaBox(value, box_settings=box_settings)
+    return value
 
-    return parse_with_toml(data) if tomlfy else data
 
+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 {}
 
-def parse_conf_data(data, tomlfy=False):
     if isinstance(data, (tuple, list)):
         # recursively parse each sequence item
-        return [parse_conf_data(item, tomlfy=tomlfy) for item in data]
+        return [
+            parse_conf_data(item, tomlfy=tomlfy, box_settings=box_settings)
+            for item in data
+        ]
     elif isinstance(data, (dict, DynaBox)):
         # recursively parse inner dict items
         _parsed = {}
         for k, v in data.items():
-            _parsed[k] = parse_conf_data(v, tomlfy=tomlfy)
+            _parsed[k] = parse_conf_data(
+                v, tomlfy=tomlfy, box_settings=box_settings
+            )
         return _parsed
     else:
         # return parsed string value
-        return _parse_conf_data(data, tomlfy=tomlfy)
+        return _parse_conf_data(data, tomlfy=tomlfy, box_settings=box_settings)
 
 
 def unparse_conf_data(value):
diff --git a/dynaconf/vendor/box/box.py b/dynaconf/vendor/box/box.py
index 68d1ff3..0605293 100644
--- a/dynaconf/vendor/box/box.py
+++ b/dynaconf/vendor/box/box.py
@@ -101,7 +101,7 @@ class Box(dict):
         "merge_update",
     ] + [attr for attr in dir({}) if not attr.startswith("_")]
 
-    def __new__(cls, *args: Any, default_box: bool = False, default_box_attr: Any = NO_DEFAULT,
+    def __new__(cls, *args: Any,  box_settings: Any, default_box: bool = False, default_box_attr: Any = NO_DEFAULT,
                 default_box_none_transform: bool = True, frozen_box: bool = False, camel_killer_box: bool = False,
                 conversion_box: bool = True, modify_tuples_box: bool = False, box_safe_prefix: str = 'x',
                 box_duplicates: str = 'ignore', box_intact_types: Union[Tuple, List] = (),
@@ -124,11 +124,12 @@ class Box(dict):
             'box_duplicates': box_duplicates,
             'box_intact_types': tuple(box_intact_types),
             'box_recast': box_recast,
-            'box_dots': box_dots
+            'box_dots': box_dots,
+            'box_settings': box_settings
         })
         return obj
 
-    def __init__(self, *args: Any, default_box: bool = False, default_box_attr: Any = NO_DEFAULT,
+    def __init__(self, *args: Any, box_settings: Any, default_box: bool = False, default_box_attr: Any = NO_DEFAULT,
                  default_box_none_transform: bool = True, frozen_box: bool = False, camel_killer_box: bool = False,
                  conversion_box: bool = True, modify_tuples_box: bool = False, box_safe_prefix: str = 'x',
                  box_duplicates: str = 'ignore', box_intact_types: Union[Tuple, List] = (),
@@ -147,7 +148,8 @@ class Box(dict):
             'box_duplicates': box_duplicates,
             'box_intact_types': tuple(box_intact_types),
             'box_recast': box_recast,
-            'box_dots': box_dots
+            'box_dots': box_dots,
+            'box_settings': box_settings
         })
         if not self._box_config['conversion_box'] and self._box_config['box_duplicates'] != 'ignore':
             raise BoxError('box_duplicates are only for conversion_boxes')
@@ -222,7 +224,7 @@ class Box(dict):
                 else:
                     return None
             if isinstance(default, dict) and not isinstance(default, Box):
-                return Box(default)
+                return Box(default, box_settings=self._box_config.get("box_settings"))
             if isinstance(default, list) and not isinstance(default, box.BoxList):
                 return box.BoxList(default)
             return default
diff --git a/example/issues/359_variable_reference/app.py b/example/issues/359_variable_reference/app.py
new file mode 100644
index 0000000..92e2bc5
--- /dev/null
+++ b/example/issues/359_variable_reference/app.py
@@ -0,0 +1,45 @@
+from dynaconf import Dynaconf
+
+settings = Dynaconf(settings_files="settings.yaml", environments=True)
+
+
+print(settings.get("level1").get("level2").get("level3"))
+print(settings.get("level1").get("level2"))
+print(settings.get("level1"))
+
+
+print(settings.get("level1.level2.level3"))
+print(settings.get("level1.level2"))
+print(settings.get("level1"))
+
+
+print(settings.get("template"))
+print(settings.get("value0"))
+print(settings.get("level1.value1"))
+print(settings.get("level1.level2.value2"))
+print(settings.get("level1.level2.level3.value3"))
+
+
+assert settings.get("level1.level2.level3").to_dict() == {
+    "value3": "Whoohoo world/level3/jinja"
+}
+assert settings.get("level1.level2").to_dict() == {
+    "value2": "HELLO world/level2/jinja",
+    "level3": {"value3": "Whoohoo world/level3/jinja"},
+}
+assert settings.get("level1").to_dict() == {
+    "template2": "Whoohoo @tpl@",
+    "value1": "HELLO @tpl@/level1/format",
+    "level2": {
+        "value2": "HELLO world/level2/jinja",
+        "level3": {"value3": "Whoohoo world/level3/jinja"},
+    },
+}
+
+assert settings.get("template") == "HELLO @tpl@"
+assert settings.get("value0") == "HELLO world/level0/jinja"
+assert settings.get("level1.value1") == "HELLO @tpl@/level1/format"
+assert settings.get("level1.level2.value2") == "HELLO world/level2/jinja"
+assert (
+    settings.get("level1.level2.level3.value3") == "Whoohoo world/level3/jinja"
+)
diff --git a/example/issues/359_variable_reference/settings.yaml b/example/issues/359_variable_reference/settings.yaml
new file mode 100644
index 0000000..a1addcf
--- /dev/null
+++ b/example/issues/359_variable_reference/settings.yaml
@@ -0,0 +1,11 @@
+development:
+  template: "HELLO @tpl@"
+
+  level1:
+    template2: "Whoohoo @tpl@"
+    value1: "@format {this[TEMPLATE]}/level1/format"
+    level2:
+      value2: "@jinja {{this.TEMPLATE | replace('@tpl@','world')}}/level2/jinja"
+      level3:
+        value3: "@jinja {{this.level1.template2 | replace('@tpl@','world')}}/level3/jinja"
+  value0: "@jinja {{this.TEMPLATE | replace('@tpl@','world')}}/level0/jinja"


Test patch:
diff --git a/tests/test_dynabox.py b/tests/test_dynabox.py
index 0e4ffab..11e676a 100644
--- a/tests/test_dynabox.py
+++ b/tests/test_dynabox.py
@@ -12,7 +12,8 @@ box = DynaBox(
                 "token": {"TYPE": 1, "value": 2},
             },
         }
-    }
+    },
+    box_settings={},
 )
 
 
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 74e7177..c8eacb8 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -38,7 +38,7 @@ def test_unparse():
 
 def test_cast_bool(settings):
     """Covers https://github.com/rochacbruno/dynaconf/issues/14"""
-    assert parse_conf_data(False) is False
+    assert parse_conf_data(False, box_settings=settings) is False
     assert settings.get("SIMPLE_BOOL", cast="@bool") is False
 
 
@@ -84,31 +84,43 @@ def test_find_file(tmpdir):
 
 def test_disable_cast(monkeypatch):
     # this casts for int
-    assert parse_conf_data("@int 42") == 42
+    assert parse_conf_data("@int 42", box_settings={}) == 42
     # now gives pure string
     with monkeypatch.context() as m:
         m.setenv("AUTO_CAST_FOR_DYNACONF", "off")
-        assert parse_conf_data("@int 42") == "@int 42"
-
-
-def test_tomlfy():
-    assert parse_conf_data("1", tomlfy=True) == 1
-    assert parse_conf_data("true", tomlfy=True) is True
-    assert parse_conf_data("'true'", tomlfy=True) == "true"
-    assert parse_conf_data('"42"', tomlfy=True) == "42"
-    assert parse_conf_data("[1, 32, 3]", tomlfy=True) == [1, 32, 3]
-    assert parse_conf_data("[1.1, 32.1, 3.3]", tomlfy=True) == [1.1, 32.1, 3.3]
-    assert parse_conf_data("['a', 'b', 'c']", tomlfy=True) == ["a", "b", "c"]
-    assert parse_conf_data("[true, false]", tomlfy=True) == [True, False]
-    assert parse_conf_data("{key='value', v=1}", tomlfy=True) == {
-        "key": "value",
-        "v": 1,
-    }
+        assert parse_conf_data("@int 42", box_settings={}) == "@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
+    assert (
+        parse_conf_data("'true'", tomlfy=True, box_settings=settings) == "true"
+    )
+    assert parse_conf_data('"42"', tomlfy=True, box_settings=settings) == "42"
+    assert parse_conf_data(
+        "[1, 32, 3]", tomlfy=True, box_settings=settings
+    ) == [1, 32, 3]
+    assert parse_conf_data(
+        "[1.1, 32.1, 3.3]", tomlfy=True, box_settings=settings
+    ) == [1.1, 32.1, 3.3]
+    assert parse_conf_data(
+        "['a', 'b', 'c']", tomlfy=True, box_settings=settings
+    ) == ["a", "b", "c"]
+    assert parse_conf_data(
+        "[true, false]", tomlfy=True, box_settings=settings
+    ) == [True, False]
+    assert parse_conf_data(
+        "{key='value', v=1}", tomlfy=True, box_settings=settings
+    ) == {"key": "value", "v": 1}
 
 
 @pytest.mark.parametrize("test_input", ["something=42"])
-def test_tomlfy_unparseable(test_input):
-    assert parse_conf_data(test_input, tomlfy=True) == test_input
+def test_tomlfy_unparseable(test_input, settings):
+    assert (
+        parse_conf_data(test_input, tomlfy=True, box_settings=settings)
+        == test_input
+    )
 
 
 def test_missing_sentinel():
@@ -131,14 +143,16 @@ def test_missing_sentinel():
     assert str(missing) == "<dynaconf.missing>"
 
 
-def test_meta_values():
-    reset = parse_conf_data("@reset [1, 2]", tomlfy=True)
+def test_meta_values(settings):
+    reset = parse_conf_data(
+        "@reset [1, 2]", tomlfy=True, box_settings=settings
+    )
     # @reset is DEPRECATED in v3.0.0 but kept for backwards compatibility
     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)
+    _del = parse_conf_data("@del", tomlfy=True, box_settings=settings)
     assert _del.value == ""
     assert _del._dynaconf_del is True
     assert "Del()" in repr(_del)
@@ -174,11 +188,11 @@ def test_merge_existing_dict():
     assert new == {"host": "localhost", "port": 666, "user": "admin"}
 
 
-def test_merge_dict_with_meta_values():
+def test_merge_dict_with_meta_values(settings):
     existing = {"A": 1, "B": 2, "C": 3}
     new = {
-        "B": parse_conf_data("@del", tomlfy=True),
-        "C": parse_conf_data("4", tomlfy=True),
+        "B": parse_conf_data("@del", tomlfy=True, box_settings=settings),
+        "C": parse_conf_data("4", tomlfy=True, box_settings=settings),
     }
     object_merge(existing, new)
     assert new == {"A": 1, "C": 4}
@@ -261,13 +275,13 @@ def test_lazy_format_class():
     assert repr(value) == f"'@{value.formatter} {value.value}'"
 
 
-def test_evaluate_lazy_format_decorator():
+def test_evaluate_lazy_format_decorator(settings):
     class Settings:
         FOO = "foo"
 
         @evaluate_lazy_format
         def get(self):
-            return parse_conf_data("@format {this.FOO}/bar")
+            return parse_conf_data("@format {this.FOO}/bar", box_settings=self)
 
     settings = Settings()
     assert settings.get() == "foo/bar"
@@ -288,13 +302,15 @@ def test_lazy_format_class_jinja():
     assert value(settings) == "foo/bar"
 
 
-def test_evaluate_lazy_format_decorator_jinja():
+def test_evaluate_lazy_format_decorator_jinja(settings):
     class Settings:
         FOO = "foo"
 
         @evaluate_lazy_format
         def get(self):
-            return parse_conf_data("@jinja {{this.FOO}}/bar")
+            return parse_conf_data(
+                "@jinja {{this.FOO}}/bar", box_settings=settings
+            )
 
     settings = Settings()
     assert settings.get() == "foo/bar"
@@ -324,8 +340,8 @@ def test_try_to_encode():
     assert try_to_encode(value) == "@format {this[FOO]}/bar"
 
 
-def test_del_raises_on_unwrap():
-    value = parse_conf_data("@del ")
+def test_del_raises_on_unwrap(settings):
+    value = parse_conf_data("@del ", box_settings=settings)
     with pytest.raises(ValueError):
         value.unwrap()