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. 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" 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 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 ``` 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", }