debug-dojo 0.7.0__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {debug_dojo-0.7.0/src/debug_dojo.egg-info → debug_dojo-0.8.0}/PKG-INFO +14 -6
  2. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/README.md +12 -5
  3. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/pyproject.toml +5 -4
  4. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/src/debug_dojo/__main__.py +1 -1
  5. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/src/debug_dojo/_cli.py +35 -80
  6. debug_dojo-0.8.0/src/debug_dojo/_compare.py +181 -0
  7. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/src/debug_dojo/_config.py +48 -12
  8. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/src/debug_dojo/_config_models.py +2 -0
  9. debug_dojo-0.8.0/src/debug_dojo/_execution.py +178 -0
  10. debug_dojo-0.8.0/src/debug_dojo/_gamification.py +242 -0
  11. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/src/debug_dojo/_installers.py +23 -6
  12. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/src/debug_dojo/install.py +2 -2
  13. {debug_dojo-0.7.0 → debug_dojo-0.8.0/src/debug_dojo.egg-info}/PKG-INFO +14 -6
  14. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/src/debug_dojo.egg-info/SOURCES.txt +4 -0
  15. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/src/debug_dojo.egg-info/requires.txt +1 -0
  16. debug_dojo-0.8.0/tests/test_gamification.py +159 -0
  17. debug_dojo-0.8.0/tests/test_gamification_config.py +64 -0
  18. debug_dojo-0.7.0/src/debug_dojo/_compare.py +0 -141
  19. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/LICENSE +0 -0
  20. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/setup.cfg +0 -0
  21. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/src/debug_dojo/__init__.py +0 -0
  22. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/src/debug_dojo/py.typed +0 -0
  23. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/src/debug_dojo.egg-info/dependency_links.txt +0 -0
  24. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/src/debug_dojo.egg-info/entry_points.txt +0 -0
  25. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/src/debug_dojo.egg-info/top_level.txt +0 -0
  26. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/tests/test_compare.py +0 -0
  27. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/tests/test_config.py +0 -0
  28. {debug_dojo-0.7.0 → debug_dojo-0.8.0}/tests/test_installers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: debug-dojo
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: 🏣 debug dojo, a place for zen debugging
5
5
  Author-email: bwrob <bartosz.marcin.wroblewski@gmail.com>
6
6
  License-Expression: MIT
@@ -22,6 +22,7 @@ Requires-Python: <3.15,>=3.10
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: dacite>=1.0.0
25
+ Requires-Dist: pyscn>=1.5.2
25
26
  Requires-Dist: rich>=10.0.0
26
27
  Requires-Dist: tomlkit>=0.7.0
27
28
  Requires-Dist: typer>=0.3.0
@@ -64,8 +65,9 @@ Dynamic: license-file
64
65
  ## ✨ Features
65
66
 
66
67
  * **Unified Debugging Interface:** Easily launch your scripts or modules with your preferred debugger (`debugpy`, `pudb`, `pdb`, `ipdb`).
68
+ * **Dojo Belts Gamification:** Earn belts based on your debugging experience and track your progress.
67
69
  * **Enhanced Output with Rich:** Leverages [`rich`](https://github.com/Textualize/rich) for stunning, syntax-highlighted code, pretty-printed objects, and improved tracebacks.
68
- * **Side-by-Side Object Comparison:** Quickly identify differences between two Python objects.
70
+ * **Side-by-side Object Comparison:** Quickly identify differences between two Python objects.
69
71
  * **Interactive Object Inspection:** Dive deep into object structures with a powerful `inspect` utility.
70
72
  * **Simplified Breakpoints:** Set breakpoints effortlessly with a concise helper function.
71
73
 
@@ -85,22 +87,28 @@ pip install "debug-dojo[all-debuggers]"
85
87
 
86
88
  ## 💻 CLI Usage
87
89
 
88
- Run your Python script or module with `debug-dojo`:
90
+ Run your Python script or module with `dojo run`:
89
91
 
90
92
  ```console
91
- dojo my_script.py
93
+ dojo run my_script.py
94
+ ```
95
+
96
+ Check your progress and current belt with:
97
+
98
+ ```console
99
+ dojo belt
92
100
  ```
93
101
 
94
102
  Specify a debugger, configuration file, or enable verbose output:
95
103
 
96
104
  ```console
97
- dojo --debugger ipdb --config dojo.toml --verbose --module my_module
105
+ dojo run --debugger ipdb --config dojo.toml --verbose --module my_module
98
106
  ```
99
107
 
100
108
  Run an executable command with debugging tools:
101
109
 
102
110
  ```console
103
- dojo --exec pytest
111
+ dojo run --exec pytest
104
112
  ```
105
113
 
106
114
  ## 🐍 Usage in Code
@@ -31,8 +31,9 @@
31
31
  ## ✨ Features
32
32
 
33
33
  * **Unified Debugging Interface:** Easily launch your scripts or modules with your preferred debugger (`debugpy`, `pudb`, `pdb`, `ipdb`).
34
+ * **Dojo Belts Gamification:** Earn belts based on your debugging experience and track your progress.
34
35
  * **Enhanced Output with Rich:** Leverages [`rich`](https://github.com/Textualize/rich) for stunning, syntax-highlighted code, pretty-printed objects, and improved tracebacks.
35
- * **Side-by-Side Object Comparison:** Quickly identify differences between two Python objects.
36
+ * **Side-by-side Object Comparison:** Quickly identify differences between two Python objects.
36
37
  * **Interactive Object Inspection:** Dive deep into object structures with a powerful `inspect` utility.
37
38
  * **Simplified Breakpoints:** Set breakpoints effortlessly with a concise helper function.
38
39
 
@@ -52,22 +53,28 @@ pip install "debug-dojo[all-debuggers]"
52
53
 
53
54
  ## 💻 CLI Usage
54
55
 
55
- Run your Python script or module with `debug-dojo`:
56
+ Run your Python script or module with `dojo run`:
56
57
 
57
58
  ```console
58
- dojo my_script.py
59
+ dojo run my_script.py
60
+ ```
61
+
62
+ Check your progress and current belt with:
63
+
64
+ ```console
65
+ dojo belt
59
66
  ```
60
67
 
61
68
  Specify a debugger, configuration file, or enable verbose output:
62
69
 
63
70
  ```console
64
- dojo --debugger ipdb --config dojo.toml --verbose --module my_module
71
+ dojo run --debugger ipdb --config dojo.toml --verbose --module my_module
65
72
  ```
66
73
 
67
74
  Run an executable command with debugging tools:
68
75
 
69
76
  ```console
70
- dojo --exec pytest
77
+ dojo run --exec pytest
71
78
  ```
72
79
 
73
80
  ## 🐍 Usage in Code
@@ -15,6 +15,7 @@
15
15
  ]
16
16
  dependencies = [
17
17
  "dacite>=1.0.0",
18
+ "pyscn>=1.5.2",
18
19
  "rich>=10.0.0",
19
20
  "tomlkit>=0.7.0",
20
21
  "typer>=0.3.0",
@@ -31,7 +32,7 @@
31
32
  name = "debug-dojo"
32
33
  readme = "README.md"
33
34
  requires-python = ">=3.10,<3.15"
34
- version = "0.7.0"
35
+ version = "0.8.0"
35
36
 
36
37
  [project.urls]
37
38
  Changelog = "https://bwrob.github.io/debug-dojo/changelog/"
@@ -54,6 +55,7 @@
54
55
 
55
56
  dev = [
56
57
  "basedpyright>=1.31.2",
58
+ "complexipy>=5.2.0",
57
59
  "coverage>=7.10.4",
58
60
  "debug-dojo[all-debuggers]",
59
61
  "diff-cover>=9.6.0",
@@ -62,8 +64,7 @@
62
64
  "pytest-cov>=6.2.1",
63
65
  "ruff>=0.12.9",
64
66
  "tach>=0.29.0",
65
- ]
66
- docs = [
67
+ "ty>=0.0.15",
67
68
  "mkdocs>=1.6.1",
68
69
  "mkdocs-git-authors-plugin>=0.10.0",
69
70
  "mkdocs-git-committers-plugin-2>=2.5.0",
@@ -78,7 +79,7 @@
78
79
 
79
80
  [tool.uv]
80
81
  package = true
81
- required-version = ">=0.8.0"
82
+ required-version = ">=0.10.0,<0.12.0"
82
83
 
83
84
  [tool.uv.workspace]
84
85
  members = ["."]
@@ -1,6 +1,6 @@
1
1
  """Entry point for the debug_dojo package."""
2
2
 
3
3
  if __name__ == "__main__":
4
- from ._cli import main
4
+ from debug_dojo._cli import main
5
5
 
6
6
  main()
@@ -2,30 +2,19 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import runpy
5
+ import contextlib
6
6
  import sys
7
- import traceback
8
- from bdb import BdbQuit
9
- from enum import Enum
7
+ import time
10
8
  from pathlib import Path
11
- from shutil import which
12
9
  from typing import Annotated
13
10
 
14
11
  import typer
15
12
  from rich import print as rich_print
16
13
 
17
- from ._config import load_config
18
- from ._config_models import DebugDojoConfig, DebuggerType # noqa: TC001
19
- from ._installers import install_by_config
20
-
21
-
22
- class ExecMode(Enum):
23
- """Execution mode for the target."""
24
-
25
- FILE = "file"
26
- MODULE = "module"
27
- EXECUTABLE = "executable"
28
-
14
+ from debug_dojo._config import load_config
15
+ from debug_dojo._config_models import DebuggerType # noqa: TC001
16
+ from debug_dojo._execution import ExecMode, execute_with_debug
17
+ from debug_dojo._gamification import GamificationManager
29
18
 
30
19
  cli = typer.Typer(
31
20
  name="debug_dojo",
@@ -34,73 +23,25 @@ cli = typer.Typer(
34
23
  )
35
24
 
36
25
 
37
- def __execute_with_debug( # noqa: C901
38
- target_name: str,
39
- target_args: list[str],
40
- *,
41
- target_mode: ExecMode,
42
- verbose: bool,
43
- config: DebugDojoConfig,
26
+ @cli.command(help="Show your current Dojo Belt and stats.")
27
+ def belt(
28
+ config_path: Annotated[
29
+ Path | None, typer.Option("--config", "-c", help="Show configuration")
30
+ ] = None,
44
31
  ) -> None:
45
- """Execute a target script or module with installed debugging tools.
32
+ """Display the current user's Dojo Belt status."""
33
+ config = load_config(config_path)
46
34
 
47
- Args:
48
- target_name (str): The name of the script, module, or executable to run.
49
- target_args (list[str]): Arguments to pass to the target.
50
- target_mode (ExecMode): The execution mode (FILE, MODULE, or EXECUTABLE).
51
- verbose (bool): If True, print verbose output.
52
- config (DebugDojoConfig): The debug-dojo configuration.
35
+ if not config.gamification:
36
+ rich_print("[yellow]Gamification is disabled in your configuration.[/yellow]")
37
+ return
53
38
 
54
- Raises:
55
- typer.Exit: If the target file is not found, or if an import error occurs,
56
- or if the script exits with a non-zero code.
57
-
58
- """
59
- sys.argv = [target_name, *target_args]
60
-
61
- if verbose:
62
- rich_print(f"[blue]Installing debugging tools for {target_name}.[/blue]")
63
- rich_print(f"[blue]Arguments for target: {target_args}[/blue]")
64
-
65
- install_by_config(config)
66
-
67
- if target_mode is ExecMode.MODULE:
68
- runner = runpy.run_module
69
- else:
70
- if target_mode is ExecMode.EXECUTABLE:
71
- target_name = which(target_name) or target_name
72
-
73
- if not Path(target_name).exists():
74
- raise typer.Exit(1)
75
-
76
- runner = runpy.run_path
77
-
78
- try:
79
- _ = runner(target_name, run_name="__main__")
80
- except ImportError as e:
81
- rich_print(f"[red]Error importing {target_name}:[/red]\n{e}")
82
- raise typer.Exit(1) from e
83
- except BdbQuit:
84
- rich_print("[red]Debugging session terminated by user.[/red]")
85
- raise typer.Exit(0) from None
86
- except KeyboardInterrupt:
87
- rich_print("[red]Execution interrupted by user.[/red]")
88
- raise typer.Exit(0) from None
89
- except SystemExit as e:
90
- if e.code:
91
- rich_print(f"[red]Script exited with code {e.code}.[/red]")
92
- except Exception as e:
93
- rich_print(f"[red]Error while running {target_name}:[/red]\n{e}")
94
- rich_print(traceback.format_exc())
95
- if config.exceptions.post_mortem:
96
- import ipdb # pyright: ignore[reportMissingTypeStubs] # noqa: PLC0415, T100
97
-
98
- rich_print("[blue]Entering post-mortem debugging session...[/blue]")
99
- ipdb.post_mortem(e.__traceback__) # pyright: ignore[reportUnknownMemberType]
100
- raise typer.Exit(1) from e
39
+ manager = GamificationManager()
40
+ manager.display_status()
101
41
 
102
42
 
103
43
  @cli.command(
44
+ name="run",
104
45
  help="Run a Python script or module with debugging tools installed.",
105
46
  no_args_is_help=True,
106
47
  context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
@@ -108,7 +49,8 @@ def __execute_with_debug( # noqa: C901
108
49
  def run_debug( # noqa: PLR0913
109
50
  ctx: typer.Context,
110
51
  target_name: Annotated[
111
- str | None, typer.Argument(help="The target script or module to debug.")
52
+ str | None,
53
+ typer.Argument(help="The target script or module to debug.", metavar="target"),
112
54
  ] = None,
113
55
  *,
114
56
  config_path: Annotated[
@@ -178,7 +120,9 @@ def run_debug( # noqa: PLR0913
178
120
  rich_print(f"[blue]Using debug-dojo configuration: {config} [/blue]")
179
121
 
180
122
  if target_name:
181
- __execute_with_debug(
123
+ start_time = time.perf_counter()
124
+
125
+ execute_with_debug(
182
126
  target_name=target_name,
183
127
  target_mode=mode,
184
128
  target_args=ctx.args,
@@ -186,6 +130,17 @@ def run_debug( # noqa: PLR0913
186
130
  config=config,
187
131
  )
188
132
 
133
+ duration = time.perf_counter() - start_time
134
+
135
+ # Gamification: Increment session count if enabled
136
+ if config.gamification:
137
+ with contextlib.suppress(Exception):
138
+ command = " ".join(sys.argv)
139
+ GamificationManager().increment_session(
140
+ duration_seconds=duration,
141
+ command=command,
142
+ )
143
+
189
144
 
190
145
  def main() -> None:
191
146
  """Run the command-line interface."""
@@ -0,0 +1,181 @@
1
+ """Utilities for side-by-side inspection and comparison of Python objects.
2
+
3
+ This module provides functions to display attributes and methods of two objects in a
4
+ visually appealing, side-by-side format in the terminal.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import contextlib
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ from rich.console import Console
13
+ from rich.panel import Panel
14
+ from rich.table import Table
15
+ from rich.text import Text
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Callable
19
+
20
+
21
+ def _get_members(
22
+ obj: object,
23
+ predicate: Callable[[str, Any], bool], # pyright: ignore[reportExplicitAny]
24
+ formatter: Callable[[str, Any], str], # pyright: ignore[reportExplicitAny]
25
+ ) -> list[str]:
26
+ members: list[str] = []
27
+ for name in sorted(dir(obj)):
28
+ try:
29
+ value = getattr(obj, name) # pyright: ignore[reportAny]
30
+ except Exception: # noqa: S112, BLE001
31
+ continue
32
+
33
+ if predicate(name, value):
34
+ with contextlib.suppress(Exception):
35
+ members.append(formatter(name, value))
36
+ return members
37
+
38
+
39
+ def get_object_attributes(obj: object) -> list[str]:
40
+ """Extract and format non-callable attributes of an object.
41
+
42
+ Args:
43
+ obj (object): The object to extract attributes from.
44
+
45
+ Returns:
46
+ list[str]: A list of formatted strings, each representing an attribute.
47
+
48
+ """
49
+ return _get_members(
50
+ obj,
51
+ lambda n, v: not n.startswith("__") and not callable(v), # pyright: ignore[reportAny]
52
+ lambda n, v: f"{n}={v!r}", # pyright: ignore[reportAny]
53
+ )
54
+
55
+
56
+ def get_object_methods(obj: object) -> list[str]:
57
+ """Extract and format public callable methods of an object.
58
+
59
+ Args:
60
+ obj (object): The object to extract methods from.
61
+
62
+ Returns:
63
+ list[str]: A list of method names.
64
+
65
+ """
66
+ return _get_members(
67
+ obj,
68
+ lambda n, v: not n.startswith("_") and callable(v), # pyright: ignore[reportAny]
69
+ lambda n, _v: n, # pyright: ignore[reportAny]
70
+ )
71
+
72
+
73
+ def _is_basic_type(obj: object) -> bool:
74
+ """Check if the object is a basic Python type."""
75
+ return (
76
+ isinstance(obj, (str, int, float, list, dict, tuple, set, bool)) or obj is None
77
+ )
78
+
79
+
80
+ def _get_basic_info(obj: object) -> list[Text]:
81
+ """Get information for basic types."""
82
+ return [
83
+ Text("Value:", style="bold"),
84
+ Text(f" {obj!r}", style="yellow"),
85
+ Text(""),
86
+ Text("No attributes or methods to display for this type.", style="dim"),
87
+ ]
88
+
89
+
90
+ def _format_section(title: str, items: list[str], empty_message: str) -> list[Text]:
91
+ """Format a section of the inspection output."""
92
+ lines: list[Text] = []
93
+ if items:
94
+ lines.append(Text(title, style="bold"))
95
+ lines.extend([Text(f" {item}") for item in items])
96
+ else:
97
+ lines.append(Text(empty_message, style="dim"))
98
+ lines.append(Text(""))
99
+ return lines
100
+
101
+
102
+ def _get_attributes_section(obj: object) -> list[Text]:
103
+ """Get the attributes section for the object info."""
104
+ return _format_section(
105
+ "Attributes:",
106
+ get_object_attributes(obj),
107
+ "No attributes found.",
108
+ )
109
+
110
+
111
+ def _get_methods_section(obj: object) -> list[Text]:
112
+ """Get the methods section for the object info."""
113
+ return _format_section(
114
+ "Methods:",
115
+ [f"{method}()" for method in get_object_methods(obj)],
116
+ "No public methods found.",
117
+ )
118
+
119
+
120
+ def get_simplified_object_info(obj: object) -> list[Text]:
121
+ """Generate a simplified, Rich-formatted inspection output for an object.
122
+
123
+ Handles basic Python types by displaying their value directly. For other objects, it
124
+ lists their attributes and public methods.
125
+
126
+ Args:
127
+ obj (object): The object to generate info for.
128
+
129
+ Returns:
130
+ list[Text]: A list of Rich Text objects representing the object's information.
131
+
132
+ """
133
+ info_lines: list[Text] = []
134
+ obj_type: str = type(obj).__name__
135
+ info_lines.append(Text(f"<class '{obj_type}'>", style="cyan bold"))
136
+ info_lines.append(Text(""))
137
+
138
+ if _is_basic_type(obj):
139
+ info_lines.extend(_get_basic_info(obj))
140
+ return info_lines
141
+
142
+ info_lines.extend(_get_attributes_section(obj))
143
+ info_lines.extend(_get_methods_section(obj))
144
+
145
+ return info_lines
146
+
147
+
148
+ def inspect_objects_side_by_side(
149
+ obj1: object,
150
+ obj2: object,
151
+ ) -> None:
152
+ """Display two Python objects side-by-side in the terminal using Rich.
153
+
154
+ Showing their attributes and methods in a simplified, aligned format.
155
+
156
+ Args:
157
+ obj1 (object): The first object to display.
158
+ obj2 (object): The second object to display.
159
+
160
+ """
161
+ main_console: Console = Console()
162
+
163
+ # Get info for both objects
164
+ lines1: list[Text] = get_simplified_object_info(obj1)
165
+ lines2: list[Text] = get_simplified_object_info(obj2)
166
+
167
+ # Convert list of Text to a single Renderable for Panel
168
+ inspect_text1: Text = Text("\n").join(lines1)
169
+ inspect_text2: Text = Text("\n").join(lines2)
170
+
171
+ # Create Panels for each object's info
172
+ panel1: Panel = Panel(inspect_text1, border_style="green", expand=True)
173
+ panel2: Panel = Panel(inspect_text2, border_style="green", expand=True)
174
+
175
+ # Use a Table to display panels side-by-side
176
+ table = Table(show_header=False, show_lines=False, expand=True)
177
+ table.add_column(width=main_console.width // 2 - 1)
178
+ table.add_column(width=main_console.width // 2 - 1)
179
+ table.add_row(panel1, panel2)
180
+
181
+ main_console.print(table)
@@ -16,7 +16,7 @@ from tomlkit import parse
16
16
  from tomlkit.exceptions import TOMLKitError
17
17
  from typer import Exit
18
18
 
19
- from ._config_models import (
19
+ from debug_dojo._config_models import (
20
20
  DACITE_CONFIG,
21
21
  DebugDojoConfig,
22
22
  DebugDojoConfigV1,
@@ -33,6 +33,20 @@ def _validate_model(
33
33
  model: type[DebugDojoConfigV1 | DebugDojoConfigV2],
34
34
  raw_config: JSON,
35
35
  ) -> DebugDojoConfigV1 | DebugDojoConfigV2:
36
+ """Validate the raw configuration against a specific model.
37
+
38
+ Args:
39
+ model (type[DebugDojoConfigV1 | DebugDojoConfigV2]): The model class to use
40
+ for validation.
41
+ raw_config (JSON): The raw configuration data.
42
+
43
+ Returns:
44
+ DebugDojoConfigV1 | DebugDojoConfigV2: The validated configuration object.
45
+
46
+ Raises:
47
+ DaciteError: If the configuration does not match the model.
48
+
49
+ """
36
50
  if not isinstance(raw_config, dict):
37
51
  msg = "Configuration must be a dictionary."
38
52
  raise DaciteError(msg)
@@ -107,22 +121,22 @@ def load_raw_config(config_path: Path) -> JSON:
107
121
  return dojo_config
108
122
 
109
123
 
110
- def validated_and_updated_config(raw_config: JSON, *, verbose: bool) -> DebugDojoConfig:
111
- """Validate and update the raw configuration to the latest DebugDojoConfig version.
124
+ def _try_validate_config(
125
+ raw_config: JSON,
126
+ *,
127
+ verbose: bool,
128
+ ) -> DebugDojoConfigV1 | DebugDojoConfigV2 | None:
129
+ """Try to validate the raw configuration against known configuration models.
112
130
 
113
131
  Args:
114
- raw_config (JSON): The raw configuration data loaded from a file.
115
- verbose (bool): If True, print verbose messages during validation and update.
132
+ raw_config (JSON): The raw configuration data.
133
+ verbose (bool): If True, print verbose messages during validation.
116
134
 
117
135
  Returns:
118
- DebugDojoConfig: A validated and updated DebugDojoConfig instance.
119
-
120
- Raises:
121
- typer.Exit: If the configuration cannot be validated against any known version.
136
+ DebugDojoConfigV1 | DebugDojoConfigV2 | None: The validated configuration
137
+ model, or None if validation fails for all models.
122
138
 
123
139
  """
124
- config: DebugDojoConfigV1 | DebugDojoConfigV2 | None = None
125
-
126
140
  for model in (DebugDojoConfigV2, DebugDojoConfigV1):
127
141
  model_name = model.__name__
128
142
  try:
@@ -140,7 +154,29 @@ def validated_and_updated_config(raw_config: JSON, *, verbose: bool) -> DebugDoj
140
154
  f"Current configuration model {DebugDojoConfig.__name__}. [/blue]"
141
155
  )
142
156
  rich_print(msg)
143
- break
157
+ return config
158
+ return None
159
+
160
+
161
+ def validated_and_updated_config(
162
+ raw_config: JSON,
163
+ *,
164
+ verbose: bool,
165
+ ) -> DebugDojoConfig:
166
+ """Validate and update the raw configuration to the latest DebugDojoConfig version.
167
+
168
+ Args:
169
+ raw_config (JSON): The raw configuration data loaded from a file.
170
+ verbose (bool): If True, print verbose messages during validation and update.
171
+
172
+ Returns:
173
+ DebugDojoConfig: A validated and updated DebugDojoConfig instance.
174
+
175
+ Raises:
176
+ typer.Exit: If the configuration cannot be validated against any known version.
177
+
178
+ """
179
+ config = _try_validate_config(raw_config, verbose=verbose)
144
180
 
145
181
  if not config:
146
182
  msg = "[red]Unsupported configuration version or error.[/red]"
@@ -140,6 +140,8 @@ class DebugDojoConfigV2:
140
140
  """Default debugger and configs."""
141
141
  features: FeaturesConfig = field(default_factory=FeaturesConfig)
142
142
  """Features mnemonics."""
143
+ gamification: bool = True
144
+ """Enable or disable gamification (Dojo Belts)."""
143
145
 
144
146
 
145
147
  @dataclass