diff --git a/CHANGELOG.md b/CHANGELOG.md index c54af609..13ab899b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and - {pull}`708` updates mypy and fixes type issues. - {pull}`709` add uv pre-commit check. - {pull}`713` removes uv as a test dependency. Closes {issue}`712`. Thanks to {user}`erooke`! +- {pull}`718` fixes {issue}`717` by properly parsing the `pdbcls` configuration option from config files. Thanks to {user}`MImmesberger` for the report! ## 0.5.5 - 2025-07-25 diff --git a/docs/source/reference_guides/configuration.md b/docs/source/reference_guides/configuration.md index da8ece2b..2f809ce8 100644 --- a/docs/source/reference_guides/configuration.md +++ b/docs/source/reference_guides/configuration.md @@ -191,6 +191,34 @@ paths = ["folder_1", "folder_2/task_2.py"] ``` ```` +````{confval} pdbcls + +If you want to use a custom debugger instead of the standard Python debugger, you can +specify it with the `pdbcls` option. The value must be in the format +`module_name:class_name`. + +```console +$ pytask build --pdbcls=IPython.terminal.debugger:TerminalPdb +``` + +Or, use the configuration file: + +```toml +pdbcls = "IPython.terminal.debugger:TerminalPdb" +``` + +This is particularly useful when working with enhanced debuggers like `pdbpp` or `pdbp` +that provide additional features such as syntax highlighting and tab completion: + +```toml +pdbcls = "pdbp:Pdb" +``` + +The custom debugger will be used when you invoke the `--pdb` flag for post-mortem +debugging or when using `breakpoint()` in your task code. + +```` + ````{confval} show_errors_immediately If you want to print the exception and tracebacks of errors as soon as they occur, diff --git a/src/_pytask/debugging.py b/src/_pytask/debugging.py index 29df7539..85e1dc8b 100644 --- a/src/_pytask/debugging.py +++ b/src/_pytask/debugging.py @@ -60,22 +60,41 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: cli.commands["build"].params.extend(additional_parameters) +def _parse_pdbcls(value: str | None) -> tuple[str, str] | None: + """Parse and validate pdbcls string format.""" + if value is None: + return None + if isinstance(value, tuple) and len(value) == 2: # noqa: PLR2004 + return value + if not isinstance(value, str): + msg = "'pdbcls' must be a string in format 'module:classname', got {value!r}" + raise TypeError(msg) + split = value.split(":") + if len(split) != 2: # noqa: PLR2004 + msg = ( + f"Invalid 'pdbcls' format: {value!r}. " + "Must be like 'IPython.terminal.debugger:TerminalPdb'" + ) + raise ValueError(msg) + return tuple(split) # type: ignore[return-value] + + def _pdbcls_callback( ctx: click.Context, # noqa: ARG001 name: str, # noqa: ARG001 value: str | None, ) -> tuple[str, str] | None: """Validate the debugger class string passed to pdbcls.""" - message = "'pdbcls' must be like IPython.terminal.debugger:TerminalPdb" + try: + return _parse_pdbcls(value) + except Exception as e: + raise click.BadParameter(str(e)) from e - if value is None: - return None - if isinstance(value, str): - split = value.split(":") - if len(split) != 2: # noqa: PLR2004 - raise click.BadParameter(message) - return tuple(split) # type: ignore[return-value] - raise click.BadParameter(message) + +@hookimpl +def pytask_parse_config(config: dict[str, Any]) -> None: + """Parse the debugger configuration.""" + config["pdbcls"] = _parse_pdbcls(config.get("pdbcls")) @hookimpl(trylast=True)