From 7278496c8f748a0482cd424bc24deee3030a0d73 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Thu, 13 Aug 2026 14:14:48 -0700 Subject: [PATCH] docs: document from_json/from_str and kind-typed outputs in the README The README showed exactly one way to build a workflow (from_file) and only image-file outputs, so the in-memory constructors and the non-image/bytes paths were undiscoverable without reading SDK source. - Getting started gains a 'Constructing a workflow' subsection: a table of from_file / from_json / from_str, and a dict-built-graph example that runs set_input on it, submits, and pulls the result with to_bytes(). - Downloading outputs gains 'Outputs are kind-typed': output.type is the normalized kind (image, video, audio, text, file, latent) next to content_type/name/size_bytes, with an example branching on it across to_file, to_bytes and get_download_url. Docs only - no API or behavior change. Every documented name was checked against the published 0.1.8 release as well as this tree. --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a6b8e98..c221e11 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,33 @@ for output in job.get_outputs("9"): output.to_file(output.name) ``` +### Constructing a workflow + +`client.workflows` builds a `Workflow` from wherever your API-format graph already lives. All three constructors are local — none of them touches the network: + +| Constructor | Takes | +|---|---| +| `from_file(path)` | a path to a `workflow_api.json` on disk | +| `from_json(graph)` | a graph already in memory, as a `dict` | +| `from_str(text)` | the JSON text of a graph | + +Callers that assemble the graph in code — a service or a cron with no JSON file to ship alongside it — want `from_json`: + +```python +graph = { # API format, same shape as workflow_api.json (abridged) + "3": {"class_type": "KSampler", "inputs": {"seed": 0, "steps": 20}}, + "9": {"class_type": "SaveImage", "inputs": {"images": ["8", 0]}}, +} + +wf = client.workflows.from_json(graph) # or from_str(json.dumps(graph)) +wf.set_input("3", "seed", 42) # sugar for graph["3"]["inputs"]["seed"] = 42 + +job = client.run(wf) +data = job.get_outputs("9")[0].to_bytes() # bytes in memory, no file written +``` + +`from_json` wraps the dict you hand it rather than copying, and `wf.json` *is* that graph — still a plain, freely-mutable `dict` if you'd rather edit it directly than go through `set_input`. `AsyncComfy` exposes the same three constructors on `client.workflows`. + ## Authentication — one client, per-surface key | Surface | `api_key` | @@ -249,8 +276,26 @@ On Comfy Cloud / serverless the URL is a short-lived, **self-authorizing** signed storage URL: whoever holds it can read the asset until `expires_at` with no API key of their own. On a self-hosted proxy it's the content endpoint (normal auth still applies) and `expires_at` is `None`. It works on every -backend and never downloads the bytes first. (`AsyncOutput` mirrors all of the -above with `await`.) +backend and never downloads the bytes first. + +### Outputs are kind-typed + +Outputs aren't assumed to be images. `output.type` is the normalized kind of what the node produced — one of `image`, `video`, `audio`, `text`, `file`, `latent` — and sits alongside `output.content_type` (the exact MIME type), `output.name` and `output.size_bytes`. Branch on it rather than sniffing the filename: + +```python +for out in job.outputs: + match out.type: + case "image": + out.to_file(out.name) # stream straight to disk + case "audio": + transcode(out.to_bytes(), out.content_type) # bytes in memory, nothing written + case "video": + enqueue(out.get_download_url().url) # hand the URL off, transfer nothing + case _: + print(out.type, out.name, out.size_bytes) +``` + +(`AsyncOutput` mirrors all of the above with `await`.) ## Sync and async