mirror of
https://github.com/Comfy-Org/comfy-python-sdk.git
synced 2026-11-04 23:37:47 +00:00
[PR #215] feat(models): expose the queued submit's cost quote as RequestHandle.estimate #215
Labels
No labels
audit:ai-readiness
audit:community
audit:github
audit:health-scoring
audit:policy
audit:release
audit:supply-chain
effort:medium
effort:small
effort:small
effort:trivial
priority:nice-to-have
priority:nice-to-have
priority:recommended
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/comfy-python-sdk#215
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/comfy-python-sdk/pull/215
Author: @mattmillerai
Created: 10/9/2026
Status: 🔄 Open
Base:
main← Head:matt/be-19975-cost-estimate📝 Commits (6)
1430fcafeat(models): expose the queued submit's cost quote as RequestHandle.estimatea7c3fb3fix(models): drop a cost quote whose promised figure cannot be read76855b9fix(models): drop an estimated quote whose bounds are reversed01584beMerge branch 'main' into matt/be-19975-cost-estimate752b7b7test(models): cover negative and non-finite estimate range bounds0205d1cMerge remote-tracking branch 'origin/main' into matt/be-19975-cost-estimate📊 Changes
9 files changed (+505 additions, -9 deletions)
View changed files
📝
CHANGELOG.md(+8 -0)📝
README.md(+19 -0)📝
src/comfy_sdk/__init__.py(+8 -1)📝
src/comfy_sdk/model_requests.py(+225 -2)📝
src/comfy_sdk/models.py(+11 -2)📝
tests/conftest.py(+6 -0)📝
tests/test_models_queue.py(+203 -1)📝
tests/test_router_spec_contract.py(+23 -1)📝
uv.lock(+2 -2)📄 Description
ELI5
When you queue a model request, Router can tell you up front roughly what it will cost, and the SDK now keeps that quote for you instead of throwing it away. A quote that makes no sense, such as a price range whose low end is above its high end, is dropped rather than shown, so nobody gets an impossible price.
Update (brought current with
main)76855b9._estimate_ofreturnsNonewhenmin_amount > max_amount(compared asDecimal), and the CodeRabbit thread is resolved, with a reply naming that commit. CodeRabbit has since re-reviewed752b7b7and approved it, which replaces its earlierCHANGES_REQUESTEDreview (ona7c3fb3).01584bemergesmaininto the branch. The 0.5.0 cut onmainleft[Unreleased]empty, and git's auto-merge silently filed this PR's changelog entry under the released0.5.0heading. This commit moves it back under[Unreleased].752b7b7adds malformed-quote cases for anestimatedrange whosemin_amountormax_amountis negative,NaN,Infinityor a JSON number. Each one reads as no quote, alongside the existing reversed-range case. The tests only add coverage. The code already rejected all of these through the plain-decimal pattern.mainwith a merge commit (no rebase, no force-push). See Residual.What
CostEstimate(comfy_sdk.CostEstimate,comfy_sdk.model_requests.CostEstimate). It's a frozen dataclass that mirrors the spec'sRouterCostEstimate:source,currency,provider,model,pricing_as_of, the optionalamount/min_amount/max_amount(kept as the spec's decimal strings, never turned into floats),amount_cents/min_amount_cents/max_amount_cents/credits,reason, andraw. It also hasis_exact/is_estimated/is_unknown.sourceis an open string, so any value other thanexact/estimatedreads as unknown, as the spec says.RequestHandle.estimate/AsyncRequestHandle.estimateis a read-onlyCostEstimate | None. The docstring says whatNonemeans: the estimate is off for the caller, or the submit got an idempotent replay (and the SDK's own transport retry inside onesubmitcan get one), or the handle was rebuilt withclient.models.handle.submits read the quote with_estimate_of(body), which never raises. The submit already succeeded at that point, so a missing or malformed quote just means no quote. A required field that is missing, blank or has the wrong type drops the whole quote. So does a quote whosesourcepromises a figure that can't be read:exactwith no readableamount, orestimatedwithout both bounds or withmin_amountabovemax_amount. A money string must be a plain non-negative decimal. A number must be finite and non-negative. Otherwise the field is leftNoneand not converted. An unrecognisedsourcecarries no figure fields; they are still inraw.rawis left out of the hash, so a quote is hashable.__init__s gain a trailing keyword-onlyestimate=None. The four positional parameters are unchanged, soclient.models.handle()and the sync/async parity test don't change.__repr__on the handle adds, estimate='<source>'only when a quote is present.ServerState.queue_submit_estimateis sent verbatim on the submit body when it is set.Judgment calls
_estimate_ofalso returnsNonewhen a numeric field is an int too large for a float (OverflowError). This keeps the "never raises" contract airtight.CostEstimatefields", it checks that the spec'srequiredlist equals the dataclass's no-default fields, and thatRouterQueueSubmitFields.estimatestill points atRouterCostEstimate.estimatedrange branch next toexact.Provenance
mainmerge (752b7b7), on one local interpreter:ruff check .: clean.ruff format --check .: clean.mypy src: clean.pytest: 1299 passed, 9 skipped. The skips are the existing env-gated integration tests.python scripts/check_drift.py: all three checks OK. The models are byte-identical to the regenerated output, the router error types are blessed, and the run content types match.python3 scripts/check_public_repo_hygiene.py: OK.752b7b7is green: Test on py3.10–3.13, codegen drift, build-check, public-repo-hygiene and CodeRabbit.properties,required) and were not judged one by one.main, with no history rewrite.Residual
752b7b7, and every review thread is resolved.main. #209 squash-merged the same cloud@9794097 spec intomain, together with the re-blessedModelNotFoundread marker (6c0e0e327e4d). After the merge, #211's whole diff againstmainis a 12-line rewording of theModelNotFounddocstring. The spec, the generated models and the read marker are all unchanged._generated.pywas regenerated bycheck_drift.pyand is byte-identical; nothing is generated from the Router spec. The full gates pass on #211's merged head (1265 passed, 9 skipped; drift and hygiene OK). A maintainer should either merge #211 for the wording or close it as landed via #209. Both are safe.201that carriesestimatewas not exercised, because this run had no credentials for an environment with the quote on.tests/integration/test_gateway_e2e.pydoes not assert onestimate.queue_submit_statusstill defaults to200, while the real route answers201. This is cosmetic, since the SDK does not branch on the status.POST /v2/models/{provider}/{model}/estimateoperation (estimateRouterModelCost) is still not wrapped by the SDK. It would be its own change.submit()signatures. Whichever lands second needs a merge frommain.amount_cents/min_amount_cents/max_amount_cents/creditsthat are negative or non-finite are set toNoneindividually rather than dropping the whole quote, because they are optional fields. The decimal-stringamount/min_amount/max_amountare what decide whether a quote survives.Summary by CodeRabbit
CostEstimatetype. Estimates may be unavailable, including for replayed or rehydrated requests, and are not price guarantees.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.