fabricatio-comfyui
Async ComfyUI API integration for Fabricatio — generate images from typed
knobs and download the results. Built on httpx with full Pydantic-typed
API coverage.
Design: workflows are fully internal
The package owns its workflow graph: it is initialised entirely in Python
code (an internal typed Graph model) and serialized to ComfyUI's API
format only on submission. Callers never see, construct, or operate on a
workflow — they supply high-level knobs (prompt, negative_prompt,
prop, mp, seed, steps, cfg, checkpoint) and the package
parameterises the built-in template internally. There is no dict[str, Any]
workflow injection anywhere in the public signatures.
Naming follows fabricatio-skill: one Use* capability mixin
(UseComfyUI), a module-level one-shot function (generate_image), and
bare-noun response models (ExecutionResult, QueueInfo, …).
workflows/ stays a docstring-only namespace, as in fabricatio-skill.
Architecture
| Layer | Module / Class | Purpose |
| Graph | Graph / GraphSimple / AnimaGraph (models/graph.py, models/anima.py) | Typed templates initialised in Python — two-pass, single-pass, and anima — serialized to the ComfyUI wire format; internal only |
| Transport | ComfyUIHttpClient / ComfyUIClientBase | Async REST client; shared per-URL via get_comfyui_client |
| Capability | UseComfyUI (capabilities/comfyui.py) | Mixin: high-level generate (queue → poll → download) |
| API | api.py | One-shot functions that run on the shared pooled client |
| Actions | GenerateImage | Pluggable step for Fabricatio WorkFlow |
Installation
pip install fabricatio[comfyui]
# or
uv pip install fabricatio[comfyui]
Configuration
All options below are read through the fabricatio configuration chain (see the
Configuration Guide). Set them under the
[ext.comfyui] table in fabricatio.toml, equivalently under
[tool.fabricatio.ext.comfyui] in pyproject.toml, or via
FABRICATIO_EXT__COMFYUI__<FIELD_UPPER> environment variables.
[ext.comfyui]
base_url = "http://127.0.0.1:8188"
timeout = 300.0
download_dir = "./outputs"
| Option | Type | Default | Description |
|---|---|---|---|
base_url |
str |
"http://127.0.0.1:8188" |
Base URL of the ComfyUI server (default localhost:8188). |
timeout |
float |
300.0 |
Default timeout in seconds for API requests (default 5 min). |
checkpoint |
str | None |
None |
Checkpoint applied to every generation; a per-call checkpoint= knob takes precedence. |
mp |
float | None |
None |
Default megapixel budget of the finished image (1.0 = 1,000,000 px); a template that upscales before its final pass sizes its base canvas so the upscaled output lands at the budget, while single-pass templates size the latent directly; a per-call mp= wins; None keeps the active template's canvas (768x512 default, 1344x1024 anima). |
prop |
Prop | None |
None |
Default aspect-ratio preset — enum member names (prop_1_1, prop_4_3, prop_3_4, prop_3_2, prop_2_3, prop_16_9, prop_9_16, prop_5_4, prop_4_5, prop_21_9, prop_9_21); a per-call prop= wins; None keeps the active template's ratio. |
anima_checkpoint |
str | None |
None |
Default checkpoint filename for generate_anima_image (the template holds a placeholder in source). |
anima_clip |
str | None |
None |
Default CLIP filename for generate_anima_image. |
anima_vae |
str | None |
None |
Default VAE filename for generate_anima_image. |
download_dir |
str | None |
None |
Default directory for generated images; a per-call download_dir= takes precedence. |
The workflow kind is not configurable — it is the method you call:
generate_image / generate_simple_image / generate_anima_image
(client methods generate / generate_simple / generate_anima), plus
the img2img refiner generate_img2img (same name on client, api, and
capability).
The simple workflow
generate_simple_image(...) (client: generate_simple) runs a single
sampler pass with no upscale and no refine — the decode feeds the
preview directly, so the submitted graph is 7 nodes instead of 11:
graph LR
loader --> sampler_base --> decode --> preview
Use it when a hi-res pass is not worth the time (fast iteration) or when
you want the literal latent canvas. Because nothing upscales, mp
sizes the latent directly rather than dividing by the upscale factor.
The img2img refiner
generate_img2img(prompt, image, ...) (same signature on the client,
the api function, and the capability) refines an existing image instead
of sampling from a latent canvas: the image is scaled toward the mp
budget, encoded, and resampled on the two-pass template's refine
schedule.
Pass a local file as a pathlib.Path — it is uploaded via
POST /upload/image first — or the exact name of an image already in
the server's input directory as a str (a str naming an existing
local file is rejected as ambiguous rather than guessed). denoise
controls how much of the source survives (0.45 keeps the composition,
1.0 regenerates from noise); there is no prop — the aspect ratio
belongs to the input image.
from pathlib import Path
from fabricatio_comfyui import generate_img2img
path = await generate_img2img(
"a cat, best quality",
Path("./inputs/photo.png"),
mp=1.0,
denoise=0.45,
)
The anima workflow
The package ships a second bundled template — the anima preset — for
models that load checkpoint, CLIP, and VAE as separate files. Call
generate_anima_image(...) (client: generate_anima) and supply the
real server-side filenames per call (clip= / vae= / checkpoint=)
or as defaults:
[ext.comfyui]
anima_checkpoint = "your-anima-checkpoint.safetensors"
anima_clip = "your-anima-clip.safetensors"
anima_vae = "your-anima-vae.safetensors"
The template's model fields are placeholders in source; generation
resolves them from these keys (a per-call argument wins) and fails
loudly while any is unset. The
anima template samples once (er_sde, 32 steps) on a default 4:3
1344x1024 canvas (overridable per call or config via mp / prop). LoRAs apply per call via the loras knob — each
entry names a server-side file and a strength, chained as stock
LoraLoader nodes between the model/CLIP sources and the sampler
(no custom node pack required). Without a loras list the submitted
graph contains no LoRA node at all:
from fabricatio_comfyui import generate_image
from fabricatio_comfyui.models import LoraSpec
path = await generate_image(
"a cat",
loras=[LoraSpec(lora_name="my-lora.safetensors", strength=0.8)],
)
Access at runtime: from fabricatio_comfyui.config import comfyui_config.
Image size: megapixels x aspect ratio
Sizes are specified as a total pixel budget and an aspect ratio instead of raw pixel dimensions, giving LLM callers a small discrete choice surface while the package computes a model-friendly canvas:
mp— megapixel budget of the finished image (1.0= 1,000,000 px).prop— aspect-ratio preset from thePropStrEnum (members are declared withauto(), so member names double as the values).
The canvas derives from the active template's built-in canvas: a given
mp replaces the final pixel area, a given prop replaces its ratio,
then both dimensions snap to the nearest multiple of 64 (half-up, floor
64 px). Either knob may be omitted — per-call knobs win over
[ext.comfyui] defaults, which win over the template canvas (768x512
for the two-pass template, 1344x1024 for the anima preset).
The default two-pass template upscales its base canvas by 2.3x (area
5.29x) before the refine pass, so its base canvas is sized to
mp / 2.3**2 and the finished image lands at the budget. Templates with
no upscale step (generate_simple_image and the anima preset) have a
latent canvas that is the finished image, so the budget applies
directly.
Presets: prop_1_1 (1:1), prop_16_9 / prop_9_16 (16:9 / 9:16),
prop_3_2 / prop_2_3 (3:2 / 2:3), prop_4_3 / prop_3_4 (4:3 /
3:4), prop_5_4 / prop_4_5 (5:4 / 4:5), prop_21_9 / prop_9_21
(21:9 / 9:21).
Usage
One-shot functions
The lowest-friction entry point — no Role, no client, no workflow:
import asyncio
from fabricatio_comfyui import generate_image
from fabricatio_comfyui.models import Prop
async def main() -> None:
path = await generate_image(
"masterpiece, best quality, a mountain landscape",
negative_prompt="worst quality, blurry",
prop=Prop.prop_16_9,
mp=1.0,
)
print(path) # Path to the generated image, or None on failure
asyncio.run(main())
Capability mixin (with a Role or Action)
Mix UseComfyUI into a Role to get the same typed-knob methods:
from fabricatio import Role
from fabricatio_comfyui import UseComfyUI
class ImageRole(Role, UseComfyUI):
"""Role with ComfyUI image generation capability."""
# then: path = await role.generate_image("a mountain landscape") # Path | None
Propose the whole instruction (SketchSpec)
SketchSpec is the propose-able generation instruction: positive prompt,
negative prompt, and canvas size in one Pydantic model. Let the LLM fill
it in a single propose call (role needs the framework's Propose mixin
as usual) instead of composing prompt and size knobs by hand:
from fabricatio_comfyui import SketchSpec
spec = await role.propose(SketchSpec, "a wide establishing shot of the harbour at dusk")
# spec: SketchSpec | None — prompt/negative_prompt plus the LLM-chosen prop and mp
path = await role.generate_image(
spec.prompt, negative_prompt=spec.negative_prompt, prop=spec.prop, mp=spec.mp
)
Action (in a WorkFlow)
Use GenerateImage as a composable step:
from fabricatio import WorkFlow
from fabricatio_comfyui import GenerateImage
GenerateImageWorkflow = WorkFlow(
name="ComfyUI Generate",
steps=(
GenerateImage(
prompt="masterpiece, best quality",
download_dir="./outputs",
),
),
)
Standalone client (advanced)
The mixin and the one-shot functions share one pooled client per server
URL, obtained from the cached factory
fabricatio_comfyui.http_client.get_comfyui_client — never close the
client it returns. When you need a private pool instead (tests,
alternate backends), build a scoped one with async with:
import asyncio
from fabricatio_comfyui import ComfyUIHttpClient
async def main() -> None:
async with ComfyUIHttpClient.create() as client:
results = await client.generate("a mountain landscape", seed=42)
if results[0].succeeded():
await client.download_images(results[0], "./outputs")
asyncio.run(main())
API Reference
Capability methods (UseComfyUI)
| Method | Description |
|---|---|
generate_image(prompt, …) |
Queue the bundled graph → poll → download → return the image path (Path | None); cancelling the call interrupts the running job server-side |
generate_image keyword parameters: negative_prompt, prop, mp,
seed, steps, cfg, checkpoint, loras, download_dir, timeout.
download_dir resolution: per-call argument → scoped config on the
Role (UseComfyUI inherits ComfyUIScopedConfig; set it as a subclass
default class ImageRole(Role, UseComfyUI): download_dir: str = "./outputs"
or per instance) → [ext.comfyui] download_dir config.
On failure the methods above return None — a single prompt yields at most
one image file path. A list of prompts yields a list with one entry per
prompt (Path | None each, None marking a failed generation), generated
sequentially:
paths = await role.generate_image(["a mountain", "a river"])
# paths: list[Path | None] — [path, None] when the second generation failed
The module-level function in fabricatio_comfyui.api (generate_image)
shares the exact same keyword surface and hides the client lifecycle
entirely. Server-side state inspection and control (queue, history,
interrupt) stay on the client transport, which mirrors the REST API
one-to-one — generate interrupts the running job automatically when
its awaiting task is cancelled.
Client methods (ComfyUIHttpClient / ComfyUIClientBase)
| Method | Returns | Description |
|---|---|---|
generate(prompt, …) |
list[ExecutionResult] |
Queue each prompt (str or list) and poll; loras chains LoRA nodes into the graph; one result per prompt, in order; cancelling interrupts the running job |
get_queue_info() |
QueueInfo |
Fetch current queue status |
get_history(prompt_id) |
HistoryEntry | None |
Retrieve execution history for a prompt |
wait_for_completion(prompt_id) |
ExecutionResult |
Poll until execution finishes |
get_image(filename, …) |
bytes |
Download a single generated image |
upload_image(image_path, …) |
UploadResponse |
Upload an image |
interrupt() |
None |
Interrupt the running generation |
download_images(result, dir) |
list[Path] |
Download all output images concurrently; returns their local paths |
download_first_image(result, dir) |
Path | None |
Download the first output image and return its local path |
Actions
| Class | Fields | Description |
|---|---|---|
GenerateImage |
prompt, negative_prompt, prop, mp, seed, steps, cfg, checkpoint, download_dir, timeout |
Generate images from typed knobs |
Models
API responses are deserialized into frozen Pydantic models; SketchSpec
below is the propose-able generation instruction:
| Model | Description |
|---|---|
PromptResponse |
Response from POST /prompt — contains prompt_id |
ExecutionResult |
Final result — outputs, all_images, succeeded |
OutputImage |
Single image metadata — filename, subfolder, type |
HistoryEntry |
Execution history — status, per-node outputs |
QueueInfo |
Queue state — queue_running, queue_pending |
UploadResponse |
Upload result — name, subfolder, type |
SketchSpec |
Complete generation instruction — prompt, negative_prompt, prop, mp — proposed by the LLM in one propose call |
License
MIT — see the LICENSE file.
Metadata
Release files for fabricatio-comfyui 0.8.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fabricatio_comfyui-0.8.3-py3-none-any.whl | Python 3 | none | any | Details |
Release files / fabricatio_comfyui-0.8.3-py3-none-any.whl
| Download URL | fabricatio_comfyui-0.8.3-py3-none-any.whl |
|---|---|
| Size | 62.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
00f350926790d0eadce511dc9ea6382c716319e749af498022702828278382f7
|
|
BLAKE2b-256 checksum How to use checksums |
503737154fc18e3776377c08ee3bf9a220ae2cf92fcd44f3c3253adfddfe61f4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|