Created
September 12, 2025 13:17
-
-
Save brechtm/6b3838d367b0aaedf88370bf1d939df1 to your computer and use it in GitHub Desktop.
Python post-mortem debugging with Visual Studio Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import os | |
| import sys | |
| from inspect import currentframe | |
| from socket import gethostname | |
| from traceback import print_stack | |
| import debugpy | |
| from rich.console import Console | |
| from rich.traceback import Traceback | |
| BOLD = '\033[1m' | |
| UNDERLINE = '\033[4m' | |
| RED = '\033[31m' | |
| GREEN = '\033[32m' | |
| YELLOW = '\033[33m' | |
| BLUE = '\033[34m' | |
| END = '\033[0m' | |
| DEBUGPY_PORT = 56700 | |
| def _wait_for_client(): | |
| try: | |
| debugpy.listen(('0.0.0.0', DEBUGPY_PORT)) | |
| except RuntimeError as e: | |
| message, = e.args | |
| if not message.endswith("has already been called on this process"): | |
| raise | |
| print(f"Attach the VSCode Python Debugger to port {gethostname()}:{DEBUGPY_PORT} (or Ctrl-C to continue)") | |
| try: | |
| debugpy.wait_for_client() | |
| except KeyboardInterrupt: | |
| pass | |
| # https://github.com/microsoft/debugpy/discussions/1201#discussioncomment-10602573 | |
| def vscode_breakpointhook(): | |
| breakpoint_frame = currentframe().f_back | |
| print(f"{BOLD}Hit breakpoint at{END}") | |
| print_stack(breakpoint_frame, limit=1) | |
| _wait_for_client() | |
| debugpy.breakpoint() | |
| # https://github.com/microsoft/debugpy/issues/723#issuecomment-919089064 | |
| def vscode_excepthook(type_, value, tb): | |
| traceback_console = Console(file=sys.stderr) | |
| traceback_console.print(Traceback.from_exception(type_, value, tb, width=120)) | |
| _wait_for_client() | |
| import pydevd | |
| import threading | |
| from debugpy.server.api import _settrace | |
| # handle case when debugging a module started with `python -m` | |
| while (tb.tb_frame.f_code.co_filename == '<frozen runpy>' | |
| or tb.tb_frame.f_code.co_filename.endswith('runpy.py')): | |
| tb = tb.tb_next | |
| # copied from debugpy.breakpoint() | |
| # seems to be required to make the next section work reliably | |
| pydb = pydevd.get_global_debugger() | |
| stop_at_frame = tb.tb_frame | |
| while (stop_at_frame is not None | |
| and pydb.get_file_type(stop_at_frame) == pydb.PYDEV_FILE): | |
| stop_at_frame = stop_at_frame.f_back | |
| _settrace(suspend=True, trace_only_current_thread=True, | |
| patch_multiprocessing=False, stop_at_frame=stop_at_frame) | |
| # stop on the unhandled exception | |
| thread = threading.current_thread() | |
| additional_info = pydb.set_additional_thread_info(thread) | |
| additional_info.is_tracing += 1 | |
| try: | |
| arg = (type_, value, tb) | |
| pydb.stop_on_unhandled_exception(pydb, thread, additional_info, arg) | |
| finally: | |
| additional_info.is_tracing -= 1 | |
| def ipdb_breakpointhook(): | |
| import ipdb | |
| # https://github.com/gotcha/ipdb/issues/272 | |
| try: | |
| return ipdb.set_trace(frame=sys._getframe().f_back) | |
| finally: | |
| sys.excepthook = ipdb_excepthook | |
| def ipdb_excepthook(type_, value, tb): | |
| import ipdb | |
| traceback_console = Console(file=sys.stderr) | |
| traceback_console.print(Traceback.from_exception(type_, value, tb, width=120)) | |
| ipdb.pm() | |
| match os.getenv("PYDEBUG"): | |
| case 'vscode' | None: | |
| sys.breakpointhook = vscode_breakpointhook | |
| sys.excepthook = vscode_excepthook | |
| case 'ipdb': | |
| sys.breakpointhook = ipdb_breakpointhook | |
| sys.excepthook = ipdb_excepthook |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Save this script as sitecustomize.py in your project root. These dependencies need to be installed in the project's virtualenv: debugpy, rich, and optionally ipdb.
When you run a python script or module from within the project root and an unhandled exception is encountered or
breakpoint()is called, you'll have the option to connect to the Python process using VSCode's debugger. For this, create a "remote attach" launch configuration in launch.json:{ "name": "Python Debugger: Remote Attach", "type": "debugpy", "request": "attach", "connect": { "host": "localhost", "port": "56700", }, "pathMappings": [ { "localRoot": "${workspaceFolder}", "remoteRoot": "." } ] },The PYDEBUG environment variable can be set to adjust the script's behavior:
P.S. If several people are working on a shared machine, each user needs to be assigned a unique
DEBUGPY_PORT.