Skip to content

refactor(adapters): introduce language Adapter ABC; lift Python-specific code behind it#2

Closed
niradler wants to merge 1 commit into
mainfrom
feat/adapter-interface
Closed

refactor(adapters): introduce language Adapter ABC; lift Python-specific code behind it#2
niradler wants to merge 1 commit into
mainfrom
feat/adapter-interface

Conversation

@niradler
Copy link
Copy Markdown
Owner

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.

  • New adapters.base.Adapter ABC — 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 in diagnose, IDE-attach mode, instrument probe templates).
  • PythonAdapter ports every existing debugpy code path behind this interface with no behavior change.
  • Adapter registry + language detectionget_adapter, list_adapters, detect_language (by extension), resolve_language (explicit > detected > default).
  • CLI surface — adds --lang {python,...} to session start, localize, and diagnose. Auto-detected from script extension / interpreter basename when omitted. Persisted to meta.json so the daemon picks the right adapter on (re)start.
  • Internal movesadapters/debugpy_adapter.py (module) → adapters/python.py (class) + adapters/_socket.py (truly generic helpers reusable by future Go/Node adapters).
  • Backwards-compatible — pre-refactor meta.json files (no lang field) 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.adapter spawn, Python traceback regex, python foo.py interpreter peeling in diagnose) 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

  • This PR — refactor (Python-only, no behavior change).
  • PR2GoAdapter driving dlv dap.
  • PR3NodeAdapter driving vscode-js-debug dapDebugServer.js.

Test plan

  • uv run pytest tests/unit -v — 61 passed (11 new in test_adapters.py)
  • uv run pytest tests/integration -v — 7 passed (real debugpy driven through the new adapter)
  • uv run pytest tests/e2e -v — 45 passed (CLI subprocess, includes diagnose / session / localize)
  • uv run ruff check . — clean
  • uv run ruff format --check . — clean
  • uv run mypy src — strict, clean (28 files)

…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`).
@niradler
Copy link
Copy Markdown
Owner Author

Superseded by #5, which consolidates the refactor + Go + Node work into a single PR against main (with post-review fixes applied). Closing in favor of #5.

@niradler niradler closed this May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant