1# Copyright (c) 2011-2016 Godefroid Chapelle and ipdb development team 2# 3# This file is part of ipdb. 4# Redistributable under the revised BSD license 5# https://opensource.org/licenses/BSD-3-Clause 6 7from __future__ import print_function 8import os 9import sys 10 11from decorator import contextmanager 12 13__version__ = '0.13.9' 14 15from IPython import get_ipython 16from IPython.core.debugger import BdbQuit_excepthook 17from IPython.terminal.ipapp import TerminalIPythonApp 18from IPython.terminal.embed import InteractiveShellEmbed 19try: 20 import configparser 21except: 22 import ConfigParser as configparser 23 24 25def _get_debugger_cls(): 26 shell = get_ipython() 27 if shell is None: 28 # Not inside IPython 29 # Build a terminal app in order to force ipython to load the 30 # configuration 31 ipapp = TerminalIPythonApp() 32 # Avoid output (banner, prints) 33 ipapp.interact = False 34 ipapp.initialize(["--no-term-title"]) 35 shell = ipapp.shell 36 else: 37 # Running inside IPython 38 39 # Detect if embed shell or not and display a message 40 if isinstance(shell, InteractiveShellEmbed): 41 sys.stderr.write( 42 "\nYou are currently into an embedded ipython shell,\n" 43 "the configuration will not be loaded.\n\n" 44 ) 45 46 # Let IPython decide about which debugger class to use 47 # This is especially important for tools that fiddle with stdout 48 return shell.debugger_cls 49 50 51def _init_pdb(context=None, commands=[]): 52 if context is None: 53 context = os.getenv("IPDB_CONTEXT_SIZE", get_context_from_config()) 54 debugger_cls = _get_debugger_cls() 55 try: 56 p = debugger_cls(context=context) 57 except TypeError: 58 p = debugger_cls() 59 p.rcLines.extend(commands) 60 return p 61 62 63def wrap_sys_excepthook(): 64 # make sure we wrap it only once or we would end up with a cycle 65 # BdbQuit_excepthook.excepthook_ori == BdbQuit_excepthook 66 if sys.excepthook != BdbQuit_excepthook: 67 BdbQuit_excepthook.excepthook_ori = sys.excepthook 68 sys.excepthook = BdbQuit_excepthook 69 70 71def set_trace(frame=None, context=None, cond=True): 72 if not cond: 73 return 74 wrap_sys_excepthook() 75 if frame is None: 76 frame = sys._getframe().f_back 77 p = _init_pdb(context).set_trace(frame) 78 if p and hasattr(p, 'shell'): 79 p.shell.restore_sys_module_state() 80 81 82def get_context_from_config(): 83 try: 84 parser = get_config() 85 return parser.getint("ipdb", "context") 86 except (configparser.NoSectionError, configparser.NoOptionError): 87 return 3 88 except ValueError: 89 value = parser.get("ipdb", "context") 90 raise ValueError( 91 "In %s, context value [%s] cannot be converted into an integer." 92 % (parser.filepath, value) 93 ) 94 95 96class ConfigFile(object): 97 """ 98 Filehandle wrapper that adds a "[ipdb]" section to the start of a config 99 file so that users don't actually have to manually add a [ipdb] section. 100 Works with configparser versions from both Python 2 and 3 101 """ 102 103 def __init__(self, filepath): 104 self.first = True 105 with open(filepath) as f: 106 self.lines = f.readlines() 107 108 # Python 2.7 (Older dot versions) 109 def readline(self): 110 try: 111 return self.__next__() 112 except StopIteration: 113 return '' 114 115 # Python 2.7 (Newer dot versions) 116 def next(self): 117 return self.__next__() 118 119 # Python 3 120 def __iter__(self): 121 return self 122 123 def __next__(self): 124 if self.first: 125 self.first = False 126 return "[ipdb]\n" 127 if self.lines: 128 return self.lines.pop(0) 129 raise StopIteration 130 131 132def get_config(): 133 """ 134 Get ipdb config file settings. 135 All available config files are read. If settings are in multiple configs, 136 the last value encountered wins. Values specified on the command-line take 137 precedence over all config file settings. 138 Returns: A ConfigParser object. 139 """ 140 parser = configparser.ConfigParser() 141 142 filepaths = [] 143 144 # Low priority goes first in the list 145 for cfg_file in ("setup.cfg", ".ipdb", "pyproject.toml"): 146 cwd_filepath = os.path.join(os.getcwd(), cfg_file) 147 if os.path.isfile(cwd_filepath): 148 filepaths.append(cwd_filepath) 149 150 # Medium priority (whenever user wants to set a specific path to config file) 151 home = os.getenv("HOME") 152 if home: 153 default_filepath = os.path.join(home, ".ipdb") 154 if os.path.isfile(default_filepath): 155 filepaths.append(default_filepath) 156 157 # High priority (default files) 158 env_filepath = os.getenv("IPDB_CONFIG") 159 if env_filepath and os.path.isfile(env_filepath): 160 filepaths.append(env_filepath) 161 162 if filepaths: 163 # Python 3 has parser.read_file(iterator) while Python2 has 164 # parser.readfp(obj_with_readline) 165 try: 166 read_func = parser.read_file 167 except AttributeError: 168 read_func = parser.readfp 169 for filepath in filepaths: 170 parser.filepath = filepath 171 # Users are expected to put an [ipdb] section 172 # only if they use setup.cfg 173 if filepath.endswith('setup.cfg'): 174 with open(filepath) as f: 175 parser.remove_section("ipdb") 176 read_func(f) 177 # To use on pyproject.toml, put [tool.ipdb] section 178 elif filepath.endswith('pyproject.toml'): 179 import toml 180 toml_file = toml.load(filepath) 181 if "tool" in toml_file and "ipdb" in toml_file["tool"]: 182 if not parser.has_section("ipdb"): 183 parser.add_section("ipdb") 184 for key, value in toml_file["tool"]["ipdb"].items(): 185 parser.set("ipdb", key, str(value)) 186 else: 187 read_func(ConfigFile(filepath)) 188 return parser 189 190 191def post_mortem(tb=None): 192 wrap_sys_excepthook() 193 p = _init_pdb() 194 p.reset() 195 if tb is None: 196 # sys.exc_info() returns (type, value, traceback) if an exception is 197 # being handled, otherwise it returns None 198 tb = sys.exc_info()[2] 199 if tb: 200 p.interaction(None, tb) 201 202 203def pm(): 204 post_mortem(sys.last_traceback) 205 206 207def run(statement, globals=None, locals=None): 208 _init_pdb().run(statement, globals, locals) 209 210 211def runcall(*args, **kwargs): 212 return _init_pdb().runcall(*args, **kwargs) 213 214 215def runeval(expression, globals=None, locals=None): 216 return _init_pdb().runeval(expression, globals, locals) 217 218 219@contextmanager 220def launch_ipdb_on_exception(): 221 try: 222 yield 223 except Exception: 224 e, m, tb = sys.exc_info() 225 print(m.__repr__(), file=sys.stderr) 226 post_mortem(tb) 227 finally: 228 pass 229 230 231# iex is a concise alias 232iex = launch_ipdb_on_exception() 233 234 235_usage = """\ 236usage: python -m ipdb [-m] [-c command] ... pyfile [arg] ... 237 238Debug the Python program given by pyfile. 239 240Initial commands are read from .pdbrc files in your home directory 241and in the current directory, if they exist. Commands supplied with 242-c are executed after commands from .pdbrc files. 243 244To let the script run until an exception occurs, use "-c continue". 245To let the script run up to a given line X in the debugged file, use 246"-c 'until X'" 247 248Option -m is available only in Python 3.7 and later. 249 250ipdb version %s.""" % __version__ 251 252 253def main(): 254 import traceback 255 import sys 256 import getopt 257 258 try: 259 from pdb import Restart 260 except ImportError: 261 class Restart(Exception): 262 pass 263 264 if sys.version_info >= (3, 7): 265 opts, args = getopt.getopt(sys.argv[1:], 'mhc:', ['help', 'command=']) 266 else: 267 opts, args = getopt.getopt(sys.argv[1:], 'hc:', ['help', 'command=']) 268 269 commands = [] 270 run_as_module = False 271 for opt, optarg in opts: 272 if opt in ['-h', '--help']: 273 print(_usage) 274 sys.exit() 275 elif opt in ['-c', '--command']: 276 commands.append(optarg) 277 elif opt in ['-m']: 278 run_as_module = True 279 280 if not args: 281 print(_usage) 282 sys.exit(2) 283 284 mainpyfile = args[0] # Get script filename 285 if not run_as_module and not os.path.exists(mainpyfile): 286 print('Error:', mainpyfile, 'does not exist') 287 sys.exit(1) 288 289 sys.argv = args # Hide "pdb.py" from argument list 290 291 # Replace pdb's dir with script's dir in front of module search path. 292 if not run_as_module: 293 sys.path[0] = os.path.dirname(mainpyfile) 294 295 # Note on saving/restoring sys.argv: it's a good idea when sys.argv was 296 # modified by the script being debugged. It's a bad idea when it was 297 # changed by the user from the command line. There is a "restart" command 298 # which allows explicit specification of command line arguments. 299 pdb = _init_pdb(commands=commands) 300 while 1: 301 try: 302 if run_as_module: 303 pdb._runmodule(mainpyfile) 304 else: 305 pdb._runscript(mainpyfile) 306 if pdb._user_requested_quit: 307 break 308 print("The program finished and will be restarted") 309 except Restart: 310 print("Restarting", mainpyfile, "with arguments:") 311 print("\t" + " ".join(sys.argv[1:])) 312 except SystemExit: 313 # In most cases SystemExit does not warrant a post-mortem session. 314 print("The program exited via sys.exit(). Exit status: ", end='') 315 print(sys.exc_info()[1]) 316 except: 317 traceback.print_exc() 318 print("Uncaught exception. Entering post mortem debugging") 319 print("Running 'cont' or 'step' will restart the program") 320 t = sys.exc_info()[2] 321 pdb.interaction(None, t) 322 print("Post mortem debugger finished. The " + mainpyfile + 323 " will be restarted") 324 325 326if __name__ == '__main__': 327 main() 328