From cd41b981965515a415fee9af8e7f1cd0e3f5a688 Mon Sep 17 00:00:00 2001 From: bymyself Date: Wed, 19 Aug 2026 19:17:34 -0700 Subject: [PATCH 1/5] docs: add CONTRIBUTING.md covering uv setup, required checks, and the codegen gate Public SDK with no contributor guide. Documents uv-based setup (--extra dev is required: the dev tools are a PEP 621 extra, so uv does not install them by default), the four checks CI enforces, and the pip path CI itself uses. Calls out the trap an outside contributor hits first: src/comfy_low/models/ _generated.py is generated, ruff and mypy both exclude it, so a hand-edit is invisible locally and only fails in the codegen-drift job. Also fixes the README's gen_models.sh invocation (it is a bash script, not a python one) and links the new guide from the Development section. --- CONTRIBUTING.md | 179 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 6 +- 2 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..fa2b132 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,179 @@ +# Contributing to comfy-python-sdk + +Thanks for contributing. This document covers local setup, the checks CI +requires, and the one trap that bites new contributors: `comfy_low`'s models +are generated, and hand-editing them is invisible to the linters but fails CI. + +## Prerequisites + +- **Python 3.10+** — CI tests 3.10, 3.11, 3.12, and 3.13. +- **[uv](https://docs.astral.sh/uv/)** — recommended for local work; `uv.lock` + is committed, so `uv run` gives you the same resolved dependency set as + everyone else. Plain `pip` works too (see [Using pip instead](#using-pip-instead)). + +## Setup + +```bash +git clone https://github.com/Comfy-Org/comfy-python-sdk +cd comfy-python-sdk +uv sync --extra dev +``` + +> **`--extra dev` is load-bearing.** The dev tools (ruff, mypy, pytest, +> pytest-asyncio, pytest-cov) are declared in `pyproject.toml` under +> `[project.optional-dependencies]` — a PEP 621 _extra_, not a PEP 735 +> `[dependency-groups]` entry. uv installs the default dependency groups +> automatically, but it never installs an extra unless you ask for it. Omit +> `--extra dev` and `ruff`/`mypy`/`pytest` simply will not be there. + +## Required checks + +Every one of these runs in CI on each pull request and must pass. Run them +locally before pushing: + +```bash +uv run --extra dev ruff check . # lint +uv run --extra dev ruff format --check . # formatting +uv run --extra dev mypy src # type check +uv run --extra dev pytest -v # tests +``` + +`ruff format --check .` only reports; `uv run --extra dev ruff format .` fixes. + +A few things that will fail you that are easy to miss: + +- **Formatting is a gate, not a suggestion.** CI runs `ruff format --check`. +- **`comfy_sdk` requires full annotations.** `disallow_untyped_defs` is on for + `comfy_sdk.*` only (it is the hand-written public surface and ships + `py.typed`). `comfy_low` wraps generated code, so it is exempt. +- **Deprecation warnings are errors.** `filterwarnings` turns + `DeprecationWarning` and `PendingDeprecationWarning` into test failures — + they are the advance warning that a dependency bump is about to break the SDK. +- **Markers and config are strict.** `--strict-markers --strict-config`, so a + typo'd `@pytest.mark.*` or a bad ini key fails rather than being ignored. + +CI runs two more jobs beyond the four above: + +- **`build-check`** — builds the sdist and wheel and runs `twine check`, so a + broken distribution is caught in PR CI instead of at release time. +- **`public-repo-hygiene`** — `python3 scripts/check_public_repo_hygiene.py` + scans for internal-only references. This is a public repo; the check is a + permanent gate, not a one-time cleanup. + +## The codegen trap: `src/comfy_low/models/_generated.py` + +`src/comfy_low/models/_generated.py` is **the only generated file in the repo**. +It is produced by `datamodel-code-generator` from the vendored OpenAPI document +at `spec/openapi.yaml`, and it is committed. + +**A hand-edit of that file is invisible to every local check and still fails CI.** +Both linters deliberately skip it — `[tool.ruff] extend-exclude` and +`[tool.mypy] exclude` — so its formatting stays byte-identical to the +generator's output. What catches an edit is the separate **`codegen-drift`** CI +job, which regenerates the file into a temp directory and diffs it byte-for-byte +against the committed copy. + +So if you need to change a model, **change `spec/openapi.yaml` and regenerate**: + +```bash +uv run --extra codegen bash scripts/gen_models.sh # regenerate +uv run --extra codegen python scripts/check_drift.py # the exact check CI runs +``` + +Then commit the regenerated `src/comfy_low/models/_generated.py` alongside your +spec change. + +Notes: + +- `scripts/gen_models.sh` is a **bash** script — run it with `bash`, not `python`. +- `datamodel-code-generator` is pinned (`~=0.68.1`) on purpose. The drift gate + compares byte-for-byte, so the generator version is load-bearing; an + unpinned bump would reformat the output and flag false drift. Do not loosen + that pin without regenerating in the same commit. +- ruff and mypy are pinned for the same class of reason: an unpinned bump + silently changes what CI catches between PRs. + +## Using pip instead + +uv is a convenience, not a requirement — CI itself does not use it. The CI test +job installs with pip and then invokes the tools directly: + +```bash +pip install -e ".[dev]" +ruff check . +ruff format --check . +mypy src +pytest -v +``` + +and for codegen: + +```bash +pip install -e ".[codegen]" +bash scripts/gen_models.sh +python scripts/check_drift.py +``` + +## Optional extras + +| Extra | What it is for | +| --------- | ------------------------------------------------------------------------------------ | +| `dev` | ruff, mypy, pytest, pytest-asyncio, pytest-cov — everything the required checks need | +| `codegen` | `datamodel-code-generator` + PyYAML, for regenerating `comfy_low` models | +| `pil` | Pillow, so `Preview.to_pil()` can decode an in-progress preview frame | + +## Tests + +```bash +uv run --extra dev pytest -v # the suite CI runs +uv run --extra dev pytest --cov # with coverage +uv run --extra dev pytest tests/test_jobs.py -v # a single file +``` + +`asyncio_mode = "auto"`, so `async def` tests need no `@pytest.mark.asyncio`. + +`tests/integration/` holds a live end-to-end suite against a real gateway. It is +env-gated and skipped unless `COMFY_BASE_URL` and `COMFY_API_KEY` are set, so it +does not run in normal CI. If your change touches upload/dedup, submission, +polling, SSE, or output download, running it against a real deployment is worth +the effort — several past releases were verified that way. + +## Pull requests + +- **Branch from `main`** and open the PR against `main`. +- **Conventional commits.** The history uses `feat:`, `fix:`, `docs:`, `chore:`, + `ci:`, `test:`, and `feat!:` / `fix!:` for breaking changes. PR titles follow + the same form — they become the squashed commit message. +- **No AI attribution trailers** in commit messages (no `Co-Authored-By:` for an + assistant, no "Generated with ..." lines). +- **CLA.** A first-time contributor is asked by the CLA Assistant bot to comment + the signing phrase on their PR. Only the PR author needs to sign. +- **Review.** `.github/CODEOWNERS` requires an approving review from + `@Comfy-Org/comfy-cloud-team` or `@Comfy-Org/core-engine-team` on every PR. +- **Update `CHANGELOG.md`.** Add a bullet under `## [Unreleased]` describing the + user-visible change. Purely internal changes (CI, refactors with no API + effect) do not need an entry. +- **Update the README** when you change the public surface — it is the primary + documentation for this SDK. + +## Reporting bugs and requesting features + +Use the [issue templates](https://github.com/Comfy-Org/comfy-python-sdk/issues/new/choose). +For a bug, the SDK version, the Python version, and a minimal reproducible +snippet are what make it actionable. + +## Releases + +Maintainers only. Releases are published to PyPI by +[`.github/workflows/publish.yml`](.github/workflows/publish.yml) when a GitHub +Release is published with a `vX.Y.Z` tag, using PyPI Trusted Publishing (OIDC) — +no API token lives in this repo. + +Versioning is **tag-driven**: the tag is the single source of truth and is +injected into `pyproject.toml` at build time, so the version committed there is +a placeholder and **no version-bump commit is needed**. Before cutting a +release, move the `## [Unreleased]` entries in `CHANGELOG.md` under the new +version heading. + +Running the workflow manually (`workflow_dispatch`) is a dry run: it builds and +runs `twine check` but never reaches the publish job. diff --git a/README.md b/README.md index a6b8e98..d4d977c 100644 --- a/README.md +++ b/README.md @@ -336,6 +336,10 @@ Clients for the same Comfy API v2 contract: ## Development +See [CONTRIBUTING.md](CONTRIBUTING.md) for the uv-based setup, the full list of +checks CI requires, and why `src/comfy_low/models/_generated.py` must never be +hand-edited. + ```bash pip install -e ".[dev]" ruff check . @@ -348,7 +352,7 @@ Regenerating and checking the vendored protocol layer (a separate CI job): ```bash pip install -e ".[codegen]" -python scripts/gen_models.sh # regenerate comfy_low models from spec/openapi.yaml +bash scripts/gen_models.sh # regenerate comfy_low models from spec/openapi.yaml python scripts/check_drift.py # same check CI runs; fails if committed models drifted ``` From c406d2ed00413d938b44c3e2d5c93c9a80e7d7d3 Mon Sep 17 00:00:00 2001 From: bymyself Date: Wed, 19 Aug 2026 19:17:34 -0700 Subject: [PATCH 2/5] docs: add bug report and feature request issue templates Adapted from Comfy-Org/comfy-cli's templates, with the fields a client-library report needs to be actionable: SDK version, Python version, which deployment (Cloud / serverless / self-hosted proxy), and a minimal repro. --- .github/ISSUE_TEMPLATE/bug_report.md | 48 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 35 +++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..b59c485 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,48 @@ +--- +name: Bug report +about: Create a bug report to help us improve. +title: "" +labels: bug +assignees: "" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**SDK version** +Output of `pip show comfy-sdk` (or `python -c "import comfy_sdk; print(comfy_sdk.__version__)"`). + +**Python version** +Output of `python --version`. + +**Which deployment** +Comfy Cloud / serverless / self-hosted (behind [comfy-api-proxy](https://github.com/Comfy-Org/comfy-api-proxy)) — and the proxy version if self-hosted. + +**Minimal reproduction** +The smallest snippet that reproduces it. Please redact your API key. + +```python +from comfy_sdk import Comfy + +client = Comfy(api_key="comfyui-...") +# ... +``` + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Actual behavior** +What happened instead, including the full traceback if there is one. + +``` +paste traceback here +``` + +**Nice to have** + +- [ ] Terminal output +- [ ] The workflow JSON (or a trimmed version of it) +- [ ] Screenshots + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..fd94bf5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,35 @@ +--- +name: Feature request +about: Submit a feature request for this repo. +title: "" +labels: enhancement +assignees: "" +--- + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**What are you trying to do?** +The use case behind the request — what you are building, and what is awkward or +impossible today. + +**Proposed API** +If you have a shape in mind, sketch it. + +```python +from comfy_sdk import Comfy + +client = Comfy(api_key="comfyui-...") +# ... +``` + +**Which deployment does this matter for** +Comfy Cloud / serverless / self-hosted — or all of them. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've +considered, including whether `comfy_low` (the lower-level protocol layer this +SDK is built on) already covers it. + +**Additional context** +Add any other context or screenshots about the feature request here. From 0f85c49557197d565a519476741b18d8924b4c36 Mon Sep 17 00:00:00 2001 From: bymyself Date: Wed, 19 Aug 2026 19:17:34 -0700 Subject: [PATCH 3/5] ci: enable Dependabot for pip and github-actions Weekly, with minor/patch grouped into a single PR per ecosystem so routine churn does not hide a major that needs review. datamodel-code-generator is excluded from minor/patch bumps: the codegen-drift job diffs the generated models byte-for-byte, so a generator bump reformats the output and reds CI until _generated.py is regenerated in the same commit. --- .github/dependabot.yml | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..91f59b7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,53 @@ +version: 2 +updates: + # Python dependencies declared in pyproject.toml (runtime deps plus the + # pil/codegen/dev extras). "pip" is Dependabot's ecosystem name for the + # PEP 621 project table; it reads pyproject.toml, not requirements.txt. + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + commit-message: + prefix: "chore" + include: "scope" + labels: + - "dependencies" + groups: + # One PR for the low-risk bumps, so routine patch churn does not eat + # the PR limit and hide a major that actually needs review. + python-minor-and-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + ignore: + # Pinned on purpose: the codegen-drift CI job diffs the generated models + # byte-for-byte, so a generator bump reformats the output and fails the + # gate until src/comfy_low/models/_generated.py is regenerated in the + # same commit. Majors still come through for a deliberate upgrade. + - dependency-name: "datamodel-code-generator" + update-types: + - "version-update:semver-minor" + - "version-update:semver-patch" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + commit-message: + prefix: "ci" + labels: + - "dependencies" + - "github-actions" + groups: + actions-minor-and-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" From e95edaec095dfe0ed148808c3e9370065c2c4407 Mon Sep 17 00:00:00 2001 From: bymyself Date: Wed, 19 Aug 2026 19:17:34 -0700 Subject: [PATCH 4/5] docs: add CHANGELOG seeded from the published release history Keep a Changelog format, reconstructed from the eight published GitHub Releases (v0.1.0 through v0.1.8). No entries were invented; where a release's notes were sparse the entry stays sparse and links back. Records that 0.1.6 was never published (consumed by a release-pipeline failure) so the version gap is not read as a missing entry. --- CHANGELOG.md | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b40e60c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,165 @@ +# Changelog + +All notable changes to `comfy-sdk` are documented here. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +Entries for `v0.1.0` through `v0.1.8` were reconstructed from the published +[GitHub Releases](https://github.com/Comfy-Org/comfy-python-sdk/releases); those +release notes remain the fuller account, including the end-to-end verification +notes for each version. + +## [Unreleased] + +_Nothing yet — add an entry here when your change lands._ + +## [0.1.8] - 2026-08-13 + +### Added + +- `Job.get_workflow()` / `AsyncJob.get_workflow()` — fetch the workflow behind a + job, including one rehydrated by id. Returns the graph and a `format` + discriminator: `save` (the authoring workflow at the version the job ran, with + canvas layout and editor-only nodes intact) or `api` (the executed API-format + graph). Jobs submitted through this SDK always get `api` today. +- Asset deletion — `Asset.delete()` and `assets.delete(id)`. Thanks to + [@jab416171](https://github.com/jab416171) for the implementation. Requires + backend support: Comfy Cloud has it; self-hosted needs a `comfy-api-proxy` new + enough to serve `DELETE /api/v2/assets/{id}`, older proxies return + `405 Method Not Allowed`. +- `job_id` on outputs and assets, so you can get from an output file back to the + job that produced it without a side table. Absent for uploaded assets, which + have no producing job. +- `expires_at` on assets. + +### Fixed + +- `job_id` and `expires_at` were present on the wire but not exposed by the + public wrapper classes, so they were unreachable without touching a private + attribute. + +## [0.1.7] - 2026-08-12 + +There is no 0.1.6 on PyPI — that number was consumed by a release-pipeline +failure and never published. + +### Changed + +- **Breaking:** the base URL moves from a constructor argument to the + `COMFY_BASE_URL` environment variable. `Comfy()` / `AsyncComfy()` target Comfy + Cloud by default; point the client at another deployment by setting + `COMFY_BASE_URL`. The variable is read on each construction (not at import), + must be an `http(s)` URL, and unset-or-blank means Comfy Cloud. +- **Breaking:** `api_key` is keyword-only, so the old positional form raises + `TypeError` rather than quietly reading a URL as a key. +- `comfy_low`, the documented escape hatch the clients are built on, still takes + a base URL directly and is unchanged. + +## [0.1.5] - 2026-07-30 + +Maintenance release. No API changes — existing code needs no updates. + +### Fixed + +- Ship `py.typed` (PEP 561), so type checkers in consuming projects actually see + the SDK's type information. Previously the annotations were shipped but ignored. +- Derive `__version__` from installed distribution metadata instead of a + hardcoded string, so it can no longer drift from the released version. + +### Changed + +- Ship an MIT license (the package previously declared none) and fill in the + empty package metadata. +- Stop sweeping local dev droppings into the sdist — it now contains only what is + needed to build and run the tests. +- The repository moved from `Comfy-Org/ComfyPythonSDK` to + `Comfy-Org/comfy-python-sdk`. GitHub redirects the old URLs and the PyPI + package name is unchanged (`comfy-sdk`). This is the first release to carry + the corrected repository/issues URLs in its published metadata. +- Docstrings for the public methods that had none; README aligned with the + TypeScript and Swift SDK READMEs. + +## [0.1.4] - 2026-07-28 + +Comfy Cloud now serves the v2 API on `cloud.comfy.org`. `api.comfy.org` +continues to serve the node registry. + +### Changed + +- **Breaking:** `api.comfy.org/api/v2/*` no longer responds. If you pass that + host explicitly, requests 404 until you update. +- `base_url` now defaults to `https://cloud.comfy.org`, so `Comfy(api_key=...)` + targets Comfy Cloud with no host argument. `COMFY_CLOUD_BASE_URL` is exported + for callers who want the value. +- Spec server URL, README, and docstrings updated to the new host. +- Passing an explicit `base_url` still wins — self-hosted and serverless callers + are unaffected. + +## [0.1.3] - 2026-07-27 + +### Fixed + +- Serverless gateway: follow-up links no longer 404 after submit. A gateway + serving the v2 API under a mount prefix (e.g. `/deployment/{id}/api/v2`) + returns `job.urls.*` links that already include that prefix; resolving them + against a `base_url` carrying the same prefix doubled it, so the first + `Job.refresh()` after a successful submit raised `NotFound`. Server-returned + links (leading slash, containing `/api/`) now resolve against the origin — + the link is authoritative about its own path. Internal shorthand paths still + resolve under `base_url`; Comfy Cloud and self-hosted behavior is unchanged. + +### Added + +- An env-gated live integration suite (`tests/integration/test_gateway_e2e.py`) + covering upload → blake3 dedup fast path → img2img submit → poll → output + download against a real gateway. Skipped unless `COMFY_BASE_URL` / + `COMFY_API_KEY` are set. + +## [0.1.2] - 2026-07-23 + +### Added + +- `Output.get_download_url()` — get a fetchable URL for an output instead of + streaming the bytes through your process. On Comfy Cloud / serverless it is a + short-lived, self-authorizing signed storage URL (with `expires_at`); on a + self-hosted proxy it is the content endpoint (`expires_at=None`). Available on + both `Output` and `AsyncOutput`. +- The client now identifies itself via a `User-Agent` header; pass `client_info=` + to attribute your own integration's traffic. + +### Fixed + +- SSE: a read-idle timeout, so a stalled stream can no longer hang `events()`. +- Map entity-specific 404s (`job_not_found` / `asset_not_found`) to `NotFound`. + +## [0.1.1] - 2026-07-21 + +### Added + +- Optional `api_key=` parameter on `submit()` / `run()` (sync and async) that + authenticates partner (API) nodes in a workflow, sent as + `extra_data.api_key_comfy_org`. Omit it (or pass `""`) and no `extra_data` is + sent. The key is never logged or persisted and does not participate in + idempotency. + +## [0.1.0] - 2026-07-21 + +First public release of the Comfy API v2 Python SDK (`comfy-sdk`). + +### Added + +- Run ComfyUI workflows across self-hosted, Comfy Cloud, and serverless from one + typed client: upload/dedup inputs, submit a workflow, follow it (poll or SSE), + and download outputs. +- Sync and async clients. Python 3.10+. + +[unreleased]: https://github.com/Comfy-Org/comfy-python-sdk/compare/v0.1.8...HEAD +[0.1.8]: https://github.com/Comfy-Org/comfy-python-sdk/compare/v0.1.7...v0.1.8 +[0.1.7]: https://github.com/Comfy-Org/comfy-python-sdk/compare/v0.1.5...v0.1.7 +[0.1.5]: https://github.com/Comfy-Org/comfy-python-sdk/compare/v0.1.4...v0.1.5 +[0.1.4]: https://github.com/Comfy-Org/comfy-python-sdk/compare/v0.1.3...v0.1.4 +[0.1.3]: https://github.com/Comfy-Org/comfy-python-sdk/compare/v0.1.2...v0.1.3 +[0.1.2]: https://github.com/Comfy-Org/comfy-python-sdk/compare/v0.1.1...v0.1.2 +[0.1.1]: https://github.com/Comfy-Org/comfy-python-sdk/compare/v0.1.0...v0.1.1 +[0.1.0]: https://github.com/Comfy-Org/comfy-python-sdk/releases/tag/v0.1.0 From fc02845190c7b43d30fb9e6feb329bb45536fb80 Mon Sep 17 00:00:00 2001 From: bymyself Date: Wed, 19 Aug 2026 19:19:44 -0700 Subject: [PATCH 5/5] ci: allowlist the repo's pre-rename name in the public-repo hygiene check The CHANGELOG's v0.1.5 entry records the ComfyPythonSDK -> comfy-python-sdk rename, and the hygiene check's default-deny repo allowlist flagged the old name. It is this same public repo (GitHub still redirects it), so the reference is legitimate; allowlist it rather than scrub the history note. --- scripts/check_public_repo_hygiene.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/check_public_repo_hygiene.py b/scripts/check_public_repo_hygiene.py index 42eb47e..ed45a1a 100644 --- a/scripts/check_public_repo_hygiene.py +++ b/scripts/check_public_repo_hygiene.py @@ -82,6 +82,10 @@ "comfy-python-sdk", "comfy-swift-sdk", "comfy-typescript-sdk", + # This repo's pre-rename name (v0.1.5 moved it to comfy-python-sdk). + # Public, and GitHub still redirects it, so historical references -- the + # CHANGELOG's rename note, old release-notes compare links -- stay valid. + "ComfyPythonSDK", "ComfyUI_frontend", "ComfyUI", }