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" }, +]