From 2f839051b0e99d76c15871e14aae47abe8d585b3 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Thu, 30 Oct 2025 20:31:22 +0100 Subject: [PATCH 1/5] Properly parse pdbcls from the configuration. --- docs/source/reference_guides/configuration.md | 28 +++++++++++++++ src/_pytask/debugging.py | 36 ++++++++++++++----- 2 files changed, 56 insertions(+), 8 deletions(-) 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..56c97791 100644 --- a/src/_pytask/debugging.py +++ b/src/_pytask/debugging.py @@ -60,22 +60,42 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: cli.commands["build"].params.extend(additional_parameters) +def _parse_pdbcls(value: str) -> tuple[str, str]: + """Parse and validate pdbcls string format.""" + 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" - 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) + try: + return _parse_pdbcls(value) + except ValueError as exc: + raise click.BadParameter(str(exc)) from exc + + +@hookimpl +def pytask_parse_config(config: dict[str, Any]) -> None: + """Parse the debugger configuration. + + Convert pdbcls from string format to tuple if it comes from config file. + When pdbcls comes from CLI, it's already converted by _pdbcls_callback. + """ + pdbcls = config.get("pdbcls") + if pdbcls and isinstance(pdbcls, str): + config["pdbcls"] = _parse_pdbcls(pdbcls) @hookimpl(trylast=True) From 2a33ab7d55a35ea089e7abad4ed33fb8805da53c Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Thu, 30 Oct 2025 20:33:23 +0100 Subject: [PATCH 2/5] to changes. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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 From 53db8617b2844c80f80416d4a49d77f29dc96a4b Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Thu, 30 Oct 2025 20:43:46 +0100 Subject: [PATCH 3/5] Move code. --- src/_pytask/debugging.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/_pytask/debugging.py b/src/_pytask/debugging.py index 56c97791..27da36fc 100644 --- a/src/_pytask/debugging.py +++ b/src/_pytask/debugging.py @@ -60,8 +60,13 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: cli.commands["build"].params.extend(additional_parameters) -def _parse_pdbcls(value: str) -> tuple[str, str]: +def _parse_pdbcls(value: str | None) -> tuple[str, str] | None: """Parse and validate pdbcls string format.""" + if value is None: + return None + if not isinstance(value, str): + msg = "'pdbcls' must be a string in format 'module:classname'" + raise TypeError(msg) split = value.split(":") if len(split) != 2: # noqa: PLR2004 msg = ( @@ -78,24 +83,16 @@ def _pdbcls_callback( value: str | None, ) -> tuple[str, str] | None: """Validate the debugger class string passed to pdbcls.""" - if value is None: - return None try: return _parse_pdbcls(value) - except ValueError as exc: - raise click.BadParameter(str(exc)) from exc + except Exception as e: + raise click.BadParameter(str(e)) from e @hookimpl def pytask_parse_config(config: dict[str, Any]) -> None: - """Parse the debugger configuration. - - Convert pdbcls from string format to tuple if it comes from config file. - When pdbcls comes from CLI, it's already converted by _pdbcls_callback. - """ - pdbcls = config.get("pdbcls") - if pdbcls and isinstance(pdbcls, str): - config["pdbcls"] = _parse_pdbcls(pdbcls) + """Parse the debugger configuration.""" + config["pdbcls"] = _parse_pdbcls(config.get("pdbcls")) @hookimpl(trylast=True) From 2ad3dfdd4087843eb022053787a32821adb02350 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Thu, 30 Oct 2025 21:08:33 +0100 Subject: [PATCH 4/5] fix. --- src/_pytask/debugging.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/_pytask/debugging.py b/src/_pytask/debugging.py index 27da36fc..1fef44ce 100644 --- a/src/_pytask/debugging.py +++ b/src/_pytask/debugging.py @@ -67,6 +67,8 @@ def _parse_pdbcls(value: str | None) -> tuple[str, str] | None: if not isinstance(value, str): msg = "'pdbcls' must be a string in format 'module:classname'" raise TypeError(msg) + if isinstance(value, tuple) and len(value) == 2: # noqa: PLR2004 + return value split = value.split(":") if len(split) != 2: # noqa: PLR2004 msg = ( From 4867b89c2513ca23894d12a24f434bc2f9d9072d Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Thu, 30 Oct 2025 21:15:15 +0100 Subject: [PATCH 5/5] fix. --- src/_pytask/debugging.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_pytask/debugging.py b/src/_pytask/debugging.py index 1fef44ce..85e1dc8b 100644 --- a/src/_pytask/debugging.py +++ b/src/_pytask/debugging.py @@ -64,11 +64,11 @@ def _parse_pdbcls(value: str | None) -> tuple[str, str] | None: """Parse and validate pdbcls string format.""" if value is None: return None - if not isinstance(value, str): - msg = "'pdbcls' must be a string in format 'module:classname'" - raise TypeError(msg) 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 = (