diff --git a/.gitignore b/.gitignore
index 23b2ad4..9670bd4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -175,3 +175,8 @@ cython_debug/
.pypirc
CODE_EXECUTION.md
+
+# AI
+.code-review-graph/
+.claude
+.mcp.json
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..8dc6f53
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,25 @@
+repos:
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.15.10
+ hooks:
+ - id: ruff
+ - id: ruff-format
+
+ - repo: https://github.com/pre-commit/mirrors-mypy
+ rev: v1.20.0
+ hooks:
+ - id: mypy
+ additional_dependencies:
+ - pydantic>=2.12.5
+ - httpx>=0.28.1
+ - httpx-ws>=0.9.0
+ - email-validator>=2.3.0
+
+ - repo: local
+ hooks:
+ - id: unit-tests
+ name: unit tests
+ entry: uv run pytest -m "not integration" -x -q
+ language: system
+ pass_filenames: false
+ always_run: true
diff --git a/.woodpecker/check.yml b/.woodpecker/check.yml
index 23d9bba..3b78cc7 100644
--- a/.woodpecker/check.yml
+++ b/.woodpecker/check.yml
@@ -1,8 +1,11 @@
when:
- event: push
+ event: pull_request
branch:
- main
- dev
+ path:
+ - "src/**"
+ - "tests/**"
steps:
unit-tests:
diff --git a/CLAUDE.md b/CLAUDE.md
index cc00331..4aff987 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -129,4 +129,43 @@ All values are CSS custom properties in `frontend/src/app.css`.
4. **Legible at speed.** Users scan dashboards in seconds. Strong typographic contrast (serif h1, mono IDs, sans body), consistent patterns, and predictable placement let users orientate instantly without reading everything.
-5. **Craft signals trust.** For infrastructure that runs production code, the quality of the UI is a proxy for the quality of the product. Pixel-level decisions matter. Polish is not decoration — it's a trust signal.
\ No newline at end of file
+5. **Craft signals trust.** For infrastructure that runs production code, the quality of the UI is a proxy for the quality of the product. Pixel-level decisions matter. Polish is not decoration — it's a trust signal.
+
+
+## MCP Tools: code-review-graph
+
+**IMPORTANT: This project has a knowledge graph. ALWAYS use the
+code-review-graph MCP tools BEFORE using Grep/Glob/Read to explore
+the codebase.** The graph is faster, cheaper (fewer tokens), and gives
+you structural context (callers, dependents, test coverage) that file
+scanning cannot.
+
+### When to use graph tools FIRST
+
+- **Exploring code**: `semantic_search_nodes` or `query_graph` instead of Grep
+- **Understanding impact**: `get_impact_radius` instead of manually tracing imports
+- **Code review**: `detect_changes` + `get_review_context` instead of reading entire files
+- **Finding relationships**: `query_graph` with callers_of/callees_of/imports_of/tests_for
+- **Architecture questions**: `get_architecture_overview` + `list_communities`
+
+Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need.
+
+### Key Tools
+
+| Tool | Use when |
+|------|----------|
+| `detect_changes` | Reviewing code changes — gives risk-scored analysis |
+| `get_review_context` | Need source snippets for review — token-efficient |
+| `get_impact_radius` | Understanding blast radius of a change |
+| `get_affected_flows` | Finding which execution paths are impacted |
+| `query_graph` | Tracing callers, callees, imports, tests, dependencies |
+| `semantic_search_nodes` | Finding functions/classes by name or keyword |
+| `get_architecture_overview` | Understanding high-level codebase structure |
+| `refactor_tool` | Planning renames, finding dead code |
+
+### Workflow
+
+1. The graph auto-updates on file changes (via hooks).
+2. Use `detect_changes` for code review.
+3. Use `get_affected_flows` to understand impact.
+4. Use `query_graph` pattern="tests_for" to check coverage.
diff --git a/Makefile b/Makefile
index 7b1b356..65b3a04 100644
--- a/Makefile
+++ b/Makefile
@@ -36,3 +36,7 @@ test-integration:
uv run pytest tests/ -v -m "integration or not integration"
check: lint test
+
+gen-docs:
+ mkdir -p docs
+ uv run pydoc-markdown > docs/reference.md
diff --git a/docs/reference.md b/docs/reference.md
new file mode 100644
index 0000000..7e32f6c
--- /dev/null
+++ b/docs/reference.md
@@ -0,0 +1,4274 @@
+
+
+# wrenn
+
+
+
+# wrenn.client
+
+
+
+## CapsulesResource Objects
+
+```python
+class CapsulesResource()
+```
+
+Sync capsule control-plane operations.
+
+
+
+#### create
+
+```python
+def create(template: str | None = None,
+ vcpus: int | None = None,
+ memory_mb: int | None = None,
+ timeout_sec: int | None = None) -> CapsuleModel
+```
+
+Create a new capsule.
+
+**Arguments**:
+
+- `template` _str | None_ - Template name to boot from.
+- `vcpus` _int | None_ - Number of virtual CPUs.
+- `memory_mb` _int | None_ - Memory in MiB.
+- `timeout_sec` _int | None_ - Inactivity TTL in seconds before
+ auto-pause. ``0`` disables auto-pause.
+
+
+**Returns**:
+
+- `CapsuleModel` - The newly created capsule.
+
+
+
+#### list
+
+```python
+def list() -> list[CapsuleModel]
+```
+
+List all capsules for the authenticated team.
+
+**Returns**:
+
+- `list[CapsuleModel]` - All capsules belonging to the team.
+
+
+
+#### get
+
+```python
+def get(id: str) -> CapsuleModel
+```
+
+Get a capsule by ID.
+
+**Arguments**:
+
+- `id` _str_ - Capsule ID.
+
+
+**Returns**:
+
+- `CapsuleModel` - Current state of the capsule.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no capsule with the given ID exists.
+
+
+
+#### destroy
+
+```python
+def destroy(id: str) -> None
+```
+
+Destroy a capsule permanently.
+
+**Arguments**:
+
+- `id` _str_ - Capsule ID.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no capsule with the given ID exists.
+
+
+
+#### pause
+
+```python
+def pause(id: str) -> CapsuleModel
+```
+
+Pause a running capsule.
+
+**Arguments**:
+
+- `id` _str_ - Capsule ID.
+
+
+**Returns**:
+
+- `CapsuleModel` - Updated capsule state.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no capsule with the given ID exists.
+
+
+
+#### resume
+
+```python
+def resume(id: str) -> CapsuleModel
+```
+
+Resume a paused capsule.
+
+**Arguments**:
+
+- `id` _str_ - Capsule ID.
+
+
+**Returns**:
+
+- `CapsuleModel` - Updated capsule state.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no capsule with the given ID exists.
+
+
+
+#### ping
+
+```python
+def ping(id: str) -> None
+```
+
+Reset the inactivity timer for a capsule.
+
+**Arguments**:
+
+- `id` _str_ - Capsule ID.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no capsule with the given ID exists.
+
+
+
+## AsyncCapsulesResource Objects
+
+```python
+class AsyncCapsulesResource()
+```
+
+Async capsule control-plane operations.
+
+
+
+#### create
+
+```python
+async def create(template: str | None = None,
+ vcpus: int | None = None,
+ memory_mb: int | None = None,
+ timeout_sec: int | None = None) -> CapsuleModel
+```
+
+Create a new capsule.
+
+**Arguments**:
+
+- `template` _str | None_ - Template name to boot from.
+- `vcpus` _int | None_ - Number of virtual CPUs.
+- `memory_mb` _int | None_ - Memory in MiB.
+- `timeout_sec` _int | None_ - Inactivity TTL in seconds before
+ auto-pause. ``0`` disables auto-pause.
+
+
+**Returns**:
+
+- `CapsuleModel` - The newly created capsule.
+
+
+
+#### list
+
+```python
+async def list() -> list[CapsuleModel]
+```
+
+List all capsules for the authenticated team.
+
+**Returns**:
+
+- `list[CapsuleModel]` - All capsules belonging to the team.
+
+
+
+#### get
+
+```python
+async def get(id: str) -> CapsuleModel
+```
+
+Get a capsule by ID.
+
+**Arguments**:
+
+- `id` _str_ - Capsule ID.
+
+
+**Returns**:
+
+- `CapsuleModel` - Current state of the capsule.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no capsule with the given ID exists.
+
+
+
+#### destroy
+
+```python
+async def destroy(id: str) -> None
+```
+
+Destroy a capsule permanently.
+
+**Arguments**:
+
+- `id` _str_ - Capsule ID.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no capsule with the given ID exists.
+
+
+
+#### pause
+
+```python
+async def pause(id: str) -> CapsuleModel
+```
+
+Pause a running capsule.
+
+**Arguments**:
+
+- `id` _str_ - Capsule ID.
+
+
+**Returns**:
+
+- `CapsuleModel` - Updated capsule state.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no capsule with the given ID exists.
+
+
+
+#### resume
+
+```python
+async def resume(id: str) -> CapsuleModel
+```
+
+Resume a paused capsule.
+
+**Arguments**:
+
+- `id` _str_ - Capsule ID.
+
+
+**Returns**:
+
+- `CapsuleModel` - Updated capsule state.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no capsule with the given ID exists.
+
+
+
+#### ping
+
+```python
+async def ping(id: str) -> None
+```
+
+Reset the inactivity timer for a capsule.
+
+**Arguments**:
+
+- `id` _str_ - Capsule ID.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no capsule with the given ID exists.
+
+
+
+## SnapshotsResource Objects
+
+```python
+class SnapshotsResource()
+```
+
+Sync snapshot operations.
+
+
+
+#### create
+
+```python
+def create(capsule_id: str,
+ name: str | None = None,
+ overwrite: bool = False) -> Template
+```
+
+Create a snapshot template from a running capsule.
+
+**Arguments**:
+
+- `capsule_id` _str_ - ID of the capsule to snapshot.
+- `name` _str | None_ - Name for the snapshot template. Auto-generated
+ if not provided.
+- `overwrite` _bool_ - If ``True``, overwrite an existing template with
+ the same name. Defaults to ``False``.
+
+
+**Returns**:
+
+- `Template` - The created snapshot template.
+
+
+
+#### list
+
+```python
+def list(type: str | None = None) -> list[Template]
+```
+
+List snapshot templates.
+
+**Arguments**:
+
+- `type` _str | None_ - Filter by template type. Returns all templates
+ if not provided.
+
+
+**Returns**:
+
+- `list[Template]` - Matching snapshot templates.
+
+
+
+#### delete
+
+```python
+def delete(name: str) -> None
+```
+
+Delete a snapshot template by name.
+
+**Arguments**:
+
+- `name` _str_ - Template name to delete.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no template with the given name exists.
+
+
+
+## AsyncSnapshotsResource Objects
+
+```python
+class AsyncSnapshotsResource()
+```
+
+Async snapshot operations.
+
+
+
+#### create
+
+```python
+async def create(capsule_id: str,
+ name: str | None = None,
+ overwrite: bool = False) -> Template
+```
+
+Create a snapshot template from a running capsule.
+
+**Arguments**:
+
+- `capsule_id` _str_ - ID of the capsule to snapshot.
+- `name` _str | None_ - Name for the snapshot template. Auto-generated
+ if not provided.
+- `overwrite` _bool_ - If ``True``, overwrite an existing template with
+ the same name. Defaults to ``False``.
+
+
+**Returns**:
+
+- `Template` - The created snapshot template.
+
+
+
+#### list
+
+```python
+async def list(type: str | None = None) -> list[Template]
+```
+
+List snapshot templates.
+
+**Arguments**:
+
+- `type` _str | None_ - Filter by template type. Returns all templates
+ if not provided.
+
+
+**Returns**:
+
+- `list[Template]` - Matching snapshot templates.
+
+
+
+#### delete
+
+```python
+async def delete(name: str) -> None
+```
+
+Delete a snapshot template by name.
+
+**Arguments**:
+
+- `name` _str_ - Template name to delete.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no template with the given name exists.
+
+
+
+## WrennClient Objects
+
+```python
+class WrennClient()
+```
+
+Synchronous client for the Wrenn API.
+
+Authenticates with an API key.
+
+**Arguments**:
+
+- `api_key` - API key (``wrn_...``). Falls back to ``WRENN_API_KEY`` env var.
+- `base_url` - Wrenn API base URL.
+
+
+
+#### http
+
+```python
+@property
+def http() -> httpx.Client
+```
+
+The underlying httpx.Client (for sub-objects that need direct access).
+
+
+
+#### close
+
+```python
+def close() -> None
+```
+
+Close the underlying HTTP connection pool.
+
+
+
+## AsyncWrennClient Objects
+
+```python
+class AsyncWrennClient()
+```
+
+Asynchronous client for the Wrenn API.
+
+Authenticates with an API key.
+
+**Arguments**:
+
+- `api_key` - API key (``wrn_...``). Falls back to ``WRENN_API_KEY`` env var.
+- `base_url` - Wrenn API base URL. Falls back to ``WRENN_BASE_URL`` env var.
+
+
+
+#### http
+
+```python
+@property
+def http() -> httpx.AsyncClient
+```
+
+The underlying httpx.AsyncClient.
+
+
+
+#### aclose
+
+```python
+async def aclose() -> None
+```
+
+Close the underlying async HTTP connection pool.
+
+
+
+# wrenn.sandbox
+
+
+
+# wrenn.commands
+
+
+
+## CommandResult Objects
+
+```python
+@dataclass
+class CommandResult()
+```
+
+Result from a foreground command execution.
+
+
+
+## CommandHandle Objects
+
+```python
+@dataclass
+class CommandHandle()
+```
+
+Handle for a background process.
+
+
+
+## ProcessInfo Objects
+
+```python
+@dataclass
+class ProcessInfo()
+```
+
+Information about a running process.
+
+
+
+## StreamEvent Objects
+
+```python
+class StreamEvent()
+```
+
+Base class for streaming exec events.
+
+
+
+## Commands Objects
+
+```python
+class Commands()
+```
+
+Sync command execution interface. Accessed via ``capsule.commands``.
+
+
+
+#### run
+
+```python
+def run(cmd: str,
+ *,
+ background: bool = False,
+ timeout: int | None = 30,
+ envs: dict[str, str] | None = None,
+ cwd: str | None = None,
+ tag: str | None = None) -> CommandResult | CommandHandle
+```
+
+Execute a shell command inside the capsule.
+
+**Arguments**:
+
+- `cmd` _str_ - Shell command string to execute.
+- `background` _bool_ - If ``True``, launch the process in the
+ background and return a :class:`CommandHandle` immediately.
+ Defaults to ``False``.
+- `timeout` _int | None_ - Seconds before the foreground command times
+ out. Ignored for background commands. Defaults to ``30``.
+- `envs` _dict[str, str] | None_ - Additional environment variables
+ to set for the process.
+- `cwd` _str | None_ - Working directory for the process.
+- `tag` _str | None_ - Optional label attached to background processes
+ for later retrieval via :meth:`connect`.
+
+
+**Returns**:
+
+- `CommandResult` - stdout, stderr, exit code, and duration for
+ foreground commands (``background=False``).
+
+- `CommandHandle` - PID and tag for background commands
+ (``background=True``).
+
+
+
+#### list
+
+```python
+def list() -> list[ProcessInfo]
+```
+
+List all running background processes in the capsule.
+
+**Returns**:
+
+- `list[ProcessInfo]` - Running processes with their PID, tag, and
+ command information.
+
+
+
+#### kill
+
+```python
+def kill(pid: int) -> None
+```
+
+Send SIGKILL to a background process.
+
+**Arguments**:
+
+- `pid` _int_ - PID of the process to kill.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no process with the given PID exists.
+
+
+
+#### connect
+
+```python
+def connect(pid: int) -> Iterator[StreamEvent]
+```
+
+Connect to a running background process and stream its output.
+
+**Arguments**:
+
+- `pid` _int_ - PID of the background process to attach to.
+
+
+**Yields**:
+
+- `StreamEvent` - Successive output events. Stops on
+ :class:`StreamExitEvent` or :class:`StreamErrorEvent`.
+
+
+
+#### stream
+
+```python
+def stream(cmd: str, args: list[str] | None = None) -> Iterator[StreamEvent]
+```
+
+Execute a command via WebSocket, streaming output as events.
+
+**Arguments**:
+
+- `cmd` _str_ - Command to execute.
+- `args` _list[str] | None_ - Additional arguments for the command.
+ When omitted, *cmd* is interpreted as a shell command
+ string and executed via ``/bin/sh -c``.
+
+
+**Yields**:
+
+- `StreamEvent` - Successive events including :class:`StreamStartEvent`,
+ :class:`StreamStdoutEvent`, :class:`StreamStderrEvent`,
+ :class:`StreamExitEvent`, and :class:`StreamErrorEvent`.
+
+
+
+## AsyncCommands Objects
+
+```python
+class AsyncCommands()
+```
+
+Async command execution interface. Accessed via ``capsule.commands``.
+
+
+
+#### run
+
+```python
+async def run(cmd: str,
+ *,
+ background: bool = False,
+ timeout: int | None = 30,
+ envs: dict[str, str] | None = None,
+ cwd: str | None = None,
+ tag: str | None = None) -> CommandResult | CommandHandle
+```
+
+Execute a shell command inside the capsule.
+
+**Arguments**:
+
+- `cmd` _str_ - Shell command string to execute.
+- `background` _bool_ - If ``True``, launch the process in the
+ background and return a :class:`CommandHandle` immediately.
+ Defaults to ``False``.
+- `timeout` _int | None_ - Seconds before the foreground command times
+ out. Ignored for background commands. Defaults to ``30``.
+- `envs` _dict[str, str] | None_ - Additional environment variables
+ to set for the process.
+- `cwd` _str | None_ - Working directory for the process.
+- `tag` _str | None_ - Optional label attached to background processes
+ for later retrieval via :meth:`connect`.
+
+
+**Returns**:
+
+- `CommandResult` - stdout, stderr, exit code, and duration for
+ foreground commands (``background=False``).
+
+- `CommandHandle` - PID and tag for background commands
+ (``background=True``).
+
+
+
+#### list
+
+```python
+async def list() -> list[ProcessInfo]
+```
+
+List all running background processes in the capsule.
+
+**Returns**:
+
+- `list[ProcessInfo]` - Running processes with their PID, tag, and
+ command information.
+
+
+
+#### kill
+
+```python
+async def kill(pid: int) -> None
+```
+
+Send SIGKILL to a background process.
+
+**Arguments**:
+
+- `pid` _int_ - PID of the process to kill.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no process with the given PID exists.
+
+
+
+#### connect
+
+```python
+async def connect(pid: int) -> AsyncIterator[StreamEvent]
+```
+
+Connect to a running background process and stream its output.
+
+**Arguments**:
+
+- `pid` _int_ - PID of the background process to attach to.
+
+
+**Yields**:
+
+- `StreamEvent` - Successive output events. Stops on
+ :class:`StreamExitEvent` or :class:`StreamErrorEvent`.
+
+
+
+#### stream
+
+```python
+async def stream(cmd: str,
+ args: list[str] | None = None) -> AsyncIterator[StreamEvent]
+```
+
+Execute a command via WebSocket, streaming output as events.
+
+**Arguments**:
+
+- `cmd` _str_ - Command to execute.
+- `args` _list[str] | None_ - Additional arguments for the command.
+ When omitted, *cmd* is interpreted as a shell command
+ string and executed via ``/bin/sh -c``.
+
+
+**Yields**:
+
+- `StreamEvent` - Successive events including :class:`StreamStartEvent`,
+ :class:`StreamStdoutEvent`, :class:`StreamStderrEvent`,
+ :class:`StreamExitEvent`, and :class:`StreamErrorEvent`.
+
+
+
+# wrenn.files
+
+
+
+## Files Objects
+
+```python
+class Files()
+```
+
+Sync filesystem interface. Accessed via ``capsule.files``.
+
+
+
+#### read
+
+```python
+def read(path: str) -> str
+```
+
+Read a file as a UTF-8 string.
+
+**Arguments**:
+
+- `path` _str_ - Absolute path to the file inside the capsule.
+
+
+**Returns**:
+
+- `str` - File contents decoded as UTF-8.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If the path does not exist.
+
+
+
+#### read\_bytes
+
+```python
+def read_bytes(path: str) -> bytes
+```
+
+Read a file as raw bytes.
+
+**Arguments**:
+
+- `path` _str_ - Absolute path to the file inside the capsule.
+
+
+**Returns**:
+
+- `bytes` - Raw file contents.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If the path does not exist.
+
+
+
+#### write
+
+```python
+def write(path: str, data: str | bytes) -> None
+```
+
+Write data to a file inside the capsule.
+
+Creates parent directories if they do not exist.
+
+**Arguments**:
+
+- `path` _str_ - Absolute destination path inside the capsule.
+- `data` _str | bytes_ - Content to write. Strings are UTF-8 encoded.
+
+
+
+#### list
+
+```python
+def list(path: str, depth: int = 1) -> list[FileEntry]
+```
+
+List directory contents.
+
+**Arguments**:
+
+- `path` _str_ - Absolute path to the directory inside the capsule.
+- `depth` _int_ - Recursion depth. ``1`` lists only immediate children.
+ Defaults to ``1``.
+
+
+**Returns**:
+
+- `list[FileEntry]` - Entries in the directory.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If the path does not exist.
+
+
+
+#### exists
+
+```python
+def exists(path: str) -> bool
+```
+
+Check whether a path exists inside the capsule.
+
+**Arguments**:
+
+- `path` _str_ - Absolute path to check.
+
+
+**Returns**:
+
+- `bool` - ``True`` if the path exists.
+
+
+
+#### make\_dir
+
+```python
+def make_dir(path: str) -> FileEntry
+```
+
+Create a directory (with parents). Idempotent.
+
+**Arguments**:
+
+- `path` _str_ - Absolute path of the directory to create.
+
+
+**Returns**:
+
+- `FileEntry` - The created (or already-existing) directory entry.
+
+
+
+#### remove
+
+```python
+def remove(path: str) -> None
+```
+
+Remove a file or directory recursively.
+
+**Arguments**:
+
+- `path` _str_ - Absolute path to remove.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If the path does not exist.
+
+
+
+#### upload\_stream
+
+```python
+def upload_stream(path: str, stream: Iterator[bytes]) -> None
+```
+
+Stream a large file into the capsule.
+
+Prefer this over :meth:`write` when the file is too large to hold in
+memory.
+
+**Arguments**:
+
+- `path` _str_ - Absolute destination path inside the capsule.
+- `stream` _Iterator[bytes]_ - Iterable of byte chunks to upload.
+
+
+
+#### download\_stream
+
+```python
+def download_stream(path: str) -> Iterator[bytes]
+```
+
+Stream a large file out of the capsule.
+
+Prefer this over :meth:`read_bytes` when the file is too large to hold
+in memory.
+
+**Arguments**:
+
+- `path` _str_ - Absolute path to the file inside the capsule.
+
+
+**Yields**:
+
+- `bytes` - Successive byte chunks of the file.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If the path does not exist.
+
+
+
+## AsyncFiles Objects
+
+```python
+class AsyncFiles()
+```
+
+Async filesystem interface. Accessed via ``capsule.files``.
+
+
+
+#### read
+
+```python
+async def read(path: str) -> str
+```
+
+Read a file as a UTF-8 string.
+
+**Arguments**:
+
+- `path` _str_ - Absolute path to the file inside the capsule.
+
+
+**Returns**:
+
+- `str` - File contents decoded as UTF-8.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If the path does not exist.
+
+
+
+#### read\_bytes
+
+```python
+async def read_bytes(path: str) -> bytes
+```
+
+Read a file as raw bytes.
+
+**Arguments**:
+
+- `path` _str_ - Absolute path to the file inside the capsule.
+
+
+**Returns**:
+
+- `bytes` - Raw file contents.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If the path does not exist.
+
+
+
+#### write
+
+```python
+async def write(path: str, data: str | bytes) -> None
+```
+
+Write data to a file inside the capsule.
+
+Creates parent directories if they do not exist.
+
+**Arguments**:
+
+- `path` _str_ - Absolute destination path inside the capsule.
+- `data` _str | bytes_ - Content to write. Strings are UTF-8 encoded.
+
+
+
+#### list
+
+```python
+async def list(path: str, depth: int = 1) -> list[FileEntry]
+```
+
+List directory contents.
+
+**Arguments**:
+
+- `path` _str_ - Absolute path to the directory inside the capsule.
+- `depth` _int_ - Recursion depth. ``1`` lists only immediate children.
+ Defaults to ``1``.
+
+
+**Returns**:
+
+- `list[FileEntry]` - Entries in the directory.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If the path does not exist.
+
+
+
+#### exists
+
+```python
+async def exists(path: str) -> bool
+```
+
+Check whether a path exists inside the capsule.
+
+**Arguments**:
+
+- `path` _str_ - Absolute path to check.
+
+
+**Returns**:
+
+- `bool` - ``True`` if the path exists.
+
+
+
+#### make\_dir
+
+```python
+async def make_dir(path: str) -> FileEntry
+```
+
+Create a directory (with parents). Idempotent.
+
+**Arguments**:
+
+- `path` _str_ - Absolute path of the directory to create.
+
+
+**Returns**:
+
+- `FileEntry` - The created (or already-existing) directory entry.
+
+
+
+#### remove
+
+```python
+async def remove(path: str) -> None
+```
+
+Remove a file or directory recursively.
+
+**Arguments**:
+
+- `path` _str_ - Absolute path to remove.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If the path does not exist.
+
+
+
+#### upload\_stream
+
+```python
+async def upload_stream(path: str, stream: AsyncIterator[bytes]) -> None
+```
+
+Stream a large file into the capsule.
+
+Prefer this over :meth:`write` when the file is too large to hold in
+memory.
+
+**Arguments**:
+
+- `path` _str_ - Absolute destination path inside the capsule.
+- `stream` _AsyncIterator[bytes]_ - Async iterable of byte chunks to
+ upload.
+
+
+
+#### download\_stream
+
+```python
+async def download_stream(path: str) -> AsyncIterator[bytes]
+```
+
+Stream a large file out of the capsule.
+
+Prefer this over :meth:`read_bytes` when the file is too large to hold
+in memory.
+
+**Arguments**:
+
+- `path` _str_ - Absolute path to the file inside the capsule.
+
+
+**Yields**:
+
+- `bytes` - Successive byte chunks of the file.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If the path does not exist.
+
+
+
+# wrenn.code\_interpreter.models
+
+
+
+## ExecutionError Objects
+
+```python
+@dataclass
+class ExecutionError()
+```
+
+Error raised during code execution.
+
+**Attributes**:
+
+- `name` - Exception class name (e.g. ``"NameError"``).
+- `value` - Exception message.
+- `traceback` - Full traceback string.
+
+
+
+## Logs Objects
+
+```python
+@dataclass
+class Logs()
+```
+
+Captured stdout/stderr streams.
+
+Each element in the list is one chunk of text as it arrived from
+the kernel.
+
+
+
+## Result Objects
+
+```python
+@dataclass
+class Result()
+```
+
+A single rich output from code execution.
+
+Jupyter cells can produce multiple outputs — one ``execute_result``
+(the expression value) and zero or more ``display_data`` messages
+(from ``plt.show()``, ``display()``, etc.). Each becomes a
+``Result``.
+
+Known MIME types are unpacked into named attributes; anything else
+lands in :pyattr:`extra`.
+
+
+
+#### text
+
+``text/plain`` representation.
+
+
+
+#### html
+
+``text/html`` representation.
+
+
+
+#### markdown
+
+``text/markdown`` representation.
+
+
+
+#### svg
+
+``image/svg+xml`` representation.
+
+
+
+#### png
+
+``image/png`` — base64-encoded.
+
+
+
+#### jpeg
+
+``image/jpeg`` — base64-encoded.
+
+
+
+#### pdf
+
+``application/pdf`` — base64-encoded.
+
+
+
+#### latex
+
+``text/latex`` representation.
+
+
+
+#### json
+
+``application/json`` representation.
+
+
+
+#### javascript
+
+``application/javascript`` representation.
+
+
+
+#### extra
+
+MIME types not covered by the named fields above.
+
+
+
+#### is\_main\_result
+
+``True`` when this came from an ``execute_result`` message
+(i.e. the value of the last expression in the cell). ``False``
+for ``display_data`` outputs.
+
+
+
+#### from\_bundle
+
+```python
+@classmethod
+def from_bundle(cls,
+ bundle: dict[str, str],
+ *,
+ is_main_result: bool = False) -> Result
+```
+
+Build a ``Result`` from a Jupyter MIME bundle dict.
+
+
+
+#### formats
+
+```python
+def formats() -> list[str]
+```
+
+Return names of non-``None`` MIME-type fields.
+
+
+
+## Execution Objects
+
+```python
+@dataclass
+class Execution()
+```
+
+Complete result of a ``run_code`` call.
+
+**Attributes**:
+
+- `results` - All rich outputs produced by the cell — charts, tables,
+ images, expression values, etc.
+- `logs` - Captured stdout/stderr text.
+- `error` - Populated when the cell raised an exception.
+- `execution_count` - Jupyter execution counter (the ``[N]`` number).
+
+
+
+#### text
+
+```python
+@property
+def text() -> str | None
+```
+
+Convenience — ``text/plain`` of the main ``execute_result``,
+or ``None`` if the cell had no expression value.
+
+
+
+# wrenn.code\_interpreter.async\_capsule
+
+
+
+## AsyncCapsule Objects
+
+```python
+class AsyncCapsule(BaseAsyncCapsule)
+```
+
+Async code interpreter capsule with ``run_code`` support.
+
+Uses ``code-runner-beta`` template by default::
+
+from wrenn.code_interpreter import AsyncCapsule
+
+capsule = await AsyncCapsule.create()
+result = await capsule.run_code("print('hello')")
+
+
+
+#### create
+
+```python
+@classmethod
+async def create(cls,
+ template: str | None = None,
+ vcpus: int | None = None,
+ memory_mb: int | None = None,
+ timeout: int | None = None,
+ *,
+ wait: bool = False,
+ api_key: str | None = None,
+ base_url: str | None = None) -> AsyncCapsule
+```
+
+Create a new async code interpreter capsule.
+
+**Arguments**:
+
+- `template` _str | None_ - Template to boot from. Defaults to
+ ``"code-runner-beta"``.
+- `vcpus` _int | None_ - Number of virtual CPUs.
+- `memory_mb` _int | None_ - Memory in MiB.
+- `timeout` _int | None_ - Inactivity TTL in seconds before auto-pause.
+- `wait` _bool_ - Await until the capsule reaches ``running`` status.
+- `api_key` _str | None_ - Wrenn API key. Falls back to
+ ``WRENN_API_KEY`` env var.
+- `base_url` _str | None_ - API base URL override.
+
+
+**Returns**:
+
+- `AsyncCapsule` - A new async code interpreter capsule instance.
+
+
+
+#### run\_code
+
+```python
+async def run_code(
+ code: str,
+ language: str = "python",
+ timeout: float = 30,
+ jupyter_timeout: float = 30,
+ on_result: Callable[[Result], Any] | None = None,
+ on_stdout: Callable[[str], Any] | None = None,
+ on_stderr: Callable[[str], Any] | None = None,
+ on_error: Callable[[ExecutionError], Any] | None = None) -> Execution
+```
+
+Execute code in a persistent Jupyter kernel (async).
+
+**Arguments**:
+
+- `code` - Code string to execute.
+- `language` - Execution backend language. Currently only ``"python"``.
+- `timeout` - Maximum seconds to wait for execution to complete.
+- `jupyter_timeout` - Maximum seconds to wait for Jupyter to become
+ available.
+- `on_result` - Called for each rich output (charts, images, expression
+ values).
+- `on_stdout` - Called for each stdout chunk.
+- `on_stderr` - Called for each stderr chunk.
+- `on_error` - Called when the cell raises an exception.
+
+
+**Returns**:
+
+ An :class:`Execution` with ``.results``, ``.logs``, ``.error``,
+ and a convenience ``.text`` property.
+
+
+
+# wrenn.code\_interpreter
+
+
+
+# wrenn.code\_interpreter.capsule
+
+
+
+## Capsule Objects
+
+```python
+class Capsule(BaseCapsule)
+```
+
+Code interpreter capsule with ``run_code`` support.
+
+Uses ``code-runner-beta`` template by default::
+
+from wrenn.code_interpreter import Capsule
+
+capsule = Capsule()
+result = capsule.run_code("print('hello')")
+print(result.logs.stdout) # ["hello\n"]
+
+
+
+#### \_\_init\_\_
+
+```python
+def __init__(template: str | None = None,
+ vcpus: int | None = None,
+ memory_mb: int | None = None,
+ timeout: int | None = None,
+ *,
+ api_key: str | None = None,
+ base_url: str | None = None,
+ **kwargs) -> None
+```
+
+Create a code interpreter capsule.
+
+**Arguments**:
+
+- `template` _str | None_ - Template to boot from. Defaults to
+ ``"code-runner-beta"``.
+- `vcpus` _int | None_ - Number of virtual CPUs.
+- `memory_mb` _int | None_ - Memory in MiB.
+- `timeout` _int | None_ - Inactivity TTL in seconds before auto-pause.
+- `api_key` _str | None_ - Wrenn API key. Falls back to
+ ``WRENN_API_KEY`` env var.
+- `base_url` _str | None_ - API base URL override.
+
+
+
+#### create
+
+```python
+@classmethod
+def create(cls,
+ template: str | None = None,
+ vcpus: int | None = None,
+ memory_mb: int | None = None,
+ timeout: int | None = None,
+ *,
+ wait: bool = False,
+ api_key: str | None = None,
+ base_url: str | None = None) -> Capsule
+```
+
+Create a new code interpreter capsule.
+
+**Arguments**:
+
+- `template` _str | None_ - Template to boot from. Defaults to
+ ``"code-runner-beta"``.
+- `vcpus` _int | None_ - Number of virtual CPUs.
+- `memory_mb` _int | None_ - Memory in MiB.
+- `timeout` _int | None_ - Inactivity TTL in seconds before auto-pause.
+- `wait` _bool_ - Block until the capsule reaches ``running`` status.
+- `api_key` _str | None_ - Wrenn API key. Falls back to
+ ``WRENN_API_KEY`` env var.
+- `base_url` _str | None_ - API base URL override.
+
+
+**Returns**:
+
+- `Capsule` - A new code interpreter capsule instance.
+
+
+
+#### run\_code
+
+```python
+def run_code(
+ code: str,
+ language: str = "python",
+ timeout: float = 30,
+ jupyter_timeout: float = 30,
+ on_result: Callable[[Result], Any] | None = None,
+ on_stdout: Callable[[str], Any] | None = None,
+ on_stderr: Callable[[str], Any] | None = None,
+ on_error: Callable[[ExecutionError], Any] | None = None) -> Execution
+```
+
+Execute code in a persistent Jupyter kernel.
+
+Variables, imports, and function definitions survive across calls.
+
+**Arguments**:
+
+- `code` - Code string to execute.
+- `language` - Execution backend language. Currently only ``"python"``.
+- `timeout` - Maximum seconds to wait for execution to complete.
+- `jupyter_timeout` - Maximum seconds to wait for Jupyter to become
+ available.
+- `on_result` - Called for each rich output (charts, images, expression
+ values).
+- `on_stdout` - Called for each stdout chunk.
+- `on_stderr` - Called for each stderr chunk.
+- `on_error` - Called when the cell raises an exception.
+
+
+**Returns**:
+
+ An :class:`Execution` with ``.results``, ``.logs``, ``.error``,
+ and a convenience ``.text`` property.
+
+
+
+# wrenn.exceptions
+
+
+
+## WrennError Objects
+
+```python
+class WrennError(Exception)
+```
+
+Base exception for all Wrenn SDK errors.
+
+All SDK exceptions inherit from this class, so you can catch
+``WrennError`` to handle any API error generically.
+
+**Attributes**:
+
+- `code` _str_ - Machine-readable error code from the API
+ (e.g. ``"not_found"``).
+- `message` _str_ - Human-readable error description.
+- `status_code` _int_ - HTTP status code of the response.
+
+
+
+#### \_\_init\_\_
+
+```python
+def __init__(code: str, message: str, status_code: int) -> None
+```
+
+Initialize a WrennError.
+
+**Arguments**:
+
+- `code` _str_ - Machine-readable error code.
+- `message` _str_ - Human-readable error description.
+- `status_code` _int_ - HTTP status code of the response.
+
+
+
+## WrennValidationError Objects
+
+```python
+class WrennValidationError(WrennError)
+```
+
+400 — Invalid request parameters.
+
+
+
+## WrennAuthenticationError Objects
+
+```python
+class WrennAuthenticationError(WrennError)
+```
+
+401 — Invalid or missing authentication.
+
+
+
+## WrennForbiddenError Objects
+
+```python
+class WrennForbiddenError(WrennError)
+```
+
+403 — Authenticated but not authorized.
+
+
+
+## WrennNotFoundError Objects
+
+```python
+class WrennNotFoundError(WrennError)
+```
+
+404 — Resource not found.
+
+
+
+## WrennConflictError Objects
+
+```python
+class WrennConflictError(WrennError)
+```
+
+409 — State conflict (e.g. invalid_state).
+
+
+
+## WrennHostHasCapsulesError Objects
+
+```python
+class WrennHostHasCapsulesError(WrennConflictError)
+```
+
+409 — Host still has running capsules.
+
+**Attributes**:
+
+- `capsule_ids` _list[str]_ - IDs of the capsules still running on the host.
+
+
+
+#### \_\_init\_\_
+
+```python
+def __init__(code: str, message: str, status_code: int,
+ capsule_ids: list[str]) -> None
+```
+
+Initialize a WrennHostHasCapsulesError.
+
+**Arguments**:
+
+- `code` _str_ - Machine-readable error code.
+- `message` _str_ - Human-readable error description.
+- `status_code` _int_ - HTTP status code of the response.
+- `capsule_ids` _list[str]_ - IDs of capsules still on the host.
+
+
+
+## WrennHostUnavailableError Objects
+
+```python
+class WrennHostUnavailableError(WrennError)
+```
+
+503 — No suitable host available.
+
+
+
+## WrennAgentError Objects
+
+```python
+class WrennAgentError(WrennError)
+```
+
+502 — Host agent returned an error.
+
+
+
+## WrennInternalError Objects
+
+```python
+class WrennInternalError(WrennError)
+```
+
+500 — Unexpected server error.
+
+
+
+# wrenn.async\_capsule
+
+
+
+## AsyncCapsule Objects
+
+```python
+class AsyncCapsule()
+```
+
+Async Wrenn capsule with e2b-compatible interface.
+
+Create via classmethod::
+
+capsule = await AsyncCapsule.create(template="minimal")
+
+Use as async context manager::
+
+async with await AsyncCapsule.create() as capsule:
+await capsule.commands.run("echo hello")
+
+
+
+#### capsule\_id
+
+```python
+@property
+def capsule_id() -> str
+```
+
+The capsule's unique identifier.
+
+**Returns**:
+
+- `str` - Capsule ID assigned by the Wrenn API.
+
+
+
+#### info
+
+```python
+@property
+def info() -> CapsuleModel | None
+```
+
+Cached capsule metadata from the last API call.
+
+**Returns**:
+
+ CapsuleModel | None: The last-fetched capsule model, or ``None``
+ if the capsule was connected without an initial fetch.
+
+
+
+#### create
+
+```python
+@classmethod
+async def create(cls,
+ template: str | None = None,
+ vcpus: int | None = None,
+ memory_mb: int | None = None,
+ timeout: int | None = None,
+ *,
+ wait: bool = False,
+ api_key: str | None = None,
+ base_url: str | None = None) -> AsyncCapsule
+```
+
+Create a new capsule.
+
+**Arguments**:
+
+- `template` _str | None_ - Template name to boot from.
+- `vcpus` _int | None_ - Number of virtual CPUs.
+- `memory_mb` _int | None_ - Memory in MiB.
+- `timeout` _int | None_ - Inactivity TTL in seconds before auto-pause.
+- `wait` _bool_ - Await until the capsule reaches ``running`` status.
+- `api_key` _str | None_ - Wrenn API key. Falls back to
+ ``WRENN_API_KEY`` env var.
+- `base_url` _str | None_ - API base URL override.
+
+
+**Returns**:
+
+- `AsyncCapsule` - A new capsule instance.
+
+
+
+#### connect
+
+```python
+@classmethod
+async def connect(cls,
+ capsule_id: str,
+ *,
+ api_key: str | None = None,
+ base_url: str | None = None) -> AsyncCapsule
+```
+
+Connect to an existing capsule, resuming it if paused.
+
+**Arguments**:
+
+- `capsule_id` _str_ - ID of the capsule to connect to.
+- `api_key` _str | None_ - Wrenn API key. Falls back to
+ ``WRENN_API_KEY`` env var.
+- `base_url` _str | None_ - API base URL override.
+
+
+**Returns**:
+
+- `AsyncCapsule` - A capsule instance bound to the existing capsule.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no capsule with the given ID exists.
+
+
+
+#### ping
+
+```python
+async def ping() -> None
+```
+
+Reset the capsule inactivity timer.
+
+Call this to prevent the capsule from being auto-paused when the
+inactivity TTL is set.
+
+
+
+#### wait\_ready
+
+```python
+async def wait_ready(timeout: float = 30, interval: float = 0.5) -> None
+```
+
+Await until the capsule status is ``running``.
+
+**Arguments**:
+
+- `timeout` _float_ - Maximum seconds to wait. Defaults to ``30``.
+- `interval` _float_ - Polling interval in seconds. Defaults to ``0.5``.
+
+
+**Raises**:
+
+- `TimeoutError` - If the capsule does not reach ``running`` state
+ within ``timeout`` seconds.
+- `RuntimeError` - If the capsule enters an error, stopped, or paused
+ state while waiting.
+
+
+
+#### is\_running
+
+```python
+async def is_running() -> bool
+```
+
+Check whether the capsule is currently running.
+
+Makes a live API call to fetch current status.
+
+**Returns**:
+
+- `bool` - ``True`` if the capsule status is ``running``.
+
+
+
+#### list
+
+```python
+@classmethod
+async def list(cls,
+ *,
+ api_key: str | None = None,
+ base_url: str | None = None) -> list[CapsuleModel]
+```
+
+List all capsules belonging to the team.
+
+**Arguments**:
+
+- `api_key` _str | None_ - Wrenn API key. Falls back to
+ ``WRENN_API_KEY`` env var.
+- `base_url` _str | None_ - API base URL override.
+
+
+**Returns**:
+
+- `list[CapsuleModel]` - All capsules for the authenticated team.
+
+
+
+#### pty
+
+```python
+@asynccontextmanager
+async def pty(cmd: str = "/bin/bash",
+ args: list[str] | None = None,
+ cols: int = 80,
+ rows: int = 24,
+ envs: dict[str, str] | None = None,
+ cwd: str | None = None) -> AsyncIterator[AsyncPtySession]
+```
+
+Open an async interactive PTY session backed by a WebSocket.
+
+Use as an async context manager and async iterate over
+:class:`PtyEvent` objects::
+
+async with capsule.pty() as term:
+await term.write(b"echo hello\n")
+async for event in term:
+if event.type == "output":
+print(event.data.decode())
+
+**Arguments**:
+
+- `cmd` _str_ - Command to run inside the PTY. Defaults to
+ ``"/bin/bash"``.
+- `args` _list[str] | None_ - Additional arguments for ``cmd``.
+- `cols` _int_ - Initial terminal column count. Defaults to ``80``.
+- `rows` _int_ - Initial terminal row count. Defaults to ``24``.
+- `envs` _dict[str, str] | None_ - Additional environment variables
+ to inject into the process.
+- `cwd` _str | None_ - Working directory for the process.
+
+
+**Yields**:
+
+- `AsyncPtySession` - An interactive async PTY session.
+
+
+
+#### pty\_connect
+
+```python
+@asynccontextmanager
+async def pty_connect(tag: str) -> AsyncIterator[AsyncPtySession]
+```
+
+Reconnect to an existing PTY session by tag.
+
+**Arguments**:
+
+- `tag` _str_ - Session tag returned in the ``started`` PTY event.
+
+
+**Yields**:
+
+- `AsyncPtySession` - The reconnected async PTY session.
+
+
+
+#### get\_url
+
+```python
+def get_url(port: int) -> str
+```
+
+Get the proxy URL for a port exposed inside this capsule.
+
+**Arguments**:
+
+- `port` _int_ - Port number to proxy.
+
+
+**Returns**:
+
+- `str` - A ``wss://`` (or ``ws://``) URL that proxies to the given
+ port inside the capsule.
+
+
+
+#### create\_snapshot
+
+```python
+async def create_snapshot(name: str | None = None,
+ overwrite: bool = False) -> Template
+```
+
+Create a snapshot template from this capsule's current state.
+
+**Arguments**:
+
+- `name` _str | None_ - Name for the snapshot template. Auto-generated
+ if not provided.
+- `overwrite` _bool_ - If ``True``, overwrite an existing template with
+ the same name. Defaults to ``False``.
+
+
+**Returns**:
+
+- `Template` - The created snapshot template.
+
+
+
+# wrenn.pty
+
+
+
+## PtySession Objects
+
+```python
+class PtySession()
+```
+
+Interactive PTY session backed by a WebSocket.
+
+Use as a context manager and iterate over events::
+
+with sb.pty(cmd="/bin/bash") as term:
+term.write(b"ls -la\n")
+for event in term:
+if event.type == "output":
+sys.stdout.buffer.write(event.data)
+elif event.type == "exit":
+break
+
+
+
+#### tag
+
+```python
+@property
+def tag() -> str | None
+```
+
+Session tag. Available after the ``started`` event.
+
+
+
+#### pid
+
+```python
+@property
+def pid() -> int | None
+```
+
+Process PID. Available after the ``started`` event.
+
+
+
+#### write
+
+```python
+def write(data: bytes) -> None
+```
+
+Send raw bytes to the PTY stdin.
+
+**Arguments**:
+
+- `data` - Raw bytes to send. Base64-encoded internally.
+
+
+
+#### resize
+
+```python
+def resize(cols: int, rows: int) -> None
+```
+
+Resize the PTY terminal.
+
+**Arguments**:
+
+- `cols` - New column count. Must be > 0.
+- `rows` - New row count. Must be > 0.
+
+
+**Raises**:
+
+- `ValueError` - If cols or rows is 0.
+
+
+
+#### kill
+
+```python
+def kill() -> None
+```
+
+Send SIGKILL to the PTY process.
+
+
+
+## AsyncPtySession Objects
+
+```python
+class AsyncPtySession()
+```
+
+Async interactive PTY session backed by a WebSocket.
+
+Use as an async context manager and async iterate over events::
+
+async with sb.pty(cmd="/bin/bash") as term:
+await term.write(b"ls -la\n")
+async for event in term:
+if event.type == "output":
+sys.stdout.buffer.write(event.data)
+elif event.type == "exit":
+break
+
+
+
+#### tag
+
+```python
+@property
+def tag() -> str | None
+```
+
+Session tag. Available after the ``started`` event.
+
+
+
+#### pid
+
+```python
+@property
+def pid() -> int | None
+```
+
+Process PID. Available after the ``started`` event.
+
+
+
+#### write
+
+```python
+async def write(data: bytes) -> None
+```
+
+Send raw bytes to the PTY stdin.
+
+**Arguments**:
+
+- `data` - Raw bytes to send. Base64-encoded internally.
+
+
+
+#### resize
+
+```python
+async def resize(cols: int, rows: int) -> None
+```
+
+Resize the PTY terminal.
+
+**Arguments**:
+
+- `cols` - New column count. Must be > 0.
+- `rows` - New row count. Must be > 0.
+
+
+**Raises**:
+
+- `ValueError` - If cols or rows is 0.
+
+
+
+#### kill
+
+```python
+async def kill() -> None
+```
+
+Send SIGKILL to the PTY process.
+
+
+
+# wrenn.models.\_generated
+
+
+
+## Peaks Objects
+
+```python
+class Peaks(BaseModel)
+```
+
+Maximum values over the last 30 days.
+
+
+
+## Series Objects
+
+```python
+class Series(BaseModel)
+```
+
+Parallel arrays for chart rendering.
+
+
+
+## Encoding Objects
+
+```python
+class Encoding(StrEnum)
+```
+
+Output encoding. "base64" when stdout/stderr contain binary data.
+
+
+
+## Type2 Objects
+
+```python
+class Type2(StrEnum)
+```
+
+Host type. Regular hosts are shared; BYOC hosts belong to a team.
+
+
+
+# wrenn.models
+
+
+
+# wrenn.capsule
+
+
+
+## Capsule Objects
+
+```python
+class Capsule()
+```
+
+A Wrenn capsule (sandbox) with e2b-compatible interface.
+
+Create directly::
+
+capsule = Capsule(api_key="wrn_...")
+capsule = Capsule(template="minimal") # reads WRENN_API_KEY env
+
+Or via classmethod::
+
+capsule = Capsule.create(template="minimal")
+
+Use as context manager for automatic cleanup::
+
+with Capsule() as capsule:
+capsule.commands.run("echo hello")
+
+
+
+#### \_\_init\_\_
+
+```python
+def __init__(template: str | None = None,
+ vcpus: int | None = None,
+ memory_mb: int | None = None,
+ timeout: int | None = None,
+ *,
+ wait: bool = False,
+ api_key: str | None = None,
+ base_url: str | None = None,
+ _capsule_id: str | None = None,
+ _client: WrennClient | None = None,
+ _info: CapsuleModel | None = None) -> None
+```
+
+Create and start a new capsule.
+
+**Arguments**:
+
+- `template` _str | None_ - Template name to boot from. Defaults to
+ the server-side default (``"minimal"``).
+- `vcpus` _int | None_ - Number of virtual CPUs. Defaults to the
+ server-side default.
+- `memory_mb` _int | None_ - Memory in MiB. Defaults to the
+ server-side default.
+- `timeout` _int | None_ - Inactivity TTL in seconds before the capsule
+ is auto-paused. ``0`` disables auto-pause.
+- `wait` _bool_ - If ``True``, block until the capsule status is
+ ``running`` before returning.
+- `api_key` _str | None_ - Wrenn API key (``wrn_...``). Falls back to
+ the ``WRENN_API_KEY`` environment variable.
+- `base_url` _str | None_ - Wrenn API base URL. Falls back to
+ ``WRENN_BASE_URL`` or the default production endpoint.
+
+
+
+#### capsule\_id
+
+```python
+@property
+def capsule_id() -> str
+```
+
+The capsule's unique identifier.
+
+**Returns**:
+
+- `str` - Capsule ID assigned by the Wrenn API.
+
+
+
+#### info
+
+```python
+@property
+def info() -> CapsuleModel | None
+```
+
+Cached capsule metadata from the last API call.
+
+**Returns**:
+
+ CapsuleModel | None: The last-fetched capsule model, or ``None``
+ if the capsule was connected without an initial fetch.
+
+
+
+#### create
+
+```python
+@classmethod
+def create(cls,
+ template: str | None = None,
+ vcpus: int | None = None,
+ memory_mb: int | None = None,
+ timeout: int | None = None,
+ *,
+ wait: bool = False,
+ api_key: str | None = None,
+ base_url: str | None = None) -> Capsule
+```
+
+Create a new capsule.
+
+Equivalent to calling ``Capsule(...)`` directly.
+
+**Arguments**:
+
+- `template` _str | None_ - Template name to boot from.
+- `vcpus` _int | None_ - Number of virtual CPUs.
+- `memory_mb` _int | None_ - Memory in MiB.
+- `timeout` _int | None_ - Inactivity TTL in seconds before auto-pause.
+- `wait` _bool_ - Block until the capsule reaches ``running`` status.
+- `api_key` _str | None_ - Wrenn API key. Falls back to
+ ``WRENN_API_KEY`` env var.
+- `base_url` _str | None_ - API base URL override.
+
+
+**Returns**:
+
+- `Capsule` - A new capsule instance.
+
+
+
+#### connect
+
+```python
+@classmethod
+def connect(cls,
+ capsule_id: str,
+ *,
+ api_key: str | None = None,
+ base_url: str | None = None) -> Capsule
+```
+
+Connect to an existing capsule, resuming it if paused.
+
+**Arguments**:
+
+- `capsule_id` _str_ - ID of the capsule to connect to.
+- `api_key` _str | None_ - Wrenn API key. Falls back to
+ ``WRENN_API_KEY`` env var.
+- `base_url` _str | None_ - API base URL override.
+
+
+**Returns**:
+
+- `Capsule` - A capsule instance bound to the existing capsule.
+
+
+**Raises**:
+
+- `WrennNotFoundError` - If no capsule with the given ID exists.
+
+
+
+#### ping
+
+```python
+def ping() -> None
+```
+
+Reset the capsule inactivity timer.
+
+Call this to prevent the capsule from being auto-paused when the
+inactivity TTL is set.
+
+
+
+#### wait\_ready
+
+```python
+def wait_ready(timeout: float = 30, interval: float = 0.5) -> None
+```
+
+Block until the capsule status is ``running``.
+
+**Arguments**:
+
+- `timeout` _float_ - Maximum seconds to wait. Defaults to ``30``.
+- `interval` _float_ - Polling interval in seconds. Defaults to ``0.5``.
+
+
+**Raises**:
+
+- `TimeoutError` - If the capsule does not reach ``running`` state
+ within ``timeout`` seconds.
+- `RuntimeError` - If the capsule enters an error, stopped, or paused
+ state while waiting.
+
+
+
+#### is\_running
+
+```python
+def is_running() -> bool
+```
+
+Check whether the capsule is currently running.
+
+Makes a live API call to fetch current status.
+
+**Returns**:
+
+- `bool` - ``True`` if the capsule status is ``running``.
+
+
+
+#### list
+
+```python
+@classmethod
+def list(cls,
+ *,
+ api_key: str | None = None,
+ base_url: str | None = None) -> list[CapsuleModel]
+```
+
+List all capsules belonging to the team.
+
+**Arguments**:
+
+- `api_key` _str | None_ - Wrenn API key. Falls back to
+ ``WRENN_API_KEY`` env var.
+- `base_url` _str | None_ - API base URL override.
+
+
+**Returns**:
+
+- `list[CapsuleModel]` - All capsules for the authenticated team.
+
+
+
+#### pty
+
+```python
+@contextmanager
+def pty(cmd: str = "/bin/bash",
+ args: list[str] | None = None,
+ cols: int = 80,
+ rows: int = 24,
+ envs: dict[str, str] | None = None,
+ cwd: str | None = None) -> Iterator[PtySession]
+```
+
+Open an interactive PTY session backed by a WebSocket.
+
+Use as a context manager and iterate over :class:`PtyEvent` objects::
+
+with capsule.pty() as term:
+term.write(b"echo hello\n")
+for event in term:
+if event.type == "output":
+print(event.data.decode())
+
+**Arguments**:
+
+- `cmd` _str_ - Command to run inside the PTY. Defaults to
+ ``"/bin/bash"``.
+- `args` _list[str] | None_ - Additional arguments for ``cmd``.
+- `cols` _int_ - Initial terminal column count. Defaults to ``80``.
+- `rows` _int_ - Initial terminal row count. Defaults to ``24``.
+- `envs` _dict[str, str] | None_ - Additional environment variables to
+ inject into the process.
+- `cwd` _str | None_ - Working directory for the process.
+
+
+**Yields**:
+
+- `PtySession` - An interactive PTY session.
+
+
+
+#### pty\_connect
+
+```python
+@contextmanager
+def pty_connect(tag: str) -> Iterator[PtySession]
+```
+
+Reconnect to an existing PTY session by tag.
+
+**Arguments**:
+
+- `tag` _str_ - Session tag returned in the ``started`` PTY event.
+
+
+**Yields**:
+
+- `PtySession` - The reconnected PTY session.
+
+
+
+#### get\_url
+
+```python
+def get_url(port: int) -> str
+```
+
+Get the proxy URL for a port exposed inside this capsule.
+
+**Arguments**:
+
+- `port` _int_ - Port number to proxy.
+
+
+**Returns**:
+
+- `str` - A ``wss://`` (or ``ws://``) URL that proxies to the given
+ port inside the capsule.
+
+
+
+#### create\_snapshot
+
+```python
+def create_snapshot(name: str | None = None,
+ overwrite: bool = False) -> Template
+```
+
+Create a snapshot template from this capsule's current state.
+
+**Arguments**:
+
+- `name` _str | None_ - Name for the snapshot template. Auto-generated
+ if not provided.
+- `overwrite` _bool_ - If ``True``, overwrite an existing template with
+ the same name. Defaults to ``False``.
+
+
+**Returns**:
+
+- `Template` - The created snapshot template.
+
+
+
+# wrenn.\_config
+
+
+
+## ConnectionConfig Objects
+
+```python
+@dataclass(frozen=True)
+class ConnectionConfig()
+```
+
+Resolved credentials and base URL for Wrenn API calls.
+
+
+
+# wrenn.\_git.\_auth
+
+
+
+#### embed\_credentials
+
+```python
+def embed_credentials(url: str, username: str, password: str) -> str
+```
+
+Embed HTTP(S) credentials into a git URL.
+
+**Arguments**:
+
+- `url` - Git repository URL.
+- `username` - Username for authentication.
+- `password` - Password or personal access token.
+
+
+**Returns**:
+
+ URL with ``username:password@`` embedded in the netloc.
+
+
+**Raises**:
+
+- `ValueError` - If the URL scheme is not ``http`` or ``https``.
+
+
+
+#### strip\_credentials
+
+```python
+def strip_credentials(url: str) -> str
+```
+
+Remove embedded credentials from a git URL.
+
+**Arguments**:
+
+- `url` - Git repository URL, possibly with credentials.
+
+
+**Returns**:
+
+ URL with credentials removed. Non-HTTP(S) URLs are returned
+ unchanged.
+
+
+
+#### is\_auth\_error
+
+```python
+def is_auth_error(stderr: str) -> bool
+```
+
+Check whether git stderr indicates an authentication failure.
+
+**Arguments**:
+
+- `stderr` - Combined stderr output from a git command.
+
+
+**Returns**:
+
+ ``True`` if any known auth-failure pattern is found.
+
+
+
+#### build\_credential\_approve\_cmd
+
+```python
+def build_credential_approve_cmd(username: str,
+ password: str,
+ host: str = "github.com",
+ protocol: str = "https") -> str
+```
+
+Build a shell command that pipes credentials into ``git credential approve``.
+
+**Arguments**:
+
+- `username` - Git username.
+- `password` - Password or personal access token.
+- `host` - Target host. Defaults to ``"github.com"``.
+- `protocol` - Protocol. Defaults to ``"https"``.
+
+
+**Returns**:
+
+ A shell command string safe to pass to ``commands.run()``.
+
+
+
+# wrenn.\_git.\_cmd
+
+Pure functions that build git argument lists and parse git output.
+
+No I/O, no network, no imports from ``wrenn``. Every ``build_*`` function
+returns a ``list[str]`` suitable for ``shlex.join()``. Every ``parse_*``
+function takes raw stdout and returns a typed structure.
+
+
+
+## FileStatus Objects
+
+```python
+@dataclass
+class FileStatus()
+```
+
+A single entry from ``git status --porcelain=v1``.
+
+**Attributes**:
+
+- `path` _str_ - File path relative to the repository root.
+- `index_status` _str_ - Index (staged) status character.
+- `work_tree_status` _str_ - Working-tree status character.
+- `renamed_from` _str | None_ - Original path when status is a rename.
+
+
+
+#### staged
+
+```python
+@property
+def staged() -> bool
+```
+
+Whether the change is staged in the index.
+
+
+
+#### status
+
+```python
+@property
+def status() -> str
+```
+
+Normalized human-readable status label.
+
+
+
+## GitStatus Objects
+
+```python
+@dataclass
+class GitStatus()
+```
+
+Parsed output of ``git status --porcelain=v1 --branch``.
+
+**Attributes**:
+
+- `branch` _str | None_ - Current branch name, or ``None`` if detached.
+- `upstream` _str | None_ - Upstream tracking branch.
+- `ahead` _int_ - Commits ahead of upstream.
+- `behind` _int_ - Commits behind upstream.
+- `detached` _bool_ - Whether HEAD is detached.
+- `files` _list[FileStatus]_ - Per-file status entries.
+
+
+
+#### is\_clean
+
+```python
+@property
+def is_clean() -> bool
+```
+
+``True`` when there are no changed or untracked files.
+
+
+
+#### has\_staged
+
+```python
+@property
+def has_staged() -> bool
+```
+
+``True`` when at least one file has staged changes.
+
+
+
+#### has\_untracked
+
+```python
+@property
+def has_untracked() -> bool
+```
+
+``True`` when at least one file is untracked.
+
+
+
+#### has\_conflicts
+
+```python
+@property
+def has_conflicts() -> bool
+```
+
+``True`` when at least one file has merge conflicts.
+
+
+
+## GitBranch Objects
+
+```python
+@dataclass
+class GitBranch()
+```
+
+A single branch entry.
+
+**Attributes**:
+
+- `name` _str_ - Branch name (short ref).
+- `is_current` _bool_ - Whether this is the checked-out branch.
+
+
+
+#### build\_clone
+
+```python
+def build_clone(url: str,
+ dest: str | None = None,
+ *,
+ branch: str | None = None,
+ depth: int | None = None) -> list[str]
+```
+
+Build ``git clone`` arguments.
+
+
+
+#### build\_init
+
+```python
+def build_init(path: str = ".",
+ *,
+ bare: bool = False,
+ initial_branch: str | None = None) -> list[str]
+```
+
+Build ``git init`` arguments.
+
+
+
+#### build\_add
+
+```python
+def build_add(paths: list[str] | None = None,
+ *,
+ all: bool = False) -> list[str]
+```
+
+Build ``git add`` arguments.
+
+
+
+#### build\_commit
+
+```python
+def build_commit(message: str,
+ *,
+ allow_empty: bool = False,
+ author_name: str | None = None,
+ author_email: str | None = None) -> list[str]
+```
+
+Build ``git commit`` arguments.
+
+
+
+#### build\_push
+
+```python
+def build_push(remote: str = "origin",
+ branch: str | None = None,
+ *,
+ force: bool = False,
+ set_upstream: bool = False) -> list[str]
+```
+
+Build ``git push`` arguments.
+
+
+
+#### build\_pull
+
+```python
+def build_pull(remote: str = "origin",
+ branch: str | None = None,
+ *,
+ rebase: bool = False,
+ ff_only: bool = False) -> list[str]
+```
+
+Build ``git pull`` arguments.
+
+
+
+#### build\_status
+
+```python
+def build_status() -> list[str]
+```
+
+Build ``git status`` arguments for porcelain parsing.
+
+
+
+#### build\_branches
+
+```python
+def build_branches() -> list[str]
+```
+
+Build ``git branch`` arguments for structured parsing.
+
+
+
+#### build\_create\_branch
+
+```python
+def build_create_branch(name: str,
+ *,
+ start_point: str | None = None) -> list[str]
+```
+
+Build ``git checkout -b`` arguments.
+
+
+
+#### build\_checkout
+
+```python
+def build_checkout(name: str) -> list[str]
+```
+
+Build ``git checkout`` arguments.
+
+
+
+#### build\_delete\_branch
+
+```python
+def build_delete_branch(name: str, *, force: bool = False) -> list[str]
+```
+
+Build ``git branch -d/-D`` arguments.
+
+
+
+#### build\_remote\_add
+
+```python
+def build_remote_add(name: str, url: str, *, fetch: bool = False) -> list[str]
+```
+
+Build ``git remote add`` arguments.
+
+
+
+#### build\_remote\_get\_url
+
+```python
+def build_remote_get_url(name: str = "origin") -> list[str]
+```
+
+Build ``git remote get-url`` arguments.
+
+
+
+#### build\_remote\_set\_url
+
+```python
+def build_remote_set_url(name: str, url: str) -> list[str]
+```
+
+Build ``git remote set-url`` arguments.
+
+
+
+#### build\_reset
+
+```python
+def build_reset(*,
+ mode: str | None = None,
+ ref: str | None = None,
+ paths: list[str] | None = None) -> list[str]
+```
+
+Build ``git reset`` arguments.
+
+**Arguments**:
+
+- `mode` - Reset mode (``soft``, ``mixed``, ``hard``, ``merge``, ``keep``).
+- `ref` - Commit, branch, or ref to reset to.
+- `paths` - Paths to reset (mutually exclusive with ``mode``).
+
+
+
+#### build\_restore
+
+```python
+def build_restore(paths: list[str],
+ *,
+ staged: bool = False,
+ worktree: bool = False,
+ source: str | None = None) -> list[str]
+```
+
+Build ``git restore`` arguments.
+
+**Arguments**:
+
+- `paths` - Paths to restore.
+- `staged` - Restore the index (unstage).
+- `worktree` - Restore working-tree files.
+- `source` - Commit or ref to restore from.
+
+
+
+#### build\_config\_set
+
+```python
+def build_config_set(key: str,
+ value: str,
+ *,
+ scope: str = "local",
+ repo_path: str | None = None) -> list[str]
+```
+
+Build ``git config`` set arguments.
+
+
+
+#### build\_config\_get
+
+```python
+def build_config_get(key: str,
+ *,
+ scope: str = "local",
+ repo_path: str | None = None) -> list[str]
+```
+
+Build ``git config --get`` arguments.
+
+
+
+#### build\_has\_upstream
+
+```python
+def build_has_upstream() -> list[str]
+```
+
+Build arguments to check if current branch has upstream tracking.
+
+
+
+#### parse\_status
+
+```python
+def parse_status(stdout: str) -> GitStatus
+```
+
+Parse ``git status --porcelain=v1 --branch`` output.
+
+**Arguments**:
+
+- `stdout` - Raw stdout from the git status command.
+
+
+**Returns**:
+
+ Parsed :class:`GitStatus`.
+
+
+
+#### parse\_branches
+
+```python
+def parse_branches(stdout: str) -> list[GitBranch]
+```
+
+Parse ``git branch --format=%(refname:short)\t%(HEAD)`` output.
+
+**Arguments**:
+
+- `stdout` - Raw stdout from the git branch command.
+
+
+**Returns**:
+
+ List of :class:`GitBranch`.
+
+
+
+# wrenn.\_git.exceptions
+
+
+
+## GitError Objects
+
+```python
+class GitError(Exception)
+```
+
+Base exception for all git operations inside a capsule.
+
+Not a subclass of :class:`WrennError` because git errors originate
+from a process exit code, not an HTTP response.
+
+**Attributes**:
+
+- `message` _str_ - Human-readable error description.
+- `stderr` _str_ - Raw stderr output from the git process.
+- `exit_code` _int_ - Process exit code.
+
+
+
+## GitCommandError Objects
+
+```python
+class GitCommandError(GitError)
+```
+
+A git command exited with a non-zero exit code.
+
+
+
+## GitAuthError Objects
+
+```python
+class GitAuthError(GitError)
+```
+
+Authentication failed when communicating with a remote.
+
+
+
+# wrenn.\_git
+
+Git operations inside a Wrenn capsule.
+
+Provides :class:`Git` (sync) and :class:`AsyncGit` (async) interfaces
+accessed via ``capsule.git``. All operations execute the real ``git``
+binary inside the capsule through :class:`~wrenn.commands.Commands`.
+
+
+
+## Git Objects
+
+```python
+class Git()
+```
+
+Sync git interface. Accessed via ``capsule.git``.
+
+Executes the real ``git`` binary inside the capsule through
+:meth:`Commands.run`. Methods raise :class:`GitCommandError` (or
+:class:`GitAuthError`) on non-zero exit codes.
+
+
+
+#### clone
+
+```python
+def clone(url: str,
+ dest: str | None = None,
+ *,
+ branch: str | None = None,
+ depth: int | None = None,
+ username: str | None = None,
+ password: str | None = None,
+ dangerously_store_credentials: bool = False,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 300) -> CommandResult
+```
+
+Clone a remote repository into the capsule.
+
+**Arguments**:
+
+- `url` - Remote repository URL.
+- `dest` - Destination path. Defaults to the repository name
+ derived from the URL.
+- `branch` - Branch or tag to check out.
+- `depth` - Create a shallow clone with this many commits.
+- `username` - Username for HTTP(S) authentication.
+- `password` - Password or token for HTTP(S) authentication.
+- `dangerously_store_credentials` - If ``True``, leave credentials
+ embedded in the remote URL after cloning.
+- `cwd` - Working directory for the command.
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds. Defaults to ``300``.
+
+
+**Returns**:
+
+ Command result with stdout, stderr, exit_code, and duration.
+
+
+**Raises**:
+
+- `GitAuthError` - If the remote rejected authentication.
+- `GitCommandError` - If clone failed for another reason.
+- `ValueError` - If *password* is provided without *username*.
+
+
+
+#### init
+
+```python
+def init(path: str = ".",
+ *,
+ bare: bool = False,
+ initial_branch: str | None = None,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Initialize a new git repository.
+
+**Arguments**:
+
+- `path` - Destination path for the repository.
+- `bare` - Create a bare repository.
+- `initial_branch` - Name for the initial branch (e.g. ``"main"``).
+- `cwd` - Working directory for the command.
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Command result.
+
+
+**Raises**:
+
+- `GitCommandError` - If init failed.
+
+
+
+#### add
+
+```python
+def add(paths: list[str] | None = None,
+ *,
+ all: bool = False,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Stage files for commit.
+
+**Arguments**:
+
+- `paths` - Specific files to stage. If ``None``, stages the
+ current directory (or all with ``all=True``).
+- `all` - Stage all changes including untracked files.
+- `cwd` - Working directory (repository root).
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Command result.
+
+
+**Raises**:
+
+- `GitCommandError` - If add failed.
+
+
+
+#### commit
+
+```python
+def commit(message: str,
+ *,
+ allow_empty: bool = False,
+ author_name: str | None = None,
+ author_email: str | None = None,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Create a commit.
+
+**Arguments**:
+
+- `message` - Commit message.
+- `allow_empty` - Allow creating a commit with no changes.
+- `author_name` - Override the commit author name.
+- `author_email` - Override the commit author email.
+- `cwd` - Working directory (repository root).
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Command result.
+
+
+**Raises**:
+
+- `GitCommandError` - If commit failed.
+
+
+
+#### push
+
+```python
+def push(remote: str = "origin",
+ branch: str | None = None,
+ *,
+ force: bool = False,
+ set_upstream: bool = False,
+ username: str | None = None,
+ password: str | None = None,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 60) -> CommandResult
+```
+
+Push commits to a remote.
+
+**Arguments**:
+
+- `remote` - Remote name. Defaults to ``"origin"``.
+- `branch` - Branch to push. Defaults to the current branch.
+- `force` - Force-push.
+- `set_upstream` - Set upstream tracking reference.
+- `username` - Username for HTTP(S) authentication.
+- `password` - Password or token for HTTP(S) authentication.
+- `cwd` - Working directory (repository root).
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Command result.
+
+
+**Raises**:
+
+- `GitAuthError` - If authentication failed.
+- `GitCommandError` - If push failed.
+
+
+
+#### pull
+
+```python
+def pull(remote: str = "origin",
+ branch: str | None = None,
+ *,
+ rebase: bool = False,
+ ff_only: bool = False,
+ username: str | None = None,
+ password: str | None = None,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 60) -> CommandResult
+```
+
+Pull changes from a remote.
+
+**Arguments**:
+
+- `remote` - Remote name. Defaults to ``"origin"``.
+- `branch` - Branch to pull.
+- `rebase` - Rebase instead of merge.
+- `ff_only` - Only allow fast-forward merges.
+- `username` - Username for HTTP(S) authentication.
+- `password` - Password or token for HTTP(S) authentication.
+- `cwd` - Working directory (repository root).
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Command result.
+
+
+**Raises**:
+
+- `GitAuthError` - If authentication failed.
+- `GitCommandError` - If pull failed.
+
+
+
+#### status
+
+```python
+def status(*,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> GitStatus
+```
+
+Get repository status.
+
+**Arguments**:
+
+- `cwd` - Working directory (repository root).
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Parsed :class:`GitStatus` with branch info and file changes.
+
+
+**Raises**:
+
+- `GitCommandError` - If the command failed.
+
+
+
+#### branches
+
+```python
+def branches(*,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> list[GitBranch]
+```
+
+List local branches.
+
+**Arguments**:
+
+- `cwd` - Working directory (repository root).
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ List of :class:`GitBranch`.
+
+
+**Raises**:
+
+- `GitCommandError` - If the command failed.
+
+
+
+#### create\_branch
+
+```python
+def create_branch(name: str,
+ *,
+ start_point: str | None = None,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Create and check out a new branch.
+
+**Arguments**:
+
+- `name` - Branch name.
+- `start_point` - Commit or ref to branch from.
+- `cwd` - Working directory (repository root).
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Command result.
+
+
+**Raises**:
+
+- `GitCommandError` - If the command failed.
+
+
+
+#### checkout\_branch
+
+```python
+def checkout_branch(name: str,
+ *,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Check out an existing branch.
+
+**Arguments**:
+
+- `name` - Branch name.
+- `cwd` - Working directory (repository root).
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Command result.
+
+
+**Raises**:
+
+- `GitCommandError` - If the command failed.
+
+
+
+#### delete\_branch
+
+```python
+def delete_branch(name: str,
+ *,
+ force: bool = False,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Delete a branch.
+
+**Arguments**:
+
+- `name` - Branch name.
+- `force` - Force-delete with ``-D``.
+- `cwd` - Working directory (repository root).
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Command result.
+
+
+**Raises**:
+
+- `GitCommandError` - If the command failed.
+
+
+
+#### remote\_add
+
+```python
+def remote_add(name: str,
+ url: str,
+ *,
+ fetch: bool = False,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Add a remote.
+
+**Arguments**:
+
+- `name` - Remote name (e.g. ``"origin"``).
+- `url` - Remote URL.
+- `fetch` - Fetch after adding.
+- `cwd` - Working directory (repository root).
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Command result.
+
+
+**Raises**:
+
+- `GitCommandError` - If the command failed.
+
+
+
+#### remote\_get
+
+```python
+def remote_get(name: str = "origin",
+ *,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> str | None
+```
+
+Get the URL of a remote.
+
+Returns ``None`` if the remote does not exist rather than raising.
+
+**Arguments**:
+
+- `name` - Remote name. Defaults to ``"origin"``.
+- `cwd` - Working directory (repository root).
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Remote URL or ``None``.
+
+
+
+#### reset
+
+```python
+def reset(*,
+ mode: str | None = None,
+ ref: str | None = None,
+ paths: list[str] | None = None,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Reset the current HEAD.
+
+**Arguments**:
+
+- `mode` - Reset mode (``soft``, ``mixed``, ``hard``, ``merge``,
+ ``keep``).
+- `ref` - Commit, branch, or ref to reset to.
+- `paths` - Paths to reset.
+- `cwd` - Working directory (repository root).
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Command result.
+
+
+**Raises**:
+
+- `GitCommandError` - If the command failed.
+
+
+
+#### restore
+
+```python
+def restore(paths: list[str],
+ *,
+ staged: bool = False,
+ worktree: bool = False,
+ source: str | None = None,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Restore working-tree files or unstage changes.
+
+**Arguments**:
+
+- `paths` - Paths to restore.
+- `staged` - Restore the index (unstage).
+- `worktree` - Restore working-tree files.
+- `source` - Commit or ref to restore from.
+- `cwd` - Working directory (repository root).
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Command result.
+
+
+**Raises**:
+
+- `GitCommandError` - If the command failed.
+
+
+
+#### set\_config
+
+```python
+def set_config(key: str,
+ value: str,
+ *,
+ scope: str = "local",
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Set a git config value.
+
+**Arguments**:
+
+- `key` - Config key (e.g. ``"user.name"``).
+- `value` - Config value.
+- `scope` - Config scope: ``"local"``, ``"global"``, or
+ ``"system"``.
+- `cwd` - Working directory (repository root). Required when
+ scope is ``"local"``.
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Command result.
+
+
+**Raises**:
+
+- `GitCommandError` - If the command failed.
+
+
+
+#### get\_config
+
+```python
+def get_config(key: str,
+ *,
+ scope: str = "local",
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> str | None
+```
+
+Get a git config value.
+
+Returns ``None`` if the key is not set rather than raising.
+
+**Arguments**:
+
+- `key` - Config key (e.g. ``"user.name"``).
+- `scope` - Config scope: ``"local"``, ``"global"``, or
+ ``"system"``.
+- `cwd` - Working directory (repository root). Required when
+ scope is ``"local"``.
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Returns**:
+
+ Config value or ``None``.
+
+
+
+#### configure\_user
+
+```python
+def configure_user(name: str,
+ email: str,
+ *,
+ scope: str = "global",
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> None
+```
+
+Configure git user name and email.
+
+**Arguments**:
+
+- `name` - Git user name.
+- `email` - Git user email.
+- `scope` - Config scope. Defaults to ``"global"``.
+- `cwd` - Working directory (repository root). Required when
+ scope is ``"local"``.
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Raises**:
+
+- `ValueError` - If *name* or *email* is empty.
+- `GitCommandError` - If a config command failed.
+
+
+
+#### dangerously\_authenticate
+
+```python
+def dangerously_authenticate(username: str,
+ password: str,
+ host: str = "github.com",
+ protocol: str = "https",
+ *,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> None
+```
+
+Persist git credentials via the credential store.
+
+.. warning::
+
+Credentials are written in plain text to the capsule
+filesystem and are accessible to any process running inside
+the capsule. Prefer per-operation ``username``/``password``
+parameters on :meth:`clone`, :meth:`push`, and :meth:`pull`
+instead.
+
+**Arguments**:
+
+- `username` - Git username.
+- `password` - Password or personal access token.
+- `host` - Target host. Defaults to ``"github.com"``.
+- `protocol` - Protocol. Defaults to ``"https"``.
+- `cwd` - Working directory.
+- `envs` - Extra environment variables.
+- `timeout` - Command timeout in seconds.
+
+
+**Raises**:
+
+- `ValueError` - If *username* or *password* is empty.
+- `GitCommandError` - If a command failed.
+
+
+
+## AsyncGit Objects
+
+```python
+class AsyncGit()
+```
+
+Async git interface. Accessed via ``capsule.git``.
+
+Async mirror of :class:`Git`. See that class for full method
+documentation.
+
+
+
+#### clone
+
+```python
+async def clone(url: str,
+ dest: str | None = None,
+ *,
+ branch: str | None = None,
+ depth: int | None = None,
+ username: str | None = None,
+ password: str | None = None,
+ dangerously_store_credentials: bool = False,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 300) -> CommandResult
+```
+
+Clone a remote repository into the capsule.
+
+
+
+#### init
+
+```python
+async def init(path: str = ".",
+ *,
+ bare: bool = False,
+ initial_branch: str | None = None,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Initialize a new git repository.
+
+
+
+#### add
+
+```python
+async def add(paths: list[str] | None = None,
+ *,
+ all: bool = False,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Stage files for commit.
+
+
+
+#### commit
+
+```python
+async def commit(message: str,
+ *,
+ allow_empty: bool = False,
+ author_name: str | None = None,
+ author_email: str | None = None,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Create a commit.
+
+
+
+#### push
+
+```python
+async def push(remote: str = "origin",
+ branch: str | None = None,
+ *,
+ force: bool = False,
+ set_upstream: bool = False,
+ username: str | None = None,
+ password: str | None = None,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 60) -> CommandResult
+```
+
+Push commits to a remote.
+
+
+
+#### pull
+
+```python
+async def pull(remote: str = "origin",
+ branch: str | None = None,
+ *,
+ rebase: bool = False,
+ ff_only: bool = False,
+ username: str | None = None,
+ password: str | None = None,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 60) -> CommandResult
+```
+
+Pull changes from a remote.
+
+
+
+#### status
+
+```python
+async def status(*,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> GitStatus
+```
+
+Get repository status.
+
+
+
+#### branches
+
+```python
+async def branches(*,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> list[GitBranch]
+```
+
+List local branches.
+
+
+
+#### create\_branch
+
+```python
+async def create_branch(name: str,
+ *,
+ start_point: str | None = None,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Create and check out a new branch.
+
+
+
+#### checkout\_branch
+
+```python
+async def checkout_branch(name: str,
+ *,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Check out an existing branch.
+
+
+
+#### delete\_branch
+
+```python
+async def delete_branch(name: str,
+ *,
+ force: bool = False,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Delete a branch.
+
+
+
+#### remote\_add
+
+```python
+async def remote_add(name: str,
+ url: str,
+ *,
+ fetch: bool = False,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Add a remote.
+
+
+
+#### remote\_get
+
+```python
+async def remote_get(name: str = "origin",
+ *,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> str | None
+```
+
+Get the URL of a remote. Returns ``None`` if not found.
+
+
+
+#### reset
+
+```python
+async def reset(*,
+ mode: str | None = None,
+ ref: str | None = None,
+ paths: list[str] | None = None,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Reset the current HEAD.
+
+
+
+#### restore
+
+```python
+async def restore(paths: list[str],
+ *,
+ staged: bool = False,
+ worktree: bool = False,
+ source: str | None = None,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Restore working-tree files or unstage changes.
+
+
+
+#### set\_config
+
+```python
+async def set_config(key: str,
+ value: str,
+ *,
+ scope: str = "local",
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> CommandResult
+```
+
+Set a git config value.
+
+
+
+#### get\_config
+
+```python
+async def get_config(key: str,
+ *,
+ scope: str = "local",
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> str | None
+```
+
+Get a git config value. Returns ``None`` if not set.
+
+
+
+#### configure\_user
+
+```python
+async def configure_user(name: str,
+ email: str,
+ *,
+ scope: str = "global",
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> None
+```
+
+Configure git user name and email.
+
+
+
+#### dangerously\_authenticate
+
+```python
+async def dangerously_authenticate(username: str,
+ password: str,
+ host: str = "github.com",
+ protocol: str = "https",
+ *,
+ cwd: str | None = None,
+ envs: dict[str, str] | None = None,
+ timeout: int | None = 30) -> None
+```
+
+Persist git credentials via the credential store.
+
+.. warning::
+
+Credentials are written in plain text to the capsule
+filesystem. Prefer per-operation ``username``/``password``
+parameters instead.
+
diff --git a/pydoc-markdown.yml b/pydoc-markdown.yml
new file mode 100644
index 0000000..ab816e4
--- /dev/null
+++ b/pydoc-markdown.yml
@@ -0,0 +1,12 @@
+loaders:
+ - type: python
+ search_path: [src]
+
+processors:
+ - type: google # Use Google-style docstring parser
+ - type: filter
+ - type: crossref
+
+renderer:
+ type: markdown
+ escape_html_in_docstring: false
diff --git a/pyproject.toml b/pyproject.toml
index 98570b7..c8f5d1a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "wrenn"
-version = "0.1.0"
+version = "0.1.1"
description = "Python SDK for Wrenn"
readme = "README.md"
license = "MIT"
@@ -36,6 +36,8 @@ build-backend = "hatchling.build"
dev = [
"datamodel-code-generator[ruff]>=0.56.0",
"mypy>=1.20.0",
+ "pre-commit>=4.6.0",
+ "pydoc-markdown>=4.8.2",
"pytest>=9.0.3",
"pytest-asyncio>=1.3.0",
"respx>=0.23.1",
diff --git a/src/wrenn/async_capsule.py b/src/wrenn/async_capsule.py
index 3e92de7..509f53a 100644
--- a/src/wrenn/async_capsule.py
+++ b/src/wrenn/async_capsule.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import asyncio
+import builtins
import time
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
@@ -102,6 +103,7 @@ class AsyncCapsule:
memory_mb=memory_mb,
timeout_sec=timeout,
)
+ assert info.id is not None
capsule = cls(
_capsule_id=info.id,
_client=client,
@@ -284,7 +286,7 @@ class AsyncCapsule:
async def pty(
self,
cmd: str = "/bin/bash",
- args: list[str] | None = None,
+ args: builtins.list[str] | None = None,
cols: int = 80,
rows: int = 24,
envs: dict[str, str] | None = None,
@@ -316,7 +318,7 @@ class AsyncCapsule:
"""
async with httpx_ws.aconnect_ws(
f"/v1/capsules/{self._id}/pty", client=self._client.http
- ) as ws:
+ ) as ws: # type: httpx_ws.AsyncWebSocketSession
session = AsyncPtySession(ws, self._id)
await session._send_start(
cmd=cmd, args=args, cols=cols, rows=rows, envs=envs, cwd=cwd
@@ -335,7 +337,7 @@ class AsyncCapsule:
"""
async with httpx_ws.aconnect_ws(
f"/v1/capsules/{self._id}/pty", client=self._client.http
- ) as ws:
+ ) as ws: # type: httpx_ws.AsyncWebSocketSession
session = AsyncPtySession(ws, self._id)
await session._send_connect(tag)
yield session
diff --git a/src/wrenn/capsule.py b/src/wrenn/capsule.py
index 400409f..a5ed36a 100644
--- a/src/wrenn/capsule.py
+++ b/src/wrenn/capsule.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import builtins
import time
from collections.abc import Iterator
from contextlib import contextmanager
@@ -94,9 +95,8 @@ class Capsule:
``WRENN_BASE_URL`` or the default production endpoint.
"""
if _capsule_id is not None:
- # Internal construction path (from create/connect classmethods)
assert _client is not None
- self._id = _capsule_id
+ self._id: str = _capsule_id
self._client = _client
self._info = _info
else:
@@ -108,6 +108,7 @@ class Capsule:
memory_mb=memory_mb,
timeout_sec=timeout,
)
+ assert self._info.id is not None
self._id = self._info.id
self.commands = Commands(self._id, self._client.http)
@@ -360,7 +361,7 @@ class Capsule:
def pty(
self,
cmd: str = "/bin/bash",
- args: list[str] | None = None,
+ args: builtins.list[str] | None = None,
cols: int = 80,
rows: int = 24,
envs: dict[str, str] | None = None,
@@ -391,7 +392,7 @@ class Capsule:
"""
with httpx_ws.connect_ws(
f"/v1/capsules/{self._id}/pty", client=self._client.http
- ) as ws:
+ ) as ws: # type: httpx_ws.WebSocketSession
session = PtySession(ws, self._id)
session._send_start(
cmd=cmd, args=args, cols=cols, rows=rows, envs=envs, cwd=cwd
@@ -410,7 +411,7 @@ class Capsule:
"""
with httpx_ws.connect_ws(
f"/v1/capsules/{self._id}/pty", client=self._client.http
- ) as ws:
+ ) as ws: # type: httpx_ws.WebSocketSession
session = PtySession(ws, self._id)
session._send_connect(tag)
yield session
diff --git a/src/wrenn/client.py b/src/wrenn/client.py
index c927396..c51b190 100644
--- a/src/wrenn/client.py
+++ b/src/wrenn/client.py
@@ -6,6 +6,7 @@ import httpx
from wrenn._config import DEFAULT_BASE_URL, ENV_API_KEY, ENV_BASE_URL
from wrenn.exceptions import handle_response
+
from wrenn.models import (
Template,
)
@@ -13,6 +14,8 @@ from wrenn.models import (
Capsule as CapsuleModel,
)
+_LONG_TIMEOUT = httpx.Timeout(60.0)
+
def _resolve_api_key(api_key: str | None) -> str:
resolved = api_key or os.environ.get(ENV_API_KEY)
@@ -108,7 +111,7 @@ class CapsulesResource:
Raises:
WrennNotFoundError: If no capsule with the given ID exists.
"""
- resp = self._http.post(f"/v1/capsules/{id}/pause")
+ resp = self._http.post(f"/v1/capsules/{id}/pause", timeout=_LONG_TIMEOUT)
return CapsuleModel.model_validate(handle_response(resp))
def resume(self, id: str) -> CapsuleModel:
@@ -224,7 +227,7 @@ class AsyncCapsulesResource:
Raises:
WrennNotFoundError: If no capsule with the given ID exists.
"""
- resp = await self._http.post(f"/v1/capsules/{id}/pause")
+ resp = await self._http.post(f"/v1/capsules/{id}/pause", timeout=_LONG_TIMEOUT)
return CapsuleModel.model_validate(handle_response(resp))
async def resume(self, id: str) -> CapsuleModel:
@@ -285,7 +288,9 @@ class SnapshotsResource:
params: dict = {}
if overwrite:
params["overwrite"] = "true"
- resp = self._http.post("/v1/snapshots", json=payload, params=params)
+ resp = self._http.post(
+ "/v1/snapshots", json=payload, params=params, timeout=_LONG_TIMEOUT
+ )
return Template.model_validate(handle_response(resp))
def list(self, type: str | None = None) -> list[Template]:
@@ -347,7 +352,9 @@ class AsyncSnapshotsResource:
params: dict = {}
if overwrite:
params["overwrite"] = "true"
- resp = await self._http.post("/v1/snapshots", json=payload, params=params)
+ resp = await self._http.post(
+ "/v1/snapshots", json=payload, params=params, timeout=_LONG_TIMEOUT
+ )
return Template.model_validate(handle_response(resp))
async def list(self, type: str | None = None) -> list[Template]:
diff --git a/src/wrenn/code_interpreter/async_capsule.py b/src/wrenn/code_interpreter/async_capsule.py
index fb99752..f61937c 100644
--- a/src/wrenn/code_interpreter/async_capsule.py
+++ b/src/wrenn/code_interpreter/async_capsule.py
@@ -207,7 +207,7 @@ class AsyncCapsule(BaseAsyncCapsule):
deadline = time.monotonic() + timeout
headers = {"X-API-Key": self._client._api_key}
- async with httpx_ws.aconnect_ws(ws_url, headers=headers) as ws:
+ async with httpx_ws.aconnect_ws(ws_url, headers=headers) as ws: # type: httpx_ws.AsyncWebSocketSession
await ws.send_text(json.dumps(msg))
while time.monotonic() < deadline:
time_left = deadline - time.monotonic()
diff --git a/src/wrenn/code_interpreter/capsule.py b/src/wrenn/code_interpreter/capsule.py
index 1b1f7ea..344c3f3 100644
--- a/src/wrenn/code_interpreter/capsule.py
+++ b/src/wrenn/code_interpreter/capsule.py
@@ -233,7 +233,7 @@ class Capsule(BaseCapsule):
deadline = time.monotonic() + timeout
headers = {"X-API-Key": self._client._api_key}
- with httpx_ws.connect_ws(ws_url, headers=headers) as ws:
+ with httpx_ws.connect_ws(ws_url, headers=headers) as ws: # type: httpx_ws.WebSocketSession
ws.send_text(json.dumps(msg))
while time.monotonic() < deadline:
time_left = deadline - time.monotonic()
diff --git a/src/wrenn/commands.py b/src/wrenn/commands.py
index 7ca9f44..e24f898 100644
--- a/src/wrenn/commands.py
+++ b/src/wrenn/commands.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import base64
+import builtins
import json
from collections.abc import AsyncIterator, Iterator
from dataclasses import dataclass
@@ -199,6 +200,7 @@ class Commands:
resp = self._http.post(f"/v1/capsules/{self._capsule_id}/exec", json=payload)
data = handle_response(resp)
+ assert isinstance(data, dict)
if background:
return CommandHandle(
@@ -217,6 +219,7 @@ class Commands:
"""
resp = self._http.get(f"/v1/capsules/{self._capsule_id}/processes")
data = handle_response(resp)
+ assert isinstance(data, dict)
return [
ProcessInfo(
pid=p.get("pid", 0),
@@ -252,7 +255,7 @@ class Commands:
with httpx_ws.connect_ws(
f"/v1/capsules/{self._capsule_id}/processes/{pid}/stream",
self._http,
- ) as ws:
+ ) as ws: # type: httpx_ws.WebSocketSession
while True:
try:
raw = ws.receive_json()
@@ -263,7 +266,9 @@ class Commands:
except httpx_ws.WebSocketDisconnect:
break
- def stream(self, cmd: str, args: list[str] | None = None) -> Iterator[StreamEvent]:
+ def stream(
+ self, cmd: str, args: builtins.list[str] | None = None
+ ) -> Iterator[StreamEvent]:
"""Execute a command via WebSocket, streaming output as events.
Args:
@@ -280,7 +285,7 @@ class Commands:
with httpx_ws.connect_ws(
f"/v1/capsules/{self._capsule_id}/exec/stream",
self._http,
- ) as ws:
+ ) as ws: # type: httpx_ws.WebSocketSession
if args:
start_msg: dict = {"type": "start", "cmd": cmd, "args": args}
else:
@@ -378,6 +383,7 @@ class AsyncCommands:
f"/v1/capsules/{self._capsule_id}/exec", json=payload
)
data = handle_response(resp)
+ assert isinstance(data, dict)
if background:
return CommandHandle(
@@ -396,6 +402,7 @@ class AsyncCommands:
"""
resp = await self._http.get(f"/v1/capsules/{self._capsule_id}/processes")
data = handle_response(resp)
+ assert isinstance(data, dict)
return [
ProcessInfo(
pid=p.get("pid", 0),
@@ -433,7 +440,7 @@ class AsyncCommands:
async with httpx_ws.aconnect_ws(
f"/v1/capsules/{self._capsule_id}/processes/{pid}/stream",
self._http,
- ) as ws:
+ ) as ws: # type: httpx_ws.AsyncWebSocketSession
try:
while True:
raw = await ws.receive_json()
@@ -445,7 +452,7 @@ class AsyncCommands:
pass
async def stream(
- self, cmd: str, args: list[str] | None = None
+ self, cmd: str, args: builtins.list[str] | None = None
) -> AsyncIterator[StreamEvent]:
"""Execute a command via WebSocket, streaming output as events.
@@ -463,7 +470,7 @@ class AsyncCommands:
async with httpx_ws.aconnect_ws(
f"/v1/capsules/{self._capsule_id}/exec/stream",
self._http,
- ) as ws:
+ ) as ws: # type: httpx_ws.AsyncWebSocketSession
if args:
start_msg: dict = {"type": "start", "cmd": cmd, "args": args}
else:
diff --git a/uv.lock b/uv.lock
index 985de91..36aea7d 100644
--- a/uv.lock
+++ b/uv.lock
@@ -72,6 +72,72 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
]
+[[package]]
+name = "cfgv"
+version = "3.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" },
+ { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" },
+ { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" },
+ { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" },
+ { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" },
+ { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" },
+ { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" },
+ { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" },
+ { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" },
+ { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" },
+ { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" },
+ { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" },
+ { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" },
+ { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" },
+ { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" },
+ { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" },
+ { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" },
+ { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" },
+]
+
[[package]]
name = "click"
version = "8.3.2"
@@ -93,6 +159,46 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
+[[package]]
+name = "databind"
+version = "4.5.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "deprecated" },
+ { name = "nr-date" },
+ { name = "nr-stream" },
+ { name = "typeapi" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/71/9e/835a5211eeb7228a0e3870d54def48dd7951dbd951f51b30900020a5f9fc/databind-4.5.4.tar.gz", hash = "sha256:342e170a219b1661e5c1b20778b532aecfa67e46560ba75beb7e2c6faa2150b5", size = 43193, upload-time = "2026-04-02T22:21:47.318Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e2/db/3b8eb860b5baef89b72c7aadcc5072e1648ca0c98d6ba4b9e4eabbdc2cf5/databind-4.5.4-py3-none-any.whl", hash = "sha256:78467f874a3e80bcd1d3de349167587a0d369831bc64c03798520be86074f96e", size = 52381, upload-time = "2026-04-02T22:21:45.389Z" },
+]
+
+[[package]]
+name = "databind-core"
+version = "4.5.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "databind" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9e/dc/b63a6f6a404146e8e3f1226c9243a5cb30784a1f75218d014cafce9a411f/databind_core-4.5.4.tar.gz", hash = "sha256:a7a47af183d4a8046c893fc19fa9c085f287a15e57a05e58345016086ce0f807", size = 974, upload-time = "2026-04-02T22:21:56.588Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fa/cf/1d1f4d37b4112f26ea5086d54200837d1dcbddaa536f3a70bb1d8b48ed9a/databind_core-4.5.4-py3-none-any.whl", hash = "sha256:25482c352f4f6fcade7c106c665553a18febeccda2972c00cf5af19f473960ab", size = 1666, upload-time = "2026-04-02T22:21:55.504Z" },
+]
+
+[[package]]
+name = "databind-json"
+version = "4.5.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "databind" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ab/72/9af59950a23ff6a03062acd85879de289595168ec43d6cec57253d00497c/databind_json-4.5.4.tar.gz", hash = "sha256:2c714f9c3039a81e42fc3826e47d7826ef020d93131c34daf4c9ae0483108e4d", size = 966, upload-time = "2026-04-02T22:22:05.538Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/d4/e00d531202314e29d90c9496f6b4730e3647128b9866180b8ce8ebb79394/databind_json-4.5.4-py3-none-any.whl", hash = "sha256:22e6faaeb6f2ec5cf815fd597a539dfe1f4846b80b618760112f4fe59a0898cc", size = 1664, upload-time = "2026-04-02T22:22:04.204Z" },
+]
+
[[package]]
name = "datamodel-code-generator"
version = "0.56.0"
@@ -117,6 +223,27 @@ ruff = [
{ name = "ruff" },
]
+[[package]]
+name = "deprecated"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "wrapt" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" },
+]
+
+[[package]]
+name = "distlib"
+version = "0.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
+]
+
[[package]]
name = "dnspython"
version = "2.8.0"
@@ -126,6 +253,40 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" },
]
+[[package]]
+name = "docspec"
+version = "2.2.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "databind-core" },
+ { name = "databind-json" },
+ { name = "deprecated" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3c/39/7a71382107445b2cd50c67c6194e3e584f19748a817c3b29e8be8a14f00f/docspec-2.2.1.tar.gz", hash = "sha256:4854e77edc0e2de40e785e57e95880f7095a05fe978f8b54cef7a269586e15ff", size = 8646, upload-time = "2023-05-28T11:24:18.68Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/33/aa/0c9d71cc9d450afd3993d09835e2910810a45b0703f585e1aee1d9b78969/docspec-2.2.1-py3-none-any.whl", hash = "sha256:7538f750095a9688c6980ff9a4e029a823a500f64bd00b6b4bdb27951feb31cb", size = 9844, upload-time = "2023-05-28T11:24:15.419Z" },
+]
+
+[[package]]
+name = "docspec-python"
+version = "2.2.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "black" },
+ { name = "docspec" },
+ { name = "nr-util" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ea/ea/e6d9d9c2f805c6ac8072d0e3ee5b1da2dd61886c662327df937dec9f282c/docspec_python-2.2.2.tar.gz", hash = "sha256:429be834d09549461b95bf45eb53c16859f3dfb3e9220408b3bfb12812ccb3fb", size = 22154, upload-time = "2025-05-06T12:40:33.286Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/03/c2/b3226746fb6b91893da270a60e77bb420d59cf33a7b9a4e719a236955971/docspec_python-2.2.2-py3-none-any.whl", hash = "sha256:caa32dc1e8c470af8a5ecad67cca614e68c1563ac01dab0c0486c4d7f709d6b1", size = 15988, upload-time = "2025-05-06T12:40:31.554Z" },
+]
+
+[[package]]
+name = "docstring-parser"
+version = "0.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/ce/5d6a3782b9f88097ce3e579265015db3372ae78d12f67629b863a9208c96/docstring_parser-0.11.tar.gz", hash = "sha256:93b3f8f481c7d24e37c5d9f30293c89e2933fa209421c8abd731dd3ef0715ecb", size = 22775, upload-time = "2021-09-30T07:44:10.288Z" }
+
[[package]]
name = "email-validator"
version = "2.3.0"
@@ -139,6 +300,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" },
]
+[[package]]
+name = "filelock"
+version = "3.29.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" },
+]
+
[[package]]
name = "genson"
version = "1.3.0"
@@ -200,6 +370,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/98/f8/a6bc80313a9e93c888fa10534dfce2ad76ff86911b6f485777ce6de6a073/httpx_ws-0.9.0-py3-none-any.whl", hash = "sha256:71640d2fb1bf9a225775015b33cd755cfd4c5f7e21c885192fe3adc4c387b248", size = 15759, upload-time = "2026-03-28T14:11:11.887Z" },
]
+[[package]]
+name = "identify"
+version = "2.6.19"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" },
+]
+
[[package]]
name = "idna"
version = "3.11"
@@ -405,6 +584,46 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
+[[package]]
+name = "nodeenv"
+version = "1.10.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" },
+]
+
+[[package]]
+name = "nr-date"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a0/92/08110dd3d7ff5e2b852a220752eb6c40183839f5b7cc91f9f38dd2298e7d/nr_date-2.1.0.tar.gz", hash = "sha256:0643aea13bcdc2a8bc56af9d5e6a89ef244c9744a1ef00cdc735902ba7f7d2e6", size = 8789, upload-time = "2023-08-16T13:46:04.114Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f9/10/1d2b00172537c1522fe64bbc6fb16b015632a02f7b3864e788ccbcb4dd85/nr_date-2.1.0-py3-none-any.whl", hash = "sha256:bd672a9dfbdcf7c4b9289fea6750c42490eaee08036a72059dcc78cb236ed568", size = 10496, upload-time = "2023-08-16T13:46:02.627Z" },
+]
+
+[[package]]
+name = "nr-stream"
+version = "1.1.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b7/37/e4d36d852c441233c306c5fbd98147685dce3ac9b0a8bbf4a587d0ea29ea/nr_stream-1.1.5.tar.gz", hash = "sha256:eb0216c6bfc61a46d4568dba3b588502c610ec8ddef4ac98f3932a2bd7264f65", size = 10053, upload-time = "2023-02-14T22:44:09.074Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1d/e1/f93485fe09aa36c0e1a3b76363efa1791241f7f863a010f725c95e8a74fe/nr_stream-1.1.5-py3-none-any.whl", hash = "sha256:47e12150b331ad2cb729cfd9d2abd281c9949809729ba461c6aa87dd9927b2d4", size = 10448, upload-time = "2023-02-14T22:44:07.72Z" },
+]
+
+[[package]]
+name = "nr-util"
+version = "0.8.12"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "deprecated" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/20/0c/078c567d95e25564bc1ede3c2cf6ce1c91f50648c83786354b47224326da/nr.util-0.8.12.tar.gz", hash = "sha256:a4549c2033d99d2f0379b3f3d233fd2a8ade286bbf0b3ad0cc7cea16022214f4", size = 63707, upload-time = "2022-06-20T13:29:29.192Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ba/58/eab08df9dbd69d9e21fc5e7be6f67454f386336ec71e6b64e378a2dddea4/nr.util-0.8.12-py3-none-any.whl", hash = "sha256:91da02ac9795eb8e015372275c1efe54bac9051231ee9b0e7e6f96b0b4e7d2bb", size = 90319, upload-time = "2022-06-20T13:29:27.312Z" },
+]
+
[[package]]
name = "packaging"
version = "26.0"
@@ -441,6 +660,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
+[[package]]
+name = "pre-commit"
+version = "4.6.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cfgv" },
+ { name = "identify" },
+ { name = "nodeenv" },
+ { name = "pyyaml" },
+ { name = "virtualenv" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" },
+]
+
[[package]]
name = "pydantic"
version = "2.12.5"
@@ -509,6 +744,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
]
+[[package]]
+name = "pydoc-markdown"
+version = "4.8.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+ { name = "databind-core" },
+ { name = "databind-json" },
+ { name = "docspec" },
+ { name = "docspec-python" },
+ { name = "docstring-parser" },
+ { name = "jinja2" },
+ { name = "nr-util" },
+ { name = "pyyaml" },
+ { name = "requests" },
+ { name = "tomli" },
+ { name = "tomli-w" },
+ { name = "watchdog" },
+ { name = "yapf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e1/8a/2c7f7ad656d22371a596d232fc140327b958d7f1d491b889632ea0cb7e87/pydoc_markdown-4.8.2.tar.gz", hash = "sha256:fb6c927e31386de17472d42f9bd3d3be2905977d026f6216881c65145aa67f0b", size = 44506, upload-time = "2023-06-26T12:37:01.152Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/83/5a/ce0b056d9a95fd0c06a6cfa5972477d79353392d19230c748a7ba5a9df04/pydoc_markdown-4.8.2-py3-none-any.whl", hash = "sha256:203f74119e6bb2f9deba43d452422de7c8ec31955b61e0620fa4dd8c2611715f", size = 67830, upload-time = "2023-06-26T12:36:59.502Z" },
+]
+
[[package]]
name = "pygments"
version = "2.20.0"
@@ -546,6 +806,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" },
]
+[[package]]
+name = "python-discovery"
+version = "1.2.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "filelock" },
+ { name = "platformdirs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/de/ef/3bae0e537cfe91e8431efcba4434463d2c5a65f5a89edd47c6cf2f03c55f/python_discovery-1.2.2.tar.gz", hash = "sha256:876e9c57139eb757cb5878cbdd9ae5379e5d96266c99ef731119e04fffe533bb", size = 58872, upload-time = "2026-04-07T17:28:49.249Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894, upload-time = "2026-04-07T17:28:48.09Z" },
+]
+
[[package]]
name = "pytokens"
version = "0.4.1"
@@ -606,6 +879,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
]
+[[package]]
+name = "requests"
+version = "2.33.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" },
+]
+
[[package]]
name = "respx"
version = "0.23.1"
@@ -643,6 +931,63 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" },
]
+[[package]]
+name = "tomli"
+version = "2.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" },
+ { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" },
+ { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" },
+ { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" },
+ { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
+ { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
+ { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
+ { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
+ { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
+ { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
+ { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
+ { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
+ { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
+ { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
+ { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
+ { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
+]
+
+[[package]]
+name = "tomli-w"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" },
+]
+
+[[package]]
+name = "typeapi"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d5/92/5a23ad34aa877edf00906166e339bfdc571543ea183ea7ab727bb01516c7/typeapi-2.3.0.tar.gz", hash = "sha256:a60d11f72c5ec27338cfd1c807f035b0b16ed2e3b798fb1c1d34fc5589f544be", size = 122687, upload-time = "2025-10-23T13:44:11.26Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d4/84/021bbeb7edb990dd6875cb6ab08d32faaa49fec63453d863730260a01f9e/typeapi-2.3.0-py3-none-any.whl", hash = "sha256:576b7dcb94412e91c5cae107a393674f8f99c10a24beb8be2302e3fed21d5cc2", size = 26858, upload-time = "2025-10-23T13:44:09.833Z" },
+]
+
[[package]]
name = "typeguard"
version = "4.5.1"
@@ -676,9 +1021,107 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
]
+[[package]]
+name = "urllib3"
+version = "2.6.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
+]
+
+[[package]]
+name = "virtualenv"
+version = "21.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "distlib" },
+ { name = "filelock" },
+ { name = "platformdirs" },
+ { name = "python-discovery" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3f/8b/6331f7a7fe70131c301106ec1e7cf23e2501bf7d4ca3636805801ca191bb/virtualenv-21.3.0.tar.gz", hash = "sha256:733750db978ec95c2d8eb4feadaa57091002bce404cb39ba69899cf7bd28944e", size = 7614069, upload-time = "2026-04-27T17:05:58.927Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4b/eb/03bfb1299d4c4510329e470f13f9a4ce793df7fcb5a2fd3510f911066f61/virtualenv-21.3.0-py3-none-any.whl", hash = "sha256:4d28ee41f6d9ec8f1f00cd472b9ffbcedda1b3d3b9a575b5c94a2d004fd51bd7", size = 7594690, upload-time = "2026-04-27T17:05:55.468Z" },
+]
+
+[[package]]
+name = "watchdog"
+version = "6.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" },
+ { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
+ { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
+ { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
+ { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
+ { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
+]
+
+[[package]]
+name = "wrapt"
+version = "2.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2e/64/925f213fdcbb9baeb1530449ac71a4d57fc361c053d06bf78d0c5c7cd80c/wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e", size = 81678, upload-time = "2026-03-06T02:53:25.134Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4c/7a/d936840735c828b38d26a854e85d5338894cda544cb7a85a9d5b8b9c4df7/wrapt-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd6f4d67befa6fe2abdffcbd3de2d82dfc6fb8a6d850407c53332709d030b", size = 61259, upload-time = "2026-03-06T02:53:41.922Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/88/9a9b9a90ac8ca11c2fdb6a286cb3a1fc7dd774c00ed70929a6434f6bc634/wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e", size = 61851, upload-time = "2026-03-06T02:52:48.672Z" },
+ { url = "https://files.pythonhosted.org/packages/03/a9/5b7d6a16fd6533fed2756900fc8fc923f678179aea62ada6d65c92718c00/wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb", size = 121446, upload-time = "2026-03-06T02:54:14.013Z" },
+ { url = "https://files.pythonhosted.org/packages/45/bb/34c443690c847835cfe9f892be78c533d4f32366ad2888972c094a897e39/wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca", size = 123056, upload-time = "2026-03-06T02:54:10.829Z" },
+ { url = "https://files.pythonhosted.org/packages/93/b9/ff205f391cb708f67f41ea148545f2b53ff543a7ac293b30d178af4d2271/wrapt-2.1.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:162e4e2ba7542da9027821cb6e7c5e068d64f9a10b5f15512ea28e954893a267", size = 117359, upload-time = "2026-03-06T02:53:03.623Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/3d/1ea04d7747825119c3c9a5e0874a40b33594ada92e5649347c457d982805/wrapt-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f29c827a8d9936ac320746747a016c4bc66ef639f5cd0d32df24f5eacbf9c69f", size = 121479, upload-time = "2026-03-06T02:53:45.844Z" },
+ { url = "https://files.pythonhosted.org/packages/78/cc/ee3a011920c7a023b25e8df26f306b2484a531ab84ca5c96260a73de76c0/wrapt-2.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9dd9813825f7ecb018c17fd147a01845eb330254dff86d3b5816f20f4d6aaf8", size = 116271, upload-time = "2026-03-06T02:54:46.356Z" },
+ { url = "https://files.pythonhosted.org/packages/98/fd/e5ff7ded41b76d802cf1191288473e850d24ba2e39a6ec540f21ae3b57cb/wrapt-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f8dbdd3719e534860d6a78526aafc220e0241f981367018c2875178cf83a413", size = 120573, upload-time = "2026-03-06T02:52:50.163Z" },
+ { url = "https://files.pythonhosted.org/packages/47/c5/242cae3b5b080cd09bacef0591691ba1879739050cc7c801ff35c8886b66/wrapt-2.1.2-cp313-cp313-win32.whl", hash = "sha256:5c35b5d82b16a3bc6e0a04349b606a0582bc29f573786aebe98e0c159bc48db6", size = 58205, upload-time = "2026-03-06T02:53:47.494Z" },
+ { url = "https://files.pythonhosted.org/packages/12/69/c358c61e7a50f290958809b3c61ebe8b3838ea3e070d7aac9814f95a0528/wrapt-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f8bc1c264d8d1cf5b3560a87bbdd31131573eb25f9f9447bb6252b8d4c44a3a1", size = 60452, upload-time = "2026-03-06T02:53:30.038Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/66/c8a6fcfe321295fd8c0ab1bd685b5a01462a9b3aa2f597254462fc2bc975/wrapt-2.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:3beb22f674550d5634642c645aba4c72a2c66fb185ae1aebe1e955fae5a13baf", size = 58842, upload-time = "2026-03-06T02:52:52.114Z" },
+ { url = "https://files.pythonhosted.org/packages/da/55/9c7052c349106e0b3f17ae8db4b23a691a963c334de7f9dbd60f8f74a831/wrapt-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fc04bc8664a8bc4c8e00b37b5355cffca2535209fba1abb09ae2b7c76ddf82b", size = 63075, upload-time = "2026-03-06T02:53:19.108Z" },
+ { url = "https://files.pythonhosted.org/packages/09/a8/ce7b4006f7218248dd71b7b2b732d0710845a0e49213b18faef64811ffef/wrapt-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a9b9d50c9af998875a1482a038eb05755dfd6fe303a313f6a940bb53a83c3f18", size = 63719, upload-time = "2026-03-06T02:54:33.452Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/e5/2ca472e80b9e2b7a17f106bb8f9df1db11e62101652ce210f66935c6af67/wrapt-2.1.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d3ff4f0024dd224290c0eabf0240f1bfc1f26363431505fb1b0283d3b08f11d", size = 152643, upload-time = "2026-03-06T02:52:42.721Z" },
+ { url = "https://files.pythonhosted.org/packages/36/42/30f0f2cefca9d9cbf6835f544d825064570203c3e70aa873d8ae12e23791/wrapt-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3278c471f4468ad544a691b31bb856374fbdefb7fee1a152153e64019379f015", size = 158805, upload-time = "2026-03-06T02:54:25.441Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/67/d08672f801f604889dcf58f1a0b424fe3808860ede9e03affc1876b295af/wrapt-2.1.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8914c754d3134a3032601c6984db1c576e6abaf3fc68094bb8ab1379d75ff92", size = 145990, upload-time = "2026-03-06T02:53:57.456Z" },
+ { url = "https://files.pythonhosted.org/packages/68/a7/fd371b02e73babec1de6ade596e8cd9691051058cfdadbfd62a5898f3295/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff95d4264e55839be37bafe1536db2ab2de19da6b65f9244f01f332b5286cfbf", size = 155670, upload-time = "2026-03-06T02:54:55.309Z" },
+ { url = "https://files.pythonhosted.org/packages/86/2d/9fe0095dfdb621009f40117dcebf41d7396c2c22dca6eac779f4c007b86c/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:76405518ca4e1b76fbb1b9f686cff93aebae03920cc55ceeec48ff9f719c5f67", size = 144357, upload-time = "2026-03-06T02:54:24.092Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/b6/ec7b4a254abbe4cde9fa15c5d2cca4518f6b07d0f1b77d4ee9655e30280e/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0be8b5a74c5824e9359b53e7e58bef71a729bacc82e16587db1c4ebc91f7c5a", size = 150269, upload-time = "2026-03-06T02:53:31.268Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/6b/2fabe8ebf148f4ee3c782aae86a795cc68ffe7d432ef550f234025ce0cfa/wrapt-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:f01277d9a5fc1862f26f7626da9cf443bebc0abd2f303f41c5e995b15887dabd", size = 59894, upload-time = "2026-03-06T02:54:15.391Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/fb/9ba66fc2dedc936de5f8073c0217b5d4484e966d87723415cc8262c5d9c2/wrapt-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:84ce8f1c2104d2f6daa912b1b5b039f331febfeee74f8042ad4e04992bd95c8f", size = 63197, upload-time = "2026-03-06T02:54:41.943Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/1c/012d7423c95d0e337117723eb8ecf73c622ce15a97847e84cf3f8f26cd7e/wrapt-2.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a93cd767e37faeddbe07d8fc4212d5cba660af59bdb0f6372c93faaa13e6e679", size = 60363, upload-time = "2026-03-06T02:54:48.093Z" },
+ { url = "https://files.pythonhosted.org/packages/39/25/e7ea0b417db02bb796182a5316398a75792cd9a22528783d868755e1f669/wrapt-2.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1370e516598854e5b4366e09ce81e08bfe94d42b0fd569b88ec46cc56d9164a9", size = 61418, upload-time = "2026-03-06T02:53:55.706Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/0f/fa539e2f6a770249907757eaeb9a5ff4deb41c026f8466c1c6d799088a9b/wrapt-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6de1a3851c27e0bd6a04ca993ea6f80fc53e6c742ee1601f486c08e9f9b900a9", size = 61914, upload-time = "2026-03-06T02:52:53.37Z" },
+ { url = "https://files.pythonhosted.org/packages/53/37/02af1867f5b1441aaeda9c82deed061b7cd1372572ddcd717f6df90b5e93/wrapt-2.1.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de9f1a2bbc5ac7f6012ec24525bdd444765a2ff64b5985ac6e0692144838542e", size = 120417, upload-time = "2026-03-06T02:54:30.74Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/b7/0138a6238c8ba7476c77cf786a807f871672b37f37a422970342308276e7/wrapt-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:970d57ed83fa040d8b20c52fe74a6ae7e3775ae8cff5efd6a81e06b19078484c", size = 122797, upload-time = "2026-03-06T02:54:51.539Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/ad/819ae558036d6a15b7ed290d5b14e209ca795dd4da9c58e50c067d5927b0/wrapt-2.1.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3969c56e4563c375861c8df14fa55146e81ac11c8db49ea6fb7f2ba58bc1ff9a", size = 117350, upload-time = "2026-03-06T02:54:37.651Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/2d/afc18dc57a4600a6e594f77a9ae09db54f55ba455440a54886694a84c71b/wrapt-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:57d7c0c980abdc5f1d98b11a2aa3bb159790add80258c717fa49a99921456d90", size = 121223, upload-time = "2026-03-06T02:54:35.221Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/5b/5ec189b22205697bc56eb3b62aed87a1e0423e9c8285d0781c7a83170d15/wrapt-2.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:776867878e83130c7a04237010463372e877c1c994d449ca6aaafeab6aab2586", size = 116287, upload-time = "2026-03-06T02:54:19.654Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/2d/f84939a7c9b5e6cdd8a8d0f6a26cabf36a0f7e468b967720e8b0cd2bdf69/wrapt-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fab036efe5464ec3291411fabb80a7a39e2dd80bae9bcbeeca5087fdfa891e19", size = 119593, upload-time = "2026-03-06T02:54:16.697Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/fe/ccd22a1263159c4ac811ab9374c061bcb4a702773f6e06e38de5f81a1bdc/wrapt-2.1.2-cp314-cp314-win32.whl", hash = "sha256:e6ed62c82ddf58d001096ae84ce7f833db97ae2263bff31c9b336ba8cfe3f508", size = 58631, upload-time = "2026-03-06T02:53:06.498Z" },
+ { url = "https://files.pythonhosted.org/packages/65/0a/6bd83be7bff2e7efaac7b4ac9748da9d75a34634bbbbc8ad077d527146df/wrapt-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:467e7c76315390331c67073073d00662015bb730c566820c9ca9b54e4d67fd04", size = 60875, upload-time = "2026-03-06T02:53:50.252Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/c0/0b3056397fe02ff80e5a5d72d627c11eb885d1ca78e71b1a5c1e8c7d45de/wrapt-2.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:da1f00a557c66225d53b095a97eace0fc5349e3bfda28fa34ffae238978ee575", size = 59164, upload-time = "2026-03-06T02:53:59.128Z" },
+ { url = "https://files.pythonhosted.org/packages/71/ed/5d89c798741993b2371396eb9d4634f009ff1ad8a6c78d366fe2883ea7a6/wrapt-2.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:62503ffbc2d3a69891cf29beeaccdb4d5e0a126e2b6a851688d4777e01428dbb", size = 63163, upload-time = "2026-03-06T02:52:54.873Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/8c/05d277d182bf36b0a13d6bd393ed1dec3468a25b59d01fba2dd70fe4d6ae/wrapt-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7e6cd120ef837d5b6f860a6ea3745f8763805c418bb2f12eeb1fa6e25f22d22", size = 63723, upload-time = "2026-03-06T02:52:56.374Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/27/6c51ec1eff4413c57e72d6106bb8dec6f0c7cdba6503d78f0fa98767bcc9/wrapt-2.1.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3769a77df8e756d65fbc050333f423c01ae012b4f6731aaf70cf2bef61b34596", size = 152652, upload-time = "2026-03-06T02:53:23.79Z" },
+ { url = "https://files.pythonhosted.org/packages/db/4c/d7dd662d6963fc7335bfe29d512b02b71cdfa23eeca7ab3ac74a67505deb/wrapt-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a76d61a2e851996150ba0f80582dd92a870643fa481f3b3846f229de88caf044", size = 158807, upload-time = "2026-03-06T02:53:35.742Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/4d/1e5eea1a78d539d346765727422976676615814029522c76b87a95f6bcdd/wrapt-2.1.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6f97edc9842cf215312b75fe737ee7c8adda75a89979f8e11558dfff6343cc4b", size = 146061, upload-time = "2026-03-06T02:52:57.574Z" },
+ { url = "https://files.pythonhosted.org/packages/89/bc/62cabea7695cd12a288023251eeefdcb8465056ddaab6227cb78a2de005b/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4006c351de6d5007aa33a551f600404ba44228a89e833d2fadc5caa5de8edfbf", size = 155667, upload-time = "2026-03-06T02:53:39.422Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/99/6f2888cd68588f24df3a76572c69c2de28287acb9e1972bf0c83ce97dbc1/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9372fc3639a878c8e7d87e1556fa209091b0a66e912c611e3f833e2c4202be2", size = 144392, upload-time = "2026-03-06T02:54:22.41Z" },
+ { url = "https://files.pythonhosted.org/packages/40/51/1dfc783a6c57971614c48e361a82ca3b6da9055879952587bc99fe1a7171/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3144b027ff30cbd2fca07c0a87e67011adb717eb5f5bd8496325c17e454257a3", size = 150296, upload-time = "2026-03-06T02:54:07.848Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/38/cbb8b933a0201076c1f64fc42883b0023002bdc14a4964219154e6ff3350/wrapt-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:3b8d15e52e195813efe5db8cec156eebe339aaf84222f4f4f051a6c01f237ed7", size = 60539, upload-time = "2026-03-06T02:54:00.594Z" },
+ { url = "https://files.pythonhosted.org/packages/82/dd/e5176e4b241c9f528402cebb238a36785a628179d7d8b71091154b3e4c9e/wrapt-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:08ffa54146a7559f5b8df4b289b46d963a8e74ed16ba3687f99896101a3990c5", size = 63969, upload-time = "2026-03-06T02:54:39Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/99/79f17046cf67e4a95b9987ea129632ba8bcec0bc81f3fb3d19bdb0bd60cd/wrapt-2.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:72aaa9d0d8e4ed0e2e98019cea47a21f823c9dd4b43c7b77bba6679ffcca6a00", size = 60554, upload-time = "2026-03-06T02:53:14.132Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/c7/8528ac2dfa2c1e6708f647df7ae144ead13f0a31146f43c7264b4942bf12/wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8", size = 43993, upload-time = "2026-03-06T02:53:12.905Z" },
+]
+
[[package]]
name = "wrenn"
-version = "0.1.0"
+version = "0.1.1"
source = { editable = "." }
dependencies = [
{ name = "email-validator" },
@@ -691,6 +1134,8 @@ dependencies = [
dev = [
{ name = "datamodel-code-generator", extra = ["ruff"] },
{ name = "mypy" },
+ { name = "pre-commit" },
+ { name = "pydoc-markdown" },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "respx" },
@@ -709,6 +1154,8 @@ requires-dist = [
dev = [
{ name = "datamodel-code-generator", extras = ["ruff"], specifier = ">=0.56.0" },
{ name = "mypy", specifier = ">=1.20.0" },
+ { name = "pre-commit", specifier = ">=4.6.0" },
+ { name = "pydoc-markdown", specifier = ">=4.8.2" },
{ name = "pytest", specifier = ">=9.0.3" },
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
{ name = "respx", specifier = ">=0.23.1" },
@@ -726,3 +1173,15 @@ sdist = { url = "https://files.pythonhosted.org/packages/c7/79/12135bdf8b9c9367b
wheels = [
{ url = "https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584", size = 24405, upload-time = "2025-11-20T18:18:00.454Z" },
]
+
+[[package]]
+name = "yapf"
+version = "0.43.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "platformdirs" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/37/81/6acd6601f61e31cfb8729d3da6d5df966f80f374b78eff83760714487338/yapf-0.43.0-py3-none-any.whl", hash = "sha256:224faffbc39c428cb095818cf6ef5511fdab6f7430a10783fdfb292ccf2852ca", size = 256158, upload-time = "2024-11-14T00:11:39.37Z" },
+]