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

614 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 **kwargs, 

247 ): 

248 """Create a new IPython debugger. 

249 

250 Parameters 

251 ---------- 

252 completekey : default None 

253 Passed to pdb.Pdb. 

254 stdin : default None 

255 Passed to pdb.Pdb. 

256 stdout : default None 

257 Passed to pdb.Pdb. 

258 context : int 

259 Number of lines of source code context to show when 

260 displaying stacktrace information. 

261 **kwargs 

262 Passed to pdb.Pdb. 

263 

264 Notes 

265 ----- 

266 The possibilities are python version dependent, see the python 

267 docs for more info. 

268 """ 

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

270 if context is None: 

271 context = 5 

272 if isinstance(context, str): 

273 context = int(context) 

274 self.context = context 

275 

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

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

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

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

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

281 self.curframe = None 

282 

283 # IPython changes... 

284 self.shell = get_ipython() 

285 

286 if self.shell is None: 

287 save_main = sys.modules["__main__"] 

288 # No IPython instance running, we must create one 

289 from IPython.terminal.interactiveshell import TerminalInteractiveShell 

290 

291 self.shell = TerminalInteractiveShell.instance() 

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

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

294 sys.modules["__main__"] = save_main 

295 

296 self.aliases = {} 

297 

298 theme_name = self.shell.colors 

299 assert isinstance(theme_name, str) 

300 assert theme_name.lower() == theme_name 

301 

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

303 # debugging. 

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

305 self.set_theme_name(theme_name) 

306 

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

308 self.prompt = prompt 

309 self.skip_hidden = True 

310 self.report_skipped = True 

311 

312 # list of predicates we use to skip frames 

313 self._predicates = self.default_predicates 

314 

315 if CHAIN_EXCEPTIONS: 

316 self._chained_exceptions = tuple() 

317 self._chained_exception_index = 0 

318 

319 @property 

320 def context(self) -> int: 

321 return self._context 

322 

323 @context.setter 

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

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

326 if not isinstance(value, int): 

327 value = int(value) 

328 assert isinstance(value, int) 

329 assert value >= 0 

330 self._context = value 

331 

332 def set_theme_name(self, name): 

333 assert name.lower() == name 

334 assert isinstance(name, str) 

335 self._theme_name = name 

336 self.parser.theme_name = name 

337 

338 @property 

339 def theme(self): 

340 return PyColorize.theme_table[self._theme_name] 

341 

342 # 

343 def set_colors(self, scheme): 

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

345 warnings.warn( 

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

347 DeprecationWarning, 

348 stacklevel=2, 

349 ) 

350 assert scheme == scheme.lower() 

351 self._theme_name = scheme.lower() 

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

353 

354 def set_trace(self, frame=None): 

355 if frame is None: 

356 frame = sys._getframe().f_back 

357 self.initial_frame = frame 

358 return super().set_trace(frame) 

359 

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

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

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

363 stack.pop(0) 

364 pos -= 1 

365 return stack, pos 

366 

367 def _is_internal_frame(self, frame): 

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

369 filename = frame.f_code.co_filename 

370 

371 # Skip bdb.py runcall and internal operations 

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

373 func_name = frame.f_code.co_name 

374 # Skip internal bdb operations but allow breakpoint hits 

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

376 return True 

377 

378 return False 

379 

380 def _hidden_predicate(self, frame): 

381 """ 

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

383 """ 

384 

385 if self._predicates["readonly"]: 

386 fname = frame.f_code.co_filename 

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

388 # function would otherwise appear as RO. 

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

390 return True 

391 

392 if self._predicates["tbhide"]: 

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

394 return False 

395 frame_locals = self._get_frame_locals(frame) 

396 if "__tracebackhide__" not in frame_locals: 

397 return False 

398 return frame_locals["__tracebackhide__"] 

399 return False 

400 

401 def hidden_frames(self, stack): 

402 """ 

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

404 

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

406 """ 

407 # The f_locals dictionary is updated from the actual frame 

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

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

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

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

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

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

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

415 return ip_hide 

416 

417 if CHAIN_EXCEPTIONS: 

418 

419 def _get_tb_and_exceptions(self, tb_or_exc): 

420 """ 

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

422 and current traceback to inspect. 

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

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

425 can jump to with do_exceptions. 

426 """ 

427 _exceptions = [] 

428 if isinstance(tb_or_exc, BaseException): 

429 traceback, current = tb_or_exc.__traceback__, tb_or_exc 

430 

431 while current is not None: 

432 if current in _exceptions: 

433 break 

434 _exceptions.append(current) 

435 if current.__cause__ is not None: 

436 current = current.__cause__ 

437 elif ( 

438 current.__context__ is not None 

439 and not current.__suppress_context__ 

440 ): 

441 current = current.__context__ 

442 

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

444 self.message( 

445 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}" 

446 " chained exceptions found, not all exceptions" 

447 "will be browsable with `exceptions`." 

448 ) 

449 break 

450 else: 

451 traceback = tb_or_exc 

452 return tuple(reversed(_exceptions)), traceback 

453 

454 @contextmanager 

455 def _hold_exceptions(self, exceptions): 

456 """ 

457 Context manager to ensure proper cleaning of exceptions references 

458 When given a chained exception instead of a traceback, 

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

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

461 """ 

462 try: 

463 self._chained_exceptions = exceptions 

464 self._chained_exception_index = len(exceptions) - 1 

465 yield 

466 finally: 

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

468 # be cleared on exception change 

469 self._chained_exceptions = tuple() 

470 self._chained_exception_index = 0 

471 

472 def do_exceptions(self, arg): 

473 """exceptions [number] 

474 List or change current exception in an exception chain. 

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

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

477 with an arrow. 

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

479 """ 

480 if not self._chained_exceptions: 

481 self.message( 

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

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

484 " object rather than a traceback." 

485 ) 

486 return 

487 if not arg: 

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

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

490 rep = repr(exc) 

491 if len(rep) > 80: 

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

493 indicator = ( 

494 " -" 

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

496 else f"{ix:>3}" 

497 ) 

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

499 else: 

500 try: 

501 number = int(arg) 

502 except ValueError: 

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

504 return 

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

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

507 self.error( 

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

509 ) 

510 return 

511 

512 self._chained_exception_index = number 

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

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

515 else: 

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

517 

518 def interaction(self, frame, tb_or_exc): 

519 try: 

520 if CHAIN_EXCEPTIONS: 

521 # this context manager is part of interaction in 3.13 

522 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc) 

523 if isinstance(tb_or_exc, BaseException): 

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

525 with self._hold_exceptions(_chained_exceptions): 

526 OldPdb.interaction(self, frame, tb) 

527 else: 

528 OldPdb.interaction(self, frame, tb_or_exc) 

529 

530 except KeyboardInterrupt: 

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

532 

533 def precmd(self, line): 

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

535 

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

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

538 elif line.endswith("?"): 

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

540 

541 line = super().precmd(line) 

542 

543 return line 

544 

545 def new_do_quit(self, arg): 

546 return OldPdb.do_quit(self, arg) 

547 

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

549 

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

551 if context is None: 

552 context = self.context 

553 try: 

554 skipped = 0 

555 to_print = "" 

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

557 if hidden and self.skip_hidden: 

558 skipped += 1 

559 continue 

560 if skipped: 

561 to_print += self.theme.format( 

562 [ 

563 ( 

564 Token.ExcName, 

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

566 ), 

567 (Token, "\n"), 

568 ] 

569 ) 

570 

571 skipped = 0 

572 to_print += self.format_stack_entry(frame_lineno) 

573 if skipped: 

574 to_print += self.theme.format( 

575 [ 

576 ( 

577 Token.ExcName, 

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

579 ), 

580 (Token, "\n"), 

581 ] 

582 ) 

583 print(to_print, file=self.stdout) 

584 except KeyboardInterrupt: 

585 pass 

586 

587 def print_stack_entry( 

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

589 ) -> None: 

590 """ 

591 Overwrite print_stack_entry from superclass (PDB) 

592 """ 

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

594 

595 frame, lineno = frame_lineno 

596 filename = frame.f_code.co_filename 

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

598 

599 def _get_frame_locals(self, frame): 

600 """ " 

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

602 that or the following can happen 

603 

604 ipdb> foo 

605 "old" 

606 ipdb> foo = "new" 

607 ipdb> foo 

608 "new" 

609 ipdb> where 

610 ipdb> foo 

611 "old" 

612 

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

614 

615 """ 

616 if frame is self.curframe: 

617 return self.curframe_locals 

618 else: 

619 return frame.f_locals 

620 

621 def format_stack_entry( 

622 self, 

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

624 lprefix: str = ": ", 

625 ) -> str: 

626 """ 

627 overwrite from super class so must -> str 

628 """ 

629 context = self.context 

630 try: 

631 context = int(context) 

632 if context <= 0: 

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

634 except (TypeError, ValueError): 

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

636 

637 import reprlib 

638 

639 ret_tok = [] 

640 

641 frame, lineno = frame_lineno 

642 

643 return_value = "" 

644 loc_frame = self._get_frame_locals(frame) 

645 if "__return__" in loc_frame: 

646 rv = loc_frame["__return__"] 

647 # return_value += '->' 

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

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

650 

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

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

653 link_tok = (Token.FilenameEm, filename) 

654 

655 if frame.f_code.co_name: 

656 func = frame.f_code.co_name 

657 else: 

658 func = "<lambda>" 

659 

660 call_toks = [] 

661 if func != "?": 

662 if "__args__" in loc_frame: 

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

664 else: 

665 args = "()" 

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

667 

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

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

670 if frame is self.curframe: 

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

672 else: 

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

674 

675 ret_tok.extend( 

676 [ 

677 link_tok, 

678 (Token, "("), 

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

680 (Token, ")"), 

681 *call_toks, 

682 (Token, "\n"), 

683 ] 

684 ) 

685 

686 start = lineno - 1 - context // 2 

687 lines = linecache.getlines(filename) 

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

689 start = max(start, 0) 

690 lines = lines[start : start + context] 

691 

692 for i, line in enumerate(lines): 

693 show_arrow = start + 1 + i == lineno 

694 

695 bp, num, colored_line = self.__line_content( 

696 filename, 

697 start + 1 + i, 

698 line, 

699 arrow=show_arrow, 

700 ) 

701 if frame is self.curframe or show_arrow: 

702 rlt = [ 

703 bp, 

704 (Token.LinenoEm, num), 

705 (Token, " "), 

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

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

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

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

710 (Token, colored_line), 

711 ] 

712 else: 

713 rlt = [ 

714 bp, 

715 (Token.Lineno, num), 

716 (Token, " "), 

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

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

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

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

721 (Token.Line, colored_line), 

722 ] 

723 ret_tok.extend(rlt) 

724 

725 return self.theme.format(ret_tok) 

726 

727 def __line_content( 

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

729 ): 

730 bp_mark = "" 

731 BreakpointToken = Token.Breakpoint 

732 

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

734 if not err: 

735 assert new_line is not None 

736 line = new_line 

737 

738 bp = None 

739 if lineno in self.get_file_breaks(filename): 

740 bps = self.get_breaks(filename, lineno) 

741 bp = bps[-1] 

742 

743 if bp: 

744 bp_mark = str(bp.number) 

745 BreakpointToken = Token.Breakpoint.Enabled 

746 if not bp.enabled: 

747 BreakpointToken = Token.Breakpoint.Disabled 

748 numbers_width = 7 

749 if arrow: 

750 # This is the line with the error 

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

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

753 else: 

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

755 bp_str = (BreakpointToken, bp_mark) 

756 return (bp_str, num, line) 

757 

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

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

760 command.""" 

761 toks: TokenStream = [] 

762 try: 

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

764 filename = self._exec_filename 

765 

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

767 line = linecache.getline(filename, lineno) 

768 if not line: 

769 break 

770 

771 assert self.curframe is not None 

772 

773 if lineno == self.curframe.f_lineno: 

774 bp, num, colored_line = self.__line_content( 

775 filename, lineno, line, arrow=True 

776 ) 

777 toks.extend( 

778 [ 

779 bp, 

780 (Token.LinenoEm, num), 

781 (Token, " "), 

782 # TODO: investigate Token.Line here 

783 (Token, colored_line), 

784 ] 

785 ) 

786 else: 

787 bp, num, colored_line = self.__line_content( 

788 filename, lineno, line, arrow=False 

789 ) 

790 toks.extend( 

791 [ 

792 bp, 

793 (Token.Lineno, num), 

794 (Token, " "), 

795 (Token, colored_line), 

796 ] 

797 ) 

798 

799 self.lineno = lineno 

800 

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

802 

803 except KeyboardInterrupt: 

804 pass 

805 

806 def do_skip_predicates(self, args): 

807 """ 

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

809 

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

811 

812 To change the value of a predicate 

813 

814 skip_predicates key [true|false] 

815 

816 Call without arguments to see the current values. 

817 

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

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

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

821 attribute. 

822 """ 

823 if not args.strip(): 

824 print("current predicates:") 

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

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

827 return 

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

829 if len(type_value) != 2: 

830 print( 

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

832 ) 

833 return 

834 

835 type_, value = type_value 

836 if type_ not in self._predicates: 

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

838 return 

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

840 print( 

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

842 ) 

843 return 

844 

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

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

847 print( 

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

849 ) 

850 

851 def do_skip_hidden(self, arg): 

852 """ 

853 Change whether or not we should skip frames with the 

854 __tracebackhide__ attribute. 

855 """ 

856 if not arg.strip(): 

857 print( 

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

859 ) 

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

861 self.skip_hidden = True 

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

863 self.skip_hidden = False 

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

865 print( 

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

867 ) 

868 

869 def do_list(self, arg): 

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

871 self.lastcmd = "list" 

872 last = None 

873 if arg and arg != ".": 

874 try: 

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

876 if type(x) == type(()): 

877 first, last = x 

878 first = int(first) 

879 last = int(last) 

880 if last < first: 

881 # Assume it's a count 

882 last = first + last 

883 else: 

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

885 except: 

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

887 return 

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

889 assert self.curframe is not None 

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

891 else: 

892 first = self.lineno + 1 

893 if last is None: 

894 last = first + 10 

895 assert self.curframe is not None 

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

897 

898 lineno = first 

899 filename = self.curframe.f_code.co_filename 

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

901 

902 do_l = do_list 

903 

904 def getsourcelines(self, obj): 

905 lines, lineno = inspect.findsource(obj) 

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

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

908 return lines, 1 

909 elif inspect.ismodule(obj): 

910 return lines, 1 

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

912 

913 def do_longlist(self, arg): 

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

915 

916 Shows more lines than 'list' does. 

917 """ 

918 self.lastcmd = "longlist" 

919 try: 

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

921 except OSError as err: 

922 self.error(str(err)) 

923 return 

924 last = lineno + len(lines) 

925 assert self.curframe is not None 

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

927 

928 do_ll = do_longlist 

929 

930 def do_debug(self, arg): 

931 """debug code 

932 Enter a recursive debugger that steps through the code 

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

934 executed in the current environment). 

935 """ 

936 trace_function = sys.gettrace() 

937 sys.settrace(None) 

938 assert self.curframe is not None 

939 globals = self.curframe.f_globals 

940 locals = self.curframe_locals 

941 p = self.__class__( 

942 completekey=self.completekey, stdin=self.stdin, stdout=self.stdout 

943 ) 

944 p.use_rawinput = self.use_rawinput 

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

946 self.message("ENTERING RECURSIVE DEBUGGER") 

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

948 self.message("LEAVING RECURSIVE DEBUGGER") 

949 sys.settrace(trace_function) 

950 self.lastcmd = p.lastcmd 

951 

952 def do_pdef(self, arg): 

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

954 

955 The debugger interface to %pdef""" 

956 assert self.curframe is not None 

957 namespaces = [ 

958 ("Locals", self.curframe_locals), 

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

960 ] 

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

962 

963 def do_pdoc(self, arg): 

964 """Print the docstring for an object. 

965 

966 The debugger interface to %pdoc.""" 

967 assert self.curframe is not None 

968 namespaces = [ 

969 ("Locals", self.curframe_locals), 

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

971 ] 

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

973 

974 def do_pfile(self, arg): 

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

976 

977 The debugger interface to %pfile. 

978 """ 

979 assert self.curframe is not None 

980 namespaces = [ 

981 ("Locals", self.curframe_locals), 

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

983 ] 

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

985 

986 def do_pinfo(self, arg): 

987 """Provide detailed information about an object. 

988 

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

990 assert self.curframe is not None 

991 namespaces = [ 

992 ("Locals", self.curframe_locals), 

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

994 ] 

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

996 

997 def do_pinfo2(self, arg): 

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

999 

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

1001 assert self.curframe is not None 

1002 namespaces = [ 

1003 ("Locals", self.curframe_locals), 

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

1005 ] 

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

1007 

1008 def do_psource(self, arg): 

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

1010 assert self.curframe is not None 

1011 namespaces = [ 

1012 ("Locals", self.curframe_locals), 

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

1014 ] 

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

1016 

1017 def do_where(self, arg: str): 

1018 """w(here) 

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

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

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

1022 

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

1024 print""" 

1025 if arg: 

1026 try: 

1027 context = int(arg) 

1028 except ValueError as err: 

1029 self.error(str(err)) 

1030 return 

1031 self.print_stack_trace(context) 

1032 else: 

1033 self.print_stack_trace() 

1034 

1035 do_w = do_where 

1036 

1037 def break_anywhere(self, frame): 

1038 """ 

1039 _stop_in_decorator_internals is overly restrictive, as we may still want 

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

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

1042 stop at any point inside the function 

1043 

1044 """ 

1045 

1046 sup = super().break_anywhere(frame) 

1047 if sup: 

1048 return sup 

1049 if self._predicates["debuggerskip"]: 

1050 if DEBUGGERSKIP in frame.f_code.co_varnames: 

1051 return True 

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

1053 return True 

1054 return False 

1055 

1056 def _is_in_decorator_internal_and_should_skip(self, frame): 

1057 """ 

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

1059 

1060 """ 

1061 # if we are disabled don't skip 

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

1063 return False 

1064 

1065 return self._cachable_skip(frame) 

1066 

1067 @lru_cache(1024) 

1068 def _cached_one_parent_frame_debuggerskip(self, frame): 

1069 """ 

1070 Cache looking up for DEBUGGERSKIP on parent frame. 

1071 

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

1073 one does have a debugger skip. 

1074 

1075 This is likely to introduce fake positive though. 

1076 """ 

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

1078 frame = frame.f_back 

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

1080 return True 

1081 return None 

1082 

1083 @lru_cache(1024) 

1084 def _cachable_skip(self, frame): 

1085 # if frame is tagged, skip by default. 

1086 if DEBUGGERSKIP in frame.f_code.co_varnames: 

1087 return True 

1088 

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

1090 if self._cached_one_parent_frame_debuggerskip(frame): 

1091 return True 

1092 

1093 return False 

1094 

1095 def stop_here(self, frame): 

1096 if self._is_in_decorator_internal_and_should_skip(frame) is True: 

1097 return False 

1098 

1099 hidden = False 

1100 if self.skip_hidden: 

1101 hidden = self._hidden_predicate(frame) 

1102 if hidden: 

1103 if self.report_skipped: 

1104 print( 

1105 self.theme.format( 

1106 [ 

1107 ( 

1108 Token.ExcName, 

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

1110 ), 

1111 (Token, "\n"), 

1112 ] 

1113 ) 

1114 ) 

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

1116 print( 

1117 self.theme.format( 

1118 [ 

1119 ( 

1120 Token.ExcName, 

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

1122 ), 

1123 (Token, "\n"), 

1124 ] 

1125 ) 

1126 ) 

1127 

1128 return False 

1129 

1130 return super().stop_here(frame) 

1131 

1132 def do_up(self, arg): 

1133 """u(p) [count] 

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

1135 stack trace (to an older frame). 

1136 

1137 Will skip hidden frames and ignored modules. 

1138 """ 

1139 # modified version of upstream that skips 

1140 # frames with __tracebackhide__ and ignored modules 

1141 if self.curindex == 0: 

1142 self.error("Oldest frame") 

1143 return 

1144 try: 

1145 count = int(arg or 1) 

1146 except ValueError: 

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

1148 return 

1149 

1150 hidden_skipped = 0 

1151 module_skipped = 0 

1152 

1153 if count < 0: 

1154 _newframe = 0 

1155 else: 

1156 counter = 0 

1157 hidden_frames = self.hidden_frames(self.stack) 

1158 

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

1160 should_skip_hidden = hidden_frames[i] and self.skip_hidden 

1161 should_skip_module = self.skip and self.is_skipped_module( 

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

1163 ) 

1164 

1165 if should_skip_hidden or should_skip_module: 

1166 if should_skip_hidden: 

1167 hidden_skipped += 1 

1168 if should_skip_module: 

1169 module_skipped += 1 

1170 continue 

1171 counter += 1 

1172 if counter >= count: 

1173 break 

1174 else: 

1175 # if no break occurred. 

1176 self.error( 

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

1178 ) 

1179 return 

1180 

1181 _newframe = i 

1182 self._select_frame(_newframe) 

1183 

1184 total_skipped = hidden_skipped + module_skipped 

1185 if total_skipped: 

1186 print( 

1187 self.theme.format( 

1188 [ 

1189 ( 

1190 Token.ExcName, 

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

1192 ), 

1193 (Token, "\n"), 

1194 ] 

1195 ) 

1196 ) 

1197 

1198 def do_down(self, arg): 

1199 """d(own) [count] 

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

1201 stack trace (to a newer frame). 

1202 

1203 Will skip hidden frames and ignored modules. 

1204 """ 

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

1206 self.error("Newest frame") 

1207 return 

1208 try: 

1209 count = int(arg or 1) 

1210 except ValueError: 

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

1212 return 

1213 if count < 0: 

1214 _newframe = len(self.stack) - 1 

1215 else: 

1216 counter = 0 

1217 hidden_skipped = 0 

1218 module_skipped = 0 

1219 hidden_frames = self.hidden_frames(self.stack) 

1220 

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

1222 should_skip_hidden = hidden_frames[i] and self.skip_hidden 

1223 should_skip_module = self.skip and self.is_skipped_module( 

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

1225 ) 

1226 

1227 if should_skip_hidden or should_skip_module: 

1228 if should_skip_hidden: 

1229 hidden_skipped += 1 

1230 if should_skip_module: 

1231 module_skipped += 1 

1232 continue 

1233 counter += 1 

1234 if counter >= count: 

1235 break 

1236 else: 

1237 self.error( 

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

1239 ) 

1240 return 

1241 

1242 total_skipped = hidden_skipped + module_skipped 

1243 if total_skipped: 

1244 print( 

1245 self.theme.format( 

1246 [ 

1247 ( 

1248 Token.ExcName, 

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

1250 ), 

1251 (Token, "\n"), 

1252 ] 

1253 ) 

1254 ) 

1255 _newframe = i 

1256 

1257 self._select_frame(_newframe) 

1258 

1259 do_d = do_down 

1260 do_u = do_up 

1261 

1262 def _show_ignored_modules(self): 

1263 """Display currently ignored modules.""" 

1264 if self.skip: 

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

1266 else: 

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

1268 

1269 def do_ignore_module(self, arg): 

1270 """ignore_module <module_name> 

1271 

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

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

1274 frames from that module. 

1275 

1276 Supports wildcard patterns using fnmatch syntax: 

1277 

1278 Usage: 

1279 ignore_module threading # Skip threading module frames 

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

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

1282 ignore_module # List currently ignored modules 

1283 """ 

1284 

1285 if self.skip is None: 

1286 self.skip = set() 

1287 

1288 module_name = arg.strip() 

1289 

1290 if not module_name: 

1291 self._show_ignored_modules() 

1292 return 

1293 

1294 self.skip.add(module_name) 

1295 

1296 def do_unignore_module(self, arg): 

1297 """unignore_module <module_name> 

1298 

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

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

1301 

1302 Usage: 

1303 unignore_module threading # Stop ignoring threading module frames 

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

1305 unignore_module # List currently ignored modules 

1306 """ 

1307 

1308 if self.skip is None: 

1309 self.skip = set() 

1310 

1311 module_name = arg.strip() 

1312 

1313 if not module_name: 

1314 self._show_ignored_modules() 

1315 return 

1316 

1317 try: 

1318 self.skip.remove(module_name) 

1319 except KeyError: 

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

1321 self._show_ignored_modules() 

1322 

1323 def do_context(self, context: str): 

1324 """context number_of_lines 

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

1326 stacktrace information. 

1327 """ 

1328 try: 

1329 new_context = int(context) 

1330 if new_context <= 0: 

1331 raise ValueError() 

1332 self.context = new_context 

1333 except ValueError: 

1334 self.error( 

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

1336 ) 

1337 

1338 

1339class InterruptiblePdb(Pdb): 

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

1341 

1342 def cmdloop(self, intro=None): 

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

1344 try: 

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

1346 except KeyboardInterrupt: 

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

1348 self.do_quit("") 

1349 sys.settrace(None) 

1350 self.quitting = False 

1351 raise 

1352 

1353 def _cmdloop(self): 

1354 while True: 

1355 try: 

1356 # keyboard interrupts allow for an easy way to cancel 

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

1358 self.allow_kbdint = True 

1359 self.cmdloop() 

1360 self.allow_kbdint = False 

1361 break 

1362 except KeyboardInterrupt: 

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

1364 raise 

1365 

1366 

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

1368 """ 

1369 Start debugging from `frame`. 

1370 

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

1372 """ 

1373 pdb = Pdb() 

1374 if header is not None: 

1375 pdb.message(header) 

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