Skip to content

Commit c349820

Browse files
kinuaxsamuelcolvinclaude
authored
Add support for IPython debugger to breakpoint (#154)
Co-authored-by: Samuel Colvin <s@muelcolvin.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 7bf53c8 commit c349820

3 files changed

Lines changed: 39 additions & 5 deletions

File tree

‎devtools/debug.py‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import sys
33
from collections.abc import Generator
44
from types import FrameType
5-
from typing import Any, TextIO
5+
from typing import Any, TextIO, cast
66

77
from .ansi import sformat
88
from .prettier import PrettyFormat
@@ -133,9 +133,19 @@ def format(self, *args: Any, frame_depth_: int = 2, **kwargs: Any) -> DebugOutpu
133133
return self._process(args, kwargs, frame_depth_)
134134

135135
def breakpoint(self) -> None:
136+
"""
137+
Start the IPython debugger if IPython is installed, otherwise `pdb`.
138+
"""
136139
import pdb # noqa: T100
137140

138-
pdb.Pdb(skip=['devtools.*']).set_trace()
141+
pdb_class: type[pdb.Pdb]
142+
try:
143+
from IPython.terminal.debugger import TerminalPdb # pyright: ignore[reportMissingImports, reportUnknownVariableType]
144+
except ImportError:
145+
pdb_class = pdb.Pdb
146+
else:
147+
pdb_class = cast('type[pdb.Pdb]', TerminalPdb)
148+
pdb_class(skip=['devtools.*']).set_trace()
139149

140150
def timer(self, name: str | None = None, *, verbose: bool = True, file: TextIO | None = None, dp: int = 3) -> Timer:
141151
return Timer(name=name, verbose=verbose, file=file, dp=dp)

‎docs/usage.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ The debug namespace includes a number of other useful functions:
4848

4949
* `debug.format()` same as calling `debug()` but returns a `DebugOutput` rather than printing the output
5050
* `debug.timer()` returns an instance of *devtool's* `Timer` class suitable for timing code execution
51-
* `debug.breakpoint()` introduces a breakpoint using `pdb`
51+
* `debug.breakpoint()` introduces a breakpoint using IPython's debugger if IPython is installed, otherwise `pdb`
5252

5353
```py
5454
--8<-- "examples/other.py"

‎tests/test_main.py‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import re
22
import sys
3+
import types
34
from collections.abc import Generator
45
from pathlib import Path
56
from subprocess import run
@@ -248,13 +249,36 @@ def test_inspect_error(mocker: MockerFixture):
248249
assert str(v) == "<unknown>:0 (error parsing code, call stack too shallow)\n 'x' (str) len=1"
249250

250251

251-
def test_breakpoint(mocker: MockerFixture):
252-
# not much else we can do here
252+
def test_breakpoint_pdb(mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch):
253+
# a `None` entry in sys.modules makes the import raise ImportError whether or not IPython is installed
254+
monkeypatch.setitem(sys.modules, 'IPython.terminal.debugger', None)
253255
mocked_set_trace = mocker.patch('pdb.Pdb.set_trace')
254256
debug.breakpoint()
255257
assert mocked_set_trace.called
256258

257259

260+
def test_breakpoint_ipython(mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch):
261+
ipython_set_trace = mocker.MagicMock()
262+
constructed_with: list[list[str]] = []
263+
264+
class FakeTerminalPdb:
265+
def __init__(self, skip: list[str]) -> None:
266+
constructed_with.append(skip)
267+
268+
set_trace = ipython_set_trace
269+
270+
fake_module = types.ModuleType('IPython.terminal.debugger')
271+
fake_module.TerminalPdb = FakeTerminalPdb # pyright: ignore[reportAttributeAccessIssue]
272+
monkeypatch.setitem(sys.modules, 'IPython.terminal.debugger', fake_module)
273+
mocked_pdb_set_trace = mocker.patch('pdb.Pdb.set_trace')
274+
275+
debug.breakpoint()
276+
277+
assert ipython_set_trace.called
278+
assert constructed_with == [['devtools.*']]
279+
assert not mocked_pdb_set_trace.called
280+
281+
258282
@pytest.mark.xfail(
259283
sys.platform == 'win32' and sys.version_info >= (3, 9),
260284
reason='see https://github.com/alexmojaki/executing/issues/27',

0 commit comments

Comments
 (0)