debug-dojo 0.3.2__tar.gz → 0.4.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.
@@ -205,3 +205,4 @@ __marimo__/
205
205
  # IDE specific files
206
206
  .idea/
207
207
  .vscode/
208
+ .DS_Store
@@ -1,38 +1,37 @@
1
- ## v0.3.0 (2025-07-28)
1
+ ## v0.4.0 (2025-08-10)
2
2
 
3
- *release tag*: [v0.3.2]
3
+ *release tag*: [v0.4.0]
4
+
5
+ - New configuration model `DebugDojoConfig` for better structure.
6
+ - Added support for `debugger` configuration in `dojo.toml`.
7
+ - Updated `dojo` command to include debugger type in command call.
8
+ - Configuration versioning allows for in-flight migration of old configurations.
9
+ - Improved error handling in configuration loading.
10
+ - Improved catching errors from target execution.
4
11
 
5
- ### What's Changed
12
+ ## v0.3.2 (2025-07-28)
13
+
14
+ *release tag*: [v0.3.2]
6
15
 
7
16
  - New logo for the project.
8
17
  - Typer used for CLI implementation.
9
18
  - Dojo is configured via `dojo.toml` or `pyproject.toml`.
10
19
  - Support for `debugpy` and `ipdb` for debugging.
11
-
12
- #### Fixes
13
-
14
20
  - Fixed documentation and history.
15
21
 
16
22
  ## v0.2.0 (2025-07-20)
17
23
 
18
24
  *release tag*: [v0.2.0]
19
25
 
20
- ### What's Changed
21
-
22
26
  - Added `dojo` command for easy debugging setup.
23
27
  - Added `p()` function for rich printing.
24
28
  - Added history file for tracking changes.
25
29
  - Moved to `hatch` for building and packaging.
26
-
27
- #### Fixes
28
-
29
30
  - Fixed `pyproject.toml` to point to GitHub repository as the homepage.
30
31
 
31
32
  ## v0.1.0 (2025-07-19)
32
33
 
33
34
  *release tag*: [v0.1.0]
34
35
 
35
- ### What's Changed
36
-
37
36
  - Initial module to install debugging tools.
38
37
  - Debug mode utilities for PuDB, rich tracebacks, and object inspection.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: debug-dojo
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: Debbugging dojo for Python developers
5
5
  Project-URL: Homepage, https://github.com/bwrob/debug-dojo
6
6
  Project-URL: Documentation, https://github.com/bwrob/debug-dojo/blob/main/README.md
@@ -48,7 +48,7 @@ It leverages [`rich`](https://github.com/Textualize/rich) for beautiful output a
48
48
 
49
49
  - **Convenient CLI** Quickly run your code with debugging tools enabled.
50
50
  - **Simple API:** Install all tools or only what you need.
51
- - **Debugger integration:** Quickly enable Debugpy, PuDB, PDB, or IPDB as your default debugger.
51
+ - **Debugger integration:** Quickly enable Debugpy, PuDB, PDB, or IPDB as your default pre-configured debugger.
52
52
  - **Rich tracebacks:** Get readable, colorized tracebacks for easier debugging.
53
53
  - **Side-by-side object inspection:** Visually compare Python objects, their attributes, and methods in the terminal.
54
54
  - **Configuration:** Easily configure the debugging tools using `dojo.toml` or `pyproject.toml`.
@@ -63,10 +63,10 @@ Run your Python script with debugging tools enabled using the `debug-dojo` comma
63
63
  dojo my_script.py
64
64
  ```
65
65
 
66
- You can optionally set configuration file and verbose mode:
66
+ You can optionally set configuration, verbose mode, and specify the debugger type. Both script files and modules are supported:
67
67
 
68
68
  ```console
69
- dojo --config dojo.toml --verbose my_script.py
69
+ dojo --debugger ipdb --config dojo.toml --verbose --module my_module
70
70
  ```
71
71
 
72
72
  ### From the code
@@ -74,19 +74,18 @@ dojo --config dojo.toml --verbose my_script.py
74
74
  In the `PuDB` style, you can install all debugging tools and enter the debugging mode with a single command:
75
75
 
76
76
  ```python
77
- import debug_dojo.install; b()
78
-
79
77
  object_1 = {"foo": 1, "bar": 2}
80
78
  object_2 = [1, 2, 3]
81
79
 
80
+ import debug_dojo.install; b()
82
81
  p(object_1) # Pretty print an object with Rich
83
- i(object_1) # Inspect an object
84
- c(object_1, object_2) # Compare two objects side-by-side
85
82
  ```
86
83
 
87
- Where:
84
+ ### Features in debugging mode
88
85
 
89
- - `b()` is a builtin-injected function that sets a breakpoint using PuDB's `set_trace()`.
86
+ The following functions are available in the debugging mode, injected into builtins:
87
+
88
+ - `b()` is a hook that sets a breakpoint using the configured debugger.
90
89
  - `p(object_1)` is pretty printing of an object using Rich.
91
90
  - `i(object_1)` to inspect an object using Rich.
92
91
  - `c(object_1, object_2)` to compare two objects side-by-side.
@@ -98,12 +97,23 @@ You can configure the debugging tools using a `dojo.toml` or `pyproject.toml` fi
98
97
  **Example `dojo.toml`:**
99
98
 
100
99
  ```toml
101
- debugger = "ipdb"
100
+ [debuggers]
101
+ default = "ipdb"
102
+
103
+ debugpy = { port = 1992 }
104
+ ipdb = { context_lines = 20 }
105
+
106
+ [exceptions]
107
+ locals_in_traceback = false
108
+ post_mortem = true
109
+ rich_traceback = true
102
110
 
103
111
  [features]
104
- rich_inspect = true
105
- rich_print = true
106
- comparer = false
112
+ breakpoint = "b"
113
+ # Empty string means disable the feature
114
+ comparer = ""
115
+ rich_inspect = "i"
116
+ rich_print = "p"
107
117
  ```
108
118
 
109
119
  ## Installation
@@ -133,46 +143,50 @@ Note that dojo most likely will not work when installed via `pipx` or `uvx`, as
133
143
  ```console
134
144
  ruff check src/debug_dojo --fix
135
145
  basedpyright src/debug_dojo
146
+
147
+ poe code-quality
136
148
  ```
137
149
 
150
+ ### Tests
151
+
152
+ TBA
138
153
 
139
154
  ## Changelog
140
155
 
141
- ## v0.3.0 (2025-07-28)
156
+ ## v0.4.0 (2025-08-10)
142
157
 
143
- *release tag*: [v0.3.2]
158
+ *release tag*: [v0.4.0]
144
159
 
145
- ### What's Changed
160
+ - New configuration model `DebugDojoConfig` for better structure.
161
+ - Added support for `debugger` configuration in `dojo.toml`.
162
+ - Updated `dojo` command to include debugger type in command call.
163
+ - Configuration versioning allows for in-flight migration of old configurations.
164
+ - Improved error handling in configuration loading.
165
+ - Improved catching errors from target execution.
166
+
167
+ ## v0.3.2 (2025-07-28)
168
+
169
+ *release tag*: [v0.3.2]
146
170
 
147
171
  - New logo for the project.
148
172
  - Typer used for CLI implementation.
149
173
  - Dojo is configured via `dojo.toml` or `pyproject.toml`.
150
174
  - Support for `debugpy` and `ipdb` for debugging.
151
-
152
- #### Fixes
153
-
154
175
  - Fixed documentation and history.
155
176
 
156
177
  ## v0.2.0 (2025-07-20)
157
178
 
158
179
  *release tag*: [v0.2.0]
159
180
 
160
- ### What's Changed
161
-
162
181
  - Added `dojo` command for easy debugging setup.
163
182
  - Added `p()` function for rich printing.
164
183
  - Added history file for tracking changes.
165
184
  - Moved to `hatch` for building and packaging.
166
-
167
- #### Fixes
168
-
169
185
  - Fixed `pyproject.toml` to point to GitHub repository as the homepage.
170
186
 
171
187
  ## v0.1.0 (2025-07-19)
172
188
 
173
189
  *release tag*: [v0.1.0]
174
190
 
175
- ### What's Changed
176
-
177
191
  - Initial module to install debugging tools.
178
192
  - Debug mode utilities for PuDB, rich tracebacks, and object inspection.
@@ -16,7 +16,7 @@ It leverages [`rich`](https://github.com/Textualize/rich) for beautiful output a
16
16
 
17
17
  - **Convenient CLI** Quickly run your code with debugging tools enabled.
18
18
  - **Simple API:** Install all tools or only what you need.
19
- - **Debugger integration:** Quickly enable Debugpy, PuDB, PDB, or IPDB as your default debugger.
19
+ - **Debugger integration:** Quickly enable Debugpy, PuDB, PDB, or IPDB as your default pre-configured debugger.
20
20
  - **Rich tracebacks:** Get readable, colorized tracebacks for easier debugging.
21
21
  - **Side-by-side object inspection:** Visually compare Python objects, their attributes, and methods in the terminal.
22
22
  - **Configuration:** Easily configure the debugging tools using `dojo.toml` or `pyproject.toml`.
@@ -31,10 +31,10 @@ Run your Python script with debugging tools enabled using the `debug-dojo` comma
31
31
  dojo my_script.py
32
32
  ```
33
33
 
34
- You can optionally set configuration file and verbose mode:
34
+ You can optionally set configuration, verbose mode, and specify the debugger type. Both script files and modules are supported:
35
35
 
36
36
  ```console
37
- dojo --config dojo.toml --verbose my_script.py
37
+ dojo --debugger ipdb --config dojo.toml --verbose --module my_module
38
38
  ```
39
39
 
40
40
  ### From the code
@@ -42,19 +42,18 @@ dojo --config dojo.toml --verbose my_script.py
42
42
  In the `PuDB` style, you can install all debugging tools and enter the debugging mode with a single command:
43
43
 
44
44
  ```python
45
- import debug_dojo.install; b()
46
-
47
45
  object_1 = {"foo": 1, "bar": 2}
48
46
  object_2 = [1, 2, 3]
49
47
 
48
+ import debug_dojo.install; b()
50
49
  p(object_1) # Pretty print an object with Rich
51
- i(object_1) # Inspect an object
52
- c(object_1, object_2) # Compare two objects side-by-side
53
50
  ```
54
51
 
55
- Where:
52
+ ### Features in debugging mode
56
53
 
57
- - `b()` is a builtin-injected function that sets a breakpoint using PuDB's `set_trace()`.
54
+ The following functions are available in the debugging mode, injected into builtins:
55
+
56
+ - `b()` is a hook that sets a breakpoint using the configured debugger.
58
57
  - `p(object_1)` is pretty printing of an object using Rich.
59
58
  - `i(object_1)` to inspect an object using Rich.
60
59
  - `c(object_1, object_2)` to compare two objects side-by-side.
@@ -66,12 +65,23 @@ You can configure the debugging tools using a `dojo.toml` or `pyproject.toml` fi
66
65
  **Example `dojo.toml`:**
67
66
 
68
67
  ```toml
69
- debugger = "ipdb"
68
+ [debuggers]
69
+ default = "ipdb"
70
+
71
+ debugpy = { port = 1992 }
72
+ ipdb = { context_lines = 20 }
73
+
74
+ [exceptions]
75
+ locals_in_traceback = false
76
+ post_mortem = true
77
+ rich_traceback = true
70
78
 
71
79
  [features]
72
- rich_inspect = true
73
- rich_print = true
74
- comparer = false
80
+ breakpoint = "b"
81
+ # Empty string means disable the feature
82
+ comparer = ""
83
+ rich_inspect = "i"
84
+ rich_print = "p"
75
85
  ```
76
86
 
77
87
  ## Installation
@@ -101,4 +111,10 @@ Note that dojo most likely will not work when installed via `pipx` or `uvx`, as
101
111
  ```console
102
112
  ruff check src/debug_dojo --fix
103
113
  basedpyright src/debug_dojo
114
+
115
+ poe code-quality
104
116
  ```
117
+
118
+ ### Tests
119
+
120
+ TBA
@@ -13,7 +13,7 @@
13
13
  "rich>=12.0.0",
14
14
  "tomlkit>=0.13.3",
15
15
  "typer>=0.15.0",
16
- ]
16
+ ]
17
17
  license = 'MIT'
18
18
  license-files = ['LICENSE']
19
19
  keywords = [
@@ -36,7 +36,7 @@
36
36
  "Topic :: Software Development :: Debuggers",
37
37
  "Topic :: Software Development :: Libraries :: Python Modules",
38
38
  ]
39
- version = "0.3.2"
39
+ version = "0.4.0"
40
40
  dynamic = ['readme']
41
41
 
42
42
  [project.urls]
@@ -46,8 +46,6 @@ version = "0.3.2"
46
46
  Changelog = "https://github.com/bwrob/debug-dojo/blob/main/HISTORY.md"
47
47
 
48
48
  [project.scripts]
49
- # This defines your command-line script!
50
- # It points to the 'main' function in your 'cli' module.
51
49
  "dojo" = "debug_dojo._cli:main"
52
50
 
53
51
  [dependency-groups]
@@ -56,7 +54,7 @@ version = "0.3.2"
56
54
  "jupyter>=1.1.1",
57
55
  "poethepoet>=0.36.0",
58
56
  "ruff>=0.12.4",
59
- ]
57
+ ]
60
58
 
61
59
  [build-system]
62
60
  requires = [
@@ -86,22 +84,19 @@ version = "0.3.2"
86
84
  ]
87
85
 
88
86
  [tool.basedpyright]
89
- typeCheckingMode = "all"
87
+ typeCheckingMode = "basic"
90
88
  pythonVersion = "3.10"
91
89
  strict = ["src/debug_dojo/*"]
92
90
  ignore = ["examples"]
93
91
 
94
92
  [tool.poe.tasks]
95
- # Define your tasks here
96
93
  lint = "ruff check --fix src"
97
94
  format = "ruff format src"
98
- type-check = "basedpyright"
95
+ type-check = "basedpyright --project pyproject.toml src/debug_dojo"
99
96
  code-quality = ["format", "lint", "type-check"]
100
97
  publish = [
101
- "format",
102
- "lint",
103
- "type-check",
104
- {cmd="rm -rf dist"},
105
- {cmd="uv build"},
106
- {cmd="uv publish"},
98
+ "code-quality",
99
+ { cmd = "rm -rf dist" },
100
+ { cmd = "uv build" },
101
+ { cmd = "uv publish" },
107
102
  ]
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import os
6
5
  import runpy
7
6
  import sys
8
7
  from bdb import BdbQuit
@@ -10,10 +9,9 @@ from pathlib import Path
10
9
  from typing import Annotated
11
10
 
12
11
  import typer
13
- from pydantic import ValidationError
14
12
  from rich import print as rich_print
15
13
 
16
- from ._config import DebugDojoConfig, load_config
14
+ from ._config import DebugDojoConfig, DebuggerType, load_config
17
15
  from ._installers import install_by_config
18
16
 
19
17
  cli = typer.Typer(
@@ -23,11 +21,13 @@ cli = typer.Typer(
23
21
  )
24
22
 
25
23
 
26
- def execute_with_debug(
24
+ def execute_with_debug( # noqa: C901
27
25
  target_name: str,
28
26
  target_args: list[str],
29
27
  *,
28
+ target_is_module: bool,
30
29
  verbose: bool,
30
+ config: DebugDojoConfig,
31
31
  ) -> None:
32
32
  """Execute a target script or module with installation of debugging tools."""
33
33
  sys.argv = [target_name, *target_args]
@@ -36,21 +36,37 @@ def execute_with_debug(
36
36
  rich_print(f"[blue]Installing debugging tools for {target_name}.[/blue]")
37
37
  rich_print(f"[blue]Arguments for target: {target_args}[/blue]")
38
38
 
39
- config = load_config()
40
39
  install_by_config(config)
41
40
 
42
- if (
43
- Path(target_name).exists()
44
- or target_name.endswith(".py")
45
- or os.sep in target_name
46
- ):
41
+ if target_is_module:
42
+ runner = runpy.run_module
43
+ else:
47
44
  if not Path(target_name).exists():
48
45
  sys.exit(1)
49
46
  runner = runpy.run_path
50
- else:
51
- runner = runpy.run_module
52
47
 
53
- _ = runner(target_name, run_name="__main__")
48
+ try:
49
+ _ = runner(target_name, run_name="__main__")
50
+ except ImportError as e:
51
+ rich_print(f"[red]Error importing {target_name}:[/red]\n{e}")
52
+ sys.exit(1)
53
+ except BdbQuit:
54
+ rich_print("[red]Debugging session terminated by user.[/red]")
55
+ sys.exit(0)
56
+ except KeyboardInterrupt:
57
+ rich_print("[red]Execution interrupted by user.[/red]")
58
+ sys.exit(0)
59
+ except SystemExit as e:
60
+ if e.code:
61
+ rich_print(f"[red]Script exited with code {e.code}.[/red]")
62
+ except Exception as e: # noqa: BLE001
63
+ rich_print(f"[red]Error while running {target_name}:[/red]\n{e}")
64
+ if config.exceptions.post_mortem:
65
+ import pdb # noqa: PLC0415, T100
66
+
67
+ rich_print("[blue]Entering post-mortem debugging session...[/blue]")
68
+ pdb.post_mortem(e.__traceback__)
69
+ sys.exit(1)
54
70
 
55
71
 
56
72
  def display_config(config: DebugDojoConfig) -> None:
@@ -64,36 +80,42 @@ def display_config(config: DebugDojoConfig) -> None:
64
80
  no_args_is_help=True,
65
81
  context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
66
82
  )
67
- def run_debug(
83
+ def run_debug( # noqa: PLR0913
68
84
  ctx: typer.Context,
69
85
  target_name: Annotated[
70
86
  str | None, typer.Argument(help="The target script or module to debug.")
71
87
  ] = None,
88
+ *,
72
89
  config_path: Annotated[
73
90
  Path | None, typer.Option("--config", "-c", help="Show configuration")
74
91
  ] = None,
75
- *,
92
+ debugger: Annotated[
93
+ DebuggerType | None,
94
+ typer.Option("--debugger", "-d", help="Specify the debugger to use"),
95
+ ] = None,
76
96
  verbose: Annotated[
77
97
  bool,
78
98
  typer.Option("--verbose", "-v", is_flag=True, help="Enable verbose output"),
79
99
  ] = False,
100
+ module: Annotated[
101
+ bool,
102
+ typer.Option("--module", "-m", is_flag=True, help="Run as a module"),
103
+ ] = False,
80
104
  ) -> None:
81
105
  """Run the command-line interface."""
82
- try:
83
- config = load_config(config_path, verbose=verbose)
84
- except ValidationError as e:
85
- rich_print(f"[red]Configuration error:\n{e}[/red]")
86
- sys.exit(1)
106
+ config = load_config(config_path, verbose=verbose, debugger=debugger)
87
107
 
88
108
  if verbose:
89
109
  display_config(config)
90
110
 
91
111
  if target_name:
92
- try:
93
- execute_with_debug(target_name, ctx.args, verbose=verbose)
94
- except BdbQuit:
95
- rich_print("[red]Debugging session terminated by user.[/red]")
96
- sys.exit(0)
112
+ execute_with_debug(
113
+ target_name=target_name,
114
+ target_is_module=module,
115
+ target_args=ctx.args,
116
+ verbose=verbose,
117
+ config=config,
118
+ )
97
119
 
98
120
 
99
121
  def main() -> None:
@@ -0,0 +1,136 @@
1
+ """Debug Dojo configuration module.
2
+
3
+ It includes configurations for different debuggers, exception handling,
4
+ and features that can be enabled or disabled.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Any, cast
12
+
13
+ from pydantic import ValidationError
14
+ from rich import print as rich_print
15
+ from tomlkit import parse
16
+ from tomlkit.exceptions import TOMLKitError
17
+
18
+ from ._config_models import (
19
+ DebugDojoConfig,
20
+ DebugDojoConfigV1,
21
+ DebugDojoConfigV2,
22
+ DebuggerType,
23
+ )
24
+
25
+
26
+ def __filter_pydantic_error_msg(error: ValidationError) -> str:
27
+ """Filter out specific lines from a Pydantic validation error."""
28
+ return "\n".join(
29
+ line
30
+ for line in str(error).splitlines()
31
+ if not line.startswith("For further information visit")
32
+ )
33
+
34
+
35
+ def resolve_config_path(config_path: Path | None) -> Path | None:
36
+ """Resolve the configuration path, returning a default if none is provided."""
37
+ if config_path:
38
+ if not config_path.exists():
39
+ msg = f"Configuration file not found:\n{config_path.resolve()}"
40
+ raise FileNotFoundError(msg)
41
+ return config_path.resolve()
42
+
43
+ # Default configuration path
44
+ for path in (Path("dojo.toml"), Path("pyproject.toml")):
45
+ if path.exists():
46
+ return path.resolve()
47
+ return None
48
+
49
+
50
+ def load_raw_config(
51
+ config_path: Path,
52
+ ) -> dict[str, Any]: # pyright: ignore[reportExplicitAny]
53
+ """Load the Debug Dojo configuration from a file.
54
+
55
+ Currently supports 'dojo.toml' or 'pyproject.toml'.
56
+ If no path is provided, it checks the current directory for these files.
57
+ """
58
+ config_str = config_path.read_text(encoding="utf-8")
59
+
60
+ try:
61
+ config_data = parse(config_str).unwrap()
62
+ except TOMLKitError as e:
63
+ msg = f"Error parsing configuration file {config_path.resolve()}."
64
+ raise ValueError(msg) from e
65
+
66
+ # If config is in [tool.debug_dojo] (pyproject.toml), extract it.
67
+ if config_path.name == "pyproject.toml":
68
+ try:
69
+ dojo_config = cast("dict[str, Any]", config_data["tool"]["debug_dojo"])
70
+ except KeyError:
71
+ return {}
72
+ else:
73
+ return dojo_config
74
+
75
+ return config_data
76
+
77
+
78
+ def load_config( # noqa: C901
79
+ config_path: Path | None = None,
80
+ *,
81
+ verbose: bool = False,
82
+ debugger: DebuggerType | None = None,
83
+ ) -> DebugDojoConfig:
84
+ """Load the Debug Dojo configuration and return a DebugDojoConfig instance."""
85
+ resolved_path = resolve_config_path(config_path)
86
+
87
+ if verbose:
88
+ if resolved_path:
89
+ msg = f"Using configuration file: {resolved_path}."
90
+ else:
91
+ msg = "No configuration file found, using default settings."
92
+ rich_print(f"[blue]{msg}[/blue]")
93
+
94
+ if not resolved_path:
95
+ return DebugDojoConfig()
96
+
97
+ raw_config = load_raw_config(resolved_path)
98
+
99
+ config = None
100
+ for model in (DebugDojoConfigV2, DebugDojoConfigV1):
101
+ model_name = model.__name__
102
+ try:
103
+ config = model.model_validate(raw_config)
104
+ except ValidationError as e:
105
+ if verbose:
106
+ msg = (
107
+ f"[yellow]Configuration validation error for {model_name}:\n"
108
+ f"{__filter_pydantic_error_msg(e)}\n\n"
109
+ f"Please check your configuration file {resolved_path}.[/yellow]"
110
+ )
111
+ rich_print(msg)
112
+ else:
113
+ if verbose or model_name != DebugDojoConfig.__name__:
114
+ msg = (
115
+ f"[blue]Using configuration model: {model_name}.\n"
116
+ f"Current configuration model {DebugDojoConfig.__name__}. [/blue]"
117
+ )
118
+ rich_print(msg)
119
+ break
120
+
121
+ if not config:
122
+ msg = (
123
+ f"[red]Unsupported configuration version in {resolved_path.resolve()}.\n"
124
+ "Please update your configuration file.[/red]"
125
+ )
126
+ rich_print(msg)
127
+ sys.exit(1)
128
+
129
+ while not isinstance(config, DebugDojoConfig):
130
+ config = config.update()
131
+
132
+ # If a debugger is specified, update the config.
133
+ if debugger:
134
+ config.debuggers.default = debugger
135
+
136
+ return config
@@ -0,0 +1,138 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+
5
+ from pydantic import BaseModel, ConfigDict
6
+
7
+
8
+ class BaseConfig(BaseModel):
9
+ """Base configuration class with extra fields forbidden."""
10
+
11
+ model_config = ConfigDict(extra="forbid", validate_assignment=True)
12
+
13
+
14
+ class DebuggerType(Enum):
15
+ """Enum for different types of debuggers."""
16
+
17
+ PDB = "pdb"
18
+ PUDB = "pudb"
19
+ IPDB = "ipdb"
20
+ DEBUGPY = "debugpy"
21
+
22
+
23
+ class Features(BaseModel):
24
+ """Configuration for installing debug features."""
25
+
26
+ model_config = ConfigDict(extra="forbid") # pyright: ignore[reportUnannotatedClassAttribute]
27
+
28
+ rich_inspect: bool = True
29
+ """Install rich inspect as 'i' for enhanced object inspection."""
30
+ rich_print: bool = True
31
+ """Install rich print as 'p' for enhanced printing."""
32
+ rich_traceback: bool = True
33
+ """Install rich traceback for better error reporting."""
34
+ comparer: bool = True
35
+ """Install comparer as 'c' for side-by-side object comparison."""
36
+ breakpoint: bool = True
37
+ """Install breakpoint as 'b' for setting breakpoints in code."""
38
+
39
+
40
+ class DebugpyConfig(BaseConfig):
41
+ """Configuration for debugpy debugger."""
42
+
43
+ port: int = 1992
44
+ """Port for debugpy debugger."""
45
+ host: str = "localhost"
46
+ """Host for debugpy debugger."""
47
+ wait_for_client: bool = True
48
+ """Whether to wait for the client to connect before starting debugging."""
49
+ log_to_file: bool = False
50
+ """Whether to log debugpy output to a file."""
51
+
52
+
53
+ class IpdbConfig(BaseConfig):
54
+ """Configuration for ipdb debugger."""
55
+
56
+ context_lines: int = 20
57
+ """Number of context lines to show in ipdb."""
58
+
59
+
60
+ class DebuggersConfig(BaseConfig):
61
+ """Configuration for debuggers."""
62
+
63
+ default: DebuggerType = DebuggerType.IPDB
64
+ """Default debugger to use."""
65
+ prompt_name: str = "debug-dojo> "
66
+ """Prompt name for the debugger, used in the REPL."""
67
+ debugpy: DebugpyConfig = DebugpyConfig()
68
+ """Configuration for debugpy debugger."""
69
+ ipdb: IpdbConfig = IpdbConfig()
70
+ """Configuration for ipdb debugger."""
71
+
72
+
73
+ class ExceptionsConfig(BaseConfig):
74
+ """Configuration for exceptions handling."""
75
+
76
+ rich_traceback: bool = True
77
+ """Enable rich traceback for better error reporting."""
78
+ locals_in_traceback: bool = False
79
+ """Include local variables in traceback."""
80
+ post_mortem: bool = True
81
+ """Enable post-mortem debugging after an exception."""
82
+
83
+
84
+ class FeaturesConfig(BaseConfig):
85
+ """Configuration for installing debug features."""
86
+
87
+ rich_inspect: str = "i"
88
+ """Install rich inspect as 'i' for enhanced object inspection."""
89
+ rich_print: str = "p"
90
+ """Install rich print as 'p' for enhanced printing."""
91
+ comparer: str = "c"
92
+ """Install comparer as 'c' for side-by-side object comparison."""
93
+ breakpoint: str = "b"
94
+ """Install breakpoint as 'b' for setting breakpoints in code."""
95
+
96
+
97
+ class DebugDojoConfigV1(BaseModel):
98
+ """Configuration for Debug Dojo."""
99
+
100
+ model_config = ConfigDict(extra="forbid") # pyright: ignore[reportUnannotatedClassAttribute]
101
+
102
+ debugger: DebuggerType = DebuggerType.PUDB
103
+ """The type of debugger to use."""
104
+ features: Features = Features()
105
+ """Features to install for debugging."""
106
+
107
+ def update(self) -> DebugDojoConfigV2:
108
+ """Update the configuration to the latest version."""
109
+ v2_exceptions = ExceptionsConfig(
110
+ rich_traceback=self.features.rich_traceback,
111
+ )
112
+ v2_debuggers = DebuggersConfig(default=self.debugger)
113
+ v2_features = FeaturesConfig(
114
+ rich_inspect="i" if self.features.rich_inspect else "",
115
+ rich_print="p" if self.features.rich_print else "",
116
+ comparer="c" if self.features.comparer else "",
117
+ breakpoint="b" if self.features.breakpoint else "",
118
+ )
119
+ return DebugDojoConfigV2(
120
+ exceptions=v2_exceptions,
121
+ debuggers=v2_debuggers,
122
+ features=v2_features,
123
+ )
124
+
125
+
126
+ class DebugDojoConfigV2(BaseModel):
127
+ """Configuration for Debug Dojo."""
128
+
129
+ model_config = ConfigDict(extra="forbid") # pyright: ignore[reportUnannotatedClassAttribute]
130
+
131
+ exceptions: ExceptionsConfig = ExceptionsConfig()
132
+ debuggers: DebuggersConfig = DebuggersConfig()
133
+ """Default debugger and configs."""
134
+ features: FeaturesConfig = FeaturesConfig()
135
+ """Features mnemonics ."""
136
+
137
+
138
+ DebugDojoConfig = DebugDojoConfigV2
@@ -14,7 +14,13 @@ import sys
14
14
  from rich import print as rich_print
15
15
 
16
16
  from ._compareres import inspect_objects_side_by_side
17
- from ._config import DebugDojoConfig, DebuggerType, Features
17
+ from ._config_models import (
18
+ DebugDojoConfig,
19
+ DebuggersConfig,
20
+ DebuggerType,
21
+ ExceptionsConfig,
22
+ FeaturesConfig,
23
+ )
18
24
 
19
25
  BREAKPOINT_ENV_VAR = "PYTHONBREAKPOINT"
20
26
 
@@ -69,11 +75,11 @@ def _use_debugpy() -> None:
69
75
  debugpy.wait_for_client()
70
76
 
71
77
 
72
- def _rich_traceback() -> None:
78
+ def _rich_traceback(*, locals_in_traceback: bool) -> None:
73
79
  """Check if Rich Traceback is available and set it as the default."""
74
80
  from rich import traceback
75
81
 
76
- _ = traceback.install(show_locals=True)
82
+ _ = traceback.install(show_locals=locals_in_traceback)
77
83
 
78
84
 
79
85
  def _inspect() -> None:
@@ -106,33 +112,42 @@ def _rich_print() -> None:
106
112
  builtins.p = rich_print # pyright: ignore[reportAttributeAccessIssue]
107
113
 
108
114
 
109
- def _install_features(features: Features) -> None:
115
+ def _install_features(features: FeaturesConfig) -> None:
110
116
  """Install the specified debugging features."""
111
117
  if features.rich_inspect:
112
118
  _inspect()
113
119
  if features.rich_print:
114
120
  _rich_print()
115
- if features.rich_traceback:
116
- _rich_traceback()
117
121
  if features.comparer:
118
122
  _compare()
119
123
  if features.breakpoint:
120
124
  _breakpoint()
121
125
 
122
126
 
123
- def _set_debugger(debugger: DebuggerType) -> None: # noqa: RET503
127
+ def _set_debugger(debugger_config: DebuggersConfig) -> None:
124
128
  """Set the debugger based on the configuration."""
129
+ debugger = debugger_config.default
130
+
125
131
  if debugger == DebuggerType.PDB:
126
- return _use_pdb()
132
+ _use_pdb()
127
133
  if debugger == DebuggerType.PUDB:
128
- return _use_pudb()
134
+ _use_pudb()
129
135
  if debugger == DebuggerType.IPDB:
130
- return _use_ipdb()
136
+ _use_ipdb()
131
137
  if debugger == DebuggerType.DEBUGPY:
132
- return _use_debugpy()
138
+ _use_debugpy()
139
+
140
+ sys.ps1 = debugger_config.prompt_name
141
+
142
+
143
+ def _set_exceptions(exceptions: ExceptionsConfig) -> None:
144
+ """Set the exception handling based on the configuration."""
145
+ if exceptions.rich_traceback:
146
+ _rich_traceback(locals_in_traceback=exceptions.locals_in_traceback)
133
147
 
134
148
 
135
149
  def install_by_config(config: DebugDojoConfig) -> None:
136
150
  """Install debugging tools."""
137
- _set_debugger(config.debugger)
151
+ _set_debugger(config.debuggers)
152
+ _set_exceptions(config.exceptions)
138
153
  _install_features(config.features)
@@ -1,117 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from enum import Enum
4
- from pathlib import Path
5
- from typing import Any, cast
6
-
7
- import tomlkit
8
- from pydantic import BaseModel, ConfigDict
9
- from rich import print as rich_print
10
-
11
-
12
- class DebuggerType(Enum):
13
- """Enum for different types of debuggers."""
14
-
15
- PDB = "pdb"
16
- PUDB = "pudb"
17
- IPDB = "ipdb"
18
- DEBUGPY = "debugpy"
19
-
20
-
21
- class Features(BaseModel):
22
- """Configuration for installing debug features."""
23
-
24
- model_config = ConfigDict(extra="forbid") # pyright: ignore[reportUnannotatedClassAttribute]
25
-
26
- rich_inspect: bool = True
27
- """Install rich inspect as 'i' for enhanced object inspection."""
28
- rich_print: bool = True
29
- """Install rich print as 'p' for enhanced printing."""
30
- rich_traceback: bool = True
31
- """Install rich traceback for better error reporting."""
32
- comparer: bool = True
33
- """Install comparer as 'c' for side-by-side object comparison."""
34
- breakpoint: bool = True
35
- """Install breakpoint as 'b' for setting breakpoints in code."""
36
-
37
-
38
- class DebugDojoConfig(BaseModel):
39
- """Configuration for Debug Dojo."""
40
-
41
- model_config = ConfigDict(extra="forbid") # pyright: ignore[reportUnannotatedClassAttribute]
42
-
43
- debugger: DebuggerType = DebuggerType.PUDB
44
- """The type of debugger to use."""
45
- features: Features = Features()
46
- """Features to install for debugging."""
47
-
48
-
49
- def resolve_config_path(config_path: Path | None) -> Path | None:
50
- """Resolve the configuration path, returning a default if none is provided."""
51
- if config_path:
52
- if not config_path.exists():
53
- msg = f"Configuration file not found:\n{config_path.resolve()}"
54
- raise FileNotFoundError(msg)
55
- return config_path
56
-
57
- # Default configuration path
58
- for path in (Path("dojo.toml"), Path("pyproject.toml")):
59
- if path.exists():
60
- return path
61
- return None
62
-
63
-
64
- def load_raw_config(
65
- config_path: Path,
66
- ) -> dict[str, Any]: # pyright: ignore[reportExplicitAny]
67
- """Load the Debug Dojo configuration from a file.
68
-
69
- Currently supports 'dojo.toml' or 'pyproject.toml'.
70
- If no path is provided, it checks the current directory for these files.
71
- """
72
- with config_path.open("rb") as f:
73
- config_data = tomlkit.load(f).unwrap()
74
-
75
- # If config is in [tool.debug_dojo] (pyproject.toml), extract it.
76
- if config_path.name == "dojo.toml":
77
- return config_data
78
-
79
- if config_path.name == "pyproject.toml":
80
- try:
81
- dojo_config = cast(
82
- "dict[str, Any]", # pyright: ignore[reportExplicitAny]
83
- config_data["tool"]["debug_dojo"],
84
- )
85
- except KeyError:
86
- return {}
87
- else:
88
- return dojo_config
89
-
90
- # If the file is not recognized, raise an error.
91
- msg = (
92
- f"Unsupported configuration file: \n{config_path.resolve()}\n"
93
- "Expected 'dojo.toml' or 'pyproject.toml'."
94
- )
95
- raise ValueError(msg)
96
-
97
-
98
- def load_config(
99
- config_path: Path | None = None,
100
- *,
101
- verbose: bool = False,
102
- ) -> DebugDojoConfig:
103
- """Load the Debug Dojo configuration and return a DebugDojoConfig instance."""
104
- resolved_path = resolve_config_path(config_path)
105
-
106
- if verbose:
107
- if resolved_path:
108
- msg = f"Using configuration file: {resolved_path.resolve()}."
109
- else:
110
- msg = "No configuration file found, using default settings."
111
- rich_print(f"[blue]{msg}[/blue]")
112
-
113
- if not resolved_path:
114
- return DebugDojoConfig()
115
-
116
- raw_config = load_raw_config(resolved_path)
117
- return DebugDojoConfig.model_validate(raw_config)
File without changes