comfy-api-proxy is a local Python/aiohttp service that puts the Comfy API v2 (/api/v2/*) in front of a self-hosted ComfyUI, so client SDK code written against Comfy Cloud runs unchanged against a ComfyUI on the user's own machine. It wraps ComfyUI's native HTTP + WebSocket API one-to-one: submit / poll / cancel jobs, a Server-Sent-Events stream bridged from ComfyUI's /ws, and asset upload/download with local blake3 dedup and guarded model-directory placement. The repo is public and ships to PyPI as comfy-api-proxy.
These are exactly the checks CI runs (.github/workflows/ci.yml). Run them from the repo root in a virtualenv:
pip install -e ".[dev]" # package + dev tools
ruff check . # lint
ruff format --check . # format check (drop --check to fix in place)
mypy src/comfy_api_proxy # type-check (lenient — see [tool.mypy] in pyproject.toml)
pytest -v # unit + end-to-end tests (~130; spawns local servers)
# spec-drift check: regenerate the models and prove nothing changed
python3 scripts/generate_models.py
git diff --exit-code src/comfy_api_proxy/schemas/_generated.pyRun the proxy against an already-running ComfyUI (foreground, Ctrl+C to stop):
comfy-api-proxy --comfyui http://127.0.0.1:8188 --port 8189No-GPU end-to-end demo (run_demo.py drives the proxy through the real Python SDK, which it imports from a sibling comfy-python-sdk checkout — clone that next to this repo first):
python demo/fake_comfyui.py & # stand-in ComfyUI on :8188
comfy-api-proxy & # the proxy on :8189
python demo/run_demo.pyCI jobs: lint, typecheck, spec-drift, test (Python 3.10 / 3.11 / 3.12 matrix), build-check (python -m build + twine check), plus a zizmor workflow audit.
| Path | What's in it |
|---|---|
src/comfy_api_proxy/ |
The package. app.py is the whole v2 HTTP surface (routes + handlers); realtime.py the ComfyUI-WebSocket→SSE bridge; assets.py the in-memory asset + hash index; security.py the upload-placement guards; middleware.py the origin guard; auth.py optional bearer-token auth; cli.py the argparse entry point; service.py background start/stop; schemas/ the generated pydantic models. |
tests/ |
pytest suite. conftest.py spawns the fake ComfyUI and the real proxy as subprocesses on free ports; test_endpoints.py / test_smoke.py drive them over plain HTTP. |
spec/ |
Vendored, filtered copy of the canonical Comfy API v2 OpenAPI contract, plus a VERSION provenance pin. Generated — never hand-edit. |
scripts/ |
sync-spec.sh (fetch + filter the upstream spec), filter_openapi.py (the redaction filter), generate_models.py (spec → pydantic models). |
demo/ |
fake_comfyui.py (a GPU-free ComfyUI stand-in) and run_demo.py (SDK-driven demo). Human-facing; CI never runs run_demo.py. |
docs/ |
sync-workflow.md — the proposed upstream-side spec-sync workflow. |
.github/workflows/ |
ci.yml, ci-audit-workflows.yml (zizmor), publish.yml (PyPI), cla.yml. |
- This repo is public. Never put an internal ticket id, internal repo slug, internal service/tool name, or private-spec text into any file, commit message, or PR.
tests/test_no_internal_leaks.pyenforces that with a pattern scan overdocs/,spec/,scripts/and every root-level*.md— including this file — so a leak failspytest, not review. spec/openapi.yaml,spec/VERSION, andsrc/comfy_api_proxy/schemas/_generated.pyare generated; never hand-edit them. Contract changes flow upstream → here only:scripts/sync-spec.sh <path-or-url> [sha], thenpython3 scripts/generate_models.py. CI'sspec-driftjob regenerates the models and fails the build on any diff, so a spec sync without a regeneration is caught immediately.- The generated models are test-only.
tests/test_schema_conformance.pyvalidates real handler responses against them; nothing on the request-handling path imports them. Don't wire them into handlers. - The security defaults are the product, not a suggestion: loopback-only bind (widening
--hostrequires--tokenor--allow-insecure-bind), a default-on origin-check middleware ported from ComfyUI core, model uploads verified safetensors-only by parsing the file's own header, an allowlist of destination roots (configsandcustom_nodesdeliberately excluded), symlink-resolved traversal checks, and atomicO_EXCLwrites. Any change tosecurity.py,middleware.py, orauth.pyships with a test proving the guard still holds — seetests/test_security.py,test_middleware.py,test_auth.py,test_upload_validation.py. - Tests use the standard library only — no SDK, no third-party HTTP client, nothing beyond localhost — so CI never depends on another repo or a credential. Keep it that way.
- Don't bump
versioninpyproject.toml. Publishing is tag-driven:.github/workflows/publish.ymlinjects the release tag (vX.Y.Z) at build time, so the committed value is a placeholder. - Workflows are zizmor-audited. Pin every action to a full commit SHA with a trailing
# vX.Y.Zcomment, and keeppersist-credentials: falseon checkouts. - Commits and PRs: conventional-commit subjects (
feat:,fix:,docs:,ci:,test:,chore:), normally squash-merged with the PR number in the subject..github/CODEOWNERSowns every path, so each PR needs an approving review from the code-owner teams. - Style: Python 3.10+ (
from __future__ import annotationsat the top of every module but the package__init__.py), ruff withline-length = 100and theE,F,I,UP,Wrule set, mypy deliberately lenient — not--strict, so full annotation coverage is not required. - CodeRabbit reviews this repo using the per-path instructions in
.coderabbit.yaml(demo/is held to a lower bar thansrc/; ruff findings are suppressed there because CI already runs them).
README.md— user-facing overview, the full/api/v2/operation table, the CLI flag reference, the security defaults, and the known limitations (in-memory state, non-streaming uploads).spec/README.md— what "filtered" means, what the filter deliberately keeps, and the one-way sync rule.docs/sync-workflow.md— the upstream-side workflow that pushes spec changes down here.