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

634 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 *, 

247 mode: str | None = None, 

248 **kwargs, 

249 ): 

250 """Create a new IPython debugger. 

251 

252 Parameters 

253 ---------- 

254 completekey : default None 

255 Passed to pdb.Pdb. 

256 stdin : default None 

257 Passed to pdb.Pdb. 

258 stdout : default None 

259 Passed to pdb.Pdb. 

260 context : int 

261 Number of lines of source code context to show when 

262 displaying stacktrace information. 

263 mode : str, optional 

264 How the debugger was invoked, one of ``'inline'`` (used by the 

265 ``breakpoint()`` builtin), ``'cli'`` (used by the command line 

266 invocation) or ``None`` (backwards compatible behaviour). This 

267 argument was added to stdlib's ``pdb.Pdb`` in Python 3.14; it is 

268 accepted on every supported Python version here but only forwarded 

269 to the underlying ``pdb.Pdb`` when it is actually supported. 

270 **kwargs 

271 Passed to pdb.Pdb. 

272 

273 Notes 

274 ----- 

275 The possibilities are python version dependent, see the python 

276 docs for more info. 

277 """ 

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

279 if context is None: 

280 context = 5 

281 if isinstance(context, str): 

282 context = int(context) 

283 self.context = context 

284 

285 # The `mode` argument was added to `pdb.Pdb` in Python 3.14. We accept 

286 # it on every supported Python version so that callers written against 

287 # 3.14+ keep working, but only forward it to the underlying `pdb.Pdb` 

288 # when it understands it. 

289 if sys.version_info >= (3, 14): 

290 kwargs["mode"] = mode 

291 else: 

292 self.mode = mode 

293 

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

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

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

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

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

299 self.curframe = None 

300 

301 # IPython changes... 

302 self.shell = get_ipython() 

303 

304 if self.shell is None: 

305 save_main = sys.modules["__main__"] 

306 # No IPython instance running, we must create one 

307 from IPython.terminal.interactiveshell import TerminalInteractiveShell 

308 

309 self.shell = TerminalInteractiveShell.instance() 

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

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

312 sys.modules["__main__"] = save_main 

313 

314 self.aliases = {} 

315 

316 theme_name = self.shell.colors 

317 assert isinstance(theme_name, str) 

318 assert theme_name.lower() == theme_name 

319 

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

321 # debugging. 

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

323 self.set_theme_name(theme_name) 

324 

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

326 self.prompt = prompt 

327 self.skip_hidden = True 

328 self.report_skipped = True 

329 

330 # list of predicates we use to skip frames 

331 self._predicates = self.default_predicates 

332 

333 if CHAIN_EXCEPTIONS: 

334 self._chained_exceptions = tuple() 

335 self._chained_exception_index = 0 

336 

337 @property 

338 def context(self) -> int: 

339 return self._context 

340 

341 @context.setter 

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

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

344 if not isinstance(value, int): 

345 value = int(value) 

346 assert isinstance(value, int) 

347 assert value >= 0 

348 self._context = value 

349 

350 def set_theme_name(self, name): 

351 assert name.lower() == name 

352 assert isinstance(name, str) 

353 self._theme_name = name 

354 self.parser.theme_name = name 

355 

356 @property 

357 def theme(self): 

358 return PyColorize.theme_table[self._theme_name] 

359 

360 # 

361 def set_colors(self, scheme): 

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

363 warnings.warn( 

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

365 DeprecationWarning, 

366 stacklevel=2, 

367 ) 

368 assert scheme == scheme.lower() 

369 self._theme_name = scheme.lower() 

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

371 

372 def set_trace(self, frame=None, **kwargs): 

373 if frame is None: 

374 frame = sys._getframe().f_back 

375 self.initial_frame = frame 

376 return super().set_trace(frame, **kwargs) 

377 

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

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

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

381 stack.pop(0) 

382 pos -= 1 

383 return stack, pos 

384 

385 def _is_internal_frame(self, frame): 

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

387 filename = frame.f_code.co_filename 

388 

389 # Skip bdb.py runcall and internal operations 

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

391 func_name = frame.f_code.co_name 

392 # Skip internal bdb operations but allow breakpoint hits 

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

394 return True 

395 

396 return False 

397 

398 def _hidden_predicate(self, frame): 

399 """ 

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

401 """ 

402 

403 if self._predicates["readonly"]: 

404 fname = frame.f_code.co_filename 

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

406 # function would otherwise appear as RO. 

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

408 return True 

409 

410 if self._predicates["tbhide"]: 

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

412 return False 

413 frame_locals = self._get_frame_locals(frame) 

414 if "__tracebackhide__" not in frame_locals: 

415 return False 

416 return frame_locals["__tracebackhide__"] 

417 return False 

418 

419 def hidden_frames(self, stack): 

420 """ 

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

422 

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

424 """ 

425 # The f_locals dictionary is updated from the actual frame 

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

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

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

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

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

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

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

433 return ip_hide 

434 

435 if CHAIN_EXCEPTIONS: 

436 

437 def _get_tb_and_exceptions(self, tb_or_exc): 

438 """ 

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

440 and current traceback to inspect. 

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

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

443 can jump to with do_exceptions. 

444 """ 

445 _exceptions = [] 

446 if isinstance(tb_or_exc, BaseException): 

447 traceback, current = tb_or_exc.__traceback__, tb_or_exc 

448 

449 while current is not None: 

450 if current in _exceptions: 

451 break 

452 _exceptions.append(current) 

453 if current.__cause__ is not None: 

454 current = current.__cause__ 

455 elif ( 

456 current.__context__ is not None 

457 and not current.__suppress_context__ 

458 ): 

459 current = current.__context__ 

460 

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

462 self.message( 

463 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}" 

464 " chained exceptions found, not all exceptions" 

465 " will be browsable with `exceptions`." 

466 ) 

467 break 

468 else: 

469 traceback = tb_or_exc 

470 return tuple(reversed(_exceptions)), traceback 

471 

472 @contextmanager 

473 def _hold_exceptions(self, exceptions): 

474 """ 

475 Context manager to ensure proper cleaning of exceptions references 

476 When given a chained exception instead of a traceback, 

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

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

479 """ 

480 try: 

481 self._chained_exceptions = exceptions 

482 self._chained_exception_index = len(exceptions) - 1 

483 yield 

484 finally: 

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

486 # be cleared on exception change 

487 self._chained_exceptions = tuple() 

488 self._chained_exception_index = 0 

489 

490 def do_exceptions(self, arg): 

491 """exceptions [number] 

492 List or change current exception in an exception chain. 

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

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

495 with an arrow. 

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

497 ``exception`` can be used as an alias for this command. 

498 """ 

499 if not self._chained_exceptions: 

500 self.message( 

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

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

503 " object rather than a traceback." 

504 ) 

505 return 

506 if not arg: 

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

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

509 rep = repr(exc) 

510 if len(rep) > 80: 

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

512 indicator = ( 

513 " -" 

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

515 else f"{ix:>3}" 

516 ) 

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

518 else: 

519 try: 

520 number = int(arg) 

521 except ValueError: 

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

523 return 

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

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

526 self.error( 

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

528 ) 

529 return 

530 

531 self._chained_exception_index = number 

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

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

534 else: 

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

536 

537 def do_exception(self, arg): 

538 """exception [number] 

539 Alias for the ``exceptions`` command. 

540 """ 

541 return self.do_exceptions(arg) 

542 

543 def _cmdloop(self): 

544 # Override to bypass Python 3.15's _maybe_use_pyrepl_as_stdin(), which 

545 # sets use_rawinput=False and conflicts with IPython's own input handling. 

546 while True: 

547 try: 

548 self.allow_kbdint = True 

549 self.cmdloop() 

550 self.allow_kbdint = False 

551 break 

552 except KeyboardInterrupt: 

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

554 

555 def interaction(self, frame, tb_or_exc): 

556 try: 

557 if CHAIN_EXCEPTIONS: 

558 # this context manager is part of interaction in 3.13 

559 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc) 

560 if isinstance(tb_or_exc, BaseException): 

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

562 with self._hold_exceptions(_chained_exceptions): 

563 OldPdb.interaction(self, frame, tb) 

564 else: 

565 OldPdb.interaction(self, frame, tb_or_exc) 

566 

567 except KeyboardInterrupt: 

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

569 

570 def precmd(self, line): 

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

572 

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

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

575 elif line.endswith("?"): 

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

577 

578 line = super().precmd(line) 

579 

580 return line 

581 

582 def new_do_quit(self, arg): 

583 return OldPdb.do_quit(self, arg) 

584 

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

586 

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

588 if context is None: 

589 context = self.context 

590 try: 

591 skipped = 0 

592 to_print = "" 

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

594 if hidden and self.skip_hidden: 

595 skipped += 1 

596 continue 

597 if skipped: 

598 to_print += self.theme.format( 

599 [ 

600 ( 

601 Token.ExcName, 

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

603 ), 

604 (Token, "\n"), 

605 ] 

606 ) 

607 

608 skipped = 0 

609 to_print += self.format_stack_entry(frame_lineno) 

610 if skipped: 

611 to_print += self.theme.format( 

612 [ 

613 ( 

614 Token.ExcName, 

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

616 ), 

617 (Token, "\n"), 

618 ] 

619 ) 

620 print(to_print, file=self.stdout) 

621 except KeyboardInterrupt: 

622 pass 

623 

624 def print_stack_entry( 

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

626 ) -> None: 

627 """ 

628 Overwrite print_stack_entry from superclass (PDB) 

629 """ 

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

631 

632 frame, lineno = frame_lineno 

633 filename = frame.f_code.co_filename 

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

635 

636 def _pdbcmd_print_frame_status(self, arg): 

637 """Use print_stack_entry to print frames in Python 3.14+.""" 

638 if sys.version_info[:2] >= (3, 14): 

639 # This is the only line changed from the base class. 

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

641 

642 # Same as in 3.14 

643 self._validate_file_mtime() 

644 self._show_display() 

645 else: 

646 # 3.13 and 3.12 don't need any changes. 

647 super()._pdbcmd_print_frame_status(arg) # type: ignore[misc] 

648 

649 def _get_frame_locals(self, frame): 

650 """ " 

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

652 that or the following can happen 

653 

654 ipdb> foo 

655 "old" 

656 ipdb> foo = "new" 

657 ipdb> foo 

658 "new" 

659 ipdb> where 

660 ipdb> foo 

661 "old" 

662 

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

664 

665 """ 

666 if frame is self.curframe: 

667 return self.curframe_locals 

668 else: 

669 return frame.f_locals 

670 

671 def format_stack_entry( 

672 self, 

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

674 lprefix: str = ": ", 

675 ) -> str: 

676 """ 

677 overwrite from super class so must -> str 

678 """ 

679 context = self.context 

680 try: 

681 context = int(context) 

682 if context <= 0: 

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

684 except (TypeError, ValueError): 

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

686 

687 import reprlib 

688 

689 ret_tok = [] 

690 

691 frame, lineno = frame_lineno 

692 

693 return_value = "" 

694 loc_frame = self._get_frame_locals(frame) 

695 if "__return__" in loc_frame: 

696 rv = loc_frame["__return__"] 

697 # return_value += '->' 

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

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

700 

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

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

703 link_tok = (Token.FilenameEm, filename) 

704 

705 if frame.f_code.co_name: 

706 func = frame.f_code.co_name 

707 else: 

708 func = "<lambda>" 

709 

710 call_toks = [] 

711 if func != "?": 

712 if "__args__" in loc_frame: 

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

714 else: 

715 args = "()" 

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

717 

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

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

720 if frame is self.curframe: 

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

722 else: 

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

724 

725 ret_tok.extend( 

726 [ 

727 link_tok, 

728 (Token, "("), 

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

730 (Token, ")"), 

731 *call_toks, 

732 (Token, "\n"), 

733 ] 

734 ) 

735 

736 start = lineno - 1 - context // 2 

737 lines = linecache.getlines(filename) 

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

739 start = max(start, 0) 

740 lines = lines[start : start + context] 

741 

742 for i, line in enumerate(lines): 

743 show_arrow = start + 1 + i == lineno 

744 

745 bp, num, colored_line = self.__line_content( 

746 filename, 

747 start + 1 + i, 

748 line, 

749 arrow=show_arrow, 

750 ) 

751 if frame is self.curframe or show_arrow: 

752 rlt = [ 

753 bp, 

754 (Token.LinenoEm, num), 

755 (Token, " "), 

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

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

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

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

760 (Token, colored_line), 

761 ] 

762 else: 

763 rlt = [ 

764 bp, 

765 (Token.Lineno, num), 

766 (Token, " "), 

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

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

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

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

771 (Token.Line, colored_line), 

772 ] 

773 ret_tok.extend(rlt) 

774 

775 return self.theme.format(ret_tok) 

776 

777 def __line_content( 

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

779 ): 

780 bp_mark = "" 

781 BreakpointToken = Token.Breakpoint 

782 

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

784 if not err: 

785 assert new_line is not None 

786 line = new_line 

787 

788 bp = None 

789 if lineno in self.get_file_breaks(filename): 

790 bps = self.get_breaks(filename, lineno) 

791 bp = bps[-1] 

792 

793 if bp: 

794 bp_mark = str(bp.number) 

795 BreakpointToken = Token.Breakpoint.Enabled 

796 if not bp.enabled: 

797 BreakpointToken = Token.Breakpoint.Disabled 

798 numbers_width = 7 

799 if arrow: 

800 # This is the line with the error 

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

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

803 else: 

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

805 bp_str = (BreakpointToken, bp_mark) 

806 return (bp_str, num, line) 

807 

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

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

810 command.""" 

811 toks: TokenStream = [] 

812 try: 

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

814 filename = self._exec_filename 

815 

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

817 line = linecache.getline(filename, lineno) 

818 if not line: 

819 break 

820 

821 assert self.curframe is not None 

822 

823 if lineno == self.curframe.f_lineno: 

824 bp, num, colored_line = self.__line_content( 

825 filename, lineno, line, arrow=True 

826 ) 

827 toks.extend( 

828 [ 

829 bp, 

830 (Token.LinenoEm, num), 

831 (Token, " "), 

832 # TODO: investigate Token.Line here 

833 (Token, colored_line), 

834 ] 

835 ) 

836 else: 

837 bp, num, colored_line = self.__line_content( 

838 filename, lineno, line, arrow=False 

839 ) 

840 toks.extend( 

841 [ 

842 bp, 

843 (Token.Lineno, num), 

844 (Token, " "), 

845 (Token, colored_line), 

846 ] 

847 ) 

848 

849 self.lineno = lineno 

850 

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

852 

853 except KeyboardInterrupt: 

854 pass 

855 

856 def do_skip_predicates(self, args): 

857 """ 

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

859 

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

861 

862 To change the value of a predicate 

863 

864 skip_predicates key [true|false] 

865 

866 Call without arguments to see the current values. 

867 

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

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

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

871 attribute. 

872 """ 

873 if not args.strip(): 

874 print("current predicates:") 

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

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

877 return 

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

879 if len(type_value) != 2: 

880 print( 

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

882 ) 

883 return 

884 

885 type_, value = type_value 

886 if type_ not in self._predicates: 

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

888 return 

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

890 print( 

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

892 ) 

893 return 

894 

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

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

897 print( 

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

899 ) 

900 

901 def do_skip_hidden(self, arg): 

902 """ 

903 Change whether or not we should skip frames with the 

904 __tracebackhide__ attribute. 

905 """ 

906 if not arg.strip(): 

907 print( 

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

909 ) 

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

911 self.skip_hidden = True 

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

913 self.skip_hidden = False 

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

915 print( 

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

917 ) 

918 

919 def do_list(self, arg): 

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

921 self.lastcmd = "list" 

922 last = None 

923 if arg and arg != ".": 

924 try: 

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

926 if type(x) == type(()): 

927 first, last = x # type: ignore[misc] 

928 first = int(first) # type: ignore[call-overload] 

929 last = int(last) # type: ignore[call-overload] 

930 if last < first: 

931 # Assume it's a count 

932 last = first + last 

933 else: 

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

935 except: 

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

937 return 

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

939 assert self.curframe is not None 

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

941 else: 

942 first = self.lineno + 1 

943 if last is None: 

944 last = first + 10 

945 assert self.curframe is not None 

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

947 

948 lineno = first 

949 filename = self.curframe.f_code.co_filename 

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

951 

952 do_l = do_list 

953 

954 def getsourcelines(self, obj): 

955 lines, lineno = inspect.findsource(obj) 

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

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

958 return lines, 1 

959 elif inspect.ismodule(obj): 

960 return lines, 1 

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

962 

963 def do_longlist(self, arg): 

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

965 

966 Shows more lines than 'list' does. 

967 """ 

968 self.lastcmd = "longlist" 

969 try: 

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

971 except OSError as err: 

972 self.error(str(err)) 

973 return 

974 last = lineno + len(lines) 

975 assert self.curframe is not None 

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

977 

978 do_ll = do_longlist 

979 

980 def do_debug(self, arg): 

981 """debug code 

982 Enter a recursive debugger that steps through the code 

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

984 executed in the current environment). 

985 """ 

986 trace_function = sys.gettrace() 

987 sys.settrace(None) 

988 assert self.curframe is not None 

989 globals = self.curframe.f_globals 

990 locals = self.curframe_locals 

991 p = self.__class__( 

992 completekey=self.completekey, stdin=self.stdin, stdout=self.stdout 

993 ) 

994 p.use_rawinput = self.use_rawinput 

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

996 self.message("ENTERING RECURSIVE DEBUGGER") 

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

998 self.message("LEAVING RECURSIVE DEBUGGER") 

999 sys.settrace(trace_function) 

1000 self.lastcmd = p.lastcmd 

1001 

1002 def do_pdef(self, arg): 

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

1004 

1005 The debugger interface to %pdef""" 

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

1012 

1013 def do_pdoc(self, arg): 

1014 """Print the docstring for an object. 

1015 

1016 The debugger interface to %pdoc.""" 

1017 assert self.curframe is not None 

1018 namespaces = [ 

1019 ("Locals", self.curframe_locals), 

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

1021 ] 

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

1023 

1024 def do_pfile(self, arg): 

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

1026 

1027 The debugger interface to %pfile. 

1028 """ 

1029 assert self.curframe is not None 

1030 namespaces = [ 

1031 ("Locals", self.curframe_locals), 

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

1033 ] 

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

1035 

1036 def do_pinfo(self, arg): 

1037 """Provide detailed information about an object. 

1038 

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

1040 assert self.curframe is not None 

1041 namespaces = [ 

1042 ("Locals", self.curframe_locals), 

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

1044 ] 

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

1046 

1047 def do_pinfo2(self, arg): 

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

1049 

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

1051 assert self.curframe is not None 

1052 namespaces = [ 

1053 ("Locals", self.curframe_locals), 

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

1055 ] 

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

1057 

1058 def do_psource(self, arg): 

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

1060 assert self.curframe is not None 

1061 namespaces = [ 

1062 ("Locals", self.curframe_locals), 

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

1064 ] 

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

1066 

1067 def do_where(self, arg: str): 

1068 """w(here) 

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

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

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

1072 

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

1074 print""" 

1075 if arg: 

1076 try: 

1077 context = int(arg) 

1078 except ValueError as err: 

1079 self.error(str(err)) 

1080 return 

1081 self.print_stack_trace(context) 

1082 else: 

1083 self.print_stack_trace() 

1084 

1085 do_w = do_where 

1086 

1087 def break_anywhere(self, frame): 

1088 """ 

1089 _stop_in_decorator_internals is overly restrictive, as we may still want 

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

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

1092 stop at any point inside the function 

1093 

1094 """ 

1095 

1096 sup = super().break_anywhere(frame) 

1097 if sup: 

1098 return sup 

1099 if self._predicates["debuggerskip"]: 

1100 if DEBUGGERSKIP in frame.f_code.co_varnames: 

1101 return True 

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

1103 return True 

1104 return False 

1105 

1106 def _is_in_decorator_internal_and_should_skip(self, frame): 

1107 """ 

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

1109 

1110 """ 

1111 # if we are disabled don't skip 

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

1113 return False 

1114 

1115 return self._cachable_skip(frame) 

1116 

1117 @lru_cache(1024) 

1118 def _cached_one_parent_frame_debuggerskip(self, frame): 

1119 """ 

1120 Cache looking up for DEBUGGERSKIP on parent frame. 

1121 

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

1123 one does have a debugger skip. 

1124 

1125 This is likely to introduce fake positive though. 

1126 """ 

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

1128 frame = frame.f_back 

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

1130 return True 

1131 return None 

1132 

1133 @lru_cache(1024) 

1134 def _cachable_skip(self, frame): 

1135 # if frame is tagged, skip by default. 

1136 if DEBUGGERSKIP in frame.f_code.co_varnames: 

1137 return True 

1138 

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

1140 if self._cached_one_parent_frame_debuggerskip(frame): 

1141 return True 

1142 

1143 return False 

1144 

1145 def stop_here(self, frame): 

1146 if self._is_in_decorator_internal_and_should_skip(frame) is True: 

1147 return False 

1148 

1149 hidden = False 

1150 if self.skip_hidden: 

1151 hidden = self._hidden_predicate(frame) 

1152 if hidden: 

1153 if self.report_skipped: 

1154 print( 

1155 self.theme.format( 

1156 [ 

1157 ( 

1158 Token.ExcName, 

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

1160 ), 

1161 (Token, "\n"), 

1162 ] 

1163 ) 

1164 ) 

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

1166 print( 

1167 self.theme.format( 

1168 [ 

1169 ( 

1170 Token.ExcName, 

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

1172 ), 

1173 (Token, "\n"), 

1174 ] 

1175 ) 

1176 ) 

1177 

1178 return False 

1179 

1180 return super().stop_here(frame) 

1181 

1182 def do_up(self, arg): 

1183 """u(p) [count] 

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

1185 stack trace (to an older frame). 

1186 

1187 Will skip hidden frames and ignored modules. 

1188 """ 

1189 # modified version of upstream that skips 

1190 # frames with __tracebackhide__ and ignored modules 

1191 if self.curindex == 0: 

1192 self.error("Oldest frame") 

1193 return 

1194 try: 

1195 count = int(arg or 1) 

1196 except ValueError: 

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

1198 return 

1199 

1200 hidden_skipped = 0 

1201 module_skipped = 0 

1202 

1203 if count < 0: 

1204 _newframe = 0 

1205 else: 

1206 counter = 0 

1207 hidden_frames = self.hidden_frames(self.stack) 

1208 

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

1210 should_skip_hidden = hidden_frames[i] and self.skip_hidden 

1211 should_skip_module = self.skip and self.is_skipped_module( 

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

1213 ) 

1214 

1215 if should_skip_hidden or should_skip_module: 

1216 if should_skip_hidden: 

1217 hidden_skipped += 1 

1218 if should_skip_module: 

1219 module_skipped += 1 

1220 continue 

1221 counter += 1 

1222 if counter >= count: 

1223 break 

1224 else: 

1225 # if no break occurred. 

1226 self.error( 

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

1228 ) 

1229 return 

1230 

1231 _newframe = i 

1232 self._select_frame(_newframe) 

1233 

1234 total_skipped = hidden_skipped + module_skipped 

1235 if total_skipped: 

1236 print( 

1237 self.theme.format( 

1238 [ 

1239 ( 

1240 Token.ExcName, 

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

1242 ), 

1243 (Token, "\n"), 

1244 ] 

1245 ) 

1246 ) 

1247 

1248 def do_down(self, arg): 

1249 """d(own) [count] 

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

1251 stack trace (to a newer frame). 

1252 

1253 Will skip hidden frames and ignored modules. 

1254 """ 

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

1256 self.error("Newest frame") 

1257 return 

1258 try: 

1259 count = int(arg or 1) 

1260 except ValueError: 

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

1262 return 

1263 if count < 0: 

1264 _newframe = len(self.stack) - 1 

1265 else: 

1266 counter = 0 

1267 hidden_skipped = 0 

1268 module_skipped = 0 

1269 hidden_frames = self.hidden_frames(self.stack) 

1270 

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

1272 should_skip_hidden = hidden_frames[i] and self.skip_hidden 

1273 should_skip_module = self.skip and self.is_skipped_module( 

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

1275 ) 

1276 

1277 if should_skip_hidden or should_skip_module: 

1278 if should_skip_hidden: 

1279 hidden_skipped += 1 

1280 if should_skip_module: 

1281 module_skipped += 1 

1282 continue 

1283 counter += 1 

1284 if counter >= count: 

1285 break 

1286 else: 

1287 self.error( 

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

1289 ) 

1290 return 

1291 

1292 total_skipped = hidden_skipped + module_skipped 

1293 if total_skipped: 

1294 print( 

1295 self.theme.format( 

1296 [ 

1297 ( 

1298 Token.ExcName, 

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

1300 ), 

1301 (Token, "\n"), 

1302 ] 

1303 ) 

1304 ) 

1305 _newframe = i 

1306 

1307 self._select_frame(_newframe) 

1308 

1309 do_d = do_down 

1310 do_u = do_up 

1311 

1312 def _show_ignored_modules(self): 

1313 """Display currently ignored modules.""" 

1314 if self.skip: 

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

1316 else: 

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

1318 

1319 def do_ignore_module(self, arg): 

1320 """ignore_module <module_name> 

1321 

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

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

1324 frames from that module. 

1325 

1326 Supports wildcard patterns using fnmatch syntax: 

1327 

1328 Usage: 

1329 ignore_module threading # Skip threading module frames 

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

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

1332 ignore_module # List currently ignored modules 

1333 """ 

1334 

1335 if self.skip is None: 

1336 self.skip = set() 

1337 

1338 module_name = arg.strip() 

1339 

1340 if not module_name: 

1341 self._show_ignored_modules() 

1342 return 

1343 

1344 self.skip.add(module_name) 

1345 

1346 def do_unignore_module(self, arg): 

1347 """unignore_module <module_name> 

1348 

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

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

1351 

1352 Usage: 

1353 unignore_module threading # Stop ignoring threading module frames 

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

1355 unignore_module # List currently ignored modules 

1356 """ 

1357 

1358 if self.skip is None: 

1359 self.skip = set() 

1360 

1361 module_name = arg.strip() 

1362 

1363 if not module_name: 

1364 self._show_ignored_modules() 

1365 return 

1366 

1367 try: 

1368 self.skip.remove(module_name) 

1369 except KeyError: 

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

1371 self._show_ignored_modules() 

1372 

1373 def do_context(self, context: str): 

1374 """context number_of_lines 

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

1376 stacktrace information. 

1377 """ 

1378 try: 

1379 new_context = int(context) 

1380 if new_context <= 0: 

1381 raise ValueError() 

1382 self.context = new_context 

1383 except ValueError: 

1384 self.error( 

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

1386 ) 

1387 

1388 

1389class InterruptiblePdb(Pdb): 

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

1391 

1392 def cmdloop(self, intro=None): 

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

1394 try: 

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

1396 except KeyboardInterrupt: 

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

1398 self.do_quit("") 

1399 sys.settrace(None) 

1400 self.quitting = False 

1401 raise 

1402 

1403 def _cmdloop(self): 

1404 while True: 

1405 try: 

1406 # keyboard interrupts allow for an easy way to cancel 

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

1408 self.allow_kbdint = True 

1409 self.cmdloop() 

1410 self.allow_kbdint = False 

1411 break 

1412 except KeyboardInterrupt: 

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

1414 raise 

1415 

1416 

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

1418 """ 

1419 Start debugging from `frame`. 

1420 

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

1422 """ 

1423 pdb = Pdb() 

1424 if header is not None: 

1425 pdb.message(header) 

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