-
Notifications
You must be signed in to change notification settings - Fork 5
feat(models): add model_provider, strict_mode and fallback_provider to run() #140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -22,7 +22,7 @@ | |||||||||
| from dataclasses import dataclass, field | ||||||||||
| from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | ||||||||||
| from typing import Any | ||||||||||
| from urllib.parse import unquote | ||||||||||
| from urllib.parse import parse_qs, unquote | ||||||||||
|
|
||||||||||
| import pytest | ||||||||||
|
|
||||||||||
|
|
@@ -197,9 +197,13 @@ class ServerState: | |||||||||
| # asserts the id the caller passed rather than a particular encoding of it. | ||||||||||
| last_model_run_provider: str | None = None | ||||||||||
| last_model_run_model: str | None = None | ||||||||||
| # ...and the raw, still-encoded request path, for the tests that are about | ||||||||||
| # the encoding itself. | ||||||||||
| # ...and the raw, still-encoded request path (query string stripped), for | ||||||||||
| # the tests that are about the encoding itself. | ||||||||||
| last_model_run_path: str | None = None | ||||||||||
| # The last model run's query string, decoded to one value per key (see | ||||||||||
| # `_post_model_run`'s own note on why `parse_qs`'s list form is not kept). | ||||||||||
| # `None` before any run; `{}` after a run that carried none. | ||||||||||
| last_model_run_query: dict[str, str] | None = None | ||||||||||
| # Every Idempotency-Key seen on a model run, in arrival order (`None` | ||||||||||
| # records a run that arrived without the header at all). | ||||||||||
| model_run_idempotency_keys: list[str | None] = field(default_factory=list) | ||||||||||
|
|
@@ -498,10 +502,14 @@ def do_POST(self) -> None: | |||||||||
| # `/api/v2` paths above (a different host in production; the same | ||||||||||
| # stub here, with `COMFY_ROUTER_BASE_URL` pointed at it). The two | ||||||||||
| # segments are the model id, so they are matched rather than | ||||||||||
| # compared to a fixed string. | ||||||||||
| m = re.match(r"/v2/models/([^/]+)/([^/]+)$", self.path) | ||||||||||
| # compared to a fixed string. Split off the query string before | ||||||||||
| # matching: `model_provider`/`strict_mode`/`fallback_provider` ride | ||||||||||
| # in it, and `[^/]+` would otherwise swallow a `?...` suffix into | ||||||||||
| # the model segment rather than leaving it for `_post_model_run`. | ||||||||||
| path_only, _, query_string = self.path.partition("?") | ||||||||||
| m = re.match(r"/v2/models/([^/]+)/([^/]+)$", path_only) | ||||||||||
| if m: | ||||||||||
| self._post_model_run(m.group(1), m.group(2)) | ||||||||||
| self._post_model_run(m.group(1), m.group(2), query_string) | ||||||||||
| return | ||||||||||
| m = re.match(r"/api/v2/jobs/([^/]+)/cancel$", self.path) | ||||||||||
| if m: | ||||||||||
|
|
@@ -527,7 +535,7 @@ def _post_from_hash(self) -> None: | |||||||||
| else: | ||||||||||
| self._err(404, "blob_not_found", "no such blob") | ||||||||||
|
|
||||||||||
| def _post_model_run(self, provider: str, model: str) -> None: | ||||||||||
| def _post_model_run(self, provider: str, model: str, query_string: str) -> None: | ||||||||||
| state.model_run_count += 1 | ||||||||||
| # Decoded, because the SDK percent-encodes each segment and a real | ||||||||||
| # origin server decodes it before routing — asserting the encoded | ||||||||||
|
|
@@ -536,7 +544,12 @@ def _post_model_run(self, provider: str, model: str) -> None: | |||||||||
| # for the tests that are about the encoding. | ||||||||||
| state.last_model_run_provider = unquote(provider) | ||||||||||
| state.last_model_run_model = unquote(model) | ||||||||||
| state.last_model_run_path = self.path | ||||||||||
| state.last_model_run_path = self.path.partition("?")[0] | ||||||||||
| # `parse_qs` drops a key with no value at all, which never happens | ||||||||||
| # here — model_run_request only ever adds a key with a real value — | ||||||||||
| # so a plain single-valued dict is the faithful, easy-to-assert | ||||||||||
| # shape rather than parse_qs's list-per-key one. | ||||||||||
| state.last_model_run_query = {k: v[0] for k, v in parse_qs(query_string).items()} | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
for file in pyproject.toml .python-version .tool-versions tox.ini; do
[ -f "$file" ] && { echo "== $file =="; cat "$file"; }
done
: "${PYTHON_BIN:?Set PYTHON_BIN to the repository-declared Python interpreter}"
"$PYTHON_BIN" - <<'PY'
from urllib.parse import parse_qs
assert parse_qs("model_provider=") == {}
assert parse_qs("model_provider=", keep_blank_values=True) == {
"model_provider": [""]
}
PYRepository: Comfy-Org/comfy-python-sdk Length of output: 4987 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '== tests/conftest.py imports and fixture =='
sed -n '1,40p' tests/conftest.py
sed -n '500,565p' tests/conftest.py
printf '%s\n' '== direct callers/definitions =='
rg -n -C 4 'model_run_request|last_model_run_query|parse_qs|model_provider' tests srcRepository: Comfy-Org/comfy-python-sdk Length of output: 38251 🤖 get_repo_knowledge executed:
Length of output: 2126 🏁 Script executed: python3 - <<'PY'
from urllib.parse import parse_qs
print(parse_qs("model_provider="))
print(parse_qs("model_provider=", keep_blank_values=True))
PYRepository: Comfy-Org/comfy-python-sdk Length of output: 192 Preserve explicitly blank query values.
Proposed fix- state.last_model_run_query = {k: v[0] for k, v in parse_qs(query_string).items()}
+ state.last_model_run_query = {
+ k: v[0] for k, v in parse_qs(query_string, keep_blank_values=True).items()
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Path instructions |
||||||||||
| state.last_model_run_body = json.loads(self._read_body() or b"{}") | ||||||||||
| key = self.headers.get("Idempotency-Key") | ||||||||||
| state.model_run_idempotency_keys.append(key) | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
Do not hand-edit the vendored Router specification.
Regenerate or import this change from the authorized upstream source. Local edits can drift from the vendored contract and be overwritten by the next sync.
As per coding guidelines,
spec/router-openapi.yamlis “Vendored, synced one-way. Never hand-edit.”🧰 Tools
🪛 Checkov (3.3.13)
[high] 7-620: Ensure that the global security field has rules defined
(CKV_OPENAPI_4)
🤖 Prompt for AI Agents
Source: Coding guidelines