Skip to content

API Reference

Debugging tools for Python.

This module provides functions to set up debugging tools like PuDB and Rich Traceback. It checks for the availability of these tools and configures them accordingly.

Importing this module will install the debugging tools based on the configuration. Example usage:

1
2
3
import debug_dojo.install

b()

This will install the debugging tools and put debug breakpoint at this line.

Another way to use this module is to run the desired script or module with the dojo command-line interface.

$ dojo --verbose --config dojo.toml target_script.py --arg_1_for_script value1

Debugging tools for Python.

This module provides functions to set up debugging tools like PuDB and Rich Traceback. It checks for the availability of these tools and configures them accordingly.

install_by_config(config)

Install debugging tools.

Source code in src/debug_dojo/_installers.py
def install_by_config(config: DebugDojoConfig) -> None:
    """Install debugging tools."""
    _set_debugger(config.debuggers)
    _set_exceptions(config.exceptions)
    _install_features(config.features)

Utilities for side-by-side inspection and comparison of Python objects using Rich.

This module provides functions to display attributes and methods of two objects in a visually appealing, side-by-side format in the terminal.

inspect_objects_side_by_side(obj1, obj2)

Display two Python objects side-by-side in the terminal using Rich.

Showing their attributes and methods in a simplified, aligned format.

Source code in src/debug_dojo/_compareres.py
def inspect_objects_side_by_side(
    obj1: object,
    obj2: object,
) -> None:
    """Display two Python objects side-by-side in the terminal using Rich.

    Showing their attributes and methods in a simplified, aligned format.
    """
    main_console: Console = Console()

    lines1: list[Text] = _get_simplified_object_info(obj1)
    lines2: list[Text] = _get_simplified_object_info(obj2)

    # Determine the maximum number of lines to ensure consistent height
    max_lines: int = max(len(lines1), len(lines2))

    # Pad the shorter list of lines with empty Text objects to match the height
    if len(lines1) < max_lines:
        lines1.extend([Text("")] * (max_lines - len(lines1)))
    if len(lines2) < max_lines:
        lines2.extend([Text("")] * (max_lines - len(lines2)))

    # Join the padded lines into a single Text object for the Panel content
    padded_inspect_text1: Text = Text("\n").join(lines1)
    padded_inspect_text2: Text = Text("\n").join(lines2)

    panel1: Panel = Panel(padded_inspect_text1, border_style="green", expand=True)
    panel2: Panel = Panel(padded_inspect_text2, border_style="green", expand=True)

    main_console.print(Columns([panel1, panel2]))

Debug Dojo configuration module.

It includes configurations for different debuggers, exception handling, and features that can be enabled or disabled.

__filter_pydantic_error_msg(error)

Filter out specific lines from a Pydantic validation error.

Source code in src/debug_dojo/_config.py
def __filter_pydantic_error_msg(error: ValidationError) -> str:
    """Filter out specific lines from a Pydantic validation error."""
    return "\n".join(
        line
        for line in str(error).splitlines()
        if not line.startswith("For further information visit")
    )

load_config(config_path=None, *, verbose=False, debugger=None)

Load the Debug Dojo configuration and return a DebugDojoConfig instance.

Source code in src/debug_dojo/_config.py
def load_config(  # noqa: C901
    config_path: Path | None = None,
    *,
    verbose: bool = False,
    debugger: DebuggerType | None = None,
) -> DebugDojoConfig:
    """Load the Debug Dojo configuration and return a DebugDojoConfig instance."""
    resolved_path = resolve_config_path(config_path)

    if verbose:
        if resolved_path:
            msg = f"Using configuration file: {resolved_path}."
        else:
            msg = "No configuration file found, using default settings."
        rich_print(f"[blue]{msg}[/blue]")

    if not resolved_path:
        return DebugDojoConfig()

    raw_config = load_raw_config(resolved_path)

    config = None
    for model in (DebugDojoConfigV2, DebugDojoConfigV1):
        model_name = model.__name__
        try:
            config = model.model_validate(raw_config)
        except ValidationError as e:
            if verbose:
                msg = (
                    f"[yellow]Configuration validation error for {model_name}:\n"
                    f"{__filter_pydantic_error_msg(e)}\n\n"
                    f"Please check your configuration file {resolved_path}.[/yellow]"
                )
                rich_print(msg)
        else:
            if verbose or model_name != DebugDojoConfig.__name__:
                msg = (
                    f"[blue]Using configuration model: {model_name}.\n"
                    f"Current configuration model {DebugDojoConfig.__name__}. [/blue]"
                )
                rich_print(msg)
            break

    if not config:
        msg = (
            f"[red]Unsupported configuration version in {resolved_path.resolve()}.\n"
            "Please update your configuration file.[/red]"
        )
        rich_print(msg)
        sys.exit(1)

    while not isinstance(config, DebugDojoConfig):
        config = config.update()

    # If a debugger is specified, update the config.
    if debugger:
        config.debuggers.default = debugger

    return config

load_raw_config(config_path)

Load the Debug Dojo configuration from a file.

Currently supports ‘dojo.toml’ or ‘pyproject.toml’. If no path is provided, it checks the current directory for these files.

Source code in src/debug_dojo/_config.py
def load_raw_config(
    config_path: Path,
) -> dict[str, Any]:  # pyright: ignore[reportExplicitAny]
    """Load the Debug Dojo configuration from a file.

    Currently supports 'dojo.toml' or 'pyproject.toml'.
    If no path is provided, it checks the current directory for these files.
    """
    config_str = config_path.read_text(encoding="utf-8")

    try:
        config_data = parse(config_str).unwrap()
    except TOMLKitError as e:
        msg = f"Error parsing configuration file {config_path.resolve()}."
        raise ValueError(msg) from e

    # If config is in [tool.debug_dojo] (pyproject.toml), extract it.
    if config_path.name == "pyproject.toml":
        try:
            dojo_config = cast("dict[str, Any]", config_data["tool"]["debug_dojo"])
        except KeyError:
            return {}
        else:
            return dojo_config

    return config_data

resolve_config_path(config_path)

Resolve the configuration path, returning a default if none is provided.

Source code in src/debug_dojo/_config.py
def resolve_config_path(config_path: Path | None) -> Path | None:
    """Resolve the configuration path, returning a default if none is provided."""
    if config_path:
        if not config_path.exists():
            msg = f"Configuration file not found:\n{config_path.resolve()}"
            raise FileNotFoundError(msg)
        return config_path.resolve()

    # Default configuration path
    for path in (Path("dojo.toml"), Path("pyproject.toml")):
        if path.exists():
            return path.resolve()
    return None

BaseConfig

Bases: BaseModel

Base configuration class with extra fields forbidden.

Source code in src/debug_dojo/_config_models.py
class BaseConfig(BaseModel):
    """Base configuration class with extra fields forbidden."""

    model_config = ConfigDict(extra="forbid", validate_assignment=True)

DebugDojoConfigV1

Bases: BaseModel

Configuration for Debug Dojo.

Source code in src/debug_dojo/_config_models.py
class DebugDojoConfigV1(BaseModel):
    """Configuration for Debug Dojo."""

    model_config = ConfigDict(extra="forbid")  # pyright: ignore[reportUnannotatedClassAttribute]

    debugger: DebuggerType = DebuggerType.PUDB
    """The type of debugger to use."""
    features: Features = Features()
    """Features to install for debugging."""

    def update(self) -> DebugDojoConfigV2:
        """Update the configuration to the latest version."""
        v2_exceptions = ExceptionsConfig(
            rich_traceback=self.features.rich_traceback,
        )
        v2_debuggers = DebuggersConfig(default=self.debugger)
        v2_features = FeaturesConfig(
            rich_inspect="i" if self.features.rich_inspect else "",
            rich_print="p" if self.features.rich_print else "",
            comparer="c" if self.features.comparer else "",
            breakpoint="b" if self.features.breakpoint else "",
        )
        return DebugDojoConfigV2(
            exceptions=v2_exceptions,
            debuggers=v2_debuggers,
            features=v2_features,
        )

debugger = DebuggerType.PUDB class-attribute instance-attribute

The type of debugger to use.

features = Features() class-attribute instance-attribute

Features to install for debugging.

update()

Update the configuration to the latest version.

Source code in src/debug_dojo/_config_models.py
def update(self) -> DebugDojoConfigV2:
    """Update the configuration to the latest version."""
    v2_exceptions = ExceptionsConfig(
        rich_traceback=self.features.rich_traceback,
    )
    v2_debuggers = DebuggersConfig(default=self.debugger)
    v2_features = FeaturesConfig(
        rich_inspect="i" if self.features.rich_inspect else "",
        rich_print="p" if self.features.rich_print else "",
        comparer="c" if self.features.comparer else "",
        breakpoint="b" if self.features.breakpoint else "",
    )
    return DebugDojoConfigV2(
        exceptions=v2_exceptions,
        debuggers=v2_debuggers,
        features=v2_features,
    )

DebugDojoConfigV2

Bases: BaseModel

Configuration for Debug Dojo.

Source code in src/debug_dojo/_config_models.py
class DebugDojoConfigV2(BaseModel):
    """Configuration for Debug Dojo."""

    model_config = ConfigDict(extra="forbid")  # pyright: ignore[reportUnannotatedClassAttribute]

    exceptions: ExceptionsConfig = ExceptionsConfig()
    debuggers: DebuggersConfig = DebuggersConfig()
    """Default debugger and configs."""
    features: FeaturesConfig = FeaturesConfig()
    """Features mnemonics ."""

debuggers = DebuggersConfig() class-attribute instance-attribute

Default debugger and configs.

features = FeaturesConfig() class-attribute instance-attribute

Features mnemonics .

DebuggerType

Bases: Enum

Enum for different types of debuggers.

Source code in src/debug_dojo/_config_models.py
class DebuggerType(Enum):
    """Enum for different types of debuggers."""

    PDB = "pdb"
    PUDB = "pudb"
    IPDB = "ipdb"
    DEBUGPY = "debugpy"

DebuggersConfig

Bases: BaseConfig

Configuration for debuggers.

Source code in src/debug_dojo/_config_models.py
class DebuggersConfig(BaseConfig):
    """Configuration for debuggers."""

    default: DebuggerType = DebuggerType.IPDB
    """Default debugger to use."""
    prompt_name: str = "debug-dojo> "
    """Prompt name for the debugger, used in the REPL."""
    debugpy: DebugpyConfig = DebugpyConfig()
    """Configuration for debugpy debugger."""
    ipdb: IpdbConfig = IpdbConfig()
    """Configuration for ipdb debugger."""

debugpy = DebugpyConfig() class-attribute instance-attribute

Configuration for debugpy debugger.

default = DebuggerType.IPDB class-attribute instance-attribute

Default debugger to use.

ipdb = IpdbConfig() class-attribute instance-attribute

Configuration for ipdb debugger.

prompt_name = 'debug-dojo> ' class-attribute instance-attribute

Prompt name for the debugger, used in the REPL.

DebugpyConfig

Bases: BaseConfig

Configuration for debugpy debugger.

Source code in src/debug_dojo/_config_models.py
class DebugpyConfig(BaseConfig):
    """Configuration for debugpy debugger."""

    port: int = 1992
    """Port for debugpy debugger."""
    host: str = "localhost"
    """Host for debugpy debugger."""
    wait_for_client: bool = True
    """Whether to wait for the client to connect before starting debugging."""
    log_to_file: bool = False
    """Whether to log debugpy output to a file."""

host = 'localhost' class-attribute instance-attribute

Host for debugpy debugger.

log_to_file = False class-attribute instance-attribute

Whether to log debugpy output to a file.

port = 1992 class-attribute instance-attribute

Port for debugpy debugger.

wait_for_client = True class-attribute instance-attribute

Whether to wait for the client to connect before starting debugging.

ExceptionsConfig

Bases: BaseConfig

Configuration for exceptions handling.

Source code in src/debug_dojo/_config_models.py
class ExceptionsConfig(BaseConfig):
    """Configuration for exceptions handling."""

    rich_traceback: bool = True
    """Enable rich traceback for better error reporting."""
    locals_in_traceback: bool = False
    """Include local variables in traceback."""
    post_mortem: bool = True
    """Enable post-mortem debugging after an exception."""

locals_in_traceback = False class-attribute instance-attribute

Include local variables in traceback.

post_mortem = True class-attribute instance-attribute

Enable post-mortem debugging after an exception.

rich_traceback = True class-attribute instance-attribute

Enable rich traceback for better error reporting.

Features

Bases: BaseModel

Configuration for installing debug features.

Source code in src/debug_dojo/_config_models.py
class Features(BaseModel):
    """Configuration for installing debug features."""

    model_config = ConfigDict(extra="forbid")  # pyright: ignore[reportUnannotatedClassAttribute]

    rich_inspect: bool = True
    """Install rich inspect as 'i' for enhanced object inspection."""
    rich_print: bool = True
    """Install rich print as 'p' for enhanced printing."""
    rich_traceback: bool = True
    """Install rich traceback for better error reporting."""
    comparer: bool = True
    """Install comparer as 'c' for side-by-side object comparison."""
    breakpoint: bool = True
    """Install breakpoint as 'b' for setting breakpoints in code."""

breakpoint = True class-attribute instance-attribute

Install breakpoint as ‘b’ for setting breakpoints in code.

comparer = True class-attribute instance-attribute

Install comparer as ‘c’ for side-by-side object comparison.

rich_inspect = True class-attribute instance-attribute

Install rich inspect as ‘i’ for enhanced object inspection.

rich_print = True class-attribute instance-attribute

Install rich print as ‘p’ for enhanced printing.

rich_traceback = True class-attribute instance-attribute

Install rich traceback for better error reporting.

FeaturesConfig

Bases: BaseConfig

Configuration for installing debug features.

Source code in src/debug_dojo/_config_models.py
class FeaturesConfig(BaseConfig):
    """Configuration for installing debug features."""

    rich_inspect: str = "i"
    """Install rich inspect as 'i' for enhanced object inspection."""
    rich_print: str = "p"
    """Install rich print as 'p' for enhanced printing."""
    comparer: str = "c"
    """Install comparer as 'c' for side-by-side object comparison."""
    breakpoint: str = "b"
    """Install breakpoint as 'b' for setting breakpoints in code."""

breakpoint = 'b' class-attribute instance-attribute

Install breakpoint as ‘b’ for setting breakpoints in code.

comparer = 'c' class-attribute instance-attribute

Install comparer as ‘c’ for side-by-side object comparison.

rich_inspect = 'i' class-attribute instance-attribute

Install rich inspect as ‘i’ for enhanced object inspection.

rich_print = 'p' class-attribute instance-attribute

Install rich print as ‘p’ for enhanced printing.

IpdbConfig

Bases: BaseConfig

Configuration for ipdb debugger.

Source code in src/debug_dojo/_config_models.py
class IpdbConfig(BaseConfig):
    """Configuration for ipdb debugger."""

    context_lines: int = 20
    """Number of context lines to show in ipdb."""

context_lines = 20 class-attribute instance-attribute

Number of context lines to show in ipdb.

Command-line interface for running Python scripts or modules with debugging tools.

display_config(config)

Display the configuration for the debug dojo.

Source code in src/debug_dojo/_cli.py
def display_config(config: DebugDojoConfig) -> None:
    """Display the configuration for the debug dojo."""
    rich_print("[blue]Using debug-dojo configuration:[/blue]")
    rich_print(config.model_dump_json(indent=4))

execute_with_debug(target_name, target_args, *, target_is_module, verbose, config)

Execute a target script or module with installation of debugging tools.

Source code in src/debug_dojo/_cli.py
def execute_with_debug(  # noqa: C901
    target_name: str,
    target_args: list[str],
    *,
    target_is_module: bool,
    verbose: bool,
    config: DebugDojoConfig,
) -> None:
    """Execute a target script or module with installation of debugging tools."""
    sys.argv = [target_name, *target_args]

    if verbose:
        rich_print(f"[blue]Installing debugging tools for {target_name}.[/blue]")
        rich_print(f"[blue]Arguments for target: {target_args}[/blue]")

    install_by_config(config)

    if target_is_module:
        runner = runpy.run_module
    else:
        if not Path(target_name).exists():
            sys.exit(1)
        runner = runpy.run_path

    try:
        _ = runner(target_name, run_name="__main__")
    except ImportError as e:
        rich_print(f"[red]Error importing {target_name}:[/red]\n{e}")
        sys.exit(1)
    except BdbQuit:
        rich_print("[red]Debugging session terminated by user.[/red]")
        sys.exit(0)
    except KeyboardInterrupt:
        rich_print("[red]Execution interrupted by user.[/red]")
        sys.exit(0)
    except SystemExit as e:
        if e.code:
            rich_print(f"[red]Script exited with code {e.code}.[/red]")
    except Exception as e:  # noqa: BLE001
        rich_print(f"[red]Error while running {target_name}:[/red]\n{e}")
        if config.exceptions.post_mortem:
            import pdb  # noqa: PLC0415, T100

            rich_print("[blue]Entering post-mortem debugging session...[/blue]")
            pdb.post_mortem(e.__traceback__)
        sys.exit(1)

main()

Run the command-line interface.

Source code in src/debug_dojo/_cli.py
def main() -> None:
    """Run the command-line interface."""
    cli()

run_debug(ctx, target_name=None, *, config_path=None, debugger=None, verbose=False, module=False)

Run the command-line interface.

Source code in src/debug_dojo/_cli.py
@cli.command(
    help="Run a Python script or module with debugging tools installed.",
    no_args_is_help=True,
    context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
)
def run_debug(  # noqa: PLR0913
    ctx: typer.Context,
    target_name: Annotated[
        str | None, typer.Argument(help="The target script or module to debug.")
    ] = None,
    *,
    config_path: Annotated[
        Path | None, typer.Option("--config", "-c", help="Show configuration")
    ] = None,
    debugger: Annotated[
        DebuggerType | None,
        typer.Option("--debugger", "-d", help="Specify the debugger to use"),
    ] = None,
    verbose: Annotated[
        bool,
        typer.Option("--verbose", "-v", is_flag=True, help="Enable verbose output"),
    ] = False,
    module: Annotated[
        bool,
        typer.Option("--module", "-m", is_flag=True, help="Run as a module"),
    ] = False,
) -> None:
    """Run the command-line interface."""
    config = load_config(config_path, verbose=verbose, debugger=debugger)

    if verbose:
        display_config(config)

    if target_name:
        execute_with_debug(
            target_name=target_name,
            target_is_module=module,
            target_args=ctx.args,
            verbose=verbose,
            config=config,
        )