Repository: dynaconf/dynaconf
Pull request #958 (2023-07-12 22:03:52)

Inspect Review
This is a review PR for the Inspect feature #939 

## Proposals

(I'm actively updating this section)

### Bug Fixes

- [x] CLI "django app detected" message breaks the output format (e.g. `dynaconf inspect | jq`)
- [x] `pre_load` and `dynaconf_hooks` identifiers are not preserved in history.
- [x] Compatibility break when defining `loader_identifier` with `str` (because of the new `SourceMetadata`)

### API changes in `dynaconf.utils.inspect`

- [x] rename `key_dotted_path`:
from `inspect_settings(key_dotted_path: str, ...)` to `inspect_settings(key: str, ...)`
from `get_history(key_dotted_path: str, ...)` to `get_history(key: str, ...)`

Justification: Explicit names are usually good, but in the context of Dynaconf `key` is used as dotted-path (as in `setting.get` for example), which makes it more consistent. Also, the docstring makes the usage clear and, if we support other path access (like `foo__bar`), `key` can accommodate that semantic.

- [x] merge/rename `custom_dumper` and `output_format` to `dumper: Callable | str (preset string)`
- [x] enforce the use of kwargs except for `settings`
- [x] add `report_builder` to customize the structure of the output report

### `Inspect` default report behavior

- [x] Change `inspect` output
  from `header.filter.history_order=ascdending | descending`
  to `header.chronological_sort=True | False` (more clear information)
- [x] Limit the number of history entries, similar to `git log -1` or `head -n 1`. Use 1 by default (users feedback)

### Clarify History Report behavior

The behavior of the history report may be counter-intuitive, specially with merge flag. It kinda snapshots the state of settings at the given source, but not exactly. This should be better explored, defined and documented.

Example, when source B merges data after A:
```python
# A loads [1, 2, 3], then: settings inspect -k LISTY:
A -> listy=[1, 2, 3]

# B loads listy=[4, "dynaconf_merge"], then: settings inspect -k LISTY:
B -> listy=[1, 2, 3, 4] # not listy=[4]
A -> listy=[1, 2, 3]
```
### Ideas
- Add `cli_inspect_options={...}` to `Dynaconf` instantiation to set custom default parameters (like limit and sort)

### General
- [ ] Add tests for new uncovered inspect-related code
- [x] Update documentation

netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *dynaconf* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | e386d452ec471acff2760cda27db0d3cc8c99c92 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/dynaconf/deploys/64af234a53f2c50008831578 |
rochacbruno: When dealing with inspect I found some things worth revisiting.

1. The CLI is printing "django app detected" and this breaks piping the output to json parser, we need to omit that line
2. Dynaconf_hooks.py will mutate the settings, but history is not kept, we need to track it, maybe by adding dynamic identifiers on every executed hook function
3. On a django application, when multiple settings.PY files are loaded, the individual filename is not preserved in history
4. When using 	`pre_load` the identifier is also not preserved in history.

I am adding this notes here because I will test more before I add issues
rochacbruno: @pedro-psb my pr #975 fixed some of the issues listed here, so what is pending is:

- [ ] Limit history results like `git log -n x`
- [ ] Renaming key_dotted_path parameter
- [ ] Output change for ascending/chrono order

If you prefer, we can have separate issues for those.

About the `Clarify History Report behavior` I also changed that on my PR #975 as now the history saves the value pre-parsing, we are going to see in history the state of value before merging.

So we have to keep the header where the `current_value` is displayed, that will be the only place to see the final value.

pedro-psb: > @pedro-psb my pr #975 fixed some of the issues listed here, so what is pending is:
> 
>     * [ ]  Limit history results like `git log -n x`
> 
>     * [ ]  Renaming key_dotted_path parameter
> 
>     * [ ]  Output change for ascending/chrono order
> 
> 
> If you prefer, we can have separate issues for those.
> 
> About the `Clarify History Report behavior` I also changed that on my PR #975 as now the history saves the value pre-parsing, we are going to see in history the state of value before merging.
> 
> So we have to keep the header where the `current_value` is displayed, that will be the only place to see the final value.

Great! I think there is no need for separate issues. I'll just start working on those remaining tasks and then update the documentation with changes included in the PR, such as the new `--all` capability.
pedro-psb: @rochacbruno I want an opinion on some changes.

### Output presentation

The general purpose is to make the output interpretation more straightforward and easily definable:
- `header`: contains info on how inspect was called
- `current`: contains the current value as defined in header (filtered key/env)
- `history`: contains the loading history as defined in header (filtered key/env + sorting/limiting)

```yaml
# Original
header:
  filters:
    key: None
    env: None
    history_ordering: "ascending"
  active_value:
    {...}
history:
  [...]

# Proposed
header:
  key_filter: None
  env_filter: None
  new_first: True
  history_limit: None
current:
  {...}
history:
  [...]
```

### Limit history default

Should the history limit be set to 1 or to `all`, by default?

### Limit CLI name

I intent to use this:

```bash
inspect [-n|--limit] <number> # similar to gnu 'head' and 'git log'
```

rochacbruno: LGTM! @pedro-psb I like having current value separated and the `-n` option is also useful.

We must default for showing everything by default and then allow `-n` to start limiting, just like git log does
codecov-commenter: ## [Codecov](https://app.codecov.io/gh/dynaconf/dynaconf/pull/958?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dynaconf) Report
> Merging [#958](https://app.codecov.io/gh/dynaconf/dynaconf/pull/958?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dynaconf) (b30e521) into [master](https://app.codecov.io/gh/dynaconf/dynaconf/commit/9ca687a4e6df9d67cf060a1fe092a782a256ec24?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dynaconf) (9ca687a) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

:exclamation: Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please [install the Github App Integration](https://github.com/apps/codecov) for your organization. [Read more](https://about.codecov.io/blog/codecov-is-updating-its-github-integration/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dynaconf).

```diff
@@           Coverage Diff           @@
##           master     #958   +/-   ##
=======================================
  Coverage   98.95%   98.95%           
=======================================
  Files          23       23           
  Lines        2196     2204    +8     
=======================================
+ Hits         2173     2181    +8     
  Misses         23       23           
```


| [Files Changed](https://app.codecov.io/gh/dynaconf/dynaconf/pull/958?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dynaconf) | Coverage Δ | |
|---|---|---|
| [dynaconf/cli.py](https://app.codecov.io/gh/dynaconf/dynaconf/pull/958?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dynaconf#diff-ZHluYWNvbmYvY2xpLnB5) | `99.27% <100.00%> (+<0.01%)` | :arrow_up: |
| [dynaconf/utils/inspect.py](https://app.codecov.io/gh/dynaconf/dynaconf/pull/958?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dynaconf#diff-ZHluYWNvbmYvdXRpbHMvaW5zcGVjdC5weQ==) | `96.29% <100.00%> (+0.25%)` | :arrow_up: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dynaconf)


Patch:
diff --git a/docs/advanced.md b/docs/advanced.md
index 0dce3f7..f347487 100644
--- a/docs/advanced.md
+++ b/docs/advanced.md
@@ -81,7 +81,7 @@ settings = Dynaconf(post_hooks=hook_function)
 You can also set the merging individually for each settings variable as seen on
 [merging](/merging/) documentation.
 
-## `inspect_settings`
+## Inspecting History
 
 > **NEW** in version 3.2.0
 
@@ -89,61 +89,97 @@ You can also set the merging individually for each settings variable as seen on
     This feature is in **tech preview** the usage interface and output format is
     subject to change.
 
-Inspect the loading history of all ingested data by its source identities.
+This feature purpose is to allow tracking any config-data loading history, that is, the loading steps that lead to a given final value. It can return a dict data report, print to `stdout` or write to a file, and is also available as a [CLI command](/cli#dynaconf-inspect-tech-preview).
 
-This function relates all ingested data to its source by establishing a set of
-source metadata on loading-time:
+It works by setting a *SourceMetadata* object to every ingested data, so it can be recovered and filtered to generate meaningful reports. The properties of this object are:
 
-- **loader**: which kind of loader *(yaml, envvar, validation_default)*
-- **identifier**: specific identifier *(eg: filename for files)*
-- **env**: which env this data belongs to *(global, main, development, etc)*
+- **loader**: the loader type *(e.g, yaml, envvar, validation_default)*
+- **identifier**: specific identifier *(e.g, filename for files)*
+- **env**: which environ this data belongs to *(global, main, development, etc)*
 - **merged**: if this data has been merged *(True or False)*
 
-It also provides key and environment filters to narrow down the results,
-descending/ascending sorting flag and some built-in output formats.
+Dynaconf offers two very similar utility functions to get data from it: `inspect_settings` and `get_history`. The difference is that `get_history` returns a simple list of dict-records with source metadata, while `inspect_settings` focus on generating a printable report (and thus it offers some convenience options for that).
 
-Sample usage:
+They are available at `dynaconf.inspect_settings` and `dynaconf.get_history`
+
+### `inspect_settings`
+
+By default, it only returns a dict-report. You can optionally print the report in a specific format, such as "yaml" or "json", or write it to a file:
 
 ```python
-$ export DYNACONF_FOO=from_environ
-$ export DYNACONF_BAR=environ_only
-$ cat file_a.yaml
-foo: from_yaml
-
-$ python
->>> from dynaconf import Dynaconf, inspect_settings
->>> settings = Dynaconf()
->>> inspect_settings(settings, key_dotted_path="foo", output_format="yaml")
+# default: returns python object
+>>> inspect_settings(settings_obj)
+{
+	"header": ... # filtering options used
+	"current": ... # currently active data/value
+	"history": ... # list of history records
+}
+
+# print report
+>>> inspect_settings(setting_obj, print_report=True, dumper="yaml")
 header:
-  filters:
-    env: None
-    key: foo
-    history_ordering: ascending
-  active_value: from_environ
+  - ...
+current:
+  - ...
+history:
+  - ...
+
+# write to file
+>>> inspect_settings(settings_obj, to_file="report.yaml", dumper="json")
+```
+
+The history looks like this:
+
+```python
+>>> inspect_settings(
+>>> 	settings_obj,
+>>> 	key="foo",
+>>> 	print_report=True,
+>>> 	dumper="yaml"
+>>> )
+>>>
+
+header:
+- ...
+current:
+- ...
 history:
-- loader: yaml
-  identifier: file_a.yaml
-  env: default
-  merged: false
-  value:
-    FOO: from_yaml
 - loader: env_global
   identifier: unique
   env: global
   merged: false
-  value:
-    FOO: from_environ
-    BAR: environ_only
+  value: FOO
+- loader: toml
+  identifier: path/to/file.yaml
+  env: development
+  merged: false
+  value: FOO
 ```
 
-To save to a file use:
+<h4>Options Overview</h4>
 
-```python
-inspect_setting(setting, to_file="filename.json", output_format="json")
-```
+There are some pre-defined ways that you can filter and customize the report.
+Here is a summary of the available argument options:
+
+Positional:
+
+- `settings` (required): A Dynaconf instance
+- `key`: Filter by this key. E.g "path.to.key"
+- `env`: Filter by this env. E.g "production"
+
+Kwargs-only:
+
+- `new_first`: If True, uses newest to oldest loading order.
+- `history_limit`: Limits how many entries are shown. By default, show all.
+- `include_internal`: If True, include internal loaders (e.g. defaults). This has effect only if key is not provided.
+- `to_file`: If specified, write dumped report to this filename.
+- `print_report`: If true, prints the dumped report to stdout.
+- `dumper`: Accepts preset strings (e.g. "yaml", "json") or custom dumper callable ``(dict, TextIO) -> None``. If not provided, does nothing.
+- `report_builder`: If provided, it is used to generate the report dict.
+
+### `get_history`
 
-Dynaconf supports some builtin formats, but you can use a custom dumper too that
-can dump nested dict/list structure into a `TextIO` stream.
+Returns a list of history-records (the same as in the `history` key of `inspect_settings` records. In fact, `inspect_settings` uses `get_history`, so this is just available if your main goal is to use the data directly. It offer some basic filtering capabilities, but it is assumed that if you choose to use this you'll probably want to process and filter the data by your own.
 
 ## Update dynaconf settings using command-line arguments (cli)
 
diff --git a/docs/cli.md b/docs/cli.md
index 0feee68..5c20238 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -113,7 +113,7 @@ Note that `-i`/`--instance` cannot be used with `init` as `-i` must point to an
 
 Inspect and dump data's loading history about a specific key or environment.
 
-This command is also available as a utility function at `dynaconf.inspect_settings` ([learn more](/advanced#inspect_settings)).
+It is also available as a [utility function](/advanced#inspecting-history).
 
 ```
 Usage: dynaconf inspect [OPTIONS]
@@ -127,7 +127,9 @@ Options:
   -e, --env TEXT                  Filters result by environment.
   -f, --format [yaml|json|json-compact]
                                   The output format.
-  -d, --descending                Set history loading order to 'last-first'
+  -s, --old-first                 Invert history sorting to 'old-first'
+  -n, --limit INTEGER             Limits how many history entries are shown.
+  -a, --all                       Show dynaconf internal settings?
   --help                          Show this message and exit.
 ```
 
@@ -135,13 +137,14 @@ Options:
 Sample usage:
 
 ```yaml
-# dynaconf -i app.settings inspect -k foo -f yaml
+>>> dynaconf -i app.settings inspect -k foo -f yaml -n 2
 header:
-  filters:
-    env: None
-    key: foo
-    history_ordering: ascending
-  active_value: from_environ
+  env_filter: 'foo'
+  key_filter: None
+  new_first: 'True'
+  history_limit: None
+  include_internal: 'False'
+current: from_environ
 history:
 - loader: yaml
   identifier: file_a.yaml
diff --git a/dynaconf/__init__.py b/dynaconf/__init__.py
index 3584a24..a40baf7 100644
--- a/dynaconf/__init__.py
+++ b/dynaconf/__init__.py
@@ -4,6 +4,7 @@ from dynaconf.base import LazySettings  # noqa
 from dynaconf.constants import DEFAULT_SETTINGS_FILES
 from dynaconf.contrib import DjangoDynaconf  # noqa
 from dynaconf.contrib import FlaskDynaconf  # noqa
+from dynaconf.utils.inspect import get_history
 from dynaconf.utils.inspect import inspect_settings
 from dynaconf.utils.parse_conf import add_converter  # noqa
 from dynaconf.utils.parse_conf import DynaconfFormatError  # noqa
@@ -35,6 +36,7 @@ __all__ = [
     "DjangoDynaconf",
     "add_converter",
     "inspect_settings",
+    "get_history",
     "DynaconfFormatError",
     "DynaconfParseError",
 ]
diff --git a/dynaconf/cli.py b/dynaconf/cli.py
index 71d1eae..3e25776 100644
--- a/dynaconf/cli.py
+++ b/dynaconf/cli.py
@@ -819,7 +819,9 @@ INSPECT_FORMATS = list(builtin_dumpers.keys())
 
 @main.command()
 @click.option("--key", "-k", help="Filters result by key.")
-@click.option("--env", "-e", help="Filters result by environment.", default="")
+@click.option(
+    "--env", "-e", help="Filters result by environment.", default=None
+)
 @click.option(
     "--format",
     "-f",
@@ -828,21 +830,32 @@ INSPECT_FORMATS = list(builtin_dumpers.keys())
     type=click.Choice(INSPECT_FORMATS),
 )
 @click.option(
-    "--descending",
-    "-d",
-    help="Set history loading order to 'last-first'",
-    default=False,
+    "--old-first",
+    "new_first",
+    "-s",
+    help="Invert history sorting to 'old-first'",
+    default=True,
     is_flag=True,
 )
+@click.option(
+    "--limit",
+    "history_limit",
+    "-n",
+    default=None,
+    type=int,
+    help="Limits how many history entries are shown.",
+)
 @click.option(
     "--all",
     "_all",
     "-a",
     default=False,
     is_flag=True,
-    help="show dynaconf internal settings?",
+    help="Show dynaconf internal settings?",
 )
-def inspect(key, env, format, descending, _all):  # pragma: no cover
+def inspect(
+    key, env, format, new_first, history_limit, _all
+):  # pragma: no cover
     """
     Inspect the loading history of the given settings instance.
 
@@ -851,11 +864,13 @@ def inspect(key, env, format, descending, _all):  # pragma: no cover
     try:
         inspect_settings(
             settings,
-            key_dotted_path=key,
-            env=env,
-            output_format=format,
-            ascending_order=(not descending),
+            key=key,
+            env=env or None,
+            dumper=format,
+            new_first=new_first,
             include_internal=_all,
+            history_limit=history_limit,
+            print_report=True,
         )
         click.echo()
     except (KeyNotFoundError, EnvNotFoundError, OutputFormatError) as err:
diff --git a/dynaconf/utils/inspect.py b/dynaconf/utils/inspect.py
index d4521f5..42533b3 100644
--- a/dynaconf/utils/inspect.py
+++ b/dynaconf/utils/inspect.py
@@ -4,9 +4,12 @@ from __future__ import annotations
 import json
 import sys
 from functools import partial
+from pathlib import PosixPath
 from typing import Any
 from typing import Callable
 from typing import Literal
+from typing import Optional
+from typing import Protocol
 from typing import TextIO
 from typing import TYPE_CHECKING
 from typing import Union
@@ -32,8 +35,29 @@ builtin_dumpers = {
     "json-compact": json_compact,
 }
 
-OutputFormat = Union[Literal["yaml"], Literal["json"], Literal["json-compact"]]
-DumperType = Callable[[dict, TextIO], None]
+DumperPreset = Union[Literal["yaml"], Literal["json"], Literal["json-compact"]]
+
+
+class DumperType(Protocol):
+    def __call__(
+        self, data: dict, text_stream: TextIO
+    ) -> None:  # pragma: no cover
+        ...
+
+
+class ReportBuilderType(Protocol):
+    def __call__(
+        self,
+        *,
+        key: str | None,
+        env: str | None,
+        new_first: bool | None,
+        include_internal: bool | None,
+        history_limit: int | None,
+        current: Any,
+        history: list[dict] | None,
+    ) -> dict:  # pragma: no cover
+        ...
 
 
 class KeyNotFoundError(Exception):
@@ -48,156 +72,187 @@ class OutputFormatError(Exception):
     pass
 
 
-# Public
-
-
 def inspect_settings(
     settings: Settings | LazySettings,
-    key_dotted_path: str = "",
-    env: str = "",
-    ascending_order: bool = True,
-    to_file: str = "",
-    output_format: OutputFormat = "yaml",
-    custom_dumper: DumperType | None = None,
+    key: str | None = None,
+    env: str | None = None,
+    *,
+    new_first: bool = True,
+    history_limit: int | None = None,
     include_internal: bool = False,
+    to_file: str | PosixPath | None = None,
+    print_report: bool = False,
+    dumper: DumperPreset | DumperType | None = None,
+    report_builder: ReportBuilderType | None = None,
 ):
     """
-    Prints loading history about a specific key (as dotted path string)
-    Optionally, writes data to file in desired format instead.
-
-    Args:
-        settings: a Dynaconf instance
-        key_dotted_path: string dotted path. E.g "path.to.key"
-        ascending_order: if True, newest to oldest loading order
-        fo_file: if specified, write to this filename
-        output_format: available output format options
-        custom_dumper: if provided, it is used instead of builtins
-        include_internal: if True, include internal loaders (e.g. defaults)
-                          This has effect only if key is not provided.
+    Print and return the loading history of a settings object.
+
+    Optional arguments must be provided as kwargs.
+
+    :param settings: A Dynaconf instance
+    :param key: String dotted path. E.g "path.to.key"
+    :param env: Filter by this env
+
+    :param new_first: If True, uses newest to oldest loading order
+    :param history_limit: Limits how many entries are shown
+    :param include_internal: If True, include internal loaders (e.g. defaults).
+        This has effect only if key is not provided.
+    :param to_file: If specified, write to this filename
+    :param print_report: If true, prints the dumped report to stdout
+    :param dumper: Accepts preset strings (e.g. "yaml", "json") or custom
+        dumper callable ``(dict, TextIO) -> None``. Defaults to "yaml"
+    :param report_builder: if provided, it is used to generate the report
+
+    :return: Dict with a dict containing report data
+    :rtype: dict
     """
-    # get filtered history
+    # choose dumper and report builder
+    if dumper is None:
+        _dumper = builtin_dumpers["yaml"]
+    elif isinstance(dumper, str):
+        _dumper = builtin_dumpers.get(dumper)
+        if _dumper is None:
+            raise OutputFormatError(
+                f"The desired format is not available: {dumper!r}"
+            )
+    else:
+        _dumper = dumper
+
+    _report_builder = report_builder or _default_report_builder
+
+    # get history and apply optional arguments
     original_settings = settings
-    settings = settings if not env else settings.from_env(env)
 
-    setting_envs = {_env.env for _env in settings._loaded_by_loaders.keys()}
-    if env and env.lower() not in setting_envs:
-        raise EnvNotFoundError(f"The requested env is not valid: {env!r}")
+    env_filter = None  # type: ignore
+    if env:
+        settings = settings.from_env(env)
+        registered_envs = {
+            src_meta.env for src_meta in settings._loaded_by_loaders.keys()
+        }
+        if env.lower() not in registered_envs:
+            raise EnvNotFoundError(f"The requested env is not valid: {env!r}")
 
-    def env_filter(src: SourceMetadata) -> bool:
-        return src.env.lower() == env.lower() if env else True
+        def env_filter(src: SourceMetadata) -> bool:
+            return src.env.lower() == env.lower()
 
     history = get_history(
         original_settings,
-        key_dotted_path=key_dotted_path,
-        filter_src_metadata=env_filter,
+        key=key,
+        filter_callable=env_filter,
         include_internal=include_internal,
     )
-    if key_dotted_path and not history:
-        raise KeyNotFoundError(
-            f"The requested key was not found: {key_dotted_path!r}"
-        )
 
-    # setup output format
-    if ascending_order:
+    if new_first:
         history.reverse()
-    history_order = "ascending" if ascending_order else "descending"
-
-    header_env = env or "None"
-    header_key = key_dotted_path or "None"
-    header_value = (
-        settings.get(key_dotted_path)
-        if key_dotted_path
-        else settings.as_dict()
-    )
 
-    output_dict = {
-        "header": {
-            "filters": {
-                "env": header_env,
-                "key": header_key,
-                "history_ordering": history_order,
-            },
-            "active_value": header_value,
-        },
-        "history": history,
-    }
+    if history_limit:
+        history = history[:history_limit]
 
-    output_dict["header"]["active_value"] = _ensure_serializable(
-        output_dict["header"]["active_value"]
+    if key:
+        current_value = settings.get(key)
+    else:
+        current_value = settings.as_dict()
+
+    # format output
+    dict_report = _report_builder(
+        history=history,
+        current=current_value,
+        key=key,
+        env=env,
+        new_first=new_first,
+        history_limit=history_limit,
+        include_internal=include_internal,
     )
 
-    # choose dumper
-    try:
-        dumper = builtin_dumpers[output_format.lower()]
-    except KeyError:
-        raise OutputFormatError(
-            f"The desired format is not available: {output_format!r}"
-        )
+    dict_report["current"] = _ensure_serializable(dict_report["current"])
 
-    dumper = dumper if not custom_dumper else custom_dumper
+    # write to stdout AND/OR to file AND return
+    if to_file is not None:
+        _encoding = settings.get("ENCODER_FOR_DYNACONF")
+        with open(to_file, "w", encoding=_encoding) as file:
+            _dumper(dict_report, file)
 
-    # write to stdout or to file
-    if not to_file:
-        dumper(output_dict, sys.stdout)
-    else:
-        with open(
-            to_file, "w", encoding=settings.get("ENCODER_FOR_DYNACONF")
-        ) as f:
-            dumper(output_dict, f)
+    if print_report is True:
+        _dumper(dict_report, sys.stdout)
+
+    return dict_report
+
+
+def _default_report_builder(**kwargs) -> dict:
+    """
+    Default inspect report builder.
+
+    Accept the kwargs passed inside `inspect_settings` and returns a dict with
+    {header, current, history} as top-level keys.
+    """
+    return {
+        "header": {
+            "env_filter": str(kwargs.get("env")),
+            "key_filter": str(kwargs.get("key")),
+            "new_first": str(kwargs.get("new_first")),
+            "history_limit": str(kwargs.get("history_limit")),
+            "include_internal": str(kwargs.get("include_internal")),
+        },
+        "current": kwargs.get("current"),
+        "history": kwargs.get("history"),
+    }
 
 
 def get_history(
     obj: Settings | LazySettings,
-    key_dotted_path: str = "",
-    filter_src_metadata: Callable[[SourceMetadata], bool] = lambda x: True,
+    key: str | None = None,
+    *,
+    filter_callable: Callable[[SourceMetadata], bool] | None = None,
     include_internal: bool = False,
+    history_limit: int | None = None,
 ) -> list[dict]:
     """
     Gets data from `settings.loaded_by_loaders` in order of loading with
     optional filtering options.
 
-    Returns a list of dict in ascending order, where the
-    dict contains the data and it's source metadata.
+    Returns a list of dict in new-first order, where the dict contains the
+    data and it's source metadata.
 
-    Args:
-        obj: Setting object which contain the data
-        key_dotted_path: dot-path to desired key. Use all if not provided
-        filter_src_metadata: takes SourceMetadata and returns a boolean
-        include_internal: if True, include internal loaders (e.g. defaults)
-                          This has effect only if key is not provided.
+    :param obj: Setting object which contain the data
+    :param key: Key path to desired key. Use all if not provided
+    :param filter_callable: Takes SourceMetadata and returns a boolean
+    :param include_internal: If True, include internal loaders (e.g. defaults).
+        This has effect only if key is not provided.
+    history_limit: limits how many entries are shown
 
     Example:
         >>> settings = Dynaconf(...)
-        >>> get_history_data(settings)
+        >>> _get_history(settings)
         [
             {
                 "loader": "yaml"
                 "identifier": "path/to/file.yml"
                 "env": "default"
-                "data": {"foo": 123, "spam": "eggs"
+                "data": {"foo": 123, "spam": "eggs"}
             },
-            {
-                "loader": "yaml"
-                "identifier": "path/to/file.yml"
-                "env": "default"
-                "data": {"foo": 123, "spam": "eggs"
-            }
+            ...
         ]
     """
+    if filter_callable is None:
+        filter_callable = lambda x: True  # noqa
+
     sep = obj.get("NESTED_SEPARATOR_FOR_DYNACONF", "__")
+
     # trigger key based hooks
-    if key_dotted_path:
-        obj.get(key_dotted_path)  # noqa
+    if key:
+        obj.get(key)  # noqa
 
     internal_identifiers = ["default_settings", "_root_path"]
     result = []
     for source_metadata, data in obj._loaded_by_loaders.items():
         # filter by source_metadata
-        if filter_src_metadata(source_metadata) is False:
+        if filter_callable(source_metadata) is False:
             continue
 
+        # filter by internal identifiers
         if (
-            not key_dotted_path
+            not key
             and include_internal is False
             and source_metadata.identifier in internal_identifiers
         ):
@@ -205,21 +260,17 @@ def get_history(
 
         # filter by key path
         try:
-            data = (
-                _get_data_by_key(data, key_dotted_path, sep=sep)
-                if key_dotted_path
-                else data
-            )
+            data = _get_data_by_key(data, key, sep=sep) if key else data
         except KeyError:
             continue  # skip: source doesn't contain the requested key
 
-        # Format output
+        # Normalize output
         data = _ensure_serializable(data)
         result.append({**source_metadata._asdict(), "value": data})
 
-    if key_dotted_path and not result:
+    if key and not result:
         # Key may be set in obj but history not tracked
-        if (data := obj.get(key_dotted_path, empty)) is not empty:
+        if (data := obj.get(key, empty)) is not empty:
             generic_source_metadata = SourceMetadata(
                 loader="undefined",
                 identifier="undefined",
@@ -227,6 +278,10 @@ def get_history(
             data = _ensure_serializable(data)
             result.append({**generic_source_metadata._asdict(), "value": data})
 
+        # Raise if still not found
+        if key and not result:
+            raise KeyNotFoundError(f"The requested key was not found: {key!r}")
+
     return result
 
 
@@ -261,6 +316,7 @@ def _get_data_by_key(
     """
     if not isinstance(data, DynaBox):
         data = DynaBox(data)  # DynaBox can handle insensitive keys
+
     if sep in key_dotted_path:
         key_dotted_path = key_dotted_path.replace(sep, ".")
 


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index faa3d46..c679ae5 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -4,6 +4,7 @@ import json
 import os
 from pathlib import Path
 from textwrap import dedent
+from unittest import mock
 
 import pytest
 
@@ -16,14 +17,14 @@ from dynaconf.cli import WRITERS
 from dynaconf.utils.files import read_file
 from dynaconf.vendor.click.testing import CliRunner
 
-
 settings = LazySettings(OPTION_FOR_TESTS=True, environments=True)
 
 
 def run(cmd, env=None, attr="output"):
-    runner = CliRunner()
-    result = runner.invoke(main, cmd, env=env, catch_exceptions=False)
-    return getattr(result, attr)
+    with mock.patch.dict(os.environ, {}):
+        runner = CliRunner()
+        result = runner.invoke(main, cmd, env=env, catch_exceptions=False)
+        return getattr(result, attr)
 
 
 def test_version():
@@ -486,15 +487,26 @@ def create_file(filename: str | Path, data: str):
     return filename
 
 
-def clear_dotenv(tmp_path):
-    Path(tmp_path / ".env").unlink()
+def get_current_test_name():
+    """
+    Utility to get the current test name.
+
+    Instance names should be unique for proper test isolation.
+    """
+    name = os.environ["PYTEST_CURRENT_TEST"]
+
+    # clean name to avoid issues with file creation:
+    # "this/test.py::"{testname}" (call)"
+    start = name.find("::") + 2
+    end = name.find(" (call")
+    name = name[start:end]
+    return name
 
 
 def test_inspect_no_args(tmp_path):
     """Inspect command with no arguments"""
-    clear_dotenv(tmp_path)
+    instance_name = get_current_test_name()
 
-    instance_name = "inspect_no_args"
     setting_file = tmp_path / "a.toml"
     environ = {"DYNACONF_FOO": "from_environ_no_args"}
     cmd = ["-i", f"{instance_name}.settings", "inspect"]
@@ -514,25 +526,34 @@ def test_inspect_no_args(tmp_path):
     expected_header = """\
         {
           "header": {
-            "filters": {
-              "env": "None",
-              "key": "None",
-              "history_ordering": "ascending"
-            },
-            "active_value": {
-              "FOO": "from_environ_no_args"
-            }
+            "env_filter": "None",
+            "key_filter": "None",
+            "new_first": "True",
+            "history_limit": "None",
+            "include_internal": "False"
           },
-        """
+          "current": {
+            "FOO": "from_environ_no_args"
+          },
+          "history": [
+            {
+              "loader": "env_global",
+              "identifier": "unique",
+              "env": "global",
+              "merged": false,
+              "value": {
+                "FOO": "from_environ_no_args"
+              }
+            },
+            """
     assert result
     assert result.startswith(dedent(expected_header))
 
 
 def test_inspect_yaml_format(tmp_path):
     """Inspect command with format argument"""
-    clear_dotenv(tmp_path)
+    instance_name = get_current_test_name()
 
-    instance_name = "inspect_yaml_format"
     setting_file = tmp_path / "a.toml"
     environ = {
         "DYNACONF_FOO": "from_environ_yaml_format",
@@ -553,12 +574,20 @@ def test_inspect_yaml_format(tmp_path):
     result = run(cmd, env=environ)
     expected_header = """\
         header:
-          filters:
-            env: None
-            key: None
-            history_ordering: ascending
-          active_value:
-            BAR: from_file_yaml_format
+          env_filter: None
+          key_filter: None
+          new_first: 'True'
+          history_limit: None
+          include_internal: 'False'
+        current:
+          BAR: from_file_yaml_format
+          FOO: from_environ_yaml_format
+        history:
+        - loader: env_global
+          identifier: unique
+          env: global
+          merged: false
+          value:
             FOO: from_environ_yaml_format
         """
     assert result
@@ -567,9 +596,8 @@ def test_inspect_yaml_format(tmp_path):
 
 def test_inspect_key_filter(tmp_path):
     """Inspect command with key filter argument"""
-    clear_dotenv(tmp_path)
+    instance_name = get_current_test_name()
 
-    instance_name = "inspect_key_filter"
     setting_file = tmp_path / "a.toml"
     environ = {"DYNACONF_FOO": "from_environ_key_filter"}
     cmd = ["-i", f"{instance_name}.settings", "inspect", "-k", "bar"]
@@ -595,13 +623,13 @@ def test_inspect_key_filter(tmp_path):
     expected_header = """\
         {
           "header": {
-            "filters": {
-              "env": "None",
-              "key": "bar",
-              "history_ordering": "ascending"
-            },
-            "active_value": "file_only"
+            "env_filter": "None",
+            "key_filter": "bar",
+            "new_first": "True",
+            "history_limit": "None",
+            "include_internal": "False"
           },
+          "current": "file_only",
         """
     assert result
     assert result.startswith(dedent(expected_header))
@@ -609,9 +637,8 @@ def test_inspect_key_filter(tmp_path):
 
 def test_inspect_env_filter(tmp_path):
     """Inspect command with env filter argument"""
-    clear_dotenv(tmp_path)
+    instance_name = get_current_test_name()
 
-    instance_name = "inspect_env_filter"
     setting_file = tmp_path / "a.toml"
     environ = {}
     cmd = ["-i", f"{instance_name}.settings", "inspect", "-e", "prod"]
@@ -640,26 +667,88 @@ def test_inspect_env_filter(tmp_path):
     expected_header = """\
         {
           "header": {
-            "filters": {
-              "env": "prod",
-              "key": "None",
-              "history_ordering": "ascending"
-            },
-            "active_value": {
-              "FOO": "from_env_default",
-              "BAR": "prod_only_and_foo_default"
-            }
+            "env_filter": "prod",
+            "key_filter": "None",
+            "new_first": "True",
+            "history_limit": "None",
+            "include_internal": "False"
+          },
+          "current": {
+            "FOO": "from_env_default",
+            "BAR": "prod_only_and_foo_default"
           },
         """
     assert result
     assert result.startswith(dedent(expected_header))
 
 
+def test_inspect_limit(tmp_path):
+    """
+    Inspect command with --limit.
+
+    Should include only the last history entry.
+    """
+    instance_name = get_current_test_name()
+
+    setting_file = tmp_path / "a.toml"
+    environ = {"DYNACONF_FOO": "from_environ"}
+    cmd = [
+        "-i",
+        f"{instance_name}.settings",
+        "inspect",
+        "--limit",
+        "1",
+    ]
+
+    create_file(
+        tmp_path / f"{instance_name}.py",
+        f"""\
+        from dynaconf import Dynaconf
+        settings = Dynaconf(
+            settings_file="{setting_file.as_posix()}",
+        )
+        """,
+    )
+
+    create_file(
+        setting_file,
+        "foo='from_file'",
+    )
+
+    result = run(cmd, env=environ)
+    expected_result = """\
+        {
+          "header": {
+            "env_filter": "None",
+            "key_filter": "None",
+            "new_first": "True",
+            "history_limit": "1",
+            "include_internal": "False"
+          },
+          "current": {
+            "FOO": "from_environ"
+          },
+          "history": [
+            {
+              "loader": "env_global",
+              "identifier": "unique",
+              "env": "global",
+              "merged": false,
+              "value": {
+                "FOO": "from_environ"
+              }
+            }
+          ]
+        }
+        """
+    assert result
+    assert result == dedent(expected_result)
+
+
 def test_inspect_all_args(tmp_path):
     """Inspect command with all arguments"""
-    clear_dotenv(tmp_path)
+    instance_name = get_current_test_name()  # should be unique for isolation
 
-    instance_name = "inspect_all_args"  # should be unique for isolation
     setting_file = tmp_path / "a.toml"
     environ = {"DYNACONF_BAR": "actual value but not in history"}
     cmd = [
@@ -698,11 +787,12 @@ def test_inspect_all_args(tmp_path):
     result = run(cmd, env=environ)
     expected_result = f"""\
         header:
-          filters:
-            env: prod
-            key: bar
-            history_ordering: ascending
-          active_value: actual value but not in history
+          env_filter: prod
+          key_filter: bar
+          new_first: 'True'
+          history_limit: None
+          include_internal: 'False'
+        current: actual value but not in history
         history:
         - loader: toml
           identifier: {setting_file.as_posix()}
@@ -715,9 +805,8 @@ def test_inspect_all_args(tmp_path):
 
 
 def test_inspect_invalid_key(tmp_path):
-    clear_dotenv(tmp_path)
+    instance_name = get_current_test_name()
 
-    instance_name = "inspect_invalid_key"
     environ = {}
     cmd = [
         "-i",
@@ -740,9 +829,8 @@ def test_inspect_invalid_key(tmp_path):
 
 
 def test_inspect_invalid_env(tmp_path):
-    clear_dotenv(tmp_path)
+    instance_name = get_current_test_name()
 
-    instance_name = "inspect_invalid_env"
     environ = {}
     cmd = [
         "-i",
@@ -765,9 +853,8 @@ def test_inspect_invalid_env(tmp_path):
 
 
 def test_inspect_invalid_format(tmp_path):
-    clear_dotenv(tmp_path)
+    instance_name = get_current_test_name()
 
-    instance_name = "inspect_invalid_format"
     environ = {}
     cmd = [
         "-i",
diff --git a/tests/test_inspect.py b/tests/test_inspect.py
index d6aed97..0819715 100644
--- a/tests/test_inspect.py
+++ b/tests/test_inspect.py
@@ -5,9 +5,7 @@ from __future__ import annotations
 
 import copy
 import os
-from pprint import pprint
 from textwrap import dedent
-from unittest import mock
 
 import pytest
 
@@ -348,7 +346,6 @@ def test_get_history_env_true__val_default_plus_file(tmp_path):
         development.foo="from_development_file"
         """,
     )
-    # print()
     settings = Dynaconf(
         validators=[
             Validator("bar", default="from_val_default_current_env"),
@@ -456,7 +453,7 @@ def test_get_history_key_filter(tmp_path):
     create_file(file_c, "a='cA'\nb='cB'\nc='cC'")
 
     settings = Dynaconf(settings_file=[file_a, file_b, file_c])
-    history = get_history(settings, key_dotted_path="a")
+    history = get_history(settings, key="a")
     assert history[0]["value"] == "aA"
     assert history[1]["value"] == "bA"
     assert history[2]["value"] == "cA"
@@ -472,7 +469,7 @@ def test_get_history_key_filter_nested(tmp_path):
     create_file(file_c, "a.b='cB'\na.c='cC'")
 
     settings = Dynaconf(settings_file=[file_a, file_b, file_c])
-    history = get_history(settings, key_dotted_path="a.c")
+    history = get_history(settings, key="a.c")
     assert len(history) == 3
     assert history[0]["value"] == "aC"
     assert history[1]["value"] == "bC"
@@ -509,7 +506,7 @@ def test_get_history_env_filter(tmp_path):
     settings = Dynaconf(settings_file=[file_a, file_b], environments=True)
     settings.from_env("prod")  # CAVEAT: activate loading of prod
     history = get_history(
-        settings, filter_src_metadata=lambda x: x.env.lower() == "prod"
+        settings, filter_callable=lambda x: x.env.lower() == "prod"
     )
 
     assert len(history) == 2
@@ -554,140 +551,14 @@ def test_get_history_env_and_key_filter(tmp_path):
     settings.from_env("prod")  # CAVEAT: activate loading of prod
     history = get_history(
         settings,
-        key_dotted_path="bar",
-        filter_src_metadata=lambda x: x.env.lower() == "prod",
+        key="bar",
+        filter_callable=lambda x: x.env.lower() == "prod",
     )
     assert len(history) == 2
     assert history[0]["value"] == "from_prod_a"
     assert history[1]["value"] == "from_prod_b"
 
 
-def test_inspect_print_key(tmp_path, capsys):
-    os.environ["DYNACONF_FOO"] = "from_environ"
-    os.environ["DYNACONF_BAR"] = "environ_only"
-    filename = create_file(tmp_path / "a.yaml", "foo: from_yaml")
-    settings = Dynaconf(settings_file=filename)
-
-    inspect_settings(settings, "foo", output_format="yaml")
-    stdout, stderr = capsys.readouterr()
-    expected = f"""\
-    header:
-      filters:
-        env: None
-        key: foo
-        history_ordering: ascending
-      active_value: from_environ
-    history:
-    - loader: env_global
-      identifier: unique
-      env: global
-      merged: false
-      value: from_environ
-    - loader: yaml
-      identifier: {filename}
-      env: default
-      merged: false
-      value: from_yaml
-    """
-    assert stdout == dedent(expected)
-
-
-def test_inspect_print_all(tmp_path, capsys):
-    os.environ["DYNACONF_FOO"] = "from_environ"
-    os.environ["DYNACONF_BAR"] = "environ_only"
-    filename = create_file(tmp_path / "a.yaml", "foo: from_yaml")
-    settings = Dynaconf(settings_file=filename)
-    inspect_settings(settings, output_format="yaml")
-
-    stdout, stderr = capsys.readouterr()
-    expected = """\
-    header:
-      filters:
-        env: None
-        key: None
-        history_ordering: ascending
-      active_value:
-        FOO: from_environ
-        BAR: environ_only
-    """
-    assert stdout.startswith(dedent(expected))
-
-
-def test_inspect_to_file_key(tmp_path):
-    os.environ["DYNACONF_FOO"] = "from_environ"
-    os.environ["DYNACONF_BAR"] = "environ_only"
-    filename = create_file(tmp_path / "a.yaml", "foo: from_yaml")
-    settings = Dynaconf(settings_file=filename)
-
-    file_out = tmp_path / "output.yml"
-    inspect_settings(settings, "foo", output_format="yaml", to_file=file_out)
-
-    # open created file
-    with open(file_out) as f:
-        file_content = yaml.YAML().load(f)
-
-    assert file_content["header"]["filters"]["key"] == "foo"
-    assert file_content["header"]["active_value"] == "from_environ"
-
-
-def test_inspect_to_file_all(tmp_path):
-    os.environ["DYNACONF_FOO"] = "from_environ"
-    os.environ["DYNACONF_BAR"] = "environ_only"
-    filename = create_file(tmp_path / "a.yaml", "foo: from_yaml")
-    settings = Dynaconf(settings_file=filename)
-
-    file_out = tmp_path / "output.yml"
-    inspect_settings(settings, output_format="yaml", to_file=file_out)
-
-    # open created file
-    with open(file_out) as f:
-        file_content = yaml.YAML().load(f)
-
-    assert file_content["header"]["filters"]["key"] == "None"
-    assert file_content["header"]["active_value"] == {
-        "FOO": "from_environ",
-        "BAR": "environ_only",
-    }
-
-
-def test_inspect_env_filter(tmp_path, capsys):
-    """
-    Caveat: env filter will show current default env values too.
-            History will be filtered properly.
-    """
-    filename = create_file(
-        tmp_path / "a.toml",
-        """\
-        default.foo="from_env_default"
-        development.foo="from_env_dev"
-        prod.bar="prod_only"
-        """,
-    )
-
-    settings = Dynaconf(settings_file=filename, environments=True)
-    inspect_settings(settings, output_format="yaml", env="prod")
-
-    stdout, stderr = capsys.readouterr()
-    expected = f"""\
-    header:
-      filters:
-        env: prod
-        key: None
-        history_ordering: ascending
-      active_value:
-        FOO: from_env_default
-        BAR: prod_only
-    history:
-    - loader: toml
-      identifier: {filename}
-      env: prod
-      merged: false
-      value:
-        BAR: prod_only
-    """
-    assert stdout == dedent(expected)
-
-
 def test_caveat__get_history_env_true(tmp_path):
     """
     Given environments=True and sources=file
@@ -788,26 +659,156 @@ def test_caveat__get_history_env_true_workaround(tmp_path):
     }
 
 
+def test_inspect_key_filter(tmp_path):
+    os.environ["DYNACONF_FOO"] = "from_environ"
+    os.environ["DYNACONF_BAR"] = "environ_only"
+    filename = create_file(tmp_path / "a.yaml", "foo: from_yaml")
+    settings = Dynaconf(settings_file=filename)
+
+    result = inspect_settings(settings, key="foo", dumper="yaml")
+
+    assert result["header"]["key_filter"] == "foo"
+    assert result["current"] == "from_environ"
+    assert result["history"] == [
+        {
+            "loader": "env_global",
+            "identifier": "unique",
+            "env": "global",
+            "merged": False,
+            "value": "from_environ",
+        },
+        {
+            "loader": "yaml",
+            "identifier": str(filename),
+            "env": "default",
+            "merged": False,
+            "value": "from_yaml",
+        },
+    ]
+
+
+def test_inspect_no_filter(tmp_path):
+    os.environ["DYNACONF_FOO"] = "from_environ"
+    os.environ["DYNACONF_BAR"] = "environ_only"
+    filename = create_file(tmp_path / "a.yaml", "foo: from_yaml")
+    settings = Dynaconf(settings_file=filename)
+    result = inspect_settings(settings, dumper="yaml")
+
+    assert result["header"]["key_filter"] == "None"
+    assert result["header"]["env_filter"] == "None"
+    assert result["current"] == {"FOO": "from_environ", "BAR": "environ_only"}
+
+    # first two entries
+    assert result["history"][:2] == [
+        {
+            "env": "global",
+            "identifier": "unique",
+            "loader": "env_global",
+            "merged": False,
+            "value": {"BAR": "environ_only", "FOO": "from_environ"},
+        },
+        {
+            "env": "default",
+            "identifier": str(filename),
+            "loader": "yaml",
+            "merged": False,
+            "value": {"FOO": "from_yaml"},
+        },
+    ]
+
+
+def test_inspect_env_filter(tmp_path):
+    """
+    Caveat: env filter will show current default env values too.
+            History will be filtered properly.
+    """
+    filename = create_file(
+        tmp_path / "a.toml",
+        """\
+        default.foo="from_env_default"
+        development.foo="from_env_dev"
+        prod.bar="prod_only"
+        """,
+    )
+
+    settings = Dynaconf(settings_file=filename, environments=True)
+    result = inspect_settings(settings, dumper="yaml", env="prod")
+    assert result["header"]["env_filter"] == "prod"
+    assert result["header"]["key_filter"] == "None"
+    assert result["current"] == {"FOO": "from_env_default", "BAR": "prod_only"}
+    assert result["history"] == [
+        {
+            "loader": "toml",
+            "identifier": str(filename),
+            "env": "prod",
+            "merged": False,
+            "value": {"BAR": "prod_only"},
+        }
+    ]
+
+
+def test_inspect_to_file(tmp_path):
+    """Assert file is written correctly with 'to_file' option"""
+    os.environ["DYNACONF_FOO"] = "from_environ"
+    os.environ["DYNACONF_BAR"] = "environ_only"
+    filename = create_file(tmp_path / "a.yaml", "foo: from_yaml")
+    settings = Dynaconf(settings_file=filename)
+
+    file_out = tmp_path / "output.yml"
+    inspect_settings(settings, dumper="yaml", to_file=file_out)
+
+    # open created file
+    assert file_out.read_text() == dedent(
+        f"""\
+        header:
+          env_filter: None
+          key_filter: None
+          new_first: 'True'
+          history_limit: None
+          include_internal: 'False'
+        current:
+          FOO: from_environ
+          BAR: environ_only
+        history:
+        - loader: env_global
+          identifier: unique
+          env: global
+          merged: false
+          value:
+            FOO: from_environ
+            BAR: environ_only
+        - loader: yaml
+          identifier: {filename}
+          env: default
+          merged: false
+          value:
+            FOO: from_yaml
+        - loader: set_method
+          identifier: settings_module_method
+          env: global
+          merged: false
+          value:
+            SETTINGS_MODULE: {filename}
+        - loader: set_method
+          identifier: init_kwargs
+          env: global
+          merged: false
+          value:
+            SETTINGS_FILE_FOR_DYNACONF: {filename}
+          """
+    )
+
+
 def test_inspect_exception_key_not_found():
     settings = Dynaconf()
     with pytest.raises(KeyNotFoundError):
-        inspect_settings(settings, key_dotted_path="non_existant")
+        inspect_settings(settings, key="non_existant")
 
 
-def test_inspect_empty_settings(capsys):
+def test_inspect_empty_settings():
     settings = Dynaconf()
-    inspect_settings(settings)
-    stdout, stderr = capsys.readouterr()
-    expected = """\
-        header:
-          filters:
-            env: None
-            key: None
-            history_ordering: ascending
-          active_value: {}
-        history: []
-        """
-    assert stdout == dedent(expected)
+    result = inspect_settings(settings)
+    assert result["history"] == []
 
 
 def test_inspect_exception_env_not_found():
@@ -819,6 +820,4 @@ def test_inspect_exception_env_not_found():
 def test_inspect_exception_invalid_format():
     settings = Dynaconf()
     with pytest.raises(OutputFormatError):
-        inspect_settings(
-            settings, output_format="invalid_format"  # type: ignore
-        )
+        inspect_settings(settings, dumper="invalid_format")
diff --git a/tests/test_redis.py b/tests/test_redis.py
index 5b44d39..644b688 100644
--- a/tests/test_redis.py
+++ b/tests/test_redis.py
@@ -118,7 +118,7 @@ def test_redis_has_proper_source_metadata(docker_redis):
     write(settings, {"SECRET": "redis_works_perfectly"})
     load(settings)
     history = get_history(
-        settings, filter_src_metadata=lambda s: s.loader == "redis"
+        settings, filter_callable=lambda s: s.loader == "redis"
     )
     assert history[0]["env"] == "development"  # default when environments=True
     assert history[0]["value"]["SECRET"] == "redis_works_perfectly"
diff --git a/tests/test_vault.py b/tests/test_vault.py
index b2353f0..f764330 100644
--- a/tests/test_vault.py
+++ b/tests/test_vault.py
@@ -121,7 +121,7 @@ def test_vault_has_proper_source_metadata(docker_vault):
             write(settings, {"SECRET": f"vault_works_in_{env}"})
     load(settings)
     history = get_history(
-        settings, filter_src_metadata=lambda s: s.loader == "vault"
+        settings, filter_callable=lambda s: s.loader == "vault"
     )
     assert history[0]["env"] == "default"
     assert history[0]["value"]["SECRET"] == "vault_works_in_default"