Skip to content

Debug Python with Bugsaur

Adapter: debugpy

Install the adapter

debugpy is a Python module, so it is installed into the interpreter that runs your code — usually the project virtualenv, not the system Python:

.venv/bin/pip install debugpy
# or, for a system interpreter
python3 -m pip install debugpy
python3 -m debugpy --version

Configure

bugsaur init detects the project by pyproject.toml, setup.py or requirements.txt, and writes one profile per entry point it finds: main.py, app.py, manage.py, __main__.py in the root, and src/<pkg>/__main__.py.

It also looks for .venv/bin/python and venv/bin/python in the project root. When it finds one, both the adapter and the debuggee are pointed at that interpreter.

shop/
├── pyproject.toml
├── main.py
├── .venv/bin/python
└── src/shop/__main__.py
version = 1
default = "main"

[profiles.main]
adapter = "debugpy"
adapter_command = "${root}/.venv/bin/python"
program = "main.py"

[profiles.main.launch_arguments]
python = "${root}/.venv/bin/python"

[profiles.shop]
adapter = "debugpy"
adapter_command = "${root}/.venv/bin/python"
program = "src/shop/__main__.py"

[profiles.shop.launch_arguments]
python = "${root}/.venv/bin/python"

Note the two separate settings — they are not redundant:

  • adapter_command — which interpreter runs the adapter;
  • launch_arguments.python — which interpreter runs your code.

With no virtualenv both fall back to python3 from PATH, which then needs debugpy installed in it.

cwd is not written: the working directory is the project root anyway, and that is exactly what makes imports of a package under src/ resolve.

Django

[profiles.web]
adapter = "debugpy"
adapter_command = "${root}/.venv/bin/python"
program = "manage.py"
args = ["runserver", "--noreload", "8000"]
env_file = ".env"

--noreload is required

Django's autoreloader starts a second process, and the debugger is not attached to it. Breakpoints in a reloaded worker never fire, and it looks like the debugger is broken rather than that there are two processes.

Debug a module instead of a file

debugpy accepts module. There is no dedicated profile field for it, but the escape hatch works:

[profiles.tests]
adapter = "debugpy"
program = "."
args = ["-x", "tests/"]

[profiles.tests.launch_arguments]
module = "pytest"

pytest

The recipe above debugs a whole run. To debug the single test under your cursor, no profile is needed:

bugsaur gui --project . --test-at tests/test_orders.py:24

Bugsaur runs the selected pytest node through debugpy, inheriting the adapter, environment and working directory from the profile. See Guides → Debug pytest.

An entry point outside the root and outside src/

A layout like services/api/app.py is not found by detection — zero profiles, and the launch stops. Write it by hand:

version = 1
default = "api"

[profiles.api]
adapter = "debugpy"
program = "services/api/app.py"

Diagnostics

Symptom Likely cause Where to look
No module named debugpy installed into a different interpreter than the one running install into the venv, check adapter_command
Breakpoints never fire in Django the autoreloader forked a second process add --noreload
ImportError for your own package the working directory is not what the code assumes Working directory
Stops in library code, not yours the exception was raised deeper Call Stack
Session never starts the interpreter path in adapter_command is wrong Adapter fails to start