| # Debug Adapter Protocol (DAP) Library | |
| This library provides a complete and extensible implementation of the [Debug | |
| Adapter Protocol | |
| (DAP)](https://microsoft.github.io/debug-adapter-protocol/specification) in | |
| Python. | |
| ## Features | |
| - **Protocol Models**: Full set of Python types representing DAP requests, | |
| responses, and events. | |
| - **Client Implementation**: A `DapClient` that handles the message framing | |
| (Content-Length headers) and asynchronous request/response matching. | |
| - **Extensible Architecture**: Easy to add vendor-specific extensions without | |
| modifying the core library. | |
| ## Client Usage Example | |
| Here is a basic example of how to use the `DapClient` to connect to a debug | |
| adapter and send an initialize request: | |
| ```python | |
| 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()) | |
| ``` | |
| ## Naming Convention | |
| 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. | |
| ## Extensibility | |
| 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: | |
| 1. Define your custom argument types by subclassing `DapBaseModel`. | |
| 2. Use the protected `DapClient::_send_request` method directly with your custom command string and model instance. | |
| ### Example: Custom Profiling Extension | |
| 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. | |
| #### Step 1: Define Custom Arguments | |
| ```python | |
| from pydap.dap_types import DapBaseModel | |
| class StartProfilingArguments(DapBaseModel): | |
| # Duration in milliseconds. | |
| duration: int | |
| ``` | |
| #### Step 2a: Direct Subclassing | |
| ```python | |
| 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) | |
| ``` | |
| #### Step 2b: Compositional Mixins (Recommended for Modular Extensions) | |
| For complex clients with multiple independent extensions, mixin classes provide a cleaner architectural pattern: | |
| ```python | |
| 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 | |
| ``` |