blob: 005eb9ae5d429dba52caf89156cb073c9c4bd113 [file] [view] [edit]
# 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
```