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

570 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 

279 # IPython changes... 

280 self.shell = get_ipython() 

281 

282 if self.shell is None: 

283 save_main = sys.modules["__main__"] 

284 # No IPython instance running, we must create one 

285 from IPython.terminal.interactiveshell import TerminalInteractiveShell 

286 

287 self.shell = TerminalInteractiveShell.instance() 

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

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

290 sys.modules["__main__"] = save_main 

291 

292 self.aliases = {} 

293 

294 theme_name = self.shell.colors 

295 assert isinstance(theme_name, str) 

296 assert theme_name.lower() == theme_name 

297 

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

299 # debugging. 

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

301 self.set_theme_name(theme_name) 

302 

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

304 self.prompt = prompt 

305 self.skip_hidden = True 

306 self.report_skipped = True 

307 

308 # list of predicates we use to skip frames 

309 self._predicates = self.default_predicates 

310 

311 if CHAIN_EXCEPTIONS: 

312 self._chained_exceptions = tuple() 

313 self._chained_exception_index = 0 

314 

315 @property 

316 def context(self) -> int: 

317 return self._context 

318 

319 @context.setter 

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

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

322 if not isinstance(value, int): 

323 value = int(value) 

324 assert isinstance(value, int) 

325 assert value >= 0 

326 self._context = value 

327 

328 def set_theme_name(self, name): 

329 assert name.lower() == name 

330 assert isinstance(name, str) 

331 self._theme_name = name 

332 self.parser.theme_name = name 

333 

334 @property 

335 def theme(self): 

336 return PyColorize.theme_table[self._theme_name] 

337 

338 # 

339 def set_colors(self, scheme): 

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

341 warnings.warn( 

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

343 DeprecationWarning, 

344 stacklevel=2, 

345 ) 

346 assert scheme == scheme.lower() 

347 self._theme_name = scheme.lower() 

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

349 

350 def set_trace(self, frame=None): 

351 if frame is None: 

352 frame = sys._getframe().f_back 

353 self.initial_frame = frame 

354 return super().set_trace(frame) 

355 

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

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

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

359 stack.pop(0) 

360 pos -= 1 

361 return stack, pos 

362 

363 def _is_internal_frame(self, frame): 

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

365 filename = frame.f_code.co_filename 

366 

367 # Skip bdb.py runcall and internal operations 

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

369 func_name = frame.f_code.co_name 

370 # Skip internal bdb operations but allow breakpoint hits 

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

372 return True 

373 

374 return False 

375 

376 def _hidden_predicate(self, frame): 

377 """ 

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

379 """ 

380 

381 if self._predicates["readonly"]: 

382 fname = frame.f_code.co_filename 

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

384 # function would otherwise appear as RO. 

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

386 return True 

387 

388 if self._predicates["tbhide"]: 

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

390 return False 

391 frame_locals = self._get_frame_locals(frame) 

392 if "__tracebackhide__" not in frame_locals: 

393 return False 

394 return frame_locals["__tracebackhide__"] 

395 return False 

396 

397 def hidden_frames(self, stack): 

398 """ 

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

400 

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

402 """ 

403 # The f_locals dictionary is updated from the actual frame 

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

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

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

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

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

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

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

411 return ip_hide 

412 

413 if CHAIN_EXCEPTIONS: 

414 

415 def _get_tb_and_exceptions(self, tb_or_exc): 

416 """ 

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

418 and current traceback to inspect. 

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

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

421 can jump to with do_exceptions. 

422 """ 

423 _exceptions = [] 

424 if isinstance(tb_or_exc, BaseException): 

425 traceback, current = tb_or_exc.__traceback__, tb_or_exc 

426 

427 while current is not None: 

428 if current in _exceptions: 

429 break 

430 _exceptions.append(current) 

431 if current.__cause__ is not None: 

432 current = current.__cause__ 

433 elif ( 

434 current.__context__ is not None 

435 and not current.__suppress_context__ 

436 ): 

437 current = current.__context__ 

438 

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

440 self.message( 

441 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}" 

442 " chained exceptions found, not all exceptions" 

443 "will be browsable with `exceptions`." 

444 ) 

445 break 

446 else: 

447 traceback = tb_or_exc 

448 return tuple(reversed(_exceptions)), traceback 

449 

450 @contextmanager 

451 def _hold_exceptions(self, exceptions): 

452 """ 

453 Context manager to ensure proper cleaning of exceptions references 

454 When given a chained exception instead of a traceback, 

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

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

457 """ 

458 try: 

459 self._chained_exceptions = exceptions 

460 self._chained_exception_index = len(exceptions) - 1 

461 yield 

462 finally: 

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

464 # be cleared on exception change 

465 self._chained_exceptions = tuple() 

466 self._chained_exception_index = 0 

467 

468 def do_exceptions(self, arg): 

469 """exceptions [number] 

470 List or change current exception in an exception chain. 

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

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

473 with an arrow. 

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

475 """ 

476 if not self._chained_exceptions: 

477 self.message( 

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

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

480 " object rather than a traceback." 

481 ) 

482 return 

483 if not arg: 

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

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

486 rep = repr(exc) 

487 if len(rep) > 80: 

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

489 indicator = ( 

490 " -" 

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

492 else f"{ix:>3}" 

493 ) 

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

495 else: 

496 try: 

497 number = int(arg) 

498 except ValueError: 

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

500 return 

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

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

503 self.error( 

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

505 ) 

506 return 

507 

508 self._chained_exception_index = number 

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

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

511 else: 

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

513 

514 def interaction(self, frame, tb_or_exc): 

515 try: 

516 if CHAIN_EXCEPTIONS: 

517 # this context manager is part of interaction in 3.13 

518 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc) 

519 if isinstance(tb_or_exc, BaseException): 

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

521 with self._hold_exceptions(_chained_exceptions): 

522 OldPdb.interaction(self, frame, tb) 

523 else: 

524 OldPdb.interaction(self, frame, tb_or_exc) 

525 

526 except KeyboardInterrupt: 

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

528 

529 def precmd(self, line): 

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

531 

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

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

534 elif line.endswith("?"): 

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

536 

537 line = super().precmd(line) 

538 

539 return line 

540 

541 def new_do_quit(self, arg): 

542 return OldPdb.do_quit(self, arg) 

543 

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

545 

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

547 if context is None: 

548 context = self.context 

549 try: 

550 skipped = 0 

551 to_print = "" 

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

553 if hidden and self.skip_hidden: 

554 skipped += 1 

555 continue 

556 if skipped: 

557 to_print += self.theme.format( 

558 [ 

559 ( 

560 Token.ExcName, 

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

562 ), 

563 (Token, "\n"), 

564 ] 

565 ) 

566 

567 skipped = 0 

568 to_print += self.format_stack_entry(frame_lineno) 

569 if skipped: 

570 to_print += self.theme.format( 

571 [ 

572 ( 

573 Token.ExcName, 

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

575 ), 

576 (Token, "\n"), 

577 ] 

578 ) 

579 print(to_print, file=self.stdout) 

580 except KeyboardInterrupt: 

581 pass 

582 

583 def print_stack_entry( 

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

585 ) -> None: 

586 """ 

587 Overwrite print_stack_entry from superclass (PDB) 

588 """ 

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

590 

591 frame, lineno = frame_lineno 

592 filename = frame.f_code.co_filename 

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

594 

595 def _get_frame_locals(self, frame): 

596 """ " 

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

598 that or the following can happen 

599 

600 ipdb> foo 

601 "old" 

602 ipdb> foo = "new" 

603 ipdb> foo 

604 "new" 

605 ipdb> where 

606 ipdb> foo 

607 "old" 

608 

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

610 

611 """ 

612 if frame is getattr(self, "curframe", None): 

613 return self.curframe_locals 

614 else: 

615 return frame.f_locals 

616 

617 def format_stack_entry( 

618 self, 

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

620 lprefix: str = ": ", 

621 ) -> str: 

622 """ 

623 overwrite from super class so must -> str 

624 """ 

625 context = self.context 

626 try: 

627 context = int(context) 

628 if context <= 0: 

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

630 except (TypeError, ValueError): 

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

632 

633 import reprlib 

634 

635 ret_tok = [] 

636 

637 frame, lineno = frame_lineno 

638 

639 return_value = "" 

640 loc_frame = self._get_frame_locals(frame) 

641 if "__return__" in loc_frame: 

642 rv = loc_frame["__return__"] 

643 # return_value += '->' 

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

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

646 

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

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

649 link_tok = (Token.FilenameEm, filename) 

650 

651 if frame.f_code.co_name: 

652 func = frame.f_code.co_name 

653 else: 

654 func = "<lambda>" 

655 

656 call_toks = [] 

657 if func != "?": 

658 if "__args__" in loc_frame: 

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

660 else: 

661 args = "()" 

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

663 

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

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

666 if frame is self.curframe: 

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

668 else: 

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

670 

671 ret_tok.extend( 

672 [ 

673 link_tok, 

674 (Token, "("), 

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

676 (Token, ")"), 

677 *call_toks, 

678 (Token, "\n"), 

679 ] 

680 ) 

681 

682 start = lineno - 1 - context // 2 

683 lines = linecache.getlines(filename) 

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

685 start = max(start, 0) 

686 lines = lines[start : start + context] 

687 

688 for i, line in enumerate(lines): 

689 show_arrow = start + 1 + i == lineno 

690 

691 bp, num, colored_line = self.__line_content( 

692 filename, 

693 start + 1 + i, 

694 line, 

695 arrow=show_arrow, 

696 ) 

697 if frame is self.curframe or show_arrow: 

698 rlt = [ 

699 bp, 

700 (Token.LinenoEm, num), 

701 (Token, " "), 

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

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

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

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

706 (Token, colored_line), 

707 ] 

708 else: 

709 rlt = [ 

710 bp, 

711 (Token.Lineno, num), 

712 (Token, " "), 

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

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

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

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

717 (Token.Line, colored_line), 

718 ] 

719 ret_tok.extend(rlt) 

720 

721 return self.theme.format(ret_tok) 

722 

723 def __line_content( 

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

725 ): 

726 bp_mark = "" 

727 BreakpointToken = Token.Breakpoint 

728 

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

730 if not err: 

731 line = new_line 

732 

733 bp = None 

734 if lineno in self.get_file_breaks(filename): 

735 bps = self.get_breaks(filename, lineno) 

736 bp = bps[-1] 

737 

738 if bp: 

739 bp_mark = str(bp.number) 

740 BreakpointToken = Token.Breakpoint.Enabled 

741 if not bp.enabled: 

742 BreakpointToken = Token.Breakpoint.Disabled 

743 numbers_width = 7 

744 if arrow: 

745 # This is the line with the error 

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

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

748 else: 

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

750 bp_str = (BreakpointToken, bp_mark) 

751 return (bp_str, num, line) 

752 

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

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

755 command.""" 

756 toks: TokenStream = [] 

757 try: 

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

759 filename = self._exec_filename 

760 

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

762 line = linecache.getline(filename, lineno) 

763 if not line: 

764 break 

765 

766 assert self.curframe is not None 

767 

768 if lineno == self.curframe.f_lineno: 

769 bp, num, colored_line = self.__line_content( 

770 filename, lineno, line, arrow=True 

771 ) 

772 toks.extend( 

773 [ 

774 bp, 

775 (Token.LinenoEm, num), 

776 (Token, " "), 

777 # TODO: investigate Token.Line here 

778 (Token, colored_line), 

779 ] 

780 ) 

781 else: 

782 bp, num, colored_line = self.__line_content( 

783 filename, lineno, line, arrow=False 

784 ) 

785 toks.extend( 

786 [ 

787 bp, 

788 (Token.Lineno, num), 

789 (Token, " "), 

790 (Token, colored_line), 

791 ] 

792 ) 

793 

794 self.lineno = lineno 

795 

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

797 

798 except KeyboardInterrupt: 

799 pass 

800 

801 def do_skip_predicates(self, args): 

802 """ 

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

804 

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

806 

807 To change the value of a predicate 

808 

809 skip_predicates key [true|false] 

810 

811 Call without arguments to see the current values. 

812 

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

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

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

816 attribute. 

817 """ 

818 if not args.strip(): 

819 print("current predicates:") 

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

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

822 return 

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

824 if len(type_value) != 2: 

825 print( 

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

827 ) 

828 return 

829 

830 type_, value = type_value 

831 if type_ not in self._predicates: 

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

833 return 

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

835 print( 

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

837 ) 

838 return 

839 

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

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

842 print( 

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

844 ) 

845 

846 def do_skip_hidden(self, arg): 

847 """ 

848 Change whether or not we should skip frames with the 

849 __tracebackhide__ attribute. 

850 """ 

851 if not arg.strip(): 

852 print( 

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

854 ) 

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

856 self.skip_hidden = True 

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

858 self.skip_hidden = False 

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

860 print( 

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

862 ) 

863 

864 def do_list(self, arg): 

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

866 self.lastcmd = "list" 

867 last = None 

868 if arg and arg != ".": 

869 try: 

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

871 if type(x) == type(()): 

872 first, last = x 

873 first = int(first) 

874 last = int(last) 

875 if last < first: 

876 # Assume it's a count 

877 last = first + last 

878 else: 

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

880 except: 

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

882 return 

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

884 assert self.curframe is not None 

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

886 else: 

887 first = self.lineno + 1 

888 if last is None: 

889 last = first + 10 

890 assert self.curframe is not None 

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

892 

893 lineno = first 

894 filename = self.curframe.f_code.co_filename 

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

896 

897 do_l = do_list 

898 

899 def getsourcelines(self, obj): 

900 lines, lineno = inspect.findsource(obj) 

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

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

903 return lines, 1 

904 elif inspect.ismodule(obj): 

905 return lines, 1 

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

907 

908 def do_longlist(self, arg): 

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

910 

911 Shows more lines than 'list' does. 

912 """ 

913 self.lastcmd = "longlist" 

914 try: 

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

916 except OSError as err: 

917 self.error(str(err)) 

918 return 

919 last = lineno + len(lines) 

920 assert self.curframe is not None 

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

922 

923 do_ll = do_longlist 

924 

925 def do_debug(self, arg): 

926 """debug code 

927 Enter a recursive debugger that steps through the code 

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

929 executed in the current environment). 

930 """ 

931 trace_function = sys.gettrace() 

932 sys.settrace(None) 

933 assert self.curframe is not None 

934 globals = self.curframe.f_globals 

935 locals = self.curframe_locals 

936 p = self.__class__( 

937 completekey=self.completekey, stdin=self.stdin, stdout=self.stdout 

938 ) 

939 p.use_rawinput = self.use_rawinput 

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

941 self.message("ENTERING RECURSIVE DEBUGGER") 

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

943 self.message("LEAVING RECURSIVE DEBUGGER") 

944 sys.settrace(trace_function) 

945 self.lastcmd = p.lastcmd 

946 

947 def do_pdef(self, arg): 

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

949 

950 The debugger interface to %pdef""" 

951 assert self.curframe is not None 

952 namespaces = [ 

953 ("Locals", self.curframe_locals), 

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

955 ] 

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

957 

958 def do_pdoc(self, arg): 

959 """Print the docstring for an object. 

960 

961 The debugger interface to %pdoc.""" 

962 assert self.curframe is not None 

963 namespaces = [ 

964 ("Locals", self.curframe_locals), 

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

966 ] 

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

968 

969 def do_pfile(self, arg): 

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

971 

972 The debugger interface to %pfile. 

973 """ 

974 assert self.curframe is not None 

975 namespaces = [ 

976 ("Locals", self.curframe_locals), 

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

978 ] 

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

980 

981 def do_pinfo(self, arg): 

982 """Provide detailed information about an object. 

983 

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

985 assert self.curframe is not None 

986 namespaces = [ 

987 ("Locals", self.curframe_locals), 

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

989 ] 

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

991 

992 def do_pinfo2(self, arg): 

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

994 

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

996 assert self.curframe is not None 

997 namespaces = [ 

998 ("Locals", self.curframe_locals), 

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

1000 ] 

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

1002 

1003 def do_psource(self, arg): 

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

1005 assert self.curframe is not None 

1006 namespaces = [ 

1007 ("Locals", self.curframe_locals), 

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

1009 ] 

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

1011 

1012 def do_where(self, arg: str): 

1013 """w(here) 

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

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

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

1017 

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

1019 print""" 

1020 if arg: 

1021 try: 

1022 context = int(arg) 

1023 except ValueError as err: 

1024 self.error(str(err)) 

1025 return 

1026 self.print_stack_trace(context) 

1027 else: 

1028 self.print_stack_trace() 

1029 

1030 do_w = do_where 

1031 

1032 def break_anywhere(self, frame): 

1033 """ 

1034 _stop_in_decorator_internals is overly restrictive, as we may still want 

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

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

1037 stop at any point inside the function 

1038 

1039 """ 

1040 

1041 sup = super().break_anywhere(frame) 

1042 if sup: 

1043 return sup 

1044 if self._predicates["debuggerskip"]: 

1045 if DEBUGGERSKIP in frame.f_code.co_varnames: 

1046 return True 

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

1048 return True 

1049 return False 

1050 

1051 def _is_in_decorator_internal_and_should_skip(self, frame): 

1052 """ 

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

1054 

1055 """ 

1056 # if we are disabled don't skip 

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

1058 return False 

1059 

1060 return self._cachable_skip(frame) 

1061 

1062 @lru_cache(1024) 

1063 def _cached_one_parent_frame_debuggerskip(self, frame): 

1064 """ 

1065 Cache looking up for DEBUGGERSKIP on parent frame. 

1066 

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

1068 one does have a debugger skip. 

1069 

1070 This is likely to introduce fake positive though. 

1071 """ 

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

1073 frame = frame.f_back 

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

1075 return True 

1076 return None 

1077 

1078 @lru_cache(1024) 

1079 def _cachable_skip(self, frame): 

1080 # if frame is tagged, skip by default. 

1081 if DEBUGGERSKIP in frame.f_code.co_varnames: 

1082 return True 

1083 

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

1085 if self._cached_one_parent_frame_debuggerskip(frame): 

1086 return True 

1087 

1088 return False 

1089 

1090 def stop_here(self, frame): 

1091 if self._is_in_decorator_internal_and_should_skip(frame) is True: 

1092 return False 

1093 

1094 hidden = False 

1095 if self.skip_hidden: 

1096 hidden = self._hidden_predicate(frame) 

1097 if hidden: 

1098 if self.report_skipped: 

1099 print( 

1100 self.theme.format( 

1101 [ 

1102 ( 

1103 Token.ExcName, 

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

1105 ), 

1106 (Token, "\n"), 

1107 ] 

1108 ) 

1109 ) 

1110 return super().stop_here(frame) 

1111 

1112 def do_up(self, arg): 

1113 """u(p) [count] 

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

1115 stack trace (to an older frame). 

1116 

1117 Will skip hidden frames. 

1118 """ 

1119 # modified version of upstream that skips 

1120 # frames with __tracebackhide__ 

1121 if self.curindex == 0: 

1122 self.error("Oldest frame") 

1123 return 

1124 try: 

1125 count = int(arg or 1) 

1126 except ValueError: 

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

1128 return 

1129 skipped = 0 

1130 if count < 0: 

1131 _newframe = 0 

1132 else: 

1133 counter = 0 

1134 hidden_frames = self.hidden_frames(self.stack) 

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

1136 if hidden_frames[i] and self.skip_hidden: 

1137 skipped += 1 

1138 continue 

1139 counter += 1 

1140 if counter >= count: 

1141 break 

1142 else: 

1143 # if no break occurred. 

1144 self.error( 

1145 "all frames above hidden, use `skip_hidden False` to get get into those." 

1146 ) 

1147 return 

1148 

1149 _newframe = i 

1150 self._select_frame(_newframe) 

1151 if skipped: 

1152 print( 

1153 self.theme.format( 

1154 [ 

1155 ( 

1156 Token.ExcName, 

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

1158 ), 

1159 (Token, "\n"), 

1160 ] 

1161 ) 

1162 ) 

1163 

1164 def do_down(self, arg): 

1165 """d(own) [count] 

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

1167 stack trace (to a newer frame). 

1168 

1169 Will skip hidden frames. 

1170 """ 

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

1172 self.error("Newest frame") 

1173 return 

1174 try: 

1175 count = int(arg or 1) 

1176 except ValueError: 

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

1178 return 

1179 if count < 0: 

1180 _newframe = len(self.stack) - 1 

1181 else: 

1182 counter = 0 

1183 skipped = 0 

1184 hidden_frames = self.hidden_frames(self.stack) 

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

1186 if hidden_frames[i] and self.skip_hidden: 

1187 skipped += 1 

1188 continue 

1189 counter += 1 

1190 if counter >= count: 

1191 break 

1192 else: 

1193 self.error( 

1194 "all frames below hidden, use `skip_hidden False` to get get into those." 

1195 ) 

1196 return 

1197 

1198 if skipped: 

1199 print( 

1200 self.theme.format( 

1201 [ 

1202 ( 

1203 Token.ExcName, 

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

1205 ), 

1206 (Token, "\n"), 

1207 ] 

1208 ) 

1209 ) 

1210 _newframe = i 

1211 

1212 self._select_frame(_newframe) 

1213 

1214 do_d = do_down 

1215 do_u = do_up 

1216 

1217 def do_context(self, context: str): 

1218 """context number_of_lines 

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

1220 stacktrace information. 

1221 """ 

1222 try: 

1223 new_context = int(context) 

1224 if new_context <= 0: 

1225 raise ValueError() 

1226 self.context = new_context 

1227 except ValueError: 

1228 self.error( 

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

1230 ) 

1231 

1232 

1233class InterruptiblePdb(Pdb): 

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

1235 

1236 def cmdloop(self, intro=None): 

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

1238 try: 

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

1240 except KeyboardInterrupt: 

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

1242 self.do_quit("") 

1243 sys.settrace(None) 

1244 self.quitting = False 

1245 raise 

1246 

1247 def _cmdloop(self): 

1248 while True: 

1249 try: 

1250 # keyboard interrupts allow for an easy way to cancel 

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

1252 self.allow_kbdint = True 

1253 self.cmdloop() 

1254 self.allow_kbdint = False 

1255 break 

1256 except KeyboardInterrupt: 

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

1258 raise 

1259 

1260 

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

1262 """ 

1263 Start debugging from `frame`. 

1264 

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

1266 """ 

1267 pdb = Pdb() 

1268 if header is not None: 

1269 pdb.message(header) 

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