This example demonstrates how to attach a DAP-compatible debugger (VSCode,
PyCharm, etc.) to a py_binary built with rules_py.
IDEs like PyCharm expect to control the Python process startup for debugging, but Bazel's hermetic launcher scripts break that assumption. The workaround is to use debugpy, a network-based debug adapter that the application starts itself, allowing the IDE to attach over TCP after the process is running.
This approach works with any IDE that supports the Debug Adapter Protocol (DAP): VSCode, PyCharm (with debugpy plugin), Neovim (nvim-dap), Emacs (dap-mode), etc.
The structure mirrors the dev_deps example, using PEP 735
dependency groups and a string_flag to conditionally include debugpy.
The key addition is that the binary's entrypoint itself is swapped in
debug mode.
[dependency-groups]
prod = ["flask"]
debug = [
{include-group = "prod"},
"debugpy",
]The py_debuggable_binary macro generates a debugpy wrapper from a
template (_debug_main.py.tpl). The wrapper starts a DAP listener, then
uses runpy.run_module() to execute the real entrypoint. You don't need
to write any debugpy boilerplate — just specify your normal main.
py_debuggable_binary(
name = "app",
srcs = ["app.py"],
main = "app.py",
deps = ["@pypi//flask"],
debug_deps = ["@pypi//debugpy"],
)In debug mode (the default), the macro generates a wrapper that starts
debugpy and then runs app.py. In prod mode, app.py is the entrypoint
directly and debugpy is absent.
common --@pypi//dep_group=debug
common:release --@pypi//dep_group=prod
common:release --//:mode=prod
cd examples/debugger
# Debug mode (default) — debugpy listener on 127.0.0.1:5678:
bazel run //:app
# Wait for IDE to attach before running app code:
DEBUGPY_WAIT=1 bazel run //:app
# Release mode — no debugpy, app.py runs directly:
bazel run //:app --config=releaseAdd to .vscode/launch.json:
{
"name": "Attach to Bazel py_binary",
"type": "debugpy",
"request": "attach",
"connect": { "host": "127.0.0.1", "port": 5678 }
}Then DEBUGPY_WAIT=1 bazel run //:app and press F5 in VSCode.
Use Run > Attach to Process or create a Python Debug Server
run configuration pointing to 127.0.0.1:5678. Requires the debugpy
plugin (see PY-63403).