Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/core/debugger.py: 18%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

619 statements  

1""" 

2Pdb debugger class. 

3 

4 

5This is an extension to PDB which adds a number of new features. 

6Note that there is also the `IPython.terminal.debugger` class which provides UI 

7improvements. 

8 

9We also strongly recommend to use this via the `ipdb` package, which provides 

10extra configuration options. 

11 

12Among other things, this subclass of PDB: 

13 - supports many IPython magics like pdef/psource 

14 - hide frames in tracebacks based on `__tracebackhide__` 

15 - allows to skip frames based on `__debuggerskip__` 

16 

17 

18Global Configuration 

19-------------------- 

20 

21The IPython debugger will by read the global ``~/.pdbrc`` file. 

22That is to say you can list all commands supported by ipdb in your `~/.pdbrc` 

23configuration file, to globally configure pdb. 

24 

25Example:: 

26 

27 # ~/.pdbrc 

28 skip_predicates debuggerskip false 

29 skip_hidden false 

30 context 25 

31 

32Features 

33-------- 

34 

35The IPython debugger can hide and skip frames when printing or moving through 

36the stack. This can have a performance impact, so can be configures. 

37 

38The skipping and hiding frames are configurable via the `skip_predicates` 

39command. 

40 

41By default, frames from readonly files will be hidden, frames containing 

42``__tracebackhide__ = True`` will be hidden. 

43 

44Frames containing ``__debuggerskip__`` will be stepped over, frames whose parent 

45frames value of ``__debuggerskip__`` is ``True`` will also be skipped. 

46 

47 >>> def helpers_helper(): 

48 ... pass 

49 ... 

50 ... def helper_1(): 

51 ... print("don't step in me") 

52 ... helpers_helpers() # will be stepped over unless breakpoint set. 

53 ... 

54 ... 

55 ... def helper_2(): 

56 ... print("in me neither") 

57 ... 

58 

59One can define a decorator that wraps a function between the two helpers: 

60 

61 >>> def pdb_skipped_decorator(function): 

62 ... 

63 ... 

64 ... def wrapped_fn(*args, **kwargs): 

65 ... __debuggerskip__ = True 

66 ... helper_1() 

67 ... __debuggerskip__ = False 

68 ... result = function(*args, **kwargs) 

69 ... __debuggerskip__ = True 

70 ... helper_2() 

71 ... # setting __debuggerskip__ to False again is not necessary 

72 ... return result 

73 ... 

74 ... return wrapped_fn 

75 

76When decorating a function, ipdb will directly step into ``bar()`` by 

77default: 

78 

79 >>> @foo_decorator 

80 ... def bar(x, y): 

81 ... return x * y 

82 

83 

84You can toggle the behavior with 

85 

86 ipdb> skip_predicates debuggerskip false 

87 

88or configure it in your ``.pdbrc`` 

89 

90 

91 

92License 

93------- 

94 

95Modified from the standard pdb.Pdb class to avoid including readline, so that 

96the command line completion of other programs which include this isn't 

97damaged. 

98 

99In the future, this class will be expanded with improvements over the standard 

100pdb. 

101 

102The original code in this file is mainly lifted out of cmd.py in Python 2.2, 

103with minor changes. Licensing should therefore be under the standard Python 

104terms. For details on the PSF (Python Software Foundation) standard license, 

105see: 

106 

107https://docs.python.org/2/license.html 

108 

109 

110All the changes since then are under the same license as IPython. 

111 

112""" 

113 

114# ***************************************************************************** 

115# 

116# This file is licensed under the PSF license. 

117# 

118# Copyright (C) 2001 Python Software Foundation, www.python.org 

119# Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu> 

120# 

121# 

122# ***************************************************************************** 

123 

124from __future__ import annotations 

125 

126import inspect 

127import linecache 

128import os 

129import re 

130import sys 

131import warnings 

132from contextlib import contextmanager 

133from functools import lru_cache 

134 

135from IPython import get_ipython 

136from IPython.core.debugger_backport import PdbClosureBackport 

137from IPython.utils import PyColorize 

138from IPython.utils.PyColorize import TokenStream 

139 

140from typing import TYPE_CHECKING 

141from types import FrameType 

142 

143# We have to check this directly from sys.argv, config struct not yet available 

144from pdb import Pdb as _OldPdb 

145from pygments.token import Token 

146 

147 

148if sys.version_info < (3, 13): 

149 

150 class OldPdb(PdbClosureBackport, _OldPdb): 

151 pass 

152 

153else: 

154 OldPdb = _OldPdb 

155 

156if TYPE_CHECKING: 

157 # otherwise circular import 

158 from IPython.core.interactiveshell import InteractiveShell 

159 

160# skip module docstests 

161__skip_doctest__ = True 

162 

163prompt = "ipdb> " 

164 

165 

166# Allow the set_trace code to operate outside of an ipython instance, even if 

167# it does so with some limitations. The rest of this support is implemented in 

168# the Tracer constructor. 

169 

170DEBUGGERSKIP = "__debuggerskip__" 

171 

172 

173# this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676 

174# on lower python versions, we backported the feature. 

175CHAIN_EXCEPTIONS = sys.version_info < (3, 13) 

176 

177 

178def BdbQuit_excepthook(et, ev, tb, excepthook=None): 

179 """Exception hook which handles `BdbQuit` exceptions. 

180 

181 All other exceptions are processed using the `excepthook` 

182 parameter. 

183 """ 

184 raise ValueError( 

185 "`BdbQuit_excepthook` is deprecated since version 5.1. It is still around only because it is still imported by ipdb.", 

186 ) 

187 

188 

189RGX_EXTRA_INDENT = re.compile(r"(?<=\n)\s+") 

190 

191 

192def strip_indentation(multiline_string): 

193 return RGX_EXTRA_INDENT.sub("", multiline_string) 

194 

195 

196def decorate_fn_with_doc(new_fn, old_fn, additional_text=""): 

197 """Make new_fn have old_fn's doc string. This is particularly useful 

198 for the ``do_...`` commands that hook into the help system. 

199 Adapted from from a comp.lang.python posting 

200 by Duncan Booth.""" 

201 

202 def wrapper(*args, **kw): 

203 return new_fn(*args, **kw) 

204 

205 if old_fn.__doc__: 

206 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text 

207 return wrapper 

208 

209 

210class Pdb(OldPdb): 

211 """Modified Pdb class, does not load readline. 

212 

213 for a standalone version that uses prompt_toolkit, see 

214 `IPython.terminal.debugger.TerminalPdb` and 

215 `IPython.terminal.debugger.set_trace()` 

216 

217 

218 This debugger can hide and skip frames that are tagged according to some predicates. 

219 See the `skip_predicates` commands. 

220 

221 """ 

222 

223 shell: InteractiveShell 

224 _theme_name: str 

225 _context: int 

226 

227 _chained_exceptions: tuple[Exception, ...] 

228 _chained_exception_index: int 

229 

230 if CHAIN_EXCEPTIONS: 

231 MAX_CHAINED_EXCEPTION_DEPTH = 999 

232 

233 default_predicates = { 

234 "tbhide": True, 

235 "readonly": False, 

236 "ipython_internal": True, 

237 "debuggerskip": True, 

238 } 

239 

240 def __init__( 

241 self, 

242 completekey=None, 

243 stdin=None, 

244 stdout=None, 

245 context: int | None | str = 5, 

246 *, 

247 mode: str | None = None, 

248 **kwargs, 

249 ): 

250 """Create a new IPython debugger. 

251 

252 Parameters 

253 ---------- 

254 completekey : default None 

255 Passed to pdb.Pdb. 

256 stdin : default None 

257 Passed to pdb.Pdb. 

258 stdout : default None 

259 Passed to pdb.Pdb. 

260 context : int 

261 Number of lines of source code context to show when 

262 displaying stacktrace information. 

263 mode : str, optional 

264 How the debugger was invoked, one of ``'inline'`` (used by the 

265 ``breakpoint()`` builtin), ``'cli'`` (used by the command line 

266 invocation) or ``None`` (backwards compatible behaviour). This 

267 argument was added to stdlib's ``pdb.Pdb`` in Python 3.14; it is 

268 accepted on every supported Python version here but only forwarded 

269 to the underlying ``pdb.Pdb`` when it is actually supported. 

270 **kwargs 

271 Passed to pdb.Pdb. 

272 

273 Notes 

274 ----- 

275 The possibilities are python version dependent, see the python 

276 docs for more info. 

277 """ 

278 # ipdb issue, see https://github.com/ipython/ipython/issues/14811 

279 if context is None: 

280 context = 5 

281 if isinstance(context, str): 

282 context = int(context) 

283 self.context = context 

284 

285 # The `mode` argument was added to `pdb.Pdb` in Python 3.14. We accept 

286 # it on every supported Python version so that callers written against 

287 # 3.14+ keep working, but only forward it to the underlying `pdb.Pdb` 

288 # when it understands it. 

289 if sys.version_info >= (3, 14): 

290 kwargs["mode"] = mode 

291 else: 

292 self.mode = mode 

293 

294 # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`. 

295 OldPdb.__init__(self, completekey, stdin, stdout, **kwargs) 

296 # Python 3.15+ should define this, so no need to initialize 

297 # this avoids some getattr(self, 'curframe') 

298 if sys.version_info < (3, 15): 

299 self.curframe = None 

300 

301 # IPython changes... 

302 self.shell = get_ipython() 

303 

304 if self.shell is None: 

305 save_main = sys.modules["__main__"] 

306 # No IPython instance running, we must create one 

307 from IPython.terminal.interactiveshell import TerminalInteractiveShell 

308 

309 self.shell = TerminalInteractiveShell.instance() 

310 # needed by any code which calls __import__("__main__") after 

311 # the debugger was entered. See also #9941. 

312 sys.modules["__main__"] = save_main 

313 

314 self.aliases = {} 

315 

316 theme_name = self.shell.colors 

317 assert isinstance(theme_name, str) 

318 assert theme_name.lower() == theme_name 

319 

320 # Add a python parser so we can syntax highlight source while 

321 # debugging. 

322 self.parser = PyColorize.Parser(theme_name=theme_name) 

323 self.set_theme_name(theme_name) 

324 

325 # Set the prompt - the default prompt is '(Pdb)' 

326 self.prompt = prompt 

327 self.skip_hidden = True 

328 self.report_skipped = True 

329 

330 # list of predicates we use to skip frames 

331 self._predicates = self.default_predicates 

332 

333 if CHAIN_EXCEPTIONS: 

334 self._chained_exceptions = tuple() 

335 self._chained_exception_index = 0 

336 

337 @property 

338 def context(self) -> int: 

339 return self._context 

340 

341 @context.setter 

342 def context(self, value: int | str) -> None: 

343 # ipdb issue see https://github.com/ipython/ipython/issues/14811 

344 if not isinstance(value, int): 

345 value = int(value) 

346 assert isinstance(value, int) 

347 assert value >= 0 

348 self._context = value 

349 

350 def set_theme_name(self, name): 

351 assert name.lower() == name 

352 assert isinstance(name, str) 

353 self._theme_name = name 

354 self.parser.theme_name = name 

355 

356 @property 

357 def theme(self): 

358 return PyColorize.theme_table[self._theme_name] 

359 

360 # 

361 def set_colors(self, scheme): 

362 """Shorthand access to the color table scheme selector method.""" 

363 warnings.warn( 

364 "set_colors is deprecated since IPython 9.0, use set_theme_name instead", 

365 DeprecationWarning, 

366 stacklevel=2, 

367 ) 

368 assert scheme == scheme.lower() 

369 self._theme_name = scheme.lower() 

370 self.parser.theme_name = scheme.lower() 

371 

372 def set_trace(self, frame=None, **kwargs): 

373 if frame is None: 

374 frame = sys._getframe().f_back 

375 self.initial_frame = frame 

376 return super().set_trace(frame, **kwargs) 

377 

378 def get_stack(self, *args, **kwargs): 

379 stack, pos = super().get_stack(*args, **kwargs) 

380 if len(stack) >= 0 and self._is_internal_frame(stack[0][0]): 

381 stack.pop(0) 

382 pos -= 1 

383 return stack, pos 

384 

385 def _is_internal_frame(self, frame): 

386 """Determine if this frame should be skipped as internal""" 

387 filename = frame.f_code.co_filename 

388 

389 # Skip bdb.py runcall and internal operations 

390 if filename.endswith("bdb.py"): 

391 func_name = frame.f_code.co_name 

392 # Skip internal bdb operations but allow breakpoint hits 

393 if func_name in ("runcall", "run", "runeval"): 

394 return True 

395 

396 return False 

397 

398 def _hidden_predicate(self, frame): 

399 """ 

400 Given a frame return whether it it should be hidden or not by IPython. 

401 """ 

402 

403 if self._predicates["readonly"]: 

404 fname = frame.f_code.co_filename 

405 # we need to check for file existence and interactively define 

406 # function would otherwise appear as RO. 

407 if os.path.isfile(fname) and not os.access(fname, os.W_OK): 

408 return True 

409 

410 if self._predicates["tbhide"]: 

411 if frame in (self.curframe, getattr(self, "initial_frame", None)): 

412 return False 

413 frame_locals = self._get_frame_locals(frame) 

414 if "__tracebackhide__" not in frame_locals: 

415 return False 

416 return frame_locals["__tracebackhide__"] 

417 return False 

418 

419 def hidden_frames(self, stack): 

420 """ 

421 Given an index in the stack return whether it should be skipped. 

422 

423 This is used in up/down and where to skip frames. 

424 """ 

425 # The f_locals dictionary is updated from the actual frame 

426 # locals whenever the .f_locals accessor is called, so we 

427 # avoid calling it here to preserve self.curframe_locals. 

428 # Furthermore, there is no good reason to hide the current frame. 

429 ip_hide = [self._hidden_predicate(s[0]) for s in stack] 

430 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"] 

431 if ip_start and self._predicates["ipython_internal"]: 

432 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)] 

433 return ip_hide 

434 

435 if CHAIN_EXCEPTIONS: 

436 

437 def _get_tb_and_exceptions(self, tb_or_exc): 

438 """ 

439 Given a tracecack or an exception, return a tuple of chained exceptions 

440 and current traceback to inspect. 

441 This will deal with selecting the right ``__cause__`` or ``__context__`` 

442 as well as handling cycles, and return a flattened list of exceptions we 

443 can jump to with do_exceptions. 

444 """ 

445 _exceptions = [] 

446 if isinstance(tb_or_exc, BaseException): 

447 traceback, current = tb_or_exc.__traceback__, tb_or_exc 

448 

449 while current is not None: 

450 if current in _exceptions: 

451 break 

452 _exceptions.append(current) 

453 if current.__cause__ is not None: 

454 current = current.__cause__ 

455 elif ( 

456 current.__context__ is not None 

457 and not current.__suppress_context__ 

458 ): 

459 current = current.__context__ 

460 

461 if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH: 

462 self.message( 

463 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}" 

464 " chained exceptions found, not all exceptions" 

465 " will be browsable with `exceptions`." 

466 ) 

467 break 

468 else: 

469 traceback = tb_or_exc 

470 return tuple(reversed(_exceptions)), traceback 

471 

472 @contextmanager 

473 def _hold_exceptions(self, exceptions): 

474 """ 

475 Context manager to ensure proper cleaning of exceptions references 

476 When given a chained exception instead of a traceback, 

477 pdb may hold references to many objects which may leak memory. 

478 We use this context manager to make sure everything is properly cleaned 

479 """ 

480 try: 

481 self._chained_exceptions = exceptions 

482 self._chained_exception_index = len(exceptions) - 1 

483 yield 

484 finally: 

485 # we can't put those in forget as otherwise they would 

486 # be cleared on exception change 

487 self._chained_exceptions = tuple() 

488 self._chained_exception_index = 0 

489 

490 def do_exceptions(self, arg): 

491 """exceptions [number] 

492 List or change current exception in an exception chain. 

493 Without arguments, list all the current exception in the exception 

494 chain. Exceptions will be numbered, with the current exception indicated 

495 with an arrow. 

496 If given an integer as argument, switch to the exception at that index. 

497 """ 

498 if not self._chained_exceptions: 

499 self.message( 

500 "Did not find chained exceptions. To move between" 

501 " exceptions, pdb/post_mortem must be given an exception" 

502 " object rather than a traceback." 

503 ) 

504 return 

505 if not arg: 

506 for ix, exc in enumerate(self._chained_exceptions): 

507 prompt = ">" if ix == self._chained_exception_index else " " 

508 rep = repr(exc) 

509 if len(rep) > 80: 

510 rep = rep[:77] + "..." 

511 indicator = ( 

512 " -" 

513 if self._chained_exceptions[ix].__traceback__ is None 

514 else f"{ix:>3}" 

515 ) 

516 self.message(f"{prompt} {indicator} {rep}") 

517 else: 

518 try: 

519 number = int(arg) 

520 except ValueError: 

521 self.error("Argument must be an integer") 

522 return 

523 if 0 <= number < len(self._chained_exceptions): 

524 if self._chained_exceptions[number].__traceback__ is None: 

525 self.error( 

526 "This exception does not have a traceback, cannot jump to it" 

527 ) 

528 return 

529 

530 self._chained_exception_index = number 

531 self.setup(None, self._chained_exceptions[number].__traceback__) 

532 self.print_stack_entry(self.stack[self.curindex]) 

533 else: 

534 self.error("No exception with that number") 

535 

536 def do_exception(self, arg): 

537 """exception [number] 

538 Alias for the ``exceptions`` command. 

539 """ 

540 return self.do_exceptions(arg) 

541 

542 def interaction(self, frame, tb_or_exc): 

543 try: 

544 if CHAIN_EXCEPTIONS: 

545 # this context manager is part of interaction in 3.13 

546 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc) 

547 if isinstance(tb_or_exc, BaseException): 

548 assert tb is not None, "main exception must have a traceback" 

549 with self._hold_exceptions(_chained_exceptions): 

550 OldPdb.interaction(self, frame, tb) 

551 else: 

552 OldPdb.interaction(self, frame, tb_or_exc) 

553 

554 except KeyboardInterrupt: 

555 self.stdout.write("\n" + self.shell.get_exception_only()) 

556 

557 def precmd(self, line): 

558 """Perform useful escapes on the command before it is executed.""" 

559 

560 if line.endswith("??"): 

561 line = "pinfo2 " + line[:-2] 

562 elif line.endswith("?"): 

563 line = "pinfo " + line[:-1] 

564 

565 line = super().precmd(line) 

566 

567 return line 

568 

569 def new_do_quit(self, arg): 

570 return OldPdb.do_quit(self, arg) 

571 

572 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit) 

573 

574 def print_stack_trace(self, context: int | None = None): 

575 if context is None: 

576 context = self.context 

577 try: 

578 skipped = 0 

579 to_print = "" 

580 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack): 

581 if hidden and self.skip_hidden: 

582 skipped += 1 

583 continue 

584 if skipped: 

585 to_print += self.theme.format( 

586 [ 

587 ( 

588 Token.ExcName, 

589 f" [... skipping {skipped} hidden frame(s)]", 

590 ), 

591 (Token, "\n"), 

592 ] 

593 ) 

594 

595 skipped = 0 

596 to_print += self.format_stack_entry(frame_lineno) 

597 if skipped: 

598 to_print += self.theme.format( 

599 [ 

600 ( 

601 Token.ExcName, 

602 f" [... skipping {skipped} hidden frame(s)]", 

603 ), 

604 (Token, "\n"), 

605 ] 

606 ) 

607 print(to_print, file=self.stdout) 

608 except KeyboardInterrupt: 

609 pass 

610 

611 def print_stack_entry( 

612 self, frame_lineno: tuple[FrameType, int], prompt_prefix: str = "\n-> " 

613 ) -> None: 

614 """ 

615 Overwrite print_stack_entry from superclass (PDB) 

616 """ 

617 print(self.format_stack_entry(frame_lineno, ""), file=self.stdout) 

618 

619 frame, lineno = frame_lineno 

620 filename = frame.f_code.co_filename 

621 self.shell.hooks.synchronize_with_editor(filename, lineno, 0) 

622 

623 def _get_frame_locals(self, frame): 

624 """ " 

625 Accessing f_local of current frame reset the namespace, so we want to avoid 

626 that or the following can happen 

627 

628 ipdb> foo 

629 "old" 

630 ipdb> foo = "new" 

631 ipdb> foo 

632 "new" 

633 ipdb> where 

634 ipdb> foo 

635 "old" 

636 

637 So if frame is self.current_frame we instead return self.curframe_locals 

638 

639 """ 

640 if frame is self.curframe: 

641 return self.curframe_locals 

642 else: 

643 return frame.f_locals 

644 

645 def format_stack_entry( 

646 self, 

647 frame_lineno: tuple[FrameType, int], # type: ignore[override] # stubs are wrong 

648 lprefix: str = ": ", 

649 ) -> str: 

650 """ 

651 overwrite from super class so must -> str 

652 """ 

653 context = self.context 

654 try: 

655 context = int(context) 

656 if context <= 0: 

657 print("Context must be a positive integer", file=self.stdout) 

658 except (TypeError, ValueError): 

659 print("Context must be a positive integer", file=self.stdout) 

660 

661 import reprlib 

662 

663 ret_tok = [] 

664 

665 frame, lineno = frame_lineno 

666 

667 return_value = "" 

668 loc_frame = self._get_frame_locals(frame) 

669 if "__return__" in loc_frame: 

670 rv = loc_frame["__return__"] 

671 # return_value += '->' 

672 return_value += reprlib.repr(rv) + "\n" 

673 ret_tok.extend([(Token, return_value)]) 

674 

675 # s = filename + '(' + `lineno` + ')' 

676 filename = self.canonic(frame.f_code.co_filename) 

677 link_tok = (Token.FilenameEm, filename) 

678 

679 if frame.f_code.co_name: 

680 func = frame.f_code.co_name 

681 else: 

682 func = "<lambda>" 

683 

684 call_toks = [] 

685 if func != "?": 

686 if "__args__" in loc_frame: 

687 args = reprlib.repr(loc_frame["__args__"]) 

688 else: 

689 args = "()" 

690 call_toks = [(Token.VName, func), (Token.ValEm, args)] 

691 

692 # The level info should be generated in the same format pdb uses, to 

693 # avoid breaking the pdbtrack functionality of python-mode in *emacs. 

694 if frame is self.curframe: 

695 ret_tok.append((Token.CurrentFrame, self.theme.make_arrow(2))) 

696 else: 

697 ret_tok.append((Token, " ")) 

698 

699 ret_tok.extend( 

700 [ 

701 link_tok, 

702 (Token, "("), 

703 (Token.Lineno, str(lineno)), 

704 (Token, ")"), 

705 *call_toks, 

706 (Token, "\n"), 

707 ] 

708 ) 

709 

710 start = lineno - 1 - context // 2 

711 lines = linecache.getlines(filename) 

712 start = min(start, len(lines) - context) 

713 start = max(start, 0) 

714 lines = lines[start : start + context] 

715 

716 for i, line in enumerate(lines): 

717 show_arrow = start + 1 + i == lineno 

718 

719 bp, num, colored_line = self.__line_content( 

720 filename, 

721 start + 1 + i, 

722 line, 

723 arrow=show_arrow, 

724 ) 

725 if frame is self.curframe or show_arrow: 

726 rlt = [ 

727 bp, 

728 (Token.LinenoEm, num), 

729 (Token, " "), 

730 # TODO: investigate Toke.Line here, likely LineEm, 

731 # Token is problematic here as line is already colored, a 

732 # and this changes the full style of the colored line. 

733 # ideally, __line_content returns the token and we modify the style. 

734 (Token, colored_line), 

735 ] 

736 else: 

737 rlt = [ 

738 bp, 

739 (Token.Lineno, num), 

740 (Token, " "), 

741 # TODO: investigate Toke.Line here, likely Line 

742 # Token is problematic here as line is already colored, a 

743 # and this changes the full style of the colored line. 

744 # ideally, __line_content returns the token and we modify the style. 

745 (Token.Line, colored_line), 

746 ] 

747 ret_tok.extend(rlt) 

748 

749 return self.theme.format(ret_tok) 

750 

751 def __line_content( 

752 self, filename: str, lineno: int, line: str, arrow: bool = False 

753 ): 

754 bp_mark = "" 

755 BreakpointToken = Token.Breakpoint 

756 

757 new_line, err = self.parser.format2(line, "str") 

758 if not err: 

759 assert new_line is not None 

760 line = new_line 

761 

762 bp = None 

763 if lineno in self.get_file_breaks(filename): 

764 bps = self.get_breaks(filename, lineno) 

765 bp = bps[-1] 

766 

767 if bp: 

768 bp_mark = str(bp.number) 

769 BreakpointToken = Token.Breakpoint.Enabled 

770 if not bp.enabled: 

771 BreakpointToken = Token.Breakpoint.Disabled 

772 numbers_width = 7 

773 if arrow: 

774 # This is the line with the error 

775 pad = numbers_width - len(str(lineno)) - len(bp_mark) 

776 num = "%s%s" % (self.theme.make_arrow(pad), str(lineno)) 

777 else: 

778 num = "%*s" % (numbers_width - len(bp_mark), str(lineno)) 

779 bp_str = (BreakpointToken, bp_mark) 

780 return (bp_str, num, line) 

781 

782 def print_list_lines(self, filename: str, first: int, last: int) -> None: 

783 """The printing (as opposed to the parsing part of a 'list' 

784 command.""" 

785 toks: TokenStream = [] 

786 try: 

787 if filename == "<string>" and hasattr(self, "_exec_filename"): 

788 filename = self._exec_filename 

789 

790 for lineno in range(first, last + 1): 

791 line = linecache.getline(filename, lineno) 

792 if not line: 

793 break 

794 

795 assert self.curframe is not None 

796 

797 if lineno == self.curframe.f_lineno: 

798 bp, num, colored_line = self.__line_content( 

799 filename, lineno, line, arrow=True 

800 ) 

801 toks.extend( 

802 [ 

803 bp, 

804 (Token.LinenoEm, num), 

805 (Token, " "), 

806 # TODO: investigate Token.Line here 

807 (Token, colored_line), 

808 ] 

809 ) 

810 else: 

811 bp, num, colored_line = self.__line_content( 

812 filename, lineno, line, arrow=False 

813 ) 

814 toks.extend( 

815 [ 

816 bp, 

817 (Token.Lineno, num), 

818 (Token, " "), 

819 (Token, colored_line), 

820 ] 

821 ) 

822 

823 self.lineno = lineno 

824 

825 print(self.theme.format(toks), file=self.stdout) 

826 

827 except KeyboardInterrupt: 

828 pass 

829 

830 def do_skip_predicates(self, args): 

831 """ 

832 Turn on/off individual predicates as to whether a frame should be hidden/skip. 

833 

834 The global option to skip (or not) hidden frames is set with skip_hidden 

835 

836 To change the value of a predicate 

837 

838 skip_predicates key [true|false] 

839 

840 Call without arguments to see the current values. 

841 

842 To permanently change the value of an option add the corresponding 

843 command to your ``~/.pdbrc`` file. If you are programmatically using the 

844 Pdb instance you can also change the ``default_predicates`` class 

845 attribute. 

846 """ 

847 if not args.strip(): 

848 print("current predicates:") 

849 for p, v in self._predicates.items(): 

850 print(" ", p, ":", v) 

851 return 

852 type_value = args.strip().split(" ") 

853 if len(type_value) != 2: 

854 print( 

855 f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}" 

856 ) 

857 return 

858 

859 type_, value = type_value 

860 if type_ not in self._predicates: 

861 print(f"{type_!r} not in {set(self._predicates.keys())}") 

862 return 

863 if value.lower() not in ("true", "yes", "1", "no", "false", "0"): 

864 print( 

865 f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')" 

866 ) 

867 return 

868 

869 self._predicates[type_] = value.lower() in ("true", "yes", "1") 

870 if not any(self._predicates.values()): 

871 print( 

872 "Warning, all predicates set to False, skip_hidden may not have any effects." 

873 ) 

874 

875 def do_skip_hidden(self, arg): 

876 """ 

877 Change whether or not we should skip frames with the 

878 __tracebackhide__ attribute. 

879 """ 

880 if not arg.strip(): 

881 print( 

882 f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change." 

883 ) 

884 elif arg.strip().lower() in ("true", "yes"): 

885 self.skip_hidden = True 

886 elif arg.strip().lower() in ("false", "no"): 

887 self.skip_hidden = False 

888 if not any(self._predicates.values()): 

889 print( 

890 "Warning, all predicates set to False, skip_hidden may not have any effects." 

891 ) 

892 

893 def do_list(self, arg): 

894 """Print lines of code from the current stack frame""" 

895 self.lastcmd = "list" 

896 last = None 

897 if arg and arg != ".": 

898 try: 

899 x = eval(arg, {}, {}) 

900 if type(x) == type(()): 

901 first, last = x # type: ignore[misc] 

902 first = int(first) # type: ignore[call-overload] 

903 last = int(last) # type: ignore[call-overload] 

904 if last < first: 

905 # Assume it's a count 

906 last = first + last 

907 else: 

908 first = max(1, int(x) - 5) 

909 except: 

910 print("*** Error in argument:", repr(arg), file=self.stdout) 

911 return 

912 elif self.lineno is None or arg == ".": 

913 assert self.curframe is not None 

914 first = max(1, self.curframe.f_lineno - 5) 

915 else: 

916 first = self.lineno + 1 

917 if last is None: 

918 last = first + 10 

919 assert self.curframe is not None 

920 self.print_list_lines(self.curframe.f_code.co_filename, first, last) 

921 

922 lineno = first 

923 filename = self.curframe.f_code.co_filename 

924 self.shell.hooks.synchronize_with_editor(filename, lineno, 0) 

925 

926 do_l = do_list 

927 

928 def getsourcelines(self, obj): 

929 lines, lineno = inspect.findsource(obj) 

930 if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj): 

931 # must be a module frame: do not try to cut a block out of it 

932 return lines, 1 

933 elif inspect.ismodule(obj): 

934 return lines, 1 

935 return inspect.getblock(lines[lineno:]), lineno + 1 

936 

937 def do_longlist(self, arg): 

938 """Print lines of code from the current stack frame. 

939 

940 Shows more lines than 'list' does. 

941 """ 

942 self.lastcmd = "longlist" 

943 try: 

944 lines, lineno = self.getsourcelines(self.curframe) 

945 except OSError as err: 

946 self.error(str(err)) 

947 return 

948 last = lineno + len(lines) 

949 assert self.curframe is not None 

950 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last) 

951 

952 do_ll = do_longlist 

953 

954 def do_debug(self, arg): 

955 """debug code 

956 Enter a recursive debugger that steps through the code 

957 argument (which is an arbitrary expression or statement to be 

958 executed in the current environment). 

959 """ 

960 trace_function = sys.gettrace() 

961 sys.settrace(None) 

962 assert self.curframe is not None 

963 globals = self.curframe.f_globals 

964 locals = self.curframe_locals 

965 p = self.__class__( 

966 completekey=self.completekey, stdin=self.stdin, stdout=self.stdout 

967 ) 

968 p.use_rawinput = self.use_rawinput 

969 p.prompt = "(%s) " % self.prompt.strip() 

970 self.message("ENTERING RECURSIVE DEBUGGER") 

971 sys.call_tracing(p.run, (arg, globals, locals)) 

972 self.message("LEAVING RECURSIVE DEBUGGER") 

973 sys.settrace(trace_function) 

974 self.lastcmd = p.lastcmd 

975 

976 def do_pdef(self, arg): 

977 """Print the call signature for any callable object. 

978 

979 The debugger interface to %pdef""" 

980 assert self.curframe is not None 

981 namespaces = [ 

982 ("Locals", self.curframe_locals), 

983 ("Globals", self.curframe.f_globals), 

984 ] 

985 self.shell.find_line_magic("pdef")(arg, namespaces=namespaces) 

986 

987 def do_pdoc(self, arg): 

988 """Print the docstring for an object. 

989 

990 The debugger interface to %pdoc.""" 

991 assert self.curframe is not None 

992 namespaces = [ 

993 ("Locals", self.curframe_locals), 

994 ("Globals", self.curframe.f_globals), 

995 ] 

996 self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces) 

997 

998 def do_pfile(self, arg): 

999 """Print (or run through pager) the file where an object is defined. 

1000 

1001 The debugger interface to %pfile. 

1002 """ 

1003 assert self.curframe is not None 

1004 namespaces = [ 

1005 ("Locals", self.curframe_locals), 

1006 ("Globals", self.curframe.f_globals), 

1007 ] 

1008 self.shell.find_line_magic("pfile")(arg, namespaces=namespaces) 

1009 

1010 def do_pinfo(self, arg): 

1011 """Provide detailed information about an object. 

1012 

1013 The debugger interface to %pinfo, i.e., obj?.""" 

1014 assert self.curframe is not None 

1015 namespaces = [ 

1016 ("Locals", self.curframe_locals), 

1017 ("Globals", self.curframe.f_globals), 

1018 ] 

1019 self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces) 

1020 

1021 def do_pinfo2(self, arg): 

1022 """Provide extra detailed information about an object. 

1023 

1024 The debugger interface to %pinfo2, i.e., obj??.""" 

1025 assert self.curframe is not None 

1026 namespaces = [ 

1027 ("Locals", self.curframe_locals), 

1028 ("Globals", self.curframe.f_globals), 

1029 ] 

1030 self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces) 

1031 

1032 def do_psource(self, arg): 

1033 """Print (or run through pager) the source code for an object.""" 

1034 assert self.curframe is not None 

1035 namespaces = [ 

1036 ("Locals", self.curframe_locals), 

1037 ("Globals", self.curframe.f_globals), 

1038 ] 

1039 self.shell.find_line_magic("psource")(arg, namespaces=namespaces) 

1040 

1041 def do_where(self, arg: str): 

1042 """w(here) 

1043 Print a stack trace, with the most recent frame at the bottom. 

1044 An arrow indicates the "current frame", which determines the 

1045 context of most commands. 'bt' is an alias for this command. 

1046 

1047 Take a number as argument as an (optional) number of context line to 

1048 print""" 

1049 if arg: 

1050 try: 

1051 context = int(arg) 

1052 except ValueError as err: 

1053 self.error(str(err)) 

1054 return 

1055 self.print_stack_trace(context) 

1056 else: 

1057 self.print_stack_trace() 

1058 

1059 do_w = do_where 

1060 

1061 def break_anywhere(self, frame): 

1062 """ 

1063 _stop_in_decorator_internals is overly restrictive, as we may still want 

1064 to trace function calls, so we need to also update break_anywhere so 

1065 that is we don't `stop_here`, because of debugger skip, we may still 

1066 stop at any point inside the function 

1067 

1068 """ 

1069 

1070 sup = super().break_anywhere(frame) 

1071 if sup: 

1072 return sup 

1073 if self._predicates["debuggerskip"]: 

1074 if DEBUGGERSKIP in frame.f_code.co_varnames: 

1075 return True 

1076 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP): 

1077 return True 

1078 return False 

1079 

1080 def _is_in_decorator_internal_and_should_skip(self, frame): 

1081 """ 

1082 Utility to tell us whether we are in a decorator internal and should stop. 

1083 

1084 """ 

1085 # if we are disabled don't skip 

1086 if not self._predicates["debuggerskip"]: 

1087 return False 

1088 

1089 return self._cachable_skip(frame) 

1090 

1091 @lru_cache(1024) 

1092 def _cached_one_parent_frame_debuggerskip(self, frame): 

1093 """ 

1094 Cache looking up for DEBUGGERSKIP on parent frame. 

1095 

1096 This should speedup walking through deep frame when one of the highest 

1097 one does have a debugger skip. 

1098 

1099 This is likely to introduce fake positive though. 

1100 """ 

1101 while getattr(frame, "f_back", None): 

1102 frame = frame.f_back 

1103 if self._get_frame_locals(frame).get(DEBUGGERSKIP): 

1104 return True 

1105 return None 

1106 

1107 @lru_cache(1024) 

1108 def _cachable_skip(self, frame): 

1109 # if frame is tagged, skip by default. 

1110 if DEBUGGERSKIP in frame.f_code.co_varnames: 

1111 return True 

1112 

1113 # if one of the parent frame value set to True skip as well. 

1114 if self._cached_one_parent_frame_debuggerskip(frame): 

1115 return True 

1116 

1117 return False 

1118 

1119 def stop_here(self, frame): 

1120 if self._is_in_decorator_internal_and_should_skip(frame) is True: 

1121 return False 

1122 

1123 hidden = False 

1124 if self.skip_hidden: 

1125 hidden = self._hidden_predicate(frame) 

1126 if hidden: 

1127 if self.report_skipped: 

1128 print( 

1129 self.theme.format( 

1130 [ 

1131 ( 

1132 Token.ExcName, 

1133 " [... skipped 1 hidden frame(s)]", 

1134 ), 

1135 (Token, "\n"), 

1136 ] 

1137 ) 

1138 ) 

1139 if self.skip and self.is_skipped_module(frame.f_globals.get("__name__", "")): 

1140 print( 

1141 self.theme.format( 

1142 [ 

1143 ( 

1144 Token.ExcName, 

1145 " [... skipped 1 ignored module(s)]", 

1146 ), 

1147 (Token, "\n"), 

1148 ] 

1149 ) 

1150 ) 

1151 

1152 return False 

1153 

1154 return super().stop_here(frame) 

1155 

1156 def do_up(self, arg): 

1157 """u(p) [count] 

1158 Move the current frame count (default one) levels up in the 

1159 stack trace (to an older frame). 

1160 

1161 Will skip hidden frames and ignored modules. 

1162 """ 

1163 # modified version of upstream that skips 

1164 # frames with __tracebackhide__ and ignored modules 

1165 if self.curindex == 0: 

1166 self.error("Oldest frame") 

1167 return 

1168 try: 

1169 count = int(arg or 1) 

1170 except ValueError: 

1171 self.error("Invalid frame count (%s)" % arg) 

1172 return 

1173 

1174 hidden_skipped = 0 

1175 module_skipped = 0 

1176 

1177 if count < 0: 

1178 _newframe = 0 

1179 else: 

1180 counter = 0 

1181 hidden_frames = self.hidden_frames(self.stack) 

1182 

1183 for i in range(self.curindex - 1, -1, -1): 

1184 should_skip_hidden = hidden_frames[i] and self.skip_hidden 

1185 should_skip_module = self.skip and self.is_skipped_module( 

1186 self.stack[i][0].f_globals.get("__name__", "") 

1187 ) 

1188 

1189 if should_skip_hidden or should_skip_module: 

1190 if should_skip_hidden: 

1191 hidden_skipped += 1 

1192 if should_skip_module: 

1193 module_skipped += 1 

1194 continue 

1195 counter += 1 

1196 if counter >= count: 

1197 break 

1198 else: 

1199 # if no break occurred. 

1200 self.error( 

1201 "all frames above skipped (hidden frames and ignored modules). Use `skip_hidden False` for hidden frames or unignore_module for ignored modules." 

1202 ) 

1203 return 

1204 

1205 _newframe = i 

1206 self._select_frame(_newframe) 

1207 

1208 total_skipped = hidden_skipped + module_skipped 

1209 if total_skipped: 

1210 print( 

1211 self.theme.format( 

1212 [ 

1213 ( 

1214 Token.ExcName, 

1215 f" [... skipped {total_skipped} frame(s): {hidden_skipped} hidden frames + {module_skipped} ignored modules]", 

1216 ), 

1217 (Token, "\n"), 

1218 ] 

1219 ) 

1220 ) 

1221 

1222 def do_down(self, arg): 

1223 """d(own) [count] 

1224 Move the current frame count (default one) levels down in the 

1225 stack trace (to a newer frame). 

1226 

1227 Will skip hidden frames and ignored modules. 

1228 """ 

1229 if self.curindex + 1 == len(self.stack): 

1230 self.error("Newest frame") 

1231 return 

1232 try: 

1233 count = int(arg or 1) 

1234 except ValueError: 

1235 self.error("Invalid frame count (%s)" % arg) 

1236 return 

1237 if count < 0: 

1238 _newframe = len(self.stack) - 1 

1239 else: 

1240 counter = 0 

1241 hidden_skipped = 0 

1242 module_skipped = 0 

1243 hidden_frames = self.hidden_frames(self.stack) 

1244 

1245 for i in range(self.curindex + 1, len(self.stack)): 

1246 should_skip_hidden = hidden_frames[i] and self.skip_hidden 

1247 should_skip_module = self.skip and self.is_skipped_module( 

1248 self.stack[i][0].f_globals.get("__name__", "") 

1249 ) 

1250 

1251 if should_skip_hidden or should_skip_module: 

1252 if should_skip_hidden: 

1253 hidden_skipped += 1 

1254 if should_skip_module: 

1255 module_skipped += 1 

1256 continue 

1257 counter += 1 

1258 if counter >= count: 

1259 break 

1260 else: 

1261 self.error( 

1262 "all frames below skipped (hidden frames and ignored modules). Use `skip_hidden False` for hidden frames or unignore_module for ignored modules." 

1263 ) 

1264 return 

1265 

1266 total_skipped = hidden_skipped + module_skipped 

1267 if total_skipped: 

1268 print( 

1269 self.theme.format( 

1270 [ 

1271 ( 

1272 Token.ExcName, 

1273 f" [... skipped {total_skipped} frame(s): {hidden_skipped} hidden frames + {module_skipped} ignored modules]", 

1274 ), 

1275 (Token, "\n"), 

1276 ] 

1277 ) 

1278 ) 

1279 _newframe = i 

1280 

1281 self._select_frame(_newframe) 

1282 

1283 do_d = do_down 

1284 do_u = do_up 

1285 

1286 def _show_ignored_modules(self): 

1287 """Display currently ignored modules.""" 

1288 if self.skip: 

1289 print(f"Currently ignored modules: {sorted(self.skip)}") 

1290 else: 

1291 print("No modules are currently ignored.") 

1292 

1293 def do_ignore_module(self, arg): 

1294 """ignore_module <module_name> 

1295 

1296 Add a module to the list of modules to skip when navigating frames. 

1297 When a module is ignored, the debugger will automatically skip over 

1298 frames from that module. 

1299 

1300 Supports wildcard patterns using fnmatch syntax: 

1301 

1302 Usage: 

1303 ignore_module threading # Skip threading module frames 

1304 ignore_module asyncio.\\* # Skip all asyncio submodules 

1305 ignore_module \\*.tests # Skip all test modules 

1306 ignore_module # List currently ignored modules 

1307 """ 

1308 

1309 if self.skip is None: 

1310 self.skip = set() 

1311 

1312 module_name = arg.strip() 

1313 

1314 if not module_name: 

1315 self._show_ignored_modules() 

1316 return 

1317 

1318 self.skip.add(module_name) 

1319 

1320 def do_unignore_module(self, arg): 

1321 """unignore_module <module_name> 

1322 

1323 Remove a module from the list of modules to skip when navigating frames. 

1324 This will allow the debugger to step into frames from the specified module. 

1325 

1326 Usage: 

1327 unignore_module threading # Stop ignoring threading module frames 

1328 unignore_module asyncio.\\* # Remove asyncio.* pattern 

1329 unignore_module # List currently ignored modules 

1330 """ 

1331 

1332 if self.skip is None: 

1333 self.skip = set() 

1334 

1335 module_name = arg.strip() 

1336 

1337 if not module_name: 

1338 self._show_ignored_modules() 

1339 return 

1340 

1341 try: 

1342 self.skip.remove(module_name) 

1343 except KeyError: 

1344 print(f"Module {module_name} is not currently ignored") 

1345 self._show_ignored_modules() 

1346 

1347 def do_context(self, context: str): 

1348 """context number_of_lines 

1349 Set the number of lines of source code to show when displaying 

1350 stacktrace information. 

1351 """ 

1352 try: 

1353 new_context = int(context) 

1354 if new_context <= 0: 

1355 raise ValueError() 

1356 self.context = new_context 

1357 except ValueError: 

1358 self.error( 

1359 f"The 'context' command requires a positive integer argument (current value {self.context})." 

1360 ) 

1361 

1362 

1363class InterruptiblePdb(Pdb): 

1364 """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" 

1365 

1366 def cmdloop(self, intro=None): 

1367 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger.""" 

1368 try: 

1369 return OldPdb.cmdloop(self, intro=intro) 

1370 except KeyboardInterrupt: 

1371 self.stop_here = lambda frame: False # type: ignore[method-assign] 

1372 self.do_quit("") 

1373 sys.settrace(None) 

1374 self.quitting = False 

1375 raise 

1376 

1377 def _cmdloop(self): 

1378 while True: 

1379 try: 

1380 # keyboard interrupts allow for an easy way to cancel 

1381 # the current command, so allow them during interactive input 

1382 self.allow_kbdint = True 

1383 self.cmdloop() 

1384 self.allow_kbdint = False 

1385 break 

1386 except KeyboardInterrupt: 

1387 self.message("--KeyboardInterrupt--") 

1388 raise 

1389 

1390 

1391def set_trace(frame=None, header=None): 

1392 """ 

1393 Start debugging from `frame`. 

1394 

1395 If frame is not specified, debugging starts from caller's frame. 

1396 """ 

1397 pdb = Pdb() 

1398 if header is not None: 

1399 pdb.message(header) 

1400 pdb.set_trace(frame or sys._getframe().f_back)