refactor(adapters): introduce language Adapter ABC; lift Python-specific code behind it#2
Closed
niradler wants to merge 1 commit into
Closed
refactor(adapters): introduce language Adapter ABC; lift Python-specific code behind it#2niradler wants to merge 1 commit into
niradler wants to merge 1 commit into
Conversation
…fic code behind it
Until now every language-specific decision (spawn debugpy.adapter, build the
`{"type": "python", ...}` launch payload, parse Python tracebacks, peel a
`python foo.py` interpreter prefix in `diagnose`) was inlined in the core /
command layer. Adding a second language would have required surgery in five
or six files.
This change introduces `adapters.base.Adapter` — an ABC that captures every
language-specific knob the rest of the codebase needs:
* `spawn_adapter(port)` — start the language's DAP server
* `launch_payload(...)` — build the DAP `launch` request body
* `parse_traceback(text)` — language-specific stack/panic parser
* `spawn_listen_mode(...)` + `supports_listen_mode()` — IDE attach (optional)
* `attach_url(host, port)` — the URL scheme an IDE should use
* `resolve_launch_target(cmd)` — peel an interpreter prefix for `diagnose`
* `probe_template(kind, code)` — hook for future `instrument` defaults
`PythonAdapter` ports every existing debugpy code path behind this interface
with no behavior change. The registry in `adapters/__init__.py` exposes
`get_adapter`, `list_adapters`, `detect_language` (by file extension), and
`resolve_language` (explicit > detected > default).
CLI surface:
* `session start --lang {python,...}` — auto-detected from script extension
when omitted; persisted to `meta.json` so the daemon picks the right
adapter on (re)start.
* `localize --lang {python,...}` — picks the traceback parser.
* `diagnose --lang {python,...}` — drives both traceback parsing and the
`python foo.py` → `foo.py` launch-target peeling; auto-inferred from the
command (interpreter basename or first script-like argument).
Internal moves:
* `adapters/debugpy_adapter.py` (module) → `adapters/python.py` (class) +
`adapters/_socket.py` (truly generic `find_free_port` /
`wait_until_listening`, reusable by future Go/Node adapters).
* `core/dap_session.py::DapSession` now takes an `Adapter` (defaults to
PythonAdapter for backwards compatibility). It calls into the adapter
instead of importing debugpy helpers directly.
* `core/session_proc.py` reads `meta["lang"]` (defaults to "python" for
pre-refactor meta files) and constructs the matching adapter.
Testing:
* 11 new unit tests in `tests/unit/test_adapters.py` cover registry,
detection, listen-mode flag, launch-payload shape, interpreter peeling,
and traceback parsing through the adapter.
* All existing 102 tests (unit + integration + e2e) pass unchanged — the
Python behavior is byte-identical.
* `ruff check`, `ruff format`, `mypy --strict src` all clean.
This is the first of a 3-PR stack. PR2 adds GoAdapter (delve `dlv dap`).
PR3 adds NodeAdapter (vscode-js-debug `dapDebugServer.js`).
This was referenced May 29, 2026
Owner
Author
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First of a 3-PR stack to support multiple languages in
dbga. This PR is the refactor only — Python behavior is byte-identical, every existing test still passes.adapters.base.AdapterABC — captures every language-specific knob the rest of the codebase needs (spawn the DAP server, build the launch payload, parse a stack trace, peel an interpreter prefix indiagnose, IDE-attach mode, instrument probe templates).PythonAdapterports every existing debugpy code path behind this interface with no behavior change.get_adapter,list_adapters,detect_language(by extension),resolve_language(explicit > detected > default).--lang {python,...}tosession start,localize, anddiagnose. Auto-detected from script extension / interpreter basename when omitted. Persisted tometa.jsonso the daemon picks the right adapter on (re)start.adapters/debugpy_adapter.py(module) →adapters/python.py(class) +adapters/_socket.py(truly generic helpers reusable by future Go/Node adapters).meta.jsonfiles (nolangfield) default to Python so any in-flight sessions resume cleanly.Why this shape
Until now every language-specific decision (
{"type": "python", ...}launch payload,python -m debugpy.adapterspawn, Python traceback regex,python foo.pyinterpreter peeling indiagnose) was inlined across five or six files. A second language would have meant surgery everywhere. The Adapter interface front-loads that work once and reduces per-language addition to "implement an ABC + register it."Stack
GoAdapterdrivingdlv dap.NodeAdapterdriving vscode-js-debugdapDebugServer.js.Test plan
uv run pytest tests/unit -v— 61 passed (11 new intest_adapters.py)uv run pytest tests/integration -v— 7 passed (realdebugpydriven through the new adapter)uv run pytest tests/e2e -v— 45 passed (CLI subprocess, includes diagnose / session / localize)uv run ruff check .— cleanuv run ruff format --check .— cleanuv run mypy src— strict, clean (28 files)