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

552 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.utils import PyColorize 

137from IPython.utils.PyColorize import TokenStream 

138 

139from typing import TYPE_CHECKING 

140from types import FrameType 

141 

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

143from pdb import Pdb as OldPdb 

144from pygments.token import Token 

145 

146if TYPE_CHECKING: 

147 # otherwise circular import 

148 from IPython.core.interactiveshell import InteractiveShell 

149 

150# skip module docstests 

151__skip_doctest__ = True 

152 

153prompt = "ipdb> " 

154 

155 

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

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

158# the Tracer constructor. 

159 

160DEBUGGERSKIP = "__debuggerskip__" 

161 

162 

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

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

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

166 

167 

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

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

170 

171 All other exceptions are processed using the `excepthook` 

172 parameter. 

173 """ 

174 raise ValueError( 

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

176 ) 

177 

178 

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

180 

181 

182def strip_indentation(multiline_string): 

183 return RGX_EXTRA_INDENT.sub("", multiline_string) 

184 

185 

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

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

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

189 Adapted from from a comp.lang.python posting 

190 by Duncan Booth.""" 

191 

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

193 return new_fn(*args, **kw) 

194 

195 if old_fn.__doc__: 

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

197 return wrapper 

198 

199 

200class Pdb(OldPdb): 

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

202 

203 for a standalone version that uses prompt_toolkit, see 

204 `IPython.terminal.debugger.TerminalPdb` and 

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

206 

207 

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

209 See the `skip_predicates` commands. 

210 

211 """ 

212 

213 shell: InteractiveShell 

214 _theme_name: str 

215 _context: int 

216 

217 _chained_exceptions: tuple[Exception, ...] 

218 _chained_exception_index: int 

219 

220 if CHAIN_EXCEPTIONS: 

221 MAX_CHAINED_EXCEPTION_DEPTH = 999 

222 

223 default_predicates = { 

224 "tbhide": True, 

225 "readonly": False, 

226 "ipython_internal": True, 

227 "debuggerskip": True, 

228 } 

229 

230 def __init__( 

231 self, 

232 completekey=None, 

233 stdin=None, 

234 stdout=None, 

235 context: int | None | str = 5, 

236 **kwargs, 

237 ): 

238 """Create a new IPython debugger. 

239 

240 Parameters 

241 ---------- 

242 completekey : default None 

243 Passed to pdb.Pdb. 

244 stdin : default None 

245 Passed to pdb.Pdb. 

246 stdout : default None 

247 Passed to pdb.Pdb. 

248 context : int 

249 Number of lines of source code context to show when 

250 displaying stacktrace information. 

251 **kwargs 

252 Passed to pdb.Pdb. 

253 

254 Notes 

255 ----- 

256 The possibilities are python version dependent, see the python 

257 docs for more info. 

258 """ 

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

260 if context is None: 

261 context = 5 

262 if isinstance(context, str): 

263 context = int(context) 

264 self.context = context 

265 

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

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

268 

269 # IPython changes... 

270 self.shell = get_ipython() 

271 

272 if self.shell is None: 

273 save_main = sys.modules["__main__"] 

274 # No IPython instance running, we must create one 

275 from IPython.terminal.interactiveshell import TerminalInteractiveShell 

276 

277 self.shell = TerminalInteractiveShell.instance() 

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

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

280 sys.modules["__main__"] = save_main 

281 

282 self.aliases = {} 

283 

284 theme_name = self.shell.colors 

285 assert isinstance(theme_name, str) 

286 assert theme_name.lower() == theme_name 

287 

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

289 # debugging. 

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

291 self.set_theme_name(theme_name) 

292 

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

294 self.prompt = prompt 

295 self.skip_hidden = True 

296 self.report_skipped = True 

297 

298 # list of predicates we use to skip frames 

299 self._predicates = self.default_predicates 

300 

301 if CHAIN_EXCEPTIONS: 

302 self._chained_exceptions = tuple() 

303 self._chained_exception_index = 0 

304 

305 @property 

306 def context(self) -> int: 

307 return self._context 

308 

309 @context.setter 

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

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

312 if not isinstance(value, int): 

313 value = int(value) 

314 assert isinstance(value, int) 

315 assert value >= 0 

316 self._context = value 

317 

318 def set_theme_name(self, name): 

319 assert name.lower() == name 

320 assert isinstance(name, str) 

321 self._theme_name = name 

322 self.parser.theme_name = name 

323 

324 @property 

325 def theme(self): 

326 return PyColorize.theme_table[self._theme_name] 

327 

328 # 

329 def set_colors(self, scheme): 

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

331 warnings.warn( 

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

333 DeprecationWarning, 

334 stacklevel=2, 

335 ) 

336 assert scheme == scheme.lower() 

337 self._theme_name = scheme.lower() 

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

339 

340 def set_trace(self, frame=None): 

341 if frame is None: 

342 frame = sys._getframe().f_back 

343 self.initial_frame = frame 

344 return super().set_trace(frame) 

345 

346 def _hidden_predicate(self, frame): 

347 """ 

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

349 """ 

350 

351 if self._predicates["readonly"]: 

352 fname = frame.f_code.co_filename 

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

354 # function would otherwise appear as RO. 

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

356 return True 

357 

358 if self._predicates["tbhide"]: 

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

360 return False 

361 frame_locals = self._get_frame_locals(frame) 

362 if "__tracebackhide__" not in frame_locals: 

363 return False 

364 return frame_locals["__tracebackhide__"] 

365 return False 

366 

367 def hidden_frames(self, stack): 

368 """ 

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

370 

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

372 """ 

373 # The f_locals dictionary is updated from the actual frame 

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

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

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

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

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

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

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

381 return ip_hide 

382 

383 if CHAIN_EXCEPTIONS: 

384 

385 def _get_tb_and_exceptions(self, tb_or_exc): 

386 """ 

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

388 and current traceback to inspect. 

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

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

391 can jump to with do_exceptions. 

392 """ 

393 _exceptions = [] 

394 if isinstance(tb_or_exc, BaseException): 

395 traceback, current = tb_or_exc.__traceback__, tb_or_exc 

396 

397 while current is not None: 

398 if current in _exceptions: 

399 break 

400 _exceptions.append(current) 

401 if current.__cause__ is not None: 

402 current = current.__cause__ 

403 elif ( 

404 current.__context__ is not None 

405 and not current.__suppress_context__ 

406 ): 

407 current = current.__context__ 

408 

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

410 self.message( 

411 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}" 

412 " chained exceptions found, not all exceptions" 

413 "will be browsable with `exceptions`." 

414 ) 

415 break 

416 else: 

417 traceback = tb_or_exc 

418 return tuple(reversed(_exceptions)), traceback 

419 

420 @contextmanager 

421 def _hold_exceptions(self, exceptions): 

422 """ 

423 Context manager to ensure proper cleaning of exceptions references 

424 When given a chained exception instead of a traceback, 

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

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

427 """ 

428 try: 

429 self._chained_exceptions = exceptions 

430 self._chained_exception_index = len(exceptions) - 1 

431 yield 

432 finally: 

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

434 # be cleared on exception change 

435 self._chained_exceptions = tuple() 

436 self._chained_exception_index = 0 

437 

438 def do_exceptions(self, arg): 

439 """exceptions [number] 

440 List or change current exception in an exception chain. 

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

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

443 with an arrow. 

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

445 """ 

446 if not self._chained_exceptions: 

447 self.message( 

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

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

450 " object rather than a traceback." 

451 ) 

452 return 

453 if not arg: 

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

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

456 rep = repr(exc) 

457 if len(rep) > 80: 

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

459 indicator = ( 

460 " -" 

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

462 else f"{ix:>3}" 

463 ) 

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

465 else: 

466 try: 

467 number = int(arg) 

468 except ValueError: 

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

470 return 

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

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

473 self.error( 

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

475 ) 

476 return 

477 

478 self._chained_exception_index = number 

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

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

481 else: 

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

483 

484 def interaction(self, frame, tb_or_exc): 

485 try: 

486 if CHAIN_EXCEPTIONS: 

487 # this context manager is part of interaction in 3.13 

488 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc) 

489 if isinstance(tb_or_exc, BaseException): 

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

491 with self._hold_exceptions(_chained_exceptions): 

492 OldPdb.interaction(self, frame, tb) 

493 else: 

494 OldPdb.interaction(self, frame, tb_or_exc) 

495 

496 except KeyboardInterrupt: 

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

498 

499 def precmd(self, line): 

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

501 

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

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

504 elif line.endswith("?"): 

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

506 

507 line = super().precmd(line) 

508 

509 return line 

510 

511 def new_do_quit(self, arg): 

512 return OldPdb.do_quit(self, arg) 

513 

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

515 

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

517 if context is None: 

518 context = self.context 

519 try: 

520 skipped = 0 

521 to_print = "" 

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

523 if hidden and self.skip_hidden: 

524 skipped += 1 

525 continue 

526 if skipped: 

527 to_print += self.theme.format( 

528 [ 

529 ( 

530 Token.ExcName, 

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

532 ), 

533 (Token, "\n"), 

534 ] 

535 ) 

536 

537 skipped = 0 

538 to_print += self.format_stack_entry(frame_lineno) 

539 if skipped: 

540 to_print += self.theme.format( 

541 [ 

542 ( 

543 Token.ExcName, 

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

545 ), 

546 (Token, "\n"), 

547 ] 

548 ) 

549 print(to_print, file=self.stdout) 

550 except KeyboardInterrupt: 

551 pass 

552 

553 def print_stack_entry( 

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

555 ) -> None: 

556 """ 

557 Overwrite print_stack_entry from superclass (PDB) 

558 """ 

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

560 

561 frame, lineno = frame_lineno 

562 filename = frame.f_code.co_filename 

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

564 

565 def _get_frame_locals(self, frame): 

566 """ " 

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

568 that or the following can happen 

569 

570 ipdb> foo 

571 "old" 

572 ipdb> foo = "new" 

573 ipdb> foo 

574 "new" 

575 ipdb> where 

576 ipdb> foo 

577 "old" 

578 

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

580 

581 """ 

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

583 return self.curframe_locals 

584 else: 

585 return frame.f_locals 

586 

587 def format_stack_entry( 

588 self, 

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

590 lprefix: str = ": ", 

591 ) -> str: 

592 """ 

593 overwrite from super class so must -> str 

594 """ 

595 context = self.context 

596 try: 

597 context = int(context) 

598 if context <= 0: 

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

600 except (TypeError, ValueError): 

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

602 

603 import reprlib 

604 

605 ret_tok = [] 

606 

607 frame, lineno = frame_lineno 

608 

609 return_value = "" 

610 loc_frame = self._get_frame_locals(frame) 

611 if "__return__" in loc_frame: 

612 rv = loc_frame["__return__"] 

613 # return_value += '->' 

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

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

616 

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

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

619 link_tok = (Token.FilenameEm, filename) 

620 

621 if frame.f_code.co_name: 

622 func = frame.f_code.co_name 

623 else: 

624 func = "<lambda>" 

625 

626 call_toks = [] 

627 if func != "?": 

628 if "__args__" in loc_frame: 

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

630 else: 

631 args = "()" 

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

633 

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

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

636 if frame is self.curframe: 

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

638 else: 

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

640 

641 ret_tok.extend( 

642 [ 

643 link_tok, 

644 (Token, "("), 

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

646 (Token, ")"), 

647 *call_toks, 

648 (Token, "\n"), 

649 ] 

650 ) 

651 

652 start = lineno - 1 - context // 2 

653 lines = linecache.getlines(filename) 

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

655 start = max(start, 0) 

656 lines = lines[start : start + context] 

657 

658 for i, line in enumerate(lines): 

659 show_arrow = start + 1 + i == lineno 

660 

661 bp, num, colored_line = self.__line_content( 

662 filename, 

663 start + 1 + i, 

664 line, 

665 arrow=show_arrow, 

666 ) 

667 if frame is self.curframe or show_arrow: 

668 rlt = [ 

669 bp, 

670 (Token.LinenoEm, num), 

671 (Token, " "), 

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

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

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

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

676 (Token, colored_line), 

677 ] 

678 else: 

679 rlt = [ 

680 bp, 

681 (Token.Lineno, num), 

682 (Token, " "), 

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

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

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

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

687 (Token.Line, colored_line), 

688 ] 

689 ret_tok.extend(rlt) 

690 

691 return self.theme.format(ret_tok) 

692 

693 def __line_content( 

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

695 ): 

696 bp_mark = "" 

697 BreakpointToken = Token.Breakpoint 

698 

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

700 if not err: 

701 line = new_line 

702 

703 bp = None 

704 if lineno in self.get_file_breaks(filename): 

705 bps = self.get_breaks(filename, lineno) 

706 bp = bps[-1] 

707 

708 if bp: 

709 bp_mark = str(bp.number) 

710 BreakpointToken = Token.Breakpoint.Enabled 

711 if not bp.enabled: 

712 BreakpointToken = Token.Breakpoint.Disabled 

713 numbers_width = 7 

714 if arrow: 

715 # This is the line with the error 

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

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

718 else: 

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

720 bp_str = (BreakpointToken, bp_mark) 

721 return (bp_str, num, line) 

722 

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

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

725 command.""" 

726 toks: TokenStream = [] 

727 try: 

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

729 filename = self._exec_filename 

730 

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

732 line = linecache.getline(filename, lineno) 

733 if not line: 

734 break 

735 

736 assert self.curframe is not None 

737 

738 if lineno == self.curframe.f_lineno: 

739 bp, num, colored_line = self.__line_content( 

740 filename, lineno, line, arrow=True 

741 ) 

742 toks.extend( 

743 [ 

744 bp, 

745 (Token.LinenoEm, num), 

746 (Token, " "), 

747 # TODO: invsetigate Toke.Line here 

748 (Token, colored_line), 

749 ] 

750 ) 

751 else: 

752 bp, num, colored_line = self.__line_content( 

753 filename, lineno, line, arrow=False 

754 ) 

755 toks.extend( 

756 [ 

757 bp, 

758 (Token.Lineno, num), 

759 (Token, " "), 

760 (Token, colored_line), 

761 ] 

762 ) 

763 

764 self.lineno = lineno 

765 

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

767 

768 except KeyboardInterrupt: 

769 pass 

770 

771 def do_skip_predicates(self, args): 

772 """ 

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

774 

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

776 

777 To change the value of a predicate 

778 

779 skip_predicates key [true|false] 

780 

781 Call without arguments to see the current values. 

782 

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

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

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

786 attribute. 

787 """ 

788 if not args.strip(): 

789 print("current predicates:") 

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

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

792 return 

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

794 if len(type_value) != 2: 

795 print( 

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

797 ) 

798 return 

799 

800 type_, value = type_value 

801 if type_ not in self._predicates: 

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

803 return 

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

805 print( 

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

807 ) 

808 return 

809 

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

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

812 print( 

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

814 ) 

815 

816 def do_skip_hidden(self, arg): 

817 """ 

818 Change whether or not we should skip frames with the 

819 __tracebackhide__ attribute. 

820 """ 

821 if not arg.strip(): 

822 print( 

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

824 ) 

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

826 self.skip_hidden = True 

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

828 self.skip_hidden = False 

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

830 print( 

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

832 ) 

833 

834 def do_list(self, arg): 

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

836 self.lastcmd = "list" 

837 last = None 

838 if arg and arg != ".": 

839 try: 

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

841 if type(x) == type(()): 

842 first, last = x 

843 first = int(first) 

844 last = int(last) 

845 if last < first: 

846 # Assume it's a count 

847 last = first + last 

848 else: 

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

850 except: 

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

852 return 

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

854 assert self.curframe is not None 

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

856 else: 

857 first = self.lineno + 1 

858 if last is None: 

859 last = first + 10 

860 assert self.curframe is not None 

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

862 

863 lineno = first 

864 filename = self.curframe.f_code.co_filename 

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

866 

867 do_l = do_list 

868 

869 def getsourcelines(self, obj): 

870 lines, lineno = inspect.findsource(obj) 

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

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

873 return lines, 1 

874 elif inspect.ismodule(obj): 

875 return lines, 1 

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

877 

878 def do_longlist(self, arg): 

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

880 

881 Shows more lines than 'list' does. 

882 """ 

883 self.lastcmd = "longlist" 

884 try: 

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

886 except OSError as err: 

887 self.error(str(err)) 

888 return 

889 last = lineno + len(lines) 

890 assert self.curframe is not None 

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

892 

893 do_ll = do_longlist 

894 

895 def do_debug(self, arg): 

896 """debug code 

897 Enter a recursive debugger that steps through the code 

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

899 executed in the current environment). 

900 """ 

901 trace_function = sys.gettrace() 

902 sys.settrace(None) 

903 assert self.curframe is not None 

904 globals = self.curframe.f_globals 

905 locals = self.curframe_locals 

906 p = self.__class__( 

907 completekey=self.completekey, stdin=self.stdin, stdout=self.stdout 

908 ) 

909 p.use_rawinput = self.use_rawinput 

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

911 self.message("ENTERING RECURSIVE DEBUGGER") 

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

913 self.message("LEAVING RECURSIVE DEBUGGER") 

914 sys.settrace(trace_function) 

915 self.lastcmd = p.lastcmd 

916 

917 def do_pdef(self, arg): 

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

919 

920 The debugger interface to %pdef""" 

921 assert self.curframe is not None 

922 namespaces = [ 

923 ("Locals", self.curframe_locals), 

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

925 ] 

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

927 

928 def do_pdoc(self, arg): 

929 """Print the docstring for an object. 

930 

931 The debugger interface to %pdoc.""" 

932 assert self.curframe is not None 

933 namespaces = [ 

934 ("Locals", self.curframe_locals), 

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

936 ] 

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

938 

939 def do_pfile(self, arg): 

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

941 

942 The debugger interface to %pfile. 

943 """ 

944 assert self.curframe is not None 

945 namespaces = [ 

946 ("Locals", self.curframe_locals), 

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

948 ] 

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

950 

951 def do_pinfo(self, arg): 

952 """Provide detailed information about an object. 

953 

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

955 assert self.curframe is not None 

956 namespaces = [ 

957 ("Locals", self.curframe_locals), 

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

959 ] 

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

961 

962 def do_pinfo2(self, arg): 

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

964 

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

966 assert self.curframe is not None 

967 namespaces = [ 

968 ("Locals", self.curframe_locals), 

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

970 ] 

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

972 

973 def do_psource(self, arg): 

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

975 assert self.curframe is not None 

976 namespaces = [ 

977 ("Locals", self.curframe_locals), 

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

979 ] 

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

981 

982 def do_where(self, arg: str): 

983 """w(here) 

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

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

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

987 

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

989 print""" 

990 if arg: 

991 try: 

992 context = int(arg) 

993 except ValueError as err: 

994 self.error(str(err)) 

995 return 

996 self.print_stack_trace(context) 

997 else: 

998 self.print_stack_trace() 

999 

1000 do_w = do_where 

1001 

1002 def break_anywhere(self, frame): 

1003 """ 

1004 _stop_in_decorator_internals is overly restrictive, as we may still want 

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

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

1007 stop at any point inside the function 

1008 

1009 """ 

1010 

1011 sup = super().break_anywhere(frame) 

1012 if sup: 

1013 return sup 

1014 if self._predicates["debuggerskip"]: 

1015 if DEBUGGERSKIP in frame.f_code.co_varnames: 

1016 return True 

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

1018 return True 

1019 return False 

1020 

1021 def _is_in_decorator_internal_and_should_skip(self, frame): 

1022 """ 

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

1024 

1025 """ 

1026 # if we are disabled don't skip 

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

1028 return False 

1029 

1030 return self._cachable_skip(frame) 

1031 

1032 @lru_cache(1024) 

1033 def _cached_one_parent_frame_debuggerskip(self, frame): 

1034 """ 

1035 Cache looking up for DEBUGGERSKIP on parent frame. 

1036 

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

1038 one does have a debugger skip. 

1039 

1040 This is likely to introduce fake positive though. 

1041 """ 

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

1043 frame = frame.f_back 

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

1045 return True 

1046 return None 

1047 

1048 @lru_cache(1024) 

1049 def _cachable_skip(self, frame): 

1050 # if frame is tagged, skip by default. 

1051 if DEBUGGERSKIP in frame.f_code.co_varnames: 

1052 return True 

1053 

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

1055 if self._cached_one_parent_frame_debuggerskip(frame): 

1056 return True 

1057 

1058 return False 

1059 

1060 def stop_here(self, frame): 

1061 if self._is_in_decorator_internal_and_should_skip(frame) is True: 

1062 return False 

1063 

1064 hidden = False 

1065 if self.skip_hidden: 

1066 hidden = self._hidden_predicate(frame) 

1067 if hidden: 

1068 if self.report_skipped: 

1069 print( 

1070 self.theme.format( 

1071 [ 

1072 ( 

1073 Token.ExcName, 

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

1075 ), 

1076 (Token, "\n"), 

1077 ] 

1078 ) 

1079 ) 

1080 return super().stop_here(frame) 

1081 

1082 def do_up(self, arg): 

1083 """u(p) [count] 

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

1085 stack trace (to an older frame). 

1086 

1087 Will skip hidden frames. 

1088 """ 

1089 # modified version of upstream that skips 

1090 # frames with __tracebackhide__ 

1091 if self.curindex == 0: 

1092 self.error("Oldest frame") 

1093 return 

1094 try: 

1095 count = int(arg or 1) 

1096 except ValueError: 

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

1098 return 

1099 skipped = 0 

1100 if count < 0: 

1101 _newframe = 0 

1102 else: 

1103 counter = 0 

1104 hidden_frames = self.hidden_frames(self.stack) 

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

1106 if hidden_frames[i] and self.skip_hidden: 

1107 skipped += 1 

1108 continue 

1109 counter += 1 

1110 if counter >= count: 

1111 break 

1112 else: 

1113 # if no break occurred. 

1114 self.error( 

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

1116 ) 

1117 return 

1118 

1119 _newframe = i 

1120 self._select_frame(_newframe) 

1121 if skipped: 

1122 print( 

1123 self.theme.format( 

1124 [ 

1125 ( 

1126 Token.ExcName, 

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

1128 ), 

1129 (Token, "\n"), 

1130 ] 

1131 ) 

1132 ) 

1133 

1134 def do_down(self, arg): 

1135 """d(own) [count] 

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

1137 stack trace (to a newer frame). 

1138 

1139 Will skip hidden frames. 

1140 """ 

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

1142 self.error("Newest 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 if count < 0: 

1150 _newframe = len(self.stack) - 1 

1151 else: 

1152 counter = 0 

1153 skipped = 0 

1154 hidden_frames = self.hidden_frames(self.stack) 

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

1156 if hidden_frames[i] and self.skip_hidden: 

1157 skipped += 1 

1158 continue 

1159 counter += 1 

1160 if counter >= count: 

1161 break 

1162 else: 

1163 self.error( 

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

1165 ) 

1166 return 

1167 

1168 if skipped: 

1169 print( 

1170 self.theme.format( 

1171 [ 

1172 ( 

1173 Token.ExcName, 

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

1175 ), 

1176 (Token, "\n"), 

1177 ] 

1178 ) 

1179 ) 

1180 _newframe = i 

1181 

1182 self._select_frame(_newframe) 

1183 

1184 do_d = do_down 

1185 do_u = do_up 

1186 

1187 def do_context(self, context: str): 

1188 """context number_of_lines 

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

1190 stacktrace information. 

1191 """ 

1192 try: 

1193 new_context = int(context) 

1194 if new_context <= 0: 

1195 raise ValueError() 

1196 self.context = new_context 

1197 except ValueError: 

1198 self.error( 

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

1200 ) 

1201 

1202 

1203class InterruptiblePdb(Pdb): 

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

1205 

1206 def cmdloop(self, intro=None): 

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

1208 try: 

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

1210 except KeyboardInterrupt: 

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

1212 self.do_quit("") 

1213 sys.settrace(None) 

1214 self.quitting = False 

1215 raise 

1216 

1217 def _cmdloop(self): 

1218 while True: 

1219 try: 

1220 # keyboard interrupts allow for an easy way to cancel 

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

1222 self.allow_kbdint = True 

1223 self.cmdloop() 

1224 self.allow_kbdint = False 

1225 break 

1226 except KeyboardInterrupt: 

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

1228 raise 

1229 

1230 

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

1232 """ 

1233 Start debugging from `frame`. 

1234 

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

1236 """ 

1237 pdb = Pdb() 

1238 if header is not None: 

1239 pdb.message(header) 

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