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

612 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 assert new_line is not None 

732 line = new_line 

733 

734 bp = None 

735 if lineno in self.get_file_breaks(filename): 

736 bps = self.get_breaks(filename, lineno) 

737 bp = bps[-1] 

738 

739 if bp: 

740 bp_mark = str(bp.number) 

741 BreakpointToken = Token.Breakpoint.Enabled 

742 if not bp.enabled: 

743 BreakpointToken = Token.Breakpoint.Disabled 

744 numbers_width = 7 

745 if arrow: 

746 # This is the line with the error 

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

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

749 else: 

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

751 bp_str = (BreakpointToken, bp_mark) 

752 return (bp_str, num, line) 

753 

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

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

756 command.""" 

757 toks: TokenStream = [] 

758 try: 

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

760 filename = self._exec_filename 

761 

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

763 line = linecache.getline(filename, lineno) 

764 if not line: 

765 break 

766 

767 assert self.curframe is not None 

768 

769 if lineno == self.curframe.f_lineno: 

770 bp, num, colored_line = self.__line_content( 

771 filename, lineno, line, arrow=True 

772 ) 

773 toks.extend( 

774 [ 

775 bp, 

776 (Token.LinenoEm, num), 

777 (Token, " "), 

778 # TODO: investigate Token.Line here 

779 (Token, colored_line), 

780 ] 

781 ) 

782 else: 

783 bp, num, colored_line = self.__line_content( 

784 filename, lineno, line, arrow=False 

785 ) 

786 toks.extend( 

787 [ 

788 bp, 

789 (Token.Lineno, num), 

790 (Token, " "), 

791 (Token, colored_line), 

792 ] 

793 ) 

794 

795 self.lineno = lineno 

796 

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

798 

799 except KeyboardInterrupt: 

800 pass 

801 

802 def do_skip_predicates(self, args): 

803 """ 

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

805 

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

807 

808 To change the value of a predicate 

809 

810 skip_predicates key [true|false] 

811 

812 Call without arguments to see the current values. 

813 

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

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

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

817 attribute. 

818 """ 

819 if not args.strip(): 

820 print("current predicates:") 

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

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

823 return 

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

825 if len(type_value) != 2: 

826 print( 

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

828 ) 

829 return 

830 

831 type_, value = type_value 

832 if type_ not in self._predicates: 

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

834 return 

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

836 print( 

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

838 ) 

839 return 

840 

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

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

843 print( 

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

845 ) 

846 

847 def do_skip_hidden(self, arg): 

848 """ 

849 Change whether or not we should skip frames with the 

850 __tracebackhide__ attribute. 

851 """ 

852 if not arg.strip(): 

853 print( 

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

855 ) 

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

857 self.skip_hidden = True 

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

859 self.skip_hidden = False 

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

861 print( 

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

863 ) 

864 

865 def do_list(self, arg): 

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

867 self.lastcmd = "list" 

868 last = None 

869 if arg and arg != ".": 

870 try: 

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

872 if type(x) == type(()): 

873 first, last = x 

874 first = int(first) 

875 last = int(last) 

876 if last < first: 

877 # Assume it's a count 

878 last = first + last 

879 else: 

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

881 except: 

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

883 return 

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

885 assert self.curframe is not None 

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

887 else: 

888 first = self.lineno + 1 

889 if last is None: 

890 last = first + 10 

891 assert self.curframe is not None 

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

893 

894 lineno = first 

895 filename = self.curframe.f_code.co_filename 

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

897 

898 do_l = do_list 

899 

900 def getsourcelines(self, obj): 

901 lines, lineno = inspect.findsource(obj) 

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

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

904 return lines, 1 

905 elif inspect.ismodule(obj): 

906 return lines, 1 

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

908 

909 def do_longlist(self, arg): 

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

911 

912 Shows more lines than 'list' does. 

913 """ 

914 self.lastcmd = "longlist" 

915 try: 

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

917 except OSError as err: 

918 self.error(str(err)) 

919 return 

920 last = lineno + len(lines) 

921 assert self.curframe is not None 

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

923 

924 do_ll = do_longlist 

925 

926 def do_debug(self, arg): 

927 """debug code 

928 Enter a recursive debugger that steps through the code 

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

930 executed in the current environment). 

931 """ 

932 trace_function = sys.gettrace() 

933 sys.settrace(None) 

934 assert self.curframe is not None 

935 globals = self.curframe.f_globals 

936 locals = self.curframe_locals 

937 p = self.__class__( 

938 completekey=self.completekey, stdin=self.stdin, stdout=self.stdout 

939 ) 

940 p.use_rawinput = self.use_rawinput 

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

942 self.message("ENTERING RECURSIVE DEBUGGER") 

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

944 self.message("LEAVING RECURSIVE DEBUGGER") 

945 sys.settrace(trace_function) 

946 self.lastcmd = p.lastcmd 

947 

948 def do_pdef(self, arg): 

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

950 

951 The debugger interface to %pdef""" 

952 assert self.curframe is not None 

953 namespaces = [ 

954 ("Locals", self.curframe_locals), 

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

956 ] 

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

958 

959 def do_pdoc(self, arg): 

960 """Print the docstring for an object. 

961 

962 The debugger interface to %pdoc.""" 

963 assert self.curframe is not None 

964 namespaces = [ 

965 ("Locals", self.curframe_locals), 

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

967 ] 

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

969 

970 def do_pfile(self, arg): 

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

972 

973 The debugger interface to %pfile. 

974 """ 

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

981 

982 def do_pinfo(self, arg): 

983 """Provide detailed information about an object. 

984 

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

986 assert self.curframe is not None 

987 namespaces = [ 

988 ("Locals", self.curframe_locals), 

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

990 ] 

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

992 

993 def do_pinfo2(self, arg): 

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

995 

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

997 assert self.curframe is not None 

998 namespaces = [ 

999 ("Locals", self.curframe_locals), 

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

1001 ] 

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

1003 

1004 def do_psource(self, arg): 

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

1006 assert self.curframe is not None 

1007 namespaces = [ 

1008 ("Locals", self.curframe_locals), 

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

1010 ] 

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

1012 

1013 def do_where(self, arg: str): 

1014 """w(here) 

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

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

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

1018 

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

1020 print""" 

1021 if arg: 

1022 try: 

1023 context = int(arg) 

1024 except ValueError as err: 

1025 self.error(str(err)) 

1026 return 

1027 self.print_stack_trace(context) 

1028 else: 

1029 self.print_stack_trace() 

1030 

1031 do_w = do_where 

1032 

1033 def break_anywhere(self, frame): 

1034 """ 

1035 _stop_in_decorator_internals is overly restrictive, as we may still want 

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

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

1038 stop at any point inside the function 

1039 

1040 """ 

1041 

1042 sup = super().break_anywhere(frame) 

1043 if sup: 

1044 return sup 

1045 if self._predicates["debuggerskip"]: 

1046 if DEBUGGERSKIP in frame.f_code.co_varnames: 

1047 return True 

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

1049 return True 

1050 return False 

1051 

1052 def _is_in_decorator_internal_and_should_skip(self, frame): 

1053 """ 

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

1055 

1056 """ 

1057 # if we are disabled don't skip 

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

1059 return False 

1060 

1061 return self._cachable_skip(frame) 

1062 

1063 @lru_cache(1024) 

1064 def _cached_one_parent_frame_debuggerskip(self, frame): 

1065 """ 

1066 Cache looking up for DEBUGGERSKIP on parent frame. 

1067 

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

1069 one does have a debugger skip. 

1070 

1071 This is likely to introduce fake positive though. 

1072 """ 

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

1074 frame = frame.f_back 

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

1076 return True 

1077 return None 

1078 

1079 @lru_cache(1024) 

1080 def _cachable_skip(self, frame): 

1081 # if frame is tagged, skip by default. 

1082 if DEBUGGERSKIP in frame.f_code.co_varnames: 

1083 return True 

1084 

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

1086 if self._cached_one_parent_frame_debuggerskip(frame): 

1087 return True 

1088 

1089 return False 

1090 

1091 def stop_here(self, frame): 

1092 if self._is_in_decorator_internal_and_should_skip(frame) is True: 

1093 return False 

1094 

1095 hidden = False 

1096 if self.skip_hidden: 

1097 hidden = self._hidden_predicate(frame) 

1098 if hidden: 

1099 if self.report_skipped: 

1100 print( 

1101 self.theme.format( 

1102 [ 

1103 ( 

1104 Token.ExcName, 

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

1106 ), 

1107 (Token, "\n"), 

1108 ] 

1109 ) 

1110 ) 

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

1112 print( 

1113 self.theme.format( 

1114 [ 

1115 ( 

1116 Token.ExcName, 

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

1118 ), 

1119 (Token, "\n"), 

1120 ] 

1121 ) 

1122 ) 

1123 

1124 return False 

1125 

1126 return super().stop_here(frame) 

1127 

1128 def do_up(self, arg): 

1129 """u(p) [count] 

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

1131 stack trace (to an older frame). 

1132 

1133 Will skip hidden frames and ignored modules. 

1134 """ 

1135 # modified version of upstream that skips 

1136 # frames with __tracebackhide__ and ignored modules 

1137 if self.curindex == 0: 

1138 self.error("Oldest frame") 

1139 return 

1140 try: 

1141 count = int(arg or 1) 

1142 except ValueError: 

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

1144 return 

1145 

1146 hidden_skipped = 0 

1147 module_skipped = 0 

1148 

1149 if count < 0: 

1150 _newframe = 0 

1151 else: 

1152 counter = 0 

1153 hidden_frames = self.hidden_frames(self.stack) 

1154 

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

1156 should_skip_hidden = hidden_frames[i] and self.skip_hidden 

1157 should_skip_module = self.skip and self.is_skipped_module( 

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

1159 ) 

1160 

1161 if should_skip_hidden or should_skip_module: 

1162 if should_skip_hidden: 

1163 hidden_skipped += 1 

1164 if should_skip_module: 

1165 module_skipped += 1 

1166 continue 

1167 counter += 1 

1168 if counter >= count: 

1169 break 

1170 else: 

1171 # if no break occurred. 

1172 self.error( 

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

1174 ) 

1175 return 

1176 

1177 _newframe = i 

1178 self._select_frame(_newframe) 

1179 

1180 total_skipped = hidden_skipped + module_skipped 

1181 if total_skipped: 

1182 print( 

1183 self.theme.format( 

1184 [ 

1185 ( 

1186 Token.ExcName, 

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

1188 ), 

1189 (Token, "\n"), 

1190 ] 

1191 ) 

1192 ) 

1193 

1194 def do_down(self, arg): 

1195 """d(own) [count] 

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

1197 stack trace (to a newer frame). 

1198 

1199 Will skip hidden frames and ignored modules. 

1200 """ 

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

1202 self.error("Newest frame") 

1203 return 

1204 try: 

1205 count = int(arg or 1) 

1206 except ValueError: 

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

1208 return 

1209 if count < 0: 

1210 _newframe = len(self.stack) - 1 

1211 else: 

1212 counter = 0 

1213 hidden_skipped = 0 

1214 module_skipped = 0 

1215 hidden_frames = self.hidden_frames(self.stack) 

1216 

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

1218 should_skip_hidden = hidden_frames[i] and self.skip_hidden 

1219 should_skip_module = self.skip and self.is_skipped_module( 

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

1221 ) 

1222 

1223 if should_skip_hidden or should_skip_module: 

1224 if should_skip_hidden: 

1225 hidden_skipped += 1 

1226 if should_skip_module: 

1227 module_skipped += 1 

1228 continue 

1229 counter += 1 

1230 if counter >= count: 

1231 break 

1232 else: 

1233 self.error( 

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

1235 ) 

1236 return 

1237 

1238 total_skipped = hidden_skipped + module_skipped 

1239 if total_skipped: 

1240 print( 

1241 self.theme.format( 

1242 [ 

1243 ( 

1244 Token.ExcName, 

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

1246 ), 

1247 (Token, "\n"), 

1248 ] 

1249 ) 

1250 ) 

1251 _newframe = i 

1252 

1253 self._select_frame(_newframe) 

1254 

1255 do_d = do_down 

1256 do_u = do_up 

1257 

1258 def _show_ignored_modules(self): 

1259 """Display currently ignored modules.""" 

1260 if self.skip: 

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

1262 else: 

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

1264 

1265 def do_ignore_module(self, arg): 

1266 """ignore_module <module_name> 

1267 

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

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

1270 frames from that module. 

1271 

1272 Supports wildcard patterns using fnmatch syntax: 

1273 

1274 Usage: 

1275 ignore_module threading # Skip threading module frames 

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

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

1278 ignore_module # List currently ignored modules 

1279 """ 

1280 

1281 if self.skip is None: 

1282 self.skip = set() 

1283 

1284 module_name = arg.strip() 

1285 

1286 if not module_name: 

1287 self._show_ignored_modules() 

1288 return 

1289 

1290 self.skip.add(module_name) 

1291 

1292 def do_unignore_module(self, arg): 

1293 """unignore_module <module_name> 

1294 

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

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

1297 

1298 Usage: 

1299 unignore_module threading # Stop ignoring threading module frames 

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

1301 unignore_module # List currently ignored modules 

1302 """ 

1303 

1304 if self.skip is None: 

1305 self.skip = set() 

1306 

1307 module_name = arg.strip() 

1308 

1309 if not module_name: 

1310 self._show_ignored_modules() 

1311 return 

1312 

1313 try: 

1314 self.skip.remove(module_name) 

1315 except KeyError: 

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

1317 self._show_ignored_modules() 

1318 

1319 def do_context(self, context: str): 

1320 """context number_of_lines 

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

1322 stacktrace information. 

1323 """ 

1324 try: 

1325 new_context = int(context) 

1326 if new_context <= 0: 

1327 raise ValueError() 

1328 self.context = new_context 

1329 except ValueError: 

1330 self.error( 

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

1332 ) 

1333 

1334 

1335class InterruptiblePdb(Pdb): 

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

1337 

1338 def cmdloop(self, intro=None): 

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

1340 try: 

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

1342 except KeyboardInterrupt: 

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

1344 self.do_quit("") 

1345 sys.settrace(None) 

1346 self.quitting = False 

1347 raise 

1348 

1349 def _cmdloop(self): 

1350 while True: 

1351 try: 

1352 # keyboard interrupts allow for an easy way to cancel 

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

1354 self.allow_kbdint = True 

1355 self.cmdloop() 

1356 self.allow_kbdint = False 

1357 break 

1358 except KeyboardInterrupt: 

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

1360 raise 

1361 

1362 

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

1364 """ 

1365 Start debugging from `frame`. 

1366 

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

1368 """ 

1369 pdb = Pdb() 

1370 if header is not None: 

1371 pdb.message(header) 

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