__main__.py 9.8 KB

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