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

557 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 _hidden_predicate(self, frame): 

357 """ 

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

359 """ 

360 

361 if self._predicates["readonly"]: 

362 fname = frame.f_code.co_filename 

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

364 # function would otherwise appear as RO. 

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

366 return True 

367 

368 if self._predicates["tbhide"]: 

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

370 return False 

371 frame_locals = self._get_frame_locals(frame) 

372 if "__tracebackhide__" not in frame_locals: 

373 return False 

374 return frame_locals["__tracebackhide__"] 

375 return False 

376 

377 def hidden_frames(self, stack): 

378 """ 

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

380 

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

382 """ 

383 # The f_locals dictionary is updated from the actual frame 

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

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

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

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

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

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

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

391 return ip_hide 

392 

393 if CHAIN_EXCEPTIONS: 

394 

395 def _get_tb_and_exceptions(self, tb_or_exc): 

396 """ 

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

398 and current traceback to inspect. 

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

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

401 can jump to with do_exceptions. 

402 """ 

403 _exceptions = [] 

404 if isinstance(tb_or_exc, BaseException): 

405 traceback, current = tb_or_exc.__traceback__, tb_or_exc 

406 

407 while current is not None: 

408 if current in _exceptions: 

409 break 

410 _exceptions.append(current) 

411 if current.__cause__ is not None: 

412 current = current.__cause__ 

413 elif ( 

414 current.__context__ is not None 

415 and not current.__suppress_context__ 

416 ): 

417 current = current.__context__ 

418 

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

420 self.message( 

421 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}" 

422 " chained exceptions found, not all exceptions" 

423 "will be browsable with `exceptions`." 

424 ) 

425 break 

426 else: 

427 traceback = tb_or_exc 

428 return tuple(reversed(_exceptions)), traceback 

429 

430 @contextmanager 

431 def _hold_exceptions(self, exceptions): 

432 """ 

433 Context manager to ensure proper cleaning of exceptions references 

434 When given a chained exception instead of a traceback, 

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

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

437 """ 

438 try: 

439 self._chained_exceptions = exceptions 

440 self._chained_exception_index = len(exceptions) - 1 

441 yield 

442 finally: 

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

444 # be cleared on exception change 

445 self._chained_exceptions = tuple() 

446 self._chained_exception_index = 0 

447 

448 def do_exceptions(self, arg): 

449 """exceptions [number] 

450 List or change current exception in an exception chain. 

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

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

453 with an arrow. 

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

455 """ 

456 if not self._chained_exceptions: 

457 self.message( 

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

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

460 " object rather than a traceback." 

461 ) 

462 return 

463 if not arg: 

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

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

466 rep = repr(exc) 

467 if len(rep) > 80: 

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

469 indicator = ( 

470 " -" 

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

472 else f"{ix:>3}" 

473 ) 

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

475 else: 

476 try: 

477 number = int(arg) 

478 except ValueError: 

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

480 return 

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

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

483 self.error( 

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

485 ) 

486 return 

487 

488 self._chained_exception_index = number 

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

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

491 else: 

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

493 

494 def interaction(self, frame, tb_or_exc): 

495 try: 

496 if CHAIN_EXCEPTIONS: 

497 # this context manager is part of interaction in 3.13 

498 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc) 

499 if isinstance(tb_or_exc, BaseException): 

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

501 with self._hold_exceptions(_chained_exceptions): 

502 OldPdb.interaction(self, frame, tb) 

503 else: 

504 OldPdb.interaction(self, frame, tb_or_exc) 

505 

506 except KeyboardInterrupt: 

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

508 

509 def precmd(self, line): 

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

511 

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

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

514 elif line.endswith("?"): 

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

516 

517 line = super().precmd(line) 

518 

519 return line 

520 

521 def new_do_quit(self, arg): 

522 return OldPdb.do_quit(self, arg) 

523 

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

525 

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

527 if context is None: 

528 context = self.context 

529 try: 

530 skipped = 0 

531 to_print = "" 

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

533 if hidden and self.skip_hidden: 

534 skipped += 1 

535 continue 

536 if skipped: 

537 to_print += self.theme.format( 

538 [ 

539 ( 

540 Token.ExcName, 

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

542 ), 

543 (Token, "\n"), 

544 ] 

545 ) 

546 

547 skipped = 0 

548 to_print += self.format_stack_entry(frame_lineno) 

549 if skipped: 

550 to_print += self.theme.format( 

551 [ 

552 ( 

553 Token.ExcName, 

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

555 ), 

556 (Token, "\n"), 

557 ] 

558 ) 

559 print(to_print, file=self.stdout) 

560 except KeyboardInterrupt: 

561 pass 

562 

563 def print_stack_entry( 

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

565 ) -> None: 

566 """ 

567 Overwrite print_stack_entry from superclass (PDB) 

568 """ 

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

570 

571 frame, lineno = frame_lineno 

572 filename = frame.f_code.co_filename 

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

574 

575 def _get_frame_locals(self, frame): 

576 """ " 

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

578 that or the following can happen 

579 

580 ipdb> foo 

581 "old" 

582 ipdb> foo = "new" 

583 ipdb> foo 

584 "new" 

585 ipdb> where 

586 ipdb> foo 

587 "old" 

588 

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

590 

591 """ 

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

593 return self.curframe_locals 

594 else: 

595 return frame.f_locals 

596 

597 def format_stack_entry( 

598 self, 

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

600 lprefix: str = ": ", 

601 ) -> str: 

602 """ 

603 overwrite from super class so must -> str 

604 """ 

605 context = self.context 

606 try: 

607 context = int(context) 

608 if context <= 0: 

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

610 except (TypeError, ValueError): 

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

612 

613 import reprlib 

614 

615 ret_tok = [] 

616 

617 frame, lineno = frame_lineno 

618 

619 return_value = "" 

620 loc_frame = self._get_frame_locals(frame) 

621 if "__return__" in loc_frame: 

622 rv = loc_frame["__return__"] 

623 # return_value += '->' 

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

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

626 

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

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

629 link_tok = (Token.FilenameEm, filename) 

630 

631 if frame.f_code.co_name: 

632 func = frame.f_code.co_name 

633 else: 

634 func = "<lambda>" 

635 

636 call_toks = [] 

637 if func != "?": 

638 if "__args__" in loc_frame: 

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

640 else: 

641 args = "()" 

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

643 

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

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

646 if frame is self.curframe: 

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

648 else: 

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

650 

651 ret_tok.extend( 

652 [ 

653 link_tok, 

654 (Token, "("), 

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

656 (Token, ")"), 

657 *call_toks, 

658 (Token, "\n"), 

659 ] 

660 ) 

661 

662 start = lineno - 1 - context // 2 

663 lines = linecache.getlines(filename) 

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

665 start = max(start, 0) 

666 lines = lines[start : start + context] 

667 

668 for i, line in enumerate(lines): 

669 show_arrow = start + 1 + i == lineno 

670 

671 bp, num, colored_line = self.__line_content( 

672 filename, 

673 start + 1 + i, 

674 line, 

675 arrow=show_arrow, 

676 ) 

677 if frame is self.curframe or show_arrow: 

678 rlt = [ 

679 bp, 

680 (Token.LinenoEm, num), 

681 (Token, " "), 

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

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

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

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

686 (Token, colored_line), 

687 ] 

688 else: 

689 rlt = [ 

690 bp, 

691 (Token.Lineno, num), 

692 (Token, " "), 

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

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

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

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

697 (Token.Line, colored_line), 

698 ] 

699 ret_tok.extend(rlt) 

700 

701 return self.theme.format(ret_tok) 

702 

703 def __line_content( 

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

705 ): 

706 bp_mark = "" 

707 BreakpointToken = Token.Breakpoint 

708 

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

710 if not err: 

711 line = new_line 

712 

713 bp = None 

714 if lineno in self.get_file_breaks(filename): 

715 bps = self.get_breaks(filename, lineno) 

716 bp = bps[-1] 

717 

718 if bp: 

719 bp_mark = str(bp.number) 

720 BreakpointToken = Token.Breakpoint.Enabled 

721 if not bp.enabled: 

722 BreakpointToken = Token.Breakpoint.Disabled 

723 numbers_width = 7 

724 if arrow: 

725 # This is the line with the error 

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

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

728 else: 

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

730 bp_str = (BreakpointToken, bp_mark) 

731 return (bp_str, num, line) 

732 

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

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

735 command.""" 

736 toks: TokenStream = [] 

737 try: 

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

739 filename = self._exec_filename 

740 

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

742 line = linecache.getline(filename, lineno) 

743 if not line: 

744 break 

745 

746 assert self.curframe is not None 

747 

748 if lineno == self.curframe.f_lineno: 

749 bp, num, colored_line = self.__line_content( 

750 filename, lineno, line, arrow=True 

751 ) 

752 toks.extend( 

753 [ 

754 bp, 

755 (Token.LinenoEm, num), 

756 (Token, " "), 

757 # TODO: invsetigate Toke.Line here 

758 (Token, colored_line), 

759 ] 

760 ) 

761 else: 

762 bp, num, colored_line = self.__line_content( 

763 filename, lineno, line, arrow=False 

764 ) 

765 toks.extend( 

766 [ 

767 bp, 

768 (Token.Lineno, num), 

769 (Token, " "), 

770 (Token, colored_line), 

771 ] 

772 ) 

773 

774 self.lineno = lineno 

775 

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

777 

778 except KeyboardInterrupt: 

779 pass 

780 

781 def do_skip_predicates(self, args): 

782 """ 

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

784 

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

786 

787 To change the value of a predicate 

788 

789 skip_predicates key [true|false] 

790 

791 Call without arguments to see the current values. 

792 

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

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

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

796 attribute. 

797 """ 

798 if not args.strip(): 

799 print("current predicates:") 

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

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

802 return 

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

804 if len(type_value) != 2: 

805 print( 

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

807 ) 

808 return 

809 

810 type_, value = type_value 

811 if type_ not in self._predicates: 

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

813 return 

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

815 print( 

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

817 ) 

818 return 

819 

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

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

822 print( 

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

824 ) 

825 

826 def do_skip_hidden(self, arg): 

827 """ 

828 Change whether or not we should skip frames with the 

829 __tracebackhide__ attribute. 

830 """ 

831 if not arg.strip(): 

832 print( 

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

834 ) 

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

836 self.skip_hidden = True 

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

838 self.skip_hidden = False 

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

840 print( 

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

842 ) 

843 

844 def do_list(self, arg): 

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

846 self.lastcmd = "list" 

847 last = None 

848 if arg and arg != ".": 

849 try: 

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

851 if type(x) == type(()): 

852 first, last = x 

853 first = int(first) 

854 last = int(last) 

855 if last < first: 

856 # Assume it's a count 

857 last = first + last 

858 else: 

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

860 except: 

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

862 return 

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

864 assert self.curframe is not None 

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

866 else: 

867 first = self.lineno + 1 

868 if last is None: 

869 last = first + 10 

870 assert self.curframe is not None 

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

872 

873 lineno = first 

874 filename = self.curframe.f_code.co_filename 

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

876 

877 do_l = do_list 

878 

879 def getsourcelines(self, obj): 

880 lines, lineno = inspect.findsource(obj) 

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

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

883 return lines, 1 

884 elif inspect.ismodule(obj): 

885 return lines, 1 

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

887 

888 def do_longlist(self, arg): 

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

890 

891 Shows more lines than 'list' does. 

892 """ 

893 self.lastcmd = "longlist" 

894 try: 

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

896 except OSError as err: 

897 self.error(str(err)) 

898 return 

899 last = lineno + len(lines) 

900 assert self.curframe is not None 

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

902 

903 do_ll = do_longlist 

904 

905 def do_debug(self, arg): 

906 """debug code 

907 Enter a recursive debugger that steps through the code 

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

909 executed in the current environment). 

910 """ 

911 trace_function = sys.gettrace() 

912 sys.settrace(None) 

913 assert self.curframe is not None 

914 globals = self.curframe.f_globals 

915 locals = self.curframe_locals 

916 p = self.__class__( 

917 completekey=self.completekey, stdin=self.stdin, stdout=self.stdout 

918 ) 

919 p.use_rawinput = self.use_rawinput 

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

921 self.message("ENTERING RECURSIVE DEBUGGER") 

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

923 self.message("LEAVING RECURSIVE DEBUGGER") 

924 sys.settrace(trace_function) 

925 self.lastcmd = p.lastcmd 

926 

927 def do_pdef(self, arg): 

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

929 

930 The debugger interface to %pdef""" 

931 assert self.curframe is not None 

932 namespaces = [ 

933 ("Locals", self.curframe_locals), 

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

935 ] 

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

937 

938 def do_pdoc(self, arg): 

939 """Print the docstring for an object. 

940 

941 The debugger interface to %pdoc.""" 

942 assert self.curframe is not None 

943 namespaces = [ 

944 ("Locals", self.curframe_locals), 

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

946 ] 

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

948 

949 def do_pfile(self, arg): 

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

951 

952 The debugger interface to %pfile. 

953 """ 

954 assert self.curframe is not None 

955 namespaces = [ 

956 ("Locals", self.curframe_locals), 

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

958 ] 

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

960 

961 def do_pinfo(self, arg): 

962 """Provide detailed information about an object. 

963 

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

965 assert self.curframe is not None 

966 namespaces = [ 

967 ("Locals", self.curframe_locals), 

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

969 ] 

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

971 

972 def do_pinfo2(self, arg): 

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

974 

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

976 assert self.curframe is not None 

977 namespaces = [ 

978 ("Locals", self.curframe_locals), 

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

980 ] 

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

982 

983 def do_psource(self, arg): 

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

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("psource")(arg, namespaces=namespaces) 

991 

992 def do_where(self, arg: str): 

993 """w(here) 

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

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

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

997 

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

999 print""" 

1000 if arg: 

1001 try: 

1002 context = int(arg) 

1003 except ValueError as err: 

1004 self.error(str(err)) 

1005 return 

1006 self.print_stack_trace(context) 

1007 else: 

1008 self.print_stack_trace() 

1009 

1010 do_w = do_where 

1011 

1012 def break_anywhere(self, frame): 

1013 """ 

1014 _stop_in_decorator_internals is overly restrictive, as we may still want 

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

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

1017 stop at any point inside the function 

1018 

1019 """ 

1020 

1021 sup = super().break_anywhere(frame) 

1022 if sup: 

1023 return sup 

1024 if self._predicates["debuggerskip"]: 

1025 if DEBUGGERSKIP in frame.f_code.co_varnames: 

1026 return True 

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

1028 return True 

1029 return False 

1030 

1031 def _is_in_decorator_internal_and_should_skip(self, frame): 

1032 """ 

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

1034 

1035 """ 

1036 # if we are disabled don't skip 

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

1038 return False 

1039 

1040 return self._cachable_skip(frame) 

1041 

1042 @lru_cache(1024) 

1043 def _cached_one_parent_frame_debuggerskip(self, frame): 

1044 """ 

1045 Cache looking up for DEBUGGERSKIP on parent frame. 

1046 

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

1048 one does have a debugger skip. 

1049 

1050 This is likely to introduce fake positive though. 

1051 """ 

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

1053 frame = frame.f_back 

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

1055 return True 

1056 return None 

1057 

1058 @lru_cache(1024) 

1059 def _cachable_skip(self, frame): 

1060 # if frame is tagged, skip by default. 

1061 if DEBUGGERSKIP in frame.f_code.co_varnames: 

1062 return True 

1063 

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

1065 if self._cached_one_parent_frame_debuggerskip(frame): 

1066 return True 

1067 

1068 return False 

1069 

1070 def stop_here(self, frame): 

1071 if self._is_in_decorator_internal_and_should_skip(frame) is True: 

1072 return False 

1073 

1074 hidden = False 

1075 if self.skip_hidden: 

1076 hidden = self._hidden_predicate(frame) 

1077 if hidden: 

1078 if self.report_skipped: 

1079 print( 

1080 self.theme.format( 

1081 [ 

1082 ( 

1083 Token.ExcName, 

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

1085 ), 

1086 (Token, "\n"), 

1087 ] 

1088 ) 

1089 ) 

1090 return super().stop_here(frame) 

1091 

1092 def do_up(self, arg): 

1093 """u(p) [count] 

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

1095 stack trace (to an older frame). 

1096 

1097 Will skip hidden frames. 

1098 """ 

1099 # modified version of upstream that skips 

1100 # frames with __tracebackhide__ 

1101 if self.curindex == 0: 

1102 self.error("Oldest frame") 

1103 return 

1104 try: 

1105 count = int(arg or 1) 

1106 except ValueError: 

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

1108 return 

1109 skipped = 0 

1110 if count < 0: 

1111 _newframe = 0 

1112 else: 

1113 counter = 0 

1114 hidden_frames = self.hidden_frames(self.stack) 

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

1116 if hidden_frames[i] and self.skip_hidden: 

1117 skipped += 1 

1118 continue 

1119 counter += 1 

1120 if counter >= count: 

1121 break 

1122 else: 

1123 # if no break occurred. 

1124 self.error( 

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

1126 ) 

1127 return 

1128 

1129 _newframe = i 

1130 self._select_frame(_newframe) 

1131 if skipped: 

1132 print( 

1133 self.theme.format( 

1134 [ 

1135 ( 

1136 Token.ExcName, 

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

1138 ), 

1139 (Token, "\n"), 

1140 ] 

1141 ) 

1142 ) 

1143 

1144 def do_down(self, arg): 

1145 """d(own) [count] 

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

1147 stack trace (to a newer frame). 

1148 

1149 Will skip hidden frames. 

1150 """ 

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

1152 self.error("Newest frame") 

1153 return 

1154 try: 

1155 count = int(arg or 1) 

1156 except ValueError: 

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

1158 return 

1159 if count < 0: 

1160 _newframe = len(self.stack) - 1 

1161 else: 

1162 counter = 0 

1163 skipped = 0 

1164 hidden_frames = self.hidden_frames(self.stack) 

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

1166 if hidden_frames[i] and self.skip_hidden: 

1167 skipped += 1 

1168 continue 

1169 counter += 1 

1170 if counter >= count: 

1171 break 

1172 else: 

1173 self.error( 

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

1175 ) 

1176 return 

1177 

1178 if skipped: 

1179 print( 

1180 self.theme.format( 

1181 [ 

1182 ( 

1183 Token.ExcName, 

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

1185 ), 

1186 (Token, "\n"), 

1187 ] 

1188 ) 

1189 ) 

1190 _newframe = i 

1191 

1192 self._select_frame(_newframe) 

1193 

1194 do_d = do_down 

1195 do_u = do_up 

1196 

1197 def do_context(self, context: str): 

1198 """context number_of_lines 

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

1200 stacktrace information. 

1201 """ 

1202 try: 

1203 new_context = int(context) 

1204 if new_context <= 0: 

1205 raise ValueError() 

1206 self.context = new_context 

1207 except ValueError: 

1208 self.error( 

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

1210 ) 

1211 

1212 

1213class InterruptiblePdb(Pdb): 

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

1215 

1216 def cmdloop(self, intro=None): 

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

1218 try: 

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

1220 except KeyboardInterrupt: 

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

1222 self.do_quit("") 

1223 sys.settrace(None) 

1224 self.quitting = False 

1225 raise 

1226 

1227 def _cmdloop(self): 

1228 while True: 

1229 try: 

1230 # keyboard interrupts allow for an easy way to cancel 

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

1232 self.allow_kbdint = True 

1233 self.cmdloop() 

1234 self.allow_kbdint = False 

1235 break 

1236 except KeyboardInterrupt: 

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

1238 raise 

1239 

1240 

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

1242 """ 

1243 Start debugging from `frame`. 

1244 

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

1246 """ 

1247 pdb = Pdb() 

1248 if header is not None: 

1249 pdb.message(header) 

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