Repository: dynaconf/dynaconf

Issue:
Ports from #1204 to master
- [x] Insert token (and related tests)
- [x] load_file source_metadata
- [x] populate_obj internal attr
- [x] `--json` on list must not echo
- [x] New way to find Django settings instance
- [x] json.dumps defaults to repr on CLI
- [x] settings_loader must load multiple environments (also build_env_list)
- [x] env_loader must allow multiple prefixes
- [x] *_loader must take identifer param
- [x] redis_loader fix for None prefix
- [x] Validator set call must take identifier
- [x] all tests from #1204 
- [x] Docs for all the above


Discussion:

Patch:
diff --git a/docs/advanced.md b/docs/advanced.md
index 79f62a83f..e90e132cd 100644
--- a/docs/advanced.md
+++ b/docs/advanced.md
@@ -310,6 +310,11 @@ Once `env` is changed via `setenv|using_env`, `reload` or `configure` invocation
 will be cleaned. To persist consider using `INCLUDES_FOR_DYNACONF` variable or assuring it will
 be loaded programmatically again.
 
+**new in 3.2.7**
+
+- Calls to `load_file` will be recorded on the inspect history and include module and line number.
+
+
 ## Prefix filtering
 
 ```toml
diff --git a/docs/configuration.md b/docs/configuration.md
index ea008b616..7f96aaa1f 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -204,6 +204,10 @@ export MY_SETTINGS_PATH="path.to.settings"
 The prefix used by dynaconf to load values from environment variables. You might want your users
 to export values using your app name, ex: `export MYPROGRAM_DEBUG=true`
 
+**new in 3.2.7**
+
+- The prefix  can be a comma separated list of prefixes, ex: `export MYPROGRAM_DEBUG=true` and `export MYAPP_DEBUG=true` will be loaded as `settings.DEBUG` if `ENVVAR_PREFIX_FOR_DYNACONF=MYPROGRAM,MYAPP`
+
 ---
 
 ### **env**
@@ -220,6 +224,11 @@ export ENV_FOR_DYNACONF=production
 
 Or per execution: `ENV_FOR_DYNACONF=production program.py`
 
+
+**new in 3.2.7**
+
+- The `env` can be a comma separated list of envs, ex: `export ENV_FOR_DYNACONF=production,staging` will activate both `production` and `staging` environments, and variables from both layers will be loaded in the order they are defined.
+
 ---
 
 ### **env_switcher**
diff --git a/docs/merging.md b/docs/merging.md
index 61f2a1e46..f6ac27e01 100644
--- a/docs/merging.md
+++ b/docs/merging.md
@@ -536,3 +536,76 @@ The **dynaconf_merge** and **@merge** functionalities work only for the first le
 ## More examples
 
 Take a look at the [example](https://github.com/dynaconf/dynaconf/tree/master/example) folder to see some examples of use with different file formats and features.
+
+
+## Inserting values to specific positions in lists
+
+> **New in 3.2.7**
+
+If you want to insert a value in a specific position in a list you can use the `@insert` token,
+
+The `@insert` token can be used to insert a value in a specific position in a list. The first argument is the index where the value will be inserted, and the second argument is the value to be inserted. If the first argument is omitted, the value will be inserted at the index 0.
+
+The `@insert` token can also be used to insert a value in a specific position in a list of dictionaries. The value to be inserted must be a valid toml dictionary or explicitly declared with a `@json` token.
+
+The insert token also accepts signed indexes, so you can insert values at the end of the list by using a negative index.
+
+## syntax
+
+```bash
+KEY = '@insert [index] value'
+```
+
+## Example
+
+Programmatically:
+
+```python
+settings = Dynaconf(settings_files=["settings.toml"])
+```
+
+`settings.toml`
+
+```toml
+colors = ["green", "blue"]
+```
+
+Environment variable:
+
+```bash
+export DYNACONF_COLORS='@insert 0 red'
+```
+
+Result:
+
+```python
+assert settings.COLORS == ["red", "green", "blue"]
+```
+
+You can also insert a dictionary into a list of dictionaries:
+
+`settings.toml`
+
+```toml
+people = [{name="Alice"}, {name="Bob"}]
+```
+
+Environment variable:
+
+Using the TOML format:
+
+```bash
+export DYNACONF_PEOPLE='@insert 1 {name="Charlie"}'
+```
+
+You can also insert using the `@json` token:
+
+```bash
+export DYNACONF_PEOPLE='@insert 1 @json {"name": "Charlie"}'
+```
+
+Result:
+
+```python
+assert settings.PEOPLE == [{name="Alice"}, {name="Charlie"}, {name="Bob"}]
+```
\ No newline at end of file
diff --git a/dynaconf/base.py b/dynaconf/base.py
index 90fee9d3e..759cf422f 100644
--- a/dynaconf/base.py
+++ b/dynaconf/base.py
@@ -1031,6 +1031,15 @@ def set(
             self.store.get(key, None) if not isinstance(parsed, Lazy) else None
         )
 
+        if getattr(parsed, "_dynaconf_insert", False):
+            # `@insert` calls insert in a list by index
+            if existing and isinstance(existing, list):
+                source_metadata = source_metadata._replace(merged=True)
+                existing.insert(parsed.index, parsed.unwrap())
+                parsed = existing
+            else:
+                parsed = [parsed.unwrap()]
+
         if getattr(parsed, "_dynaconf_del", None):
             self.unset(key, force=True)  # `@del` in a first level var.
             return
@@ -1306,12 +1315,24 @@ def load_file(
         env = (env or self.current_env).upper()
         files = ensure_a_list(path)
         if files:
+            # Using inspect take the filename and line number of the caller
+            # to be used in the source_metadata
+            frame = inspect.currentframe()
+            caller = inspect.getouterframes(frame)[1]
             already_loaded = set()
             for _filename in files:
                 # load_file() will handle validation later
                 with suppress(ValidationError):
+                    source_metadata = SourceMetadata(
+                        loader=f"load_file@{caller.filename}:{caller.lineno}",
+                        identifier=_filename,
+                        env=env,
+                    )
                     if py_loader.try_to_load_from_py_module_name(
-                        obj=self, name=_filename, silent=True
+                        obj=self,
+                        name=_filename,
+                        silent=True,
+                        identifier=source_metadata,
                     ):
                         # if it was possible to load from module name
                         # continue the loop.
@@ -1342,6 +1363,11 @@ def load_file(
 
                     # load_file() will handle validation later
                     with suppress(ValidationError):
+                        source_metadata = SourceMetadata(
+                            loader=f"load_file@{caller.filename}:{caller.lineno}",
+                            identifier=path,
+                            env=env,
+                        )
                         settings_loader(
                             obj=self,
                             env=env,
@@ -1349,6 +1375,7 @@ def load_file(
                             key=key,
                             filename=path,
                             validate=validate,
+                            identifier=source_metadata,
                         )
                         already_loaded.add(path)
 
@@ -1435,7 +1462,13 @@ def flag(self, key, env=None):
             return value is True or value in true_values
 
     def populate_obj(
-        self, obj, keys=None, ignore=None, merge=False, merge_unique=False
+        self,
+        obj,
+        keys=None,
+        ignore=None,
+        merge=False,
+        merge_unique=False,
+        internal=False,
     ):
         """Given the `obj` populate it using self.store items.
 
@@ -1446,6 +1479,9 @@ def populate_obj(
         merge = merge or self.get("MERGE_ENABLED_FOR_DYNACONF")
         keys = keys or self.keys()
         for key in keys:
+            if not internal:
+                if key in UPPER_DEFAULT_SETTINGS:
+                    continue
             key = upperfy(key)
             if ignore and key in ignore:
                 continue
diff --git a/dynaconf/cli.py b/dynaconf/cli.py
index c8b7529f1..caf4ddc3c 100644
--- a/dynaconf/cli.py
+++ b/dynaconf/cli.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 import importlib
+import inspect as python_inspect
 import json
 import os
 import pprint
@@ -17,6 +18,7 @@
 from dynaconf import LazySettings
 from dynaconf import loaders
 from dynaconf import settings as legacy_settings
+from dynaconf.base import Settings
 from dynaconf.loaders.py_loader import get_module
 from dynaconf.utils import prepare_json
 from dynaconf.utils import upperfy
@@ -57,6 +59,8 @@ def set_settings(ctx, instance=None):
     settings = None
 
     _echo_enabled = ctx.invoked_subcommand not in ["get", "inspect", None]
+    if "--json" in click.get_os_args():
+        _echo_enabled = False
 
     if instance is not None:
         if ctx.invoked_subcommand in ["init"]:
@@ -81,19 +85,16 @@ def set_settings(ctx, instance=None):
                 )
     elif "DJANGO_SETTINGS_MODULE" in os.environ:  # pragma: no cover
         sys.path.insert(0, os.path.abspath(os.getcwd()))
-        try:
-            # Django extension v2
-            from django.conf import settings  # noqa
-            import dynaconf  # noqa: F401
-            import django
-
-            # see https://docs.djangoproject.com/en/4.2/ref/applications/
-            # at #troubleshooting
-            django.setup()
+        import django  # noqa
 
-            settings.DYNACONF.configure()
-        except AttributeError:
-            settings = LazySettings()
+        django.setup()  # ensure django is setup to avoid AppRegistryNotReady
+        settings_module = import__django_settings(
+            os.environ["DJANGO_SETTINGS_MODULE"]
+        )
+        for member in python_inspect.getmembers(settings_module):
+            if isinstance(member[1], (LazySettings, Settings)):
+                settings = member[1]
+                break
 
         if settings is not None and _echo_enabled:
             click.echo(
@@ -119,6 +120,18 @@ def set_settings(ctx, instance=None):
             settings = LazySettings()
 
 
+def import__django_settings(django_settings_module):
+    """Import the Django settings module from the string importable path."""
+    try:
+        with redirect_stdout(None):
+            module = importlib.import_module(django_settings_module)
+    except ImportError as e:
+        raise click.UsageError(e)
+    except FileNotFoundError:
+        return
+    return module
+
+
 def import_settings(dotted_path):
     """Import settings instance from python dotted path.
 
@@ -488,7 +501,7 @@ def get(key, default, env, unparse):
         result = unparse_conf_data(result)
 
     if isinstance(result, (dict, list, tuple)):
-        result = json.dumps(prepare_json(result), sort_keys=True)
+        result = json.dumps(prepare_json(result), sort_keys=True, default=repr)
 
     click.echo(result, nl=False)
 
@@ -616,7 +629,9 @@ def format_setting(_k, _v):
         if output:
             loaders.write(output, prepare_json(data), env=not flat and cur_env)
         if _json:
-            json_data = json.dumps(prepare_json(data), sort_keys=True)
+            json_data = json.dumps(
+                prepare_json(data), sort_keys=True, default=repr
+            )
             click.echo(json_data, nl=False)
     else:
         key = upperfy(key)
@@ -637,7 +652,9 @@ def format_setting(_k, _v):
                 output, prepare_json({key: value}), env=not flat and cur_env
             )
         if _json:
-            click.echo(json.dumps(prepare_json({key: value})), nl=True)
+            click.echo(
+                json.dumps(prepare_json({key: value}), default=repr), nl=True
+            )
 
     if env:
         settings.setenv()
diff --git a/dynaconf/loaders/__init__.py b/dynaconf/loaders/__init__.py
index 153b64d5c..6ef953a76 100644
--- a/dynaconf/loaders/__init__.py
+++ b/dynaconf/loaders/__init__.py
@@ -12,6 +12,7 @@
 from dynaconf.loaders import py_loader
 from dynaconf.loaders import toml_loader
 from dynaconf.loaders import yaml_loader
+from dynaconf.loaders.base import SourceMetadata
 from dynaconf.utils import deduplicate
 from dynaconf.utils import ensure_a_list
 from dynaconf.utils.boxing import DynaBox
@@ -222,6 +223,7 @@ def settings_loader(
     key=None,
     filename=None,
     validate=False,
+    identifier="settings_loader",
 ):
     """Loads from defined settings module
 
@@ -231,6 +233,8 @@ def settings_loader(
     :param silent: Boolean to raise loading errors
     :param key: Load a single key if provided
     :param filename: optional filename to override the settings_module
+    :param validate: Validate the loaded data
+    :param identifier: Loader identifier
     """
     if filename is None:
         settings_module = settings_module or obj.settings_module
@@ -295,6 +299,9 @@ def settings_loader(
                 continue
 
             if mod_file.endswith(loader["ext"]):
+                if isinstance(identifier, str):
+                    # ensure it is always loader name
+                    identifier = loader["name"].lower()
                 loader["loader"].load(
                     obj,
                     filename=mod_file,
@@ -302,6 +309,7 @@ def settings_loader(
                     silent=silent,
                     key=key,
                     validate=validate,
+                    identifier=identifier,
                 )
                 continue
 
@@ -314,45 +322,73 @@ def settings_loader(
 
         # must be Python file or module
         # load from default defined module settings.py or .secrets.py if exists
-        py_loader.load(obj, mod_file, key=key, validate=validate)
+        py_loader.load(
+            obj, mod_file, key=key, validate=validate, identifier=identifier
+        )
 
         # load from the current env e.g: development_settings.py
+        # counting on the case where env is a comma separated string
         env = env or obj.current_env
-        if mod_file.endswith(".py"):
-            if ".secrets.py" == mod_file:
-                tmpl = ".{0}_{1}{2}"
-                mod_file = "secrets.py"
-            else:
-                tmpl = "{0}_{1}{2}"
-
-            dirname = os.path.dirname(mod_file)
-            filename, extension = os.path.splitext(os.path.basename(mod_file))
-            new_filename = tmpl.format(env.lower(), filename, extension)
-            env_mod_file = os.path.join(dirname, new_filename)
-            global_filename = tmpl.format("global", filename, extension)
-            global_mod_file = os.path.join(dirname, global_filename)
+        if env and isinstance(env, str):
+            for env_name in env.split(","):
+                load_from_env_named_file(
+                    obj, env_name, key, validate, identifier, mod_file
+                )
+
+
+def load_from_env_named_file(obj, env, key, validate, identifier, mod_file):
+    """Load from env named file e.g: development_settings.py"""
+    if mod_file.endswith(".py"):
+        if ".secrets.py" == mod_file:
+            tmpl = ".{0}_{1}{2}"
+            mod_file = "secrets.py"
         else:
-            env_mod_file = f"{env.lower()}_{mod_file}"
-            global_mod_file = f"global_{mod_file}"
+            tmpl = "{0}_{1}{2}"
+
+        dirname = os.path.dirname(mod_file)
+        filename, extension = os.path.splitext(os.path.basename(mod_file))
+        new_filename = tmpl.format(env.lower(), filename, extension)
+        env_mod_file = os.path.join(dirname, new_filename)
+        global_filename = tmpl.format("global", filename, extension)
+        global_mod_file = os.path.join(dirname, global_filename)
+    else:
+        parts = mod_file.rsplit(".", 1)
+        if len(parts) > 1:
+            head, tail = parts
+        else:
+            head, tail = None, parts[0]
+        tail = env_mod_file = f"{env.lower()}_{tail}"
 
-        py_loader.load(
-            obj,
-            env_mod_file,
-            identifier=f"py_{env.upper()}",
-            silent=True,
-            key=key,
-            validate=validate,
-        )
+        if head:
+            env_mod_file = f"{head}.{tail}"
+            global_mod_file = f"{head}.global_{tail}"
+        else:
+            env_mod_file = tail
+            global_mod_file = f"global_{tail}"
 
-        # load from global_settings.py
-        py_loader.load(
-            obj,
-            global_mod_file,
-            identifier="py_global",
-            silent=True,
-            key=key,
-            validate=validate,
-        )
+    source_metadata = SourceMetadata(
+        loader="py",
+        identifier=identifier,
+        env=env,
+    )
+    py_loader.load(
+        obj,
+        env_mod_file,
+        identifier=source_metadata,
+        silent=True,
+        key=key,
+        validate=validate,
+    )
+
+    # load from global_settings.py
+    py_loader.load(
+        obj,
+        global_mod_file,
+        identifier="py_global",
+        silent=True,
+        key=key,
+        validate=validate,
+    )
 
 
 def enable_external_loaders(obj):
diff --git a/dynaconf/loaders/env_loader.py b/dynaconf/loaders/env_loader.py
index a563b4e20..bda583ef9 100644
--- a/dynaconf/loaders/env_loader.py
+++ b/dynaconf/loaders/env_loader.py
@@ -15,34 +15,49 @@
 
     DOTENV_IMPORTED = True
 
-IDENTIFIER = "env"
+IDENTIFIER_PREFIX = "env"
 
 
-def load(obj, env=None, silent=True, key=None, validate=False):
+def load(
+    obj, env=None, silent=True, key=None, validate=False, identifier="global"
+):
     """Loads envvars with prefixes:
 
     `DYNACONF_` (default global) or `$(ENVVAR_PREFIX_FOR_DYNACONF)_`
+
+    if envvar_prefix is set to:
+      str: -> load {str}_*
+      str,str1 -> load [{str}_, {str1}_]
+      False -> load *
+      None -> return not loading anything
     """
     global_prefix = obj.get("ENVVAR_PREFIX_FOR_DYNACONF")
+    if global_prefix is None:
+        return
+
     if global_prefix is False or global_prefix.upper() != "DYNACONF":
         load_from_env(
             obj,
             "DYNACONF",
             key,
             silent,
-            IDENTIFIER + "_global",
+            f"{IDENTIFIER_PREFIX}_{identifier}",
             validate=validate,
         )
 
     # Load the global env if exists and overwrite everything
-    load_from_env(
-        obj,
-        global_prefix,
-        key,
-        silent,
-        IDENTIFIER + "_global",
-        validate=validate,
-    )
+    # if the prefix is separated by comma then load all prefixes
+    # counting on the case where global_prefix is set to None, False or ""
+    prefixes = global_prefix.split(",") if global_prefix else [global_prefix]
+    for prefix in prefixes:
+        load_from_env(
+            obj,
+            prefix,
+            key,
+            silent,
+            f"{IDENTIFIER_PREFIX}_{identifier}",
+            validate=validate,
+        )
 
 
 def load_from_env(
@@ -50,7 +65,7 @@ def load_from_env(
     prefix=False,
     key=None,
     silent=False,
-    identifier=IDENTIFIER,
+    identifier=IDENTIFIER_PREFIX,
     env=False,  # backwards compatibility bc renamed param
     validate=False,
 ):
@@ -66,7 +81,7 @@ def load_from_env(
         env_ = f"{prefix}_"
 
     # set source metadata
-    source_metadata = SourceMetadata(identifier, "unique", "global")
+    source_metadata = SourceMetadata(identifier, prefix, "global")
 
     # Load a single environment variable explicitly.
     if key:
diff --git a/dynaconf/loaders/ini_loader.py b/dynaconf/loaders/ini_loader.py
index bf92d2a82..668820680 100644
--- a/dynaconf/loaders/ini_loader.py
+++ b/dynaconf/loaders/ini_loader.py
@@ -5,6 +5,7 @@
 from dynaconf import default_settings
 from dynaconf.constants import INI_EXTENSIONS
 from dynaconf.loaders.base import BaseLoader
+from dynaconf.loaders.base import SourceMetadata
 from dynaconf.utils import object_merge
 
 try:
@@ -13,7 +14,15 @@
     ConfigObj = None
 
 
-def load(obj, env=None, silent=True, key=None, filename=None, validate=False):
+def load(
+    obj,
+    env=None,
+    silent=True,
+    key=None,
+    filename=None,
+    validate=False,
+    identifier="ini",
+):
     """
     Reads and loads in to "obj" a single key or all keys from source file.
 
@@ -28,10 +37,16 @@ def load(obj, env=None, silent=True, key=None, filename=None, validate=False):
         BaseLoader.warn_not_installed(obj, "ini")
         return
 
+    # when load_file function is called directly it comes with module and line number
+    if isinstance(identifier, SourceMetadata) and identifier.loader.startswith(
+        "load_file"
+    ):
+        identifier = identifier.loader
+
     loader = BaseLoader(
         obj=obj,
         env=env,
-        identifier="ini",
+        identifier=identifier,
         extensions=INI_EXTENSIONS,
         file_reader=lambda fileobj: ConfigObj(fileobj).dict(),
         string_reader=lambda strobj: ConfigObj(strobj.split("\n")).dict(),
diff --git a/dynaconf/loaders/json_loader.py b/dynaconf/loaders/json_loader.py
index 7bf9961be..d48137abb 100644
--- a/dynaconf/loaders/json_loader.py
+++ b/dynaconf/loaders/json_loader.py
@@ -6,6 +6,7 @@
 from dynaconf import default_settings
 from dynaconf.constants import JSON_EXTENSIONS
 from dynaconf.loaders.base import BaseLoader
+from dynaconf.loaders.base import SourceMetadata
 from dynaconf.utils import object_merge
 from dynaconf.utils.parse_conf import try_to_encode
 
@@ -15,7 +16,15 @@
     commentjson = None
 
 
-def load(obj, env=None, silent=True, key=None, filename=None, validate=False):
+def load(
+    obj,
+    env=None,
+    silent=True,
+    key=None,
+    filename=None,
+    validate=False,
+    identifier="json",
+):
     """
     Reads and loads in to "obj" a single key or all keys from source file.
 
@@ -35,10 +44,16 @@ def load(obj, env=None, silent=True, key=None, filename=None, validate=False):
         file_reader = json.load
         string_reader = json.loads
 
+    # when load_file function is called directly it comes with module and line number
+    if isinstance(identifier, SourceMetadata) and identifier.loader.startswith(
+        "load_file"
+    ):
+        identifier = identifier.loader
+
     loader = BaseLoader(
         obj=obj,
         env=env,
-        identifier="json",
+        identifier=identifier,
         extensions=JSON_EXTENSIONS,
         file_reader=file_reader,
         string_reader=string_reader,
diff --git a/dynaconf/loaders/py_loader.py b/dynaconf/loaders/py_loader.py
index f8f575505..289588ebb 100644
--- a/dynaconf/loaders/py_loader.py
+++ b/dynaconf/loaders/py_loader.py
@@ -37,7 +37,12 @@ def load(
         return
 
     # setup SourceMetadata (for inspecting)
-    loader_identifier = SourceMetadata(identifier, mod.__name__, "global")
+    if isinstance(identifier, SourceMetadata):
+        loader_identifier = SourceMetadata(
+            identifier.loader, mod.__name__, identifier.env
+        )
+    else:
+        loader_identifier = SourceMetadata(identifier, mod.__name__, "global")
 
     load_from_python_object(
         obj, mod, settings_module, key, loader_identifier, validate=validate
@@ -88,7 +93,10 @@ def try_to_load_from_py_module_name(
     ctx = suppress(ImportError, TypeError) if silent else suppress()
 
     # setup SourceMetadata (for inspecting)
-    loader_identifier = SourceMetadata(identifier, name, "global")
+    if isinstance(identifier, SourceMetadata):
+        loader_identifier = identifier
+    else:
+        loader_identifier = SourceMetadata(identifier, name, "global")
 
     with ctx:
         mod = importlib.import_module(str(name))
diff --git a/dynaconf/loaders/redis_loader.py b/dynaconf/loaders/redis_loader.py
index d1da4e18a..d9996d0b3 100644
--- a/dynaconf/loaders/redis_loader.py
+++ b/dynaconf/loaders/redis_loader.py
@@ -32,10 +32,17 @@ def load(obj, env=None, silent=True, key=None, validate=False):
 
     redis = StrictRedis(**obj.get("REDIS_FOR_DYNACONF"))
     prefix = obj.get("ENVVAR_PREFIX_FOR_DYNACONF")
+    env_list = build_env_list(obj, env or obj.current_env)
     # prefix is added to env_list to keep backwards compatibility
-    env_list = [prefix] + build_env_list(obj, env or obj.current_env)
+    if prefix:
+        env_list.insert(0, prefix)
+
     for env_name in env_list:
-        holder = f"{prefix.upper()}_{env_name.upper()}"
+        if prefix:
+            holder = f"{prefix.upper()}_{env_name.upper()}"
+        else:
+            holder = env_name.upper()
+
         try:
             source_metadata = SourceMetadata(IDENTIFIER, "unique", env_name)
             if key:
diff --git a/dynaconf/loaders/toml_loader.py b/dynaconf/loaders/toml_loader.py
index 42db7b366..da5be8d82 100644
--- a/dynaconf/loaders/toml_loader.py
+++ b/dynaconf/loaders/toml_loader.py
@@ -6,12 +6,21 @@
 from dynaconf import default_settings
 from dynaconf.constants import TOML_EXTENSIONS
 from dynaconf.loaders.base import BaseLoader
+from dynaconf.loaders.base import SourceMetadata
 from dynaconf.utils import object_merge
 from dynaconf.vendor import toml  # Backwards compatibility with uiri/toml
 from dynaconf.vendor import tomllib  # New tomllib stdlib on py3.11
 
 
-def load(obj, env=None, silent=True, key=None, filename=None, validate=False):
+def load(
+    obj,
+    env=None,
+    silent=True,
+    key=None,
+    filename=None,
+    validate=False,
+    identifier="toml",
+):
     """
     Reads and loads in to "obj" a single key or all keys from source file.
 
@@ -23,11 +32,17 @@ def load(obj, env=None, silent=True, key=None, filename=None, validate=False):
     :return: None
     """
 
+    # when load_file function is called directly it comes with module and line number
+    if isinstance(identifier, SourceMetadata) and identifier.loader.startswith(
+        "load_file"
+    ):
+        identifier = identifier.loader
+
     try:
         loader = BaseLoader(
             obj=obj,
             env=env,
-            identifier="toml",
+            identifier=identifier,
             extensions=TOML_EXTENSIONS,
             file_reader=tomllib.load,
             string_reader=tomllib.loads,
@@ -57,7 +72,7 @@ def load(obj, env=None, silent=True, key=None, filename=None, validate=False):
         loader = BaseLoader(
             obj=obj,
             env=env,
-            identifier="toml",
+            identifier=identifier,
             extensions=TOML_EXTENSIONS,
             file_reader=toml.load,
             string_reader=toml.loads,
diff --git a/dynaconf/loaders/yaml_loader.py b/dynaconf/loaders/yaml_loader.py
index af0629838..e646e8a01 100644
--- a/dynaconf/loaders/yaml_loader.py
+++ b/dynaconf/loaders/yaml_loader.py
@@ -6,6 +6,7 @@
 from dynaconf import default_settings
 from dynaconf.constants import YAML_EXTENSIONS
 from dynaconf.loaders.base import BaseLoader
+from dynaconf.loaders.base import SourceMetadata
 from dynaconf.utils import object_merge
 from dynaconf.utils.parse_conf import try_to_encode
 from dynaconf.vendor.ruamel import yaml
@@ -53,7 +54,15 @@ def get_source_data(self, files):
         return data
 
 
-def load(obj, env=None, silent=True, key=None, filename=None, validate=False):
+def load(
+    obj,
+    env=None,
+    silent=True,
+    key=None,
+    filename=None,
+    validate=False,
+    identifier="yaml",
+):
     """
     Reads and loads in to "obj" a single key or all keys from source file.
 
@@ -82,10 +91,16 @@ def load(obj, env=None, silent=True, key=None, filename=None, validate=False):
     if yaml_reader.__name__.endswith("_all"):
         _loader = AllLoader
 
+    # when load_file function is called directly it comes with module and line number
+    if isinstance(identifier, SourceMetadata) and identifier.loader.startswith(
+        "load_file"
+    ):
+        identifier = identifier.loader
+
     loader = _loader(
         obj=obj,
         env=env,
-        identifier="yaml",
+        identifier=identifier,
         extensions=YAML_EXTENSIONS,
         file_reader=yaml_reader,
         string_reader=yaml_reader,
diff --git a/dynaconf/typed/main.py b/dynaconf/typed/main.py
index 49bf0a6fb..19d763f98 100644
--- a/dynaconf/typed/main.py
+++ b/dynaconf/typed/main.py
@@ -43,6 +43,7 @@ class Options:
     env: str | type = Empty
     environments: bool | type = Empty
     envvar_prefix: str | type = Empty
+    env_switcher: str | type = Empty
     root_path: str | Path | type = Empty
     settings_files: list[str] | type = Empty
     settings_file: str | type = Empty
diff --git a/dynaconf/utils/__init__.py b/dynaconf/utils/__init__.py
index 1a1632e47..f623bdabe 100644
--- a/dynaconf/utils/__init__.py
+++ b/dynaconf/utils/__init__.py
@@ -191,6 +191,16 @@ def handle_metavalues(
                 new[key].unwrap(),
                 unique=new[key].unique,
             )
+        elif getattr(new[key], "_dynaconf_insert", False):
+            # Insert on `new` triggers insert with existing data
+            # if existing is a list it inserts at specified .index
+            # if existing is not a list it creates a new list with the value
+            existing = old.get(key)  # keep the same reference
+            if isinstance(existing, list):  # perform insert on it
+                existing.insert(new[key].index, new[key].unwrap())
+                new[key] = existing
+            else:
+                new[key] = [new[key].unwrap()]
 
         # Data structures containing merge tokens
         if isinstance(new.get(key), (list, tuple)):
@@ -390,14 +400,18 @@ def build_env_list(obj: Settings | LazySettings, env: str | None) -> list[str]:
     if global_env not in env_list:
         env_list.append(global_env)
 
-    # add the current env
+    # add the current env counting on the case where it is a comma separated list
     current_env = obj.current_env
-    if current_env and current_env.lower() not in env_list:
-        env_list.append(current_env.lower())
+    if current_env and isinstance(current_env, str):
+        for item in current_env.split(","):
+            if item and (_name := item.strip().lower()) not in env_list:
+                env_list.append(_name)
 
-    # add a manually set env
-    if env and env.lower() not in env_list:
-        env_list.append(env.lower())
+    # add a manually set env counting on the case where it is a comma separated list
+    if env and isinstance(env, str):
+        for item in env.split(","):
+            if item and (_name := item.strip().lower()) not in env_list:
+                env_list.append(_name)
 
     # add the [global] env
     env_list.append("global")
@@ -488,6 +502,11 @@ def _recursively_evaluate_lazy_format(
         value = value(settings)
 
     if isinstance(value, list):
+        # This must be the right way of doing it, but breaks validators
+        # To be changed on 4.0.0
+        # for idx, item in enumerate(value):
+        #     value[idx] = _recursively_evaluate_lazy_format(item, settings)
+
         # Keep the original type, can be a BoxList
         value = value.__class__(
             [
diff --git a/dynaconf/utils/parse_conf.py b/dynaconf/utils/parse_conf.py
index 5d7ef873c..1958764f1 100644
--- a/dynaconf/utils/parse_conf.py
+++ b/dynaconf/utils/parse_conf.py
@@ -141,6 +141,52 @@ def __init__(self, value, box_settings, unique=False):
         self.unique = unique
 
 
+class Insert(MetaValue):
+    """Triggers the value to be inserted into a list at specific index"""
+
+    _dynaconf_insert = True
+
+    def __init__(self, value, box_settings):
+        """
+        normally value will be a string like
+        `0 foo` or `-1 foo` and needs to get split
+        but value can also be just a single string with or without space
+        like `foo` and in this case it will be treated as `0 foo`
+        but it can also be `foo bar` and in this case it will be treated as `0 foo bar`
+        we need to check if the first part is a number
+        if it is not a number then we will treat it as `0 value`
+        if it is a number then we will split it as `index, value`
+        this must use a regex to match value, examples:
+            -1 foo -> index = -1, value = foo
+            0 foo -> index = 0, value = foo
+            0 foo bar -> index = 0, value = foo bar
+            0 42 -> index = 0, value = 42
+            0 42 foo -> index = 0, value = 42 foo
+            foo -> index = 0, value = foo
+            foo bar -> index = 0, value = foo bar
+            42 -> index = 0, value = 42
+            42 foo -> index = 42, value = foo
+            42 foo bar -> index = 42, value = foo bar
+        """
+        self.box_settings = box_settings
+
+        try:
+            if value.lstrip("-+")[0].isdigit():
+                # `0 foo` or `-1 foo` or `42 foo` or `42`(raise ValueError)
+                index, value = value.split(" ", 1)
+            else:
+                # `foo` or `foo bar`
+                index, value = 0, value
+        except ValueError:
+            # `42` or any other non split able value
+            index, value = 0, value
+
+        self.index = int(index)
+        self.value = parse_conf_data(
+            value, tomlfy=True, box_settings=box_settings
+        )
+
+
 class BaseFormatter:
     def __init__(self, function, token):
         self.function = function
@@ -309,6 +355,7 @@ def evaluate(settings, *args, **kwargs):
     "@merge_unique": lambda value, box_settings: Merge(
         value, box_settings, unique=True
     ),
+    "@insert": Insert,
     "@get": lambda value: Lazy(value, formatter=Formatters.get_formatter),
     # Special markers to be used as placeholders e.g: in prefilled forms
     # will always return None when evaluated
diff --git a/dynaconf/validator.py b/dynaconf/validator.py
index b08f044de..358de8787 100644
--- a/dynaconf/validator.py
+++ b/dynaconf/validator.py
@@ -408,7 +408,9 @@ def _validate_names(
                 value = self.cast(settings.get(name))
             if _set := getattr(settings, "set", None):
                 # Settings is Dynaconf
-                _set(name, value, validate=False)
+                _set(
+                    name, value, validate=False, loader_identifier="validator"
+                )
             else:
                 # settings is a dict
                 settings[name] = value


Test patch:
diff --git a/tests/test_base.py b/tests/test_base.py
index ce4489b58..e7c704475 100644
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -611,6 +611,71 @@ def test_set_new_merge_issue_241_5(tmpdir):
     }
 
 
+def test_set_insert_token(tmpdir):
+    data = {
+        "a_list": [1, 2],
+        "b_list": [1],
+        "a_dict": {"name": "Bruno", "teams": ["dev"]},
+        "list_of_dicts": [{"number": 1}],
+    }
+    toml_loader.write(str(tmpdir.join("settings.toml")), data, merge=False)
+    settings = LazySettings(settings_file="settings.toml")
+    assert settings.A_LIST == [1, 2]
+    assert settings.B_LIST == [1]
+    assert settings.A_DICT == {"name": "Bruno", "teams": ["dev"]}
+    assert settings.A_DICT.name == "Bruno"
+    assert settings.A_DICT.teams == ["dev"]
+
+    settings.set("a_list", "@insert 0 0")
+    assert settings.A_LIST == [0, 1, 2]
+
+    settings.set("a_list", "@insert 1 1.5")
+    assert settings.A_LIST == [0, 1.5, 1, 2]
+
+    settings.set("a_list", "@insert 4 4")
+    assert settings.A_LIST == [0, 1.5, 1, 2, 4]
+
+    settings.set("b_list", "@insert 0 42")
+    assert settings.B_LIST == [42, 1]
+
+    settings.set("b_list", "@insert 1 43")
+    assert settings.B_LIST == [42, 43, 1]
+
+    settings.set("a_dict.teams", "@insert 0 'admin'")
+    assert settings.A_DICT.teams == ["admin", "dev"]
+
+    settings.set("a_dict.teams", "@insert 1 'staff'")
+    assert settings.A_DICT.teams == ["admin", "staff", "dev"]
+
+    settings.set("a_dict.teams", "@insert 3 'user'")
+    assert settings.A_DICT.teams == ["admin", "staff", "dev", "user"]
+
+    settings.set("a_dict.teams", "@insert 0 'admin'")
+    assert settings.A_DICT.teams == ["admin", "admin", "staff", "dev", "user"]
+
+    settings.set("new_key", "@insert 0 'foo'")
+    assert settings.NEW_KEY == ["foo"]
+
+    settings.set("new_key", "@insert 'bar'")
+    assert settings.NEW_KEY == ["bar", "foo"]
+
+    settings.set("new_key", "@insert 42")
+    assert settings.NEW_KEY == [42, "bar", "foo"]
+
+    settings.set("new_key", "@insert -1 'last'")
+    assert settings.NEW_KEY == [42, "bar", "last", "foo"]
+
+    settings.set("list_of_dicts", '@insert 0 @json {"number": 0}')
+    assert settings.LIST_OF_DICTS == [{"number": 0}, {"number": 1}]
+
+    settings.set("list_of_dicts", "@insert 2 {number=2}")  # valid toml
+    assert settings.LIST_OF_DICTS == [
+        {"number": 0},
+        {"number": 1},
+        {"number": 2},
+    ]
+
+
 def test_set_with_non_str_types():
     """This replicates issue #1005 in a simplified setup."""
     settings = Dynaconf(merge_enabled=True)
diff --git a/tests/test_cli.py b/tests/test_cli.py
index c3595476e..92d0bb397 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -549,7 +549,7 @@ def test_inspect_no_args(tmp_path):
           "history": [
             {
               "loader": "env_global",
-              "identifier": "unique",
+              "identifier": "DYNACONF",
               "env": "global",
               "merged": false,
               "value": {
@@ -595,7 +595,7 @@ def test_inspect_yaml_format(tmp_path):
           FOO: from_environ_yaml_format
         history:
         - loader: env_global
-          identifier: unique
+          identifier: DYNACONF
           env: global
           merged: false
           value:
@@ -742,7 +742,7 @@ def test_inspect_limit(tmp_path):
           "history": [
             {
               "loader": "env_global",
-              "identifier": "unique",
+              "identifier": "DYNACONF",
               "env": "global",
               "merged": false,
               "value": {
diff --git a/tests/test_inspect.py b/tests/test_inspect.py
index 2e275c3f1..8720c3646 100644
--- a/tests/test_inspect.py
+++ b/tests/test_inspect.py
@@ -198,7 +198,7 @@ def test_get_history_env_false__file_plus_envvar(tmp_path):
         history[3],
         {
             "loader": "env_global",
-            "identifier": "unique",
+            "identifier": "DYNACONF",
             "env": "global",
             "merged": False,
         },
@@ -223,7 +223,7 @@ def test_get_history_env_false__val_default_plus_envvar():
         history[0],
         {
             "loader": "env_global",
-            "identifier": "unique",
+            "identifier": "DYNACONF",
             "env": "global",
             "merged": False,
         },
@@ -282,7 +282,7 @@ def test_get_history_env_false__merge_marks(tmp_path):
         history[5],
         {
             "loader": "env_global",
-            "identifier": "unique",
+            "identifier": "DYNACONF",
             "env": "global",
             "merged": True,
         },
@@ -326,7 +326,7 @@ def test_get_history_env_true__file_plus_envvar(tmp_path):
     }
     assert history[4] == {
         "loader": "env_global",
-        "identifier": "unique",
+        "identifier": "DYNACONF",
         "env": "global",
         "merged": False,
         "value": {"FOO": "from_environ"},
@@ -358,7 +358,7 @@ def test_get_history_env_true__val_default_plus_file(tmp_path):
     )
     history = get_history(settings)
 
-    assert len(history) == 7
+    assert len(history) == 8
     assert history[2] == {
         "loader": "toml",
         "identifier": str(file_a),
@@ -382,7 +382,8 @@ def test_get_history_env_true__val_default_plus_file(tmp_path):
     }
     # REVIEW: history[5] is not correct, validation default on other env should
     # not have side effect on current object but only on its copy/clone
-    assert history[6] == {
+    # history[6] == switching to production env, so it sets ENV_FOR_DYNACONF
+    assert history[7] == {
         "loader": "setdefault",
         "identifier": "unique",
         "env": "production",
@@ -434,7 +435,7 @@ def test_get_history_env_true__merge_marks(tmp_path):
         history[5],
         {
             "loader": "env_global",
-            "identifier": "unique",
+            "identifier": "DYNACONF",
             "env": "global",
             "merged": True,
         },
@@ -703,7 +704,7 @@ def test_inspect_key_filter(tmp_path):
     assert result["history"] == [
         {
             "loader": "env_global",
-            "identifier": "unique",
+            "identifier": "DYNACONF",
             "env": "global",
             "merged": False,
             "value": "from_environ",
@@ -733,7 +734,7 @@ def test_inspect_no_filter(tmp_path):
     assert result["history"][:2] == [
         {
             "env": "global",
-            "identifier": "unique",
+            "identifier": "DYNACONF",
             "loader": "env_global",
             "merged": False,
             "value": {"BAR": "environ_only", "FOO": "from_environ"},
@@ -802,7 +803,7 @@ def test_inspect_to_file(tmp_path):
           BAR: environ_only
         history:
         - loader: env_global
-          identifier: unique
+          identifier: DYNACONF
           env: global
           merged: false
           value:
diff --git a/tests/test_settings_loader.py b/tests/test_settings_loader.py
new file mode 100644
index 000000000..efd86caef
--- /dev/null
+++ b/tests/test_settings_loader.py
@@ -0,0 +1,167 @@
+from __future__ import annotations
+
+from dynaconf import Dynaconf
+from dynaconf.loaders import settings_loader
+
+
+def test_load_using_settings_loader(tmpdir):
+    settings = Dynaconf(envvar_prefix=None)
+    dummy_folder = tmpdir.mkdir("dummy1")
+
+    dummy_folder.join("dummy_module.py").write('FOOB = "bar"')
+    dummy_folder.join("__init__.py").write('print("initing dummy...")')
+
+    settings_loader(settings, "dummy1.dummy_module")
+    assert settings.exists("FOOB")
+    assert settings.FOOB == "bar"
+
+
+def test_load_using_settings_loader_with_environments(tmpdir):
+    settings = Dynaconf(environments=True)
+    dummy_folder = tmpdir.mkdir("dummy2")
+
+    dummy_folder.join("dummy_module.py").write('FOOB = "bar"')
+    dummy_folder.join("__init__.py").write('print("initing dummy...")')
+
+    settings_loader(settings, "dummy2.dummy_module")
+
+    assert settings.exists("FOOB")
+    assert settings.FOOB == "bar"
+    assert settings.current_env == "DEVELOPMENT"
+
+
+def test_load_using_settings_loader_with_temporary_env(tmpdir):
+    settings = Dynaconf(environments=True)
+    dummya_folder = tmpdir.mkdir("dummya")
+
+    dummya_folder.join("dummya_module.py").write('FOOB = "bar"')
+    dummya_folder.join("__init__.py").write('print("initing dummya...")')
+
+    # change the env on the fly only for this load
+    settings_loader(settings, "dummya.dummya_module", env="PRODUCTION")
+
+    assert settings.exists("FOOB")
+    assert settings.FOOB == "bar"
+    assert (
+        settings.current_env == "DEVELOPMENT"
+    )  # does not affect the current env
+
+
+def test_load_using_settings_loader_with_multi_temporary_env(tmpdir):
+    settings = Dynaconf(environments=True)
+    dummyab_folder = tmpdir.mkdir("dummyab")
+
+    dummyab_folder.join("dummyab_module.py").write('FOOB = "bar"')
+    dummyab_folder.join("__init__.py").write('print("initing dummyab...")')
+
+    dummyab_folder.join("quiet_dummyab_module.py").write("DEBUG = False")
+    dummyab_folder.join("other_dummyab_module.py").write('FOOB = "zaz"')
+
+    # change the env on the fly only for this load
+    settings_loader(
+        settings, "dummyab.dummyab_module", env="PRODUCTION,quiet,OTHER"
+    )
+
+    assert settings.exists("FOOB")
+    assert settings.FOOB == "zaz"  # comes from other_dummyab_module.py
+    assert (
+        settings.current_env == "DEVELOPMENT"
+    )  # does not affect the current env
+    assert settings.DEBUG is False
+
+
+def test_load_using_settings_loader_with_set_env(tmpdir):
+    settings = Dynaconf(environments=True)
+    settings.setenv("PRODUCTION")
+    dummyb_folder = tmpdir.mkdir("dummyb")
+
+    dummyb_folder.join("dummyb_module.py").write('FOOB = "bar"')
+    dummyb_folder.join("__init__.py").write('print("initing dummyb...")')
+
+    settings_loader(settings, "dummyb.dummyb_module")
+
+    assert settings.exists("FOOB")
+    assert settings.FOOB == "bar"
+    assert settings.current_env == "PRODUCTION"
+
+
+def test_load_using_settings_loader_with_one_env_named_file_module_path(
+    tmpdir,
+):
+    settings = Dynaconf(environments=True)
+    settings.setenv("PRODUCTION")
+    dummyc_folder = tmpdir.mkdir("dummyc")
+
+    dummyc_folder.join("dummyc_module.py").write('FOOB = "bar"')
+    dummyc_folder.join("__init__.py").write('print("initing dummyc...")')
+
+    dummyc_folder.join("production_dummyc_module.py").write('BAZ = "zaz"')
+
+    settings_loader(settings, "dummyc.dummyc_module")
+
+    assert settings.exists("FOOB")
+    assert settings.FOOB == "bar"
+    assert settings.current_env == "PRODUCTION"
+    assert settings.BAZ == "zaz"
+
+
+def test_load_using_settings_loader_with_one_env_named_file_file_path(tmpdir):
+    settings = Dynaconf(environments=True)
+    settings.setenv("PRODUCTION")
+    dummyd_folder = tmpdir.mkdir("dummyd")
+
+    dummyd_folder.join("dummyd_module.py").write('FOOB = "bar"')
+    dummyd_folder.join("__init__.py").write('print("initing dummyd...")')
+
+    dummyd_folder.join("production_dummyd_module.py").write('BAZ = "zaz"')
+
+    settings_loader(settings, "dummyd/dummyd_module.py")
+
+    assert settings.exists("FOOB")
+    assert settings.FOOB == "bar"
+    assert settings.current_env == "PRODUCTION"
+    assert settings.BAZ == "zaz"
+
+
+def test_load_using_settings_loader_with_one_env_named_file_module_path_multi_env(
+    tmpdir,
+):
+    settings = Dynaconf(environments=True)
+    settings.setenv("PRODUCTION,special")
+    dummye_folder = tmpdir.mkdir("dummye")
+
+    dummye_folder.join("dummye_module.py").write('FOOB = "bar"')
+    dummye_folder.join("__init__.py").write('print("initing dummye...")')
+
+    dummye_folder.join("production_dummye_module.py").write('BAZ = "zaz"')
+    dummye_folder.join("special_dummye_module.py").write("DEBUG = False")
+
+    settings_loader(settings, "dummye.dummye_module")
+
+    assert settings.exists("FOOB")
+    assert settings.FOOB == "bar"
+    assert "PRODUCTION" in settings.current_env
+    assert settings.BAZ == "zaz"
+    assert settings.DEBUG is False  # comes from special_
+
+
+def test_load_using_settings_loader_with_one_env_named_file_file_path_multi_env(
+    tmpdir,
+):
+    settings = Dynaconf(environments=True)
+    settings.setenv("PRODUCTION,special")
+    dummyf_folder = tmpdir.mkdir("dummyf")
+
+    dummyf_folder.join("dummyf_module.py").write('FOOB = "bar"')
+    dummyf_folder.join("__init__.py").write('print("initing dummyf...")')
+
+    dummyf_folder.join("production_dummyf_module.py").write('BAZ = "zaz"')
+    dummyf_folder.join("special_dummyf_module.py").write("DEBUG = True")
+
+    settings_loader(settings, "dummyf/dummyf_module.py")
+
+    assert settings.exists("FOOB")
+    assert settings.FOOB == "bar"
+    assert "PRODUCTION" in settings.current_env
+    assert settings.BAZ == "zaz"
+    assert settings.DEBUG is True  # comes from special_
diff --git a/tests/test_toml_loader.py b/tests/test_toml_loader.py
index 82cde8052..6ab3b05cf 100644
--- a/tests/test_toml_loader.py
+++ b/tests/test_toml_loader.py
@@ -38,6 +38,9 @@
 password = "@int 11111"
 HOST = "prodserver.com"
 
+[extra]
+password = "@int 77777"
+
 [GLOBAL]
 global_value = "global"
 """
@@ -63,6 +66,15 @@
 TOMLS = [TOML, TOML2]
 
 
+def test_load_from_toml_with_multiple_envs(tmpdir):
+    """Asserts loader can read from TOML when multiple comma separated envs are provided"""
+    _settings = LazySettings(environments=True)
+    load(_settings, filename=TOML, env="DEVELOPMENT,EXTRA")
+    assert _settings.HOST == "devserver.com"  # comes from [development]
+    assert _settings.PASSWORD == 77777  # comes from [extra]
+    assert _settings.GLOBAL_VALUE == "global"  # comes from [global]
+
+
 def test_load_from_toml_with_invalid_unicode(tmpdir):
     # THIS TEST MUST FAIL AND BE REMOVED ON 4.0.0
     load(settings, filename=INVALID_TOML_TO_BE_REMOVED_ON_4_0_0)
diff --git a/tests_functional/issues/905_item_duplication_in_list/app.py b/tests_functional/issues/905_item_duplication_in_list/app.py
index 2030db30a..3db7d0ab9 100644
--- a/tests_functional/issues/905_item_duplication_in_list/app.py
+++ b/tests_functional/issues/905_item_duplication_in_list/app.py
@@ -18,7 +18,7 @@
 
 settings.validators.validate()
 
-assert settings.group.test_list == ["1", "2"], settings.group
+assert settings.group.test_list == ["1", "2"], settings.group.test_list
 assert settings.group.something_new == 5
 assert settings.group.another_setting == "ciao"
 assert settings.group.another == 45
diff --git a/tests_functional/legacy/inspect_load_file/.env b/tests_functional/legacy/inspect_load_file/.env
new file mode 100644
index 000000000..8466aa686
--- /dev/null
+++ b/tests_functional/legacy/inspect_load_file/.env
@@ -0,0 +1,1 @@
+INSPECT_FROM_ENV="env"
\ No newline at end of file
diff --git a/tests_functional/legacy/inspect_load_file/Makefile b/tests_functional/legacy/inspect_load_file/Makefile
new file mode 100644
index 000000000..bad989f22
--- /dev/null
+++ b/tests_functional/legacy/inspect_load_file/Makefile
@@ -0,0 +1,8 @@
+.PHONY: test
+
+test:
+	# ensure inspect shows the loader when load_file is called
+	dynaconf -i app.settings inspect -k FROM_TOML | grep '"loader": "load_file@'
+	dynaconf -i app.settings inspect -k FROM_PYTHON | grep '"loader": "load_file@'
+	dynaconf -i app.settings inspect -k FROM_YAML | grep '"loader": "yaml"'
+
diff --git a/tests_functional/legacy/inspect_load_file/app.py b/tests_functional/legacy/inspect_load_file/app.py
new file mode 100644
index 000000000..49a601614
--- /dev/null
+++ b/tests_functional/legacy/inspect_load_file/app.py
@@ -0,0 +1,9 @@
+from dynaconf import Dynaconf
+
+settings = Dynaconf(
+    envvar_prefix="INSPECT",
+    settings_file="settings.yaml",
+    load_dotenv=True,
+)
+settings.load_file("thefile.py")
+settings.load_file("thefile.toml")
diff --git a/tests_functional/legacy/inspect_load_file/settings.yaml b/tests_functional/legacy/inspect_load_file/settings.yaml
new file mode 100644
index 000000000..258fddc94
--- /dev/null
+++ b/tests_functional/legacy/inspect_load_file/settings.yaml
@@ -0,0 +1,1 @@
+from_yaml: "settings.yaml"
\ No newline at end of file
diff --git a/tests_functional/legacy/inspect_load_file/thefile.py b/tests_functional/legacy/inspect_load_file/thefile.py
new file mode 100644
index 000000000..baf24ddb1
--- /dev/null
+++ b/tests_functional/legacy/inspect_load_file/thefile.py
@@ -0,0 +1,1 @@
+FROM_PYTHON = "thefile.py"
diff --git a/tests_functional/legacy/inspect_load_file/thefile.toml b/tests_functional/legacy/inspect_load_file/thefile.toml
new file mode 100644
index 000000000..23a97382b
--- /dev/null
+++ b/tests_functional/legacy/inspect_load_file/thefile.toml
@@ -0,0 +1,1 @@
+from_toml = "thefile.toml"
\ No newline at end of file
diff --git a/tests_functional/typed/insert/.env b/tests_functional/typed/insert/.env
new file mode 100644
index 000000000..76f785054
--- /dev/null
+++ b/tests_functional/typed/insert/.env
@@ -0,0 +1,7 @@
+INSERT_APP_COLORS="@insert 1 green"
+INSERT_APP_NUMBERS="@insert 42"
+INSERT_APP_PERSON__TEAMS="@insert 0 user"
+INSERT_APP_PERSON__ROLES="@insert 4 can_do_things"
+
+# does not exist, so it will be created as a one element list
+INSERT_APP_FRUITS="@insert 55 apple"
\ No newline at end of file
diff --git a/tests_functional/typed/insert/app.py b/tests_functional/typed/insert/app.py
new file mode 100644
index 000000000..669874f9d
--- /dev/null
+++ b/tests_functional/typed/insert/app.py
@@ -0,0 +1,60 @@
+from __future__ import annotations
+
+from dynaconf.typed import DictValue
+from dynaconf.typed import Dynaconf
+from dynaconf.typed import Options
+
+
+class Person(DictValue):
+    name: str
+    teams: list
+
+
+class Settings(Dynaconf):
+    fruits: list
+    colors: list = ["blue"]
+    numbers: list = [1, 2, 3]
+
+    # TODO: change this to use the Person class
+    person: dict = {"name": "Bruno", "teams": ["dev"]}
+
+    dynaconf_options = Options(
+        envvar_prefix="INSERT_APP",
+        settings_files=["settings.toml"],
+        load_dotenv=True,
+    )
+
+
+settings = Settings()
+
+expected_colors = [
+    "red",  # comes from settings.toml
+    "green",  # comes from .env
+    "blue",  # comes from default init parameter
+]
+
+expected_teams = [
+    "user",  # comes from .env
+    "staff",  # comes from settings.toml
+    "dev",  # comes from default init parameter
+]
+
+expected_roles = ["can_do_things"]  # comes from .env
+
+expected_numbers = [
+    42,  # comes from .env
+    1,
+    2,
+    3,
+]
+
+expected_fruits = [
+    "apple",  # comes from .env
+]
+
+assert settings.colors == expected_colors, settings.colors
+assert settings.person.name == "Bruno", settings.person
+assert settings.person.teams == expected_teams, settings.person.teams
+assert settings.person.roles == expected_roles, settings.person.roles
+assert settings.numbers == expected_numbers, settings.numbers
+assert settings.fruits == expected_fruits, settings.fruits
diff --git a/tests_functional/typed/insert/settings.toml b/tests_functional/typed/insert/settings.toml
new file mode 100644
index 000000000..ec122d183
--- /dev/null
+++ b/tests_functional/typed/insert/settings.toml
@@ -0,0 +1,2 @@
+colors = "@insert 0 red"
+person__teams = "@insert 0 staff"
\ No newline at end of file
diff --git a/tests_functional/typed/multiple_env_prefixes/.env b/tests_functional/typed/multiple_env_prefixes/.env
new file mode 100644
index 000000000..5c4dc2a36
--- /dev/null
+++ b/tests_functional/typed/multiple_env_prefixes/.env
@@ -0,0 +1,3 @@
+APP_DATA__DB__HOST=localhost
+OTHER_DATA__DB__PORT=5432
+YET_ANOTHER_DATA__DB__USER=postgres
\ No newline at end of file
diff --git a/tests_functional/typed/multiple_env_prefixes/app.py b/tests_functional/typed/multiple_env_prefixes/app.py
new file mode 100644
index 000000000..9df8a974b
--- /dev/null
+++ b/tests_functional/typed/multiple_env_prefixes/app.py
@@ -0,0 +1,18 @@
+from __future__ import annotations
+
+from dynaconf.typed import Dynaconf
+from dynaconf.typed import Options
+
+
+class Settings(Dynaconf):
+    dynaconf_options = Options(
+        envvar_prefix="APP,OTHER,YET_ANOTHER,NOTUSED",
+        load_dotenv=True,
+    )
+
+
+settings = Settings()
+
+assert settings.DATA.DB.host == "localhost"
+assert settings.DATA.DB.port == 5432
+assert settings.DATA.DB.user == "postgres"
diff --git a/tests_functional/typed/multiple_envs_at_same_time/Makefile b/tests_functional/typed/multiple_envs_at_same_time/Makefile
new file mode 100644
index 000000000..fbcfc55b0
--- /dev/null
+++ b/tests_functional/typed/multiple_envs_at_same_time/Makefile
@@ -0,0 +1,25 @@
+.PHONY: test
+
+test:
+	python app.py
+	
+	python app.py | grep "HOST='default.com'"
+	python app.py | grep "DEBUG=False"
+
+	BLARG_MODE=production python app.py | grep "HOST='production.com'"
+	
+	BLARG_MODE=extra python app.py | grep "HOST='default.com'"
+	BLARG_MODE=extra python app.py | grep "DEBUG=True"
+	BLARG_MODE=extra python app.py | grep "PORT=1234"
+
+	BLARG_MODE=production,extra python app.py | grep "HOST='production.com'"
+	BLARG_MODE=production,extra python app.py | grep "PORT=1234"
+	BLARG_MODE=production,extra python app.py | grep "DEBUG=True"
+	
+	BLARG_MODE=production,extra dynaconf -i app.settings inspect -k COUNTER
+	BLARG_MODE=production,extra dynaconf -i app.settings inspect -k COLORS
+	BLARG_MODE=production,extra dynaconf -i app.settings inspect -k PERSON
+	BLARG_MODE=production,extra dynaconf -i app.settings inspect -k INITIAL
+	BLARG_MODE=production,extra dynaconf -i app.settings inspect -k CALCULATED
+	BLARG_MODE=production,extra dynaconf -i app.settings inspect -k XCALCULATED
+
diff --git a/tests_functional/typed/multiple_envs_at_same_time/app.py b/tests_functional/typed/multiple_envs_at_same_time/app.py
new file mode 100644
index 000000000..3562c4979
--- /dev/null
+++ b/tests_functional/typed/multiple_envs_at_same_time/app.py
@@ -0,0 +1,19 @@
+from __future__ import annotations
+
+from dynaconf.typed import Dynaconf
+from dynaconf.typed import Options
+
+
+class Settings(Dynaconf):
+    dynaconf_options = Options(
+        environments=True,
+        settings_files=["defaults.py"],
+        env_switcher="BLARG_MODE",
+        envvar_prefix="BLARG",
+    )
+
+
+settings = Settings()
+
+PORT = settings.get("PORT", 0)
+print(f"{settings.DEBUG=} {settings.HOST=} {PORT=}")
diff --git a/tests_functional/typed/multiple_envs_at_same_time/config/__init__.py b/tests_functional/typed/multiple_envs_at_same_time/config/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests_functional/typed/multiple_envs_at_same_time/config/defaults.py b/tests_functional/typed/multiple_envs_at_same_time/config/defaults.py
new file mode 100644
index 000000000..ede8123df
--- /dev/null
+++ b/tests_functional/typed/multiple_envs_at_same_time/config/defaults.py
@@ -0,0 +1,8 @@
+from __future__ import annotations
+
+DEBUG = False
+HOST = "default.com"
+COUNTER = 0
+COLORS = ["red"]
+PERSON = {"name": "Bruno"}
+INITIAL = 10
diff --git a/tests_functional/typed/multiple_envs_at_same_time/config/extra_defaults.py b/tests_functional/typed/multiple_envs_at_same_time/config/extra_defaults.py
new file mode 100644
index 000000000..da25f8c46
--- /dev/null
+++ b/tests_functional/typed/multiple_envs_at_same_time/config/extra_defaults.py
@@ -0,0 +1,8 @@
+from __future__ import annotations
+
+DEBUG = True
+PORT = 1234
+COUNTER = 2
+COLORS = "@insert 0 black"
+PERSON__info__team = "devs"
+XCALCULATED = "@get CALCULATED"
diff --git a/tests_functional/typed/multiple_envs_at_same_time/config/production_defaults.py b/tests_functional/typed/multiple_envs_at_same_time/config/production_defaults.py
new file mode 100644
index 000000000..a6308273b
--- /dev/null
+++ b/tests_functional/typed/multiple_envs_at_same_time/config/production_defaults.py
@@ -0,0 +1,9 @@
+from __future__ import annotations
+
+HOST = "production.com"
+USER = "admin"
+COUNTER = 1
+COLORS = "@merge green,blue"
+PERSON__role = "admin"
+INITIAL = 12
+CALCULATED = "@int @jinja {{this.INITIAL * 2}}"