[PR #5] [MERGED] comfy-sdk (Python): the Comfy API v2 client — two-layer SDK #13

Closed
opened 2026-10-26 22:40:39 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/Comfy-Org/comfy-python-sdk/pull/5
Author: @wei-hai
Created: 7/16/2026
Status: ✅ Merged
Merged: 7/17/2026
Merged by: @wei-hai

Base: main ← Head: feat/full-v2


📝 Commits (10+)

  • 83c6f4e Add demo-slice v2 client (submit / poll / download)
  • dbd44a3 Make API key optional per surface; clear error when required
  • ca1261b Wrap run() signature to satisfy the 100-char line limit
  • c84093a Scrub internal references from README and docstring
  • dc6c60f Add CI (ruff, mypy, pytest) and a dormant PyPI publish workflow (#2)
  • df9a42d Expand demo client into the full two-layer Comfy API v2 SDK
  • 0c6bfa4 Reconcile vendored spec to the canonical Comfy API v2 contract
  • 6842e22 Fix 3 must-fix review findings: token leak, broken async upload, dropped tags
  • 82e17a1 Polish README for developer onboarding; close test gaps in redirect/auth/retry paths
  • 827d0c9 docs: correct submit() idempotency docstring

📊 Changes

37 files changed (+5301 additions, -14 deletions)

View changed files

➕ .github/workflows/ci.yml (+112 -0)
➕ .github/workflows/publish.yml (+68 -0)
📝 README.md (+195 -14)
➕ pyproject.toml (+61 -0)
➕ scripts/check_drift.py (+68 -0)
➕ scripts/check_public_repo_hygiene.py (+158 -0)
➕ scripts/gen_models.sh (+25 -0)
➕ spec/README.md (+1 -0)
➕ spec/VERSION (+1 -0)
➕ spec/openapi.yaml (+936 -0)
➕ src/comfy_low/__init__.py (+86 -0)
➕ src/comfy_low/_multipart.py (+99 -0)
➕ src/comfy_low/errors.py (+141 -0)
➕ src/comfy_low/models/__init__.py (+47 -0)
➕ src/comfy_low/models/_generated.py (+257 -0)
➕ src/comfy_low/sse.py (+70 -0)
➕ src/comfy_low/transport.py (+572 -0)
➕ src/comfy_sdk/__init__.py (+95 -0)
➕ src/comfy_sdk/_core.py (+93 -0)
➕ src/comfy_sdk/_hashing.py (+42 -0)

...and 17 more files

📄 Description

What this is

The Python SDK for running ComfyUI workflows via the Comfy API v2 — the same code against a self-hosted ComfyUI, Comfy Cloud, or a serverless deployment. Only the base URL and (where needed) an API key change.

from comfy_sdk import Comfy

client = Comfy("http://127.0.0.1:8189")                 # local (self-hosted proxy)
# client = Comfy("https://api.comfy.org", api_key="…")  # Comfy Cloud

asset = client.assets.from_file("photo.png")            # hashed + uploaded once, on use
wf.set_input("10", "image", asset)                      # becomes a core/ASSET reference
result = client.run(wf)                                 # submit → follow → wait
result.get_outputs("13")[0].to_file("out.png")

Two layers

flowchart TB
    subgraph sdk["comfy_sdk — hand-written, idiomatic (the value)"]
        assets["assets: from_file/url/…<br/>blake3 dedup, lazy upload"]
        wf["workflows + core/ASSET substitution"]
        jobs["run() · wait() · events() (SSE)"]
        out["typed outputs, events, errors"]
    end
    subgraph low["comfy_low — generated + thin transport"]
        models["models (generated from the v2 spec)"]
        transport["httpx transport: 1 fn/endpoint,<br/>streaming, abort, raw response"]
    end
    sdk --> low
    low -->|"/api/v2/*"| surface["any Comfy API v2 surface"]
  • comfy_low is generated from the Comfy API v2 OpenAPI contract (spec/openapi.yaml, a synced copy) plus a small hand-written httpx transport. It's boring and regenerated wholesale; a drift check + a spec-coverage test keep it in sync with the contract.
  • comfy_sdk is the hand-written layer that carries the actual value: content-addressed upload with local blake3 dedup, core/ASSET substitution into the raw graph, poll-authoritative run()/wait(), a reconnecting typed SSE event stream, range-aware downloads, and typed exceptions for every error code.

Notes for reviewers

  • Sync-first with a mirrored async client — the two share one I/O-free core; only the ~handful of awaiting methods are written twice.
  • Poll is the source of truth; SSE is a latency enhancement (reconnects, no replay) that never gates completion.
  • Per-surface auth: no key for a local proxy (a self-hosted ComfyUI has no auth); required for Comfy Cloud, with a clear error if missing.

Tests

28 tests against a stub server: dedup fast-path, streaming upload (asserted not buffered whole), hash-mismatch surfaced without blind retry, poll-only completion, SSE reconnect without replay, idempotent submit, error→exception mapping, ranged download — plus the async mirrors. ruff / mypy / drift check clean.

Deferred (noted)

Preview.to_pil() needs an optional image extra; browser support is out of scope for v1 (Node/Python runtimes).

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/Comfy-Org/comfy-python-sdk/pull/5 **Author:** [@wei-hai](https://github.com/wei-hai) **Created:** 7/16/2026 **Status:** ✅ Merged **Merged:** 7/17/2026 **Merged by:** [@wei-hai](https://github.com/wei-hai) **Base:** `main` ← **Head:** `feat/full-v2` --- ### 📝 Commits (10+) - [`83c6f4e`](https://github.com/Comfy-Org/comfy-python-sdk/commit/83c6f4e64c148120d737fd39007a43c09bd0d9b7) Add demo-slice v2 client (submit / poll / download) - [`dbd44a3`](https://github.com/Comfy-Org/comfy-python-sdk/commit/dbd44a3d6006ea5ac2f83609c54676d103b06e1c) Make API key optional per surface; clear error when required - [`ca1261b`](https://github.com/Comfy-Org/comfy-python-sdk/commit/ca1261bf544795aa850b1eb1e83cbd306c3204fd) Wrap run() signature to satisfy the 100-char line limit - [`c84093a`](https://github.com/Comfy-Org/comfy-python-sdk/commit/c84093a7425269e1489117bfb29dcfc7315f019d) Scrub internal references from README and docstring - [`dc6c60f`](https://github.com/Comfy-Org/comfy-python-sdk/commit/dc6c60fe70dafd4a2af9c0a48dd7b208d41bb9ac) Add CI (ruff, mypy, pytest) and a dormant PyPI publish workflow (#2) - [`df9a42d`](https://github.com/Comfy-Org/comfy-python-sdk/commit/df9a42d32c3d5ab179b031b1ca628408ab3491d9) Expand demo client into the full two-layer Comfy API v2 SDK - [`0c6bfa4`](https://github.com/Comfy-Org/comfy-python-sdk/commit/0c6bfa447c566db8ecac5cf404a51fe0c4c7955e) Reconcile vendored spec to the canonical Comfy API v2 contract - [`6842e22`](https://github.com/Comfy-Org/comfy-python-sdk/commit/6842e22223091aa74048eaa41af69f66fec9d9c3) Fix 3 must-fix review findings: token leak, broken async upload, dropped tags - [`82e17a1`](https://github.com/Comfy-Org/comfy-python-sdk/commit/82e17a1d57b3ca1b76d68469aa27bb39ddaf675d) Polish README for developer onboarding; close test gaps in redirect/auth/retry paths - [`827d0c9`](https://github.com/Comfy-Org/comfy-python-sdk/commit/827d0c9ef5e4e2281c5f89171e8a565a33f94c74) docs: correct submit() idempotency docstring ### 📊 Changes **37 files changed** (+5301 additions, -14 deletions) <details> <summary>View changed files</summary> ➕ `.github/workflows/ci.yml` (+112 -0) ➕ `.github/workflows/publish.yml` (+68 -0) 📝 `README.md` (+195 -14) ➕ `pyproject.toml` (+61 -0) ➕ `scripts/check_drift.py` (+68 -0) ➕ `scripts/check_public_repo_hygiene.py` (+158 -0) ➕ `scripts/gen_models.sh` (+25 -0) ➕ `spec/README.md` (+1 -0) ➕ `spec/VERSION` (+1 -0) ➕ `spec/openapi.yaml` (+936 -0) ➕ `src/comfy_low/__init__.py` (+86 -0) ➕ `src/comfy_low/_multipart.py` (+99 -0) ➕ `src/comfy_low/errors.py` (+141 -0) ➕ `src/comfy_low/models/__init__.py` (+47 -0) ➕ `src/comfy_low/models/_generated.py` (+257 -0) ➕ `src/comfy_low/sse.py` (+70 -0) ➕ `src/comfy_low/transport.py` (+572 -0) ➕ `src/comfy_sdk/__init__.py` (+95 -0) ➕ `src/comfy_sdk/_core.py` (+93 -0) ➕ `src/comfy_sdk/_hashing.py` (+42 -0) _...and 17 more files_ </details> ### 📄 Description ## What this is The Python SDK for running ComfyUI workflows via the **Comfy API v2** — the same code against a self-hosted ComfyUI, Comfy Cloud, or a serverless deployment. Only the base URL and (where needed) an API key change. ```python from comfy_sdk import Comfy client = Comfy("http://127.0.0.1:8189") # local (self-hosted proxy) # client = Comfy("https://api.comfy.org", api_key="…") # Comfy Cloud asset = client.assets.from_file("photo.png") # hashed + uploaded once, on use wf.set_input("10", "image", asset) # becomes a core/ASSET reference result = client.run(wf) # submit → follow → wait result.get_outputs("13")[0].to_file("out.png") ``` ## Two layers ```mermaid flowchart TB subgraph sdk["comfy_sdk — hand-written, idiomatic (the value)"] assets["assets: from_file/url/…<br/>blake3 dedup, lazy upload"] wf["workflows + core/ASSET substitution"] jobs["run() · wait() · events() (SSE)"] out["typed outputs, events, errors"] end subgraph low["comfy_low — generated + thin transport"] models["models (generated from the v2 spec)"] transport["httpx transport: 1 fn/endpoint,<br/>streaming, abort, raw response"] end sdk --> low low -->|"/api/v2/*"| surface["any Comfy API v2 surface"] ``` - **`comfy_low`** is generated from the Comfy API v2 OpenAPI contract (`spec/openapi.yaml`, a synced copy) plus a small hand-written `httpx` transport. It's boring and regenerated wholesale; a drift check + a spec-coverage test keep it in sync with the contract. - **`comfy_sdk`** is the hand-written layer that carries the actual value: content-addressed upload with local **blake3** dedup, `core/ASSET` substitution into the raw graph, poll-authoritative `run()`/`wait()`, a reconnecting typed SSE event stream, range-aware downloads, and typed exceptions for every error code. ## Notes for reviewers - **Sync-first with a mirrored async client** — the two share one I/O-free core; only the ~handful of awaiting methods are written twice. - **Poll is the source of truth**; SSE is a latency enhancement (reconnects, no replay) that never gates completion. - **Per-surface auth**: no key for a local proxy (a self-hosted ComfyUI has no auth); required for Comfy Cloud, with a clear error if missing. ## Tests 28 tests against a stub server: dedup fast-path, streaming upload (asserted not buffered whole), hash-mismatch surfaced without blind retry, poll-only completion, SSE reconnect without replay, idempotent submit, error→exception mapping, ranged download — plus the async mirrors. `ruff` / `mypy` / drift check clean. ## Deferred (noted) `Preview.to_pil()` needs an optional image extra; browser support is out of scope for v1 (Node/Python runtimes). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-26 22:40:39 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Comfy-Org/comfy-python-sdk#13
No description provided.