debug-dojo 0.7.0__tar.gz → 0.9.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.
- {debug_dojo-0.7.0/src/debug_dojo.egg-info → debug_dojo-0.9.0}/PKG-INFO +13 -6
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/README.md +12 -5
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/pyproject.toml +6 -5
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/src/debug_dojo/__main__.py +1 -1
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/src/debug_dojo/_cli.py +35 -80
- debug_dojo-0.9.0/src/debug_dojo/_compare.py +205 -0
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/src/debug_dojo/_config.py +49 -13
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/src/debug_dojo/_config_models.py +8 -1
- debug_dojo-0.9.0/src/debug_dojo/_execution.py +177 -0
- debug_dojo-0.9.0/src/debug_dojo/_gamification.py +245 -0
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/src/debug_dojo/_installers.py +23 -6
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/src/debug_dojo/install.py +2 -2
- {debug_dojo-0.7.0 → debug_dojo-0.9.0/src/debug_dojo.egg-info}/PKG-INFO +13 -6
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/SOURCES.txt +4 -0
- debug_dojo-0.9.0/tests/test_compare.py +74 -0
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/tests/test_config.py +6 -1
- debug_dojo-0.9.0/tests/test_gamification.py +146 -0
- debug_dojo-0.9.0/tests/test_gamification_config.py +64 -0
- debug_dojo-0.7.0/src/debug_dojo/_compare.py +0 -141
- debug_dojo-0.7.0/tests/test_compare.py +0 -75
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/LICENSE +0 -0
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/setup.cfg +0 -0
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/src/debug_dojo/__init__.py +0 -0
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/src/debug_dojo/py.typed +0 -0
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/dependency_links.txt +0 -0
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/entry_points.txt +0 -0
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/requires.txt +0 -0
- {debug_dojo-0.7.0 → debug_dojo-0.9.0}/src/debug_dojo.egg-info/top_level.txt +0 -0
- {debug_dojo-0.7.0 → debug_dojo-0.9.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.
|
|
3
|
+
Version: 0.9.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
|
|
@@ -64,8 +64,9 @@ Dynamic: license-file
|
|
|
64
64
|
## ✨ Features
|
|
65
65
|
|
|
66
66
|
* **Unified Debugging Interface:** Easily launch your scripts or modules with your preferred debugger (`debugpy`, `pudb`, `pdb`, `ipdb`).
|
|
67
|
+
* **Dojo Belts Gamification:** Earn belts based on your debugging experience and track your progress.
|
|
67
68
|
* **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-
|
|
69
|
+
* **Side-by-side Object Comparison:** Quickly identify differences between two Python objects.
|
|
69
70
|
* **Interactive Object Inspection:** Dive deep into object structures with a powerful `inspect` utility.
|
|
70
71
|
* **Simplified Breakpoints:** Set breakpoints effortlessly with a concise helper function.
|
|
71
72
|
|
|
@@ -85,22 +86,28 @@ pip install "debug-dojo[all-debuggers]"
|
|
|
85
86
|
|
|
86
87
|
## 💻 CLI Usage
|
|
87
88
|
|
|
88
|
-
Run your Python script or module with `
|
|
89
|
+
Run your Python script or module with `dojo run`:
|
|
89
90
|
|
|
90
91
|
```console
|
|
91
|
-
dojo my_script.py
|
|
92
|
+
dojo run my_script.py
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Check your progress and current belt with:
|
|
96
|
+
|
|
97
|
+
```console
|
|
98
|
+
dojo belt
|
|
92
99
|
```
|
|
93
100
|
|
|
94
101
|
Specify a debugger, configuration file, or enable verbose output:
|
|
95
102
|
|
|
96
103
|
```console
|
|
97
|
-
dojo --debugger ipdb --config dojo.toml --verbose --module my_module
|
|
104
|
+
dojo run --debugger ipdb --config dojo.toml --verbose --module my_module
|
|
98
105
|
```
|
|
99
106
|
|
|
100
107
|
Run an executable command with debugging tools:
|
|
101
108
|
|
|
102
109
|
```console
|
|
103
|
-
dojo --exec pytest
|
|
110
|
+
dojo run --exec pytest
|
|
104
111
|
```
|
|
105
112
|
|
|
106
113
|
## 🐍 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-
|
|
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 `
|
|
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
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
name = "debug-dojo"
|
|
32
32
|
readme = "README.md"
|
|
33
33
|
requires-python = ">=3.10,<3.15"
|
|
34
|
-
version = "0.
|
|
34
|
+
version = "0.9.0"
|
|
35
35
|
|
|
36
36
|
[project.urls]
|
|
37
37
|
Changelog = "https://bwrob.github.io/debug-dojo/changelog/"
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
|
|
55
55
|
dev = [
|
|
56
56
|
"basedpyright>=1.31.2",
|
|
57
|
+
"complexipy>=5.2.0",
|
|
57
58
|
"coverage>=7.10.4",
|
|
58
59
|
"debug-dojo[all-debuggers]",
|
|
59
60
|
"diff-cover>=9.6.0",
|
|
@@ -62,8 +63,7 @@
|
|
|
62
63
|
"pytest-cov>=6.2.1",
|
|
63
64
|
"ruff>=0.12.9",
|
|
64
65
|
"tach>=0.29.0",
|
|
65
|
-
|
|
66
|
-
docs = [
|
|
66
|
+
"ty>=0.0.15",
|
|
67
67
|
"mkdocs>=1.6.1",
|
|
68
68
|
"mkdocs-git-authors-plugin>=0.10.0",
|
|
69
69
|
"mkdocs-git-committers-plugin-2>=2.5.0",
|
|
@@ -73,12 +73,13 @@
|
|
|
73
73
|
"mkdocstrings-python>=1.17.0",
|
|
74
74
|
"pymdown-extensions>=10.16.1",
|
|
75
75
|
"typst>=0.14.2",
|
|
76
|
-
|
|
76
|
+
"refurb>=2.3.0",
|
|
77
|
+
]
|
|
77
78
|
|
|
78
79
|
|
|
79
80
|
[tool.uv]
|
|
80
81
|
package = true
|
|
81
|
-
required-version = ">=0.
|
|
82
|
+
required-version = ">=0.10.0,<0.12.0"
|
|
82
83
|
|
|
83
84
|
[tool.uv.workspace]
|
|
84
85
|
members = ["."]
|
|
@@ -2,30 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import contextlib
|
|
6
6
|
import sys
|
|
7
|
-
import
|
|
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
|
|
19
|
-
from .
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
"""
|
|
32
|
+
"""Display the current user's Dojo Belt status."""
|
|
33
|
+
config = load_config(config_path)
|
|
46
34
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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,
|
|
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
|
-
|
|
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,205 @@
|
|
|
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
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
bool: True if the object is a basic Python type, False otherwise.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
return (
|
|
81
|
+
isinstance(obj, (str, int, float, list, dict, tuple, set, bool)) or obj is None
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _get_basic_info(obj: object) -> list[Text]:
|
|
86
|
+
"""Get information for basic types.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
list[Text]: A list of Rich Text objects representing the basic info.
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
return [
|
|
93
|
+
Text("Value:", style="bold"),
|
|
94
|
+
Text(f" {obj!r}", style="yellow"),
|
|
95
|
+
Text(""),
|
|
96
|
+
Text("No attributes or methods to display for this type.", style="dim"),
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _format_section(title: str, items: list[str], empty_message: str) -> list[Text]:
|
|
101
|
+
"""Format a section of the inspection output.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
list[Text]: A list of Rich Text objects for the formatted section.
|
|
105
|
+
|
|
106
|
+
"""
|
|
107
|
+
lines: list[Text] = []
|
|
108
|
+
if items:
|
|
109
|
+
lines.append(Text(title, style="bold"))
|
|
110
|
+
lines.extend([Text(f" {item}") for item in items])
|
|
111
|
+
else:
|
|
112
|
+
lines.append(Text(empty_message, style="dim"))
|
|
113
|
+
lines.append(Text(""))
|
|
114
|
+
return lines
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _get_attributes_section(obj: object) -> list[Text]:
|
|
118
|
+
"""Get the attributes section for the object info.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
list[Text]: A list of Rich Text objects for the attributes section.
|
|
122
|
+
|
|
123
|
+
"""
|
|
124
|
+
return _format_section(
|
|
125
|
+
"Attributes:",
|
|
126
|
+
get_object_attributes(obj),
|
|
127
|
+
"No attributes found.",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _get_methods_section(obj: object) -> list[Text]:
|
|
132
|
+
"""Get the methods section for the object info.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
list[Text]: A list of Rich Text objects for the methods section.
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
return _format_section(
|
|
139
|
+
"Methods:",
|
|
140
|
+
[f"{method}()" for method in get_object_methods(obj)],
|
|
141
|
+
"No public methods found.",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def get_simplified_object_info(obj: object) -> list[Text]:
|
|
146
|
+
"""Generate a simplified, Rich-formatted inspection output for an object.
|
|
147
|
+
|
|
148
|
+
Handles basic Python types by displaying their value directly. For other objects, it
|
|
149
|
+
lists their attributes and public methods.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
obj (object): The object to generate info for.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
list[Text]: A list of Rich Text objects representing the object's information.
|
|
156
|
+
|
|
157
|
+
"""
|
|
158
|
+
info_lines: list[Text] = []
|
|
159
|
+
obj_type: str = type(obj).__name__
|
|
160
|
+
info_lines.extend((Text(f"<class '{obj_type}'>", style="cyan bold"), Text("")))
|
|
161
|
+
|
|
162
|
+
if _is_basic_type(obj):
|
|
163
|
+
info_lines.extend(_get_basic_info(obj))
|
|
164
|
+
return info_lines
|
|
165
|
+
|
|
166
|
+
info_lines.extend(_get_attributes_section(obj))
|
|
167
|
+
info_lines.extend(_get_methods_section(obj))
|
|
168
|
+
|
|
169
|
+
return info_lines
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def inspect_objects_side_by_side(
|
|
173
|
+
obj1: object,
|
|
174
|
+
obj2: object,
|
|
175
|
+
) -> None:
|
|
176
|
+
"""Display two Python objects side-by-side in the terminal using Rich.
|
|
177
|
+
|
|
178
|
+
Showing their attributes and methods in a simplified, aligned format.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
obj1 (object): The first object to display.
|
|
182
|
+
obj2 (object): The second object to display.
|
|
183
|
+
|
|
184
|
+
"""
|
|
185
|
+
main_console: Console = Console()
|
|
186
|
+
|
|
187
|
+
# Get info for both objects
|
|
188
|
+
lines1: list[Text] = get_simplified_object_info(obj1)
|
|
189
|
+
lines2: list[Text] = get_simplified_object_info(obj2)
|
|
190
|
+
|
|
191
|
+
# Convert list of Text to a single Renderable for Panel
|
|
192
|
+
inspect_text1: Text = Text("\n").join(lines1)
|
|
193
|
+
inspect_text2: Text = Text("\n").join(lines2)
|
|
194
|
+
|
|
195
|
+
# Create Panels for each object's info
|
|
196
|
+
panel1: Panel = Panel(inspect_text1, border_style="green", expand=True)
|
|
197
|
+
panel2: Panel = Panel(inspect_text2, border_style="green", expand=True)
|
|
198
|
+
|
|
199
|
+
# Use a Table to display panels side-by-side
|
|
200
|
+
table = Table(show_header=False, show_lines=False, expand=True)
|
|
201
|
+
table.add_column(width=main_console.width // 2 - 1)
|
|
202
|
+
table.add_column(width=main_console.width // 2 - 1)
|
|
203
|
+
table.add_row(panel1, panel2)
|
|
204
|
+
|
|
205
|
+
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)
|
|
@@ -100,29 +114,29 @@ def load_raw_config(config_path: Path) -> JSON:
|
|
|
100
114
|
|
|
101
115
|
# If config is in [tool.debug_dojo] (pyproject.toml), extract it.
|
|
102
116
|
try:
|
|
103
|
-
dojo_config = cast(
|
|
117
|
+
dojo_config = cast(JSON, config_data["tool"]["debug_dojo"])
|
|
104
118
|
except KeyError:
|
|
105
119
|
return {}
|
|
106
120
|
else:
|
|
107
121
|
return dojo_config
|
|
108
122
|
|
|
109
123
|
|
|
110
|
-
def
|
|
111
|
-
|
|
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
|
|
115
|
-
verbose (bool): If True, print verbose messages during validation
|
|
132
|
+
raw_config (JSON): The raw configuration data.
|
|
133
|
+
verbose (bool): If True, print verbose messages during validation.
|
|
116
134
|
|
|
117
135
|
Returns:
|
|
118
|
-
|
|
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
|
-
|
|
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
|
|
@@ -152,7 +154,12 @@ class DebugDojoConfigV1:
|
|
|
152
154
|
"""Features to install for debugging."""
|
|
153
155
|
|
|
154
156
|
def update(self) -> DebugDojoConfigV2:
|
|
155
|
-
"""Update the configuration to the latest version.
|
|
157
|
+
"""Update the configuration to the latest version.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
DebugDojoConfigV2: The updated configuration object.
|
|
161
|
+
|
|
162
|
+
"""
|
|
156
163
|
v2_exceptions = ExceptionsConfig(
|
|
157
164
|
rich_traceback=self.features.rich_traceback,
|
|
158
165
|
)
|