This library provides a complete and extensible implementation of the Debug Adapter Protocol (DAP) in Python.
DapClient that handles the message framing (Content-Length headers) and asynchronous request/response matching.Here is a basic example of how to use the DapClient to connect to a debug adapter and send an initialize request:
import asyncio from pydap.client import DapClient from pydap.models import InitializeArguments async def run(): client = DapClient() # Connect to the debug adapter (e.g., via TCP) reader, writer = await asyncio.open_connection("127.0.0.1", 12345) # Send initialize request args = InitializeArguments(adapterID="test") # We create a task to send the request # In a full implementation, you would also have a task reading from 'reader' response = await client.initialize(writer, args) print(f"Response: {response}") asyncio.run(run())
To ensure idiomatic Python development while maintaining exact compliance with the DAP specification, this library defines models using standard Python snake_case (e.g., adapter_id) and uses Pydantic's aliasing feature to automatically serialize them to the required camelCase or acronym casing (e.g., adapterID) as defined in the official protocol.
Thanks to Pydantic's populate_by_name configuration, you can instantiate models using either snake_case or camelCase keyword arguments, though snake_case is preferred for idiomatic code.
One of the key design goals of this library is extensibility. The base protocol is often extended by specific debuggers to support unique features.
To extend the library:
DapBaseModel.DapClient::_send_request method directly with your custom command string and model instance.Suppose you are building a debug adapter that supports a custom profiling feature not covered by the standard protocol. You can extend DapClient to support this server feature by subclassing or using compositional mixin classes.
from pydap.dap_types import DapBaseModel class StartProfilingArguments(DapBaseModel): # Duration in milliseconds. duration: int
import asyncio from pydap.client import DapClient class CustomDapClient(DapClient): async def start_profiling( self, writer: asyncio.StreamWriter, duration: int ): args = StartProfilingArguments(duration=duration) return await self._send_request(writer, "startProfiling", args)
For complex clients with multiple independent extensions, mixin classes provide a cleaner architectural pattern:
import asyncio from typing import Any, Protocol from pydap.client import DapClient from pydap.dap_types import DapBaseModel # Define a protocol for type checking within the mixin class DapClientProtocol(Protocol): async def _send_request( self, writer: asyncio.StreamWriter, command: str, arguments: DapBaseModel | None = None, timeout: float = 5.0, ) -> dict[str, Any]: ... class ProfilingMixin: """Mixin class adding profiling support to a DapClient.""" async def start_profiling( self: DapClientProtocol, writer: asyncio.StreamWriter, duration: int ) -> dict[str, Any]: args = StartProfilingArguments(duration=duration) return await self._send_request(writer, "startProfiling", args) # Compose your final client from base DapClient and mixins class ExtendedDapClient(DapClient, ProfilingMixin): pass