mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-06-26 09:49:26 +08:00
refactor(jobs): return NamedTuple page, early-out on empty job set
Review feedback on the jobs cursor pagination: - get_all_jobs now returns JobsPage, a NamedTuple, instead of a bare 4-tuple (callers unpack positionally either way). - Early-out when the filtered job set is empty so paging code never has to reason about indexing into an empty list. A malformed 'after' cursor is still decoded first and rejected with INVALID_CURSOR. - Document that job ids are server-assigned UUIDs, always present and unique — the empty-string fallback in _job_id_key only shields sorted() from a malformed dict, it is not part of the keyset contract.
This commit is contained in:
parent
a8b24cb0bb
commit
f4e51b9ef9
@ -3,7 +3,7 @@ Job utilities for the /api/jobs endpoint.
|
|||||||
Provides normalization and helper functions for job status tracking.
|
Provides normalization and helper functions for job status tracking.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional
|
from typing import NamedTuple, Optional
|
||||||
|
|
||||||
from comfy_api.internal import prune_dict
|
from comfy_api.internal import prune_dict
|
||||||
from utils.cursor import (
|
from utils.cursor import (
|
||||||
@ -18,6 +18,14 @@ from utils.cursor import (
|
|||||||
CURSOR_SORT_FIELD = 'created_at'
|
CURSOR_SORT_FIELD = 'created_at'
|
||||||
|
|
||||||
|
|
||||||
|
class JobsPage(NamedTuple):
|
||||||
|
"""One page of the jobs listing, as returned by get_all_jobs."""
|
||||||
|
jobs: list[dict]
|
||||||
|
total_count: int
|
||||||
|
has_more: bool
|
||||||
|
next_cursor: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
class JobStatus:
|
class JobStatus:
|
||||||
"""Job status constants."""
|
"""Job status constants."""
|
||||||
PENDING = 'pending'
|
PENDING = 'pending'
|
||||||
@ -293,6 +301,11 @@ def get_outputs_summary(outputs: dict) -> tuple[int, Optional[dict]]:
|
|||||||
|
|
||||||
|
|
||||||
def _job_id_key(job: dict) -> str:
|
def _job_id_key(job: dict) -> str:
|
||||||
|
# Job ids are server-assigned prompt UUIDs and are always present and
|
||||||
|
# unique, so the (sort_value, id) pair below is a valid keyset. The
|
||||||
|
# fallback is not part of that contract — it only keeps a malformed job
|
||||||
|
# dict from raising TypeError inside sorted() (None is unorderable
|
||||||
|
# against str).
|
||||||
return job.get('id') or ''
|
return job.get('id') or ''
|
||||||
|
|
||||||
|
|
||||||
@ -357,7 +370,7 @@ def get_all_jobs(
|
|||||||
limit: Optional[int] = None,
|
limit: Optional[int] = None,
|
||||||
offset: int = 0,
|
offset: int = 0,
|
||||||
after: Optional[str] = None
|
after: Optional[str] = None
|
||||||
) -> tuple[list[dict], int, bool, Optional[str]]:
|
) -> JobsPage:
|
||||||
"""
|
"""
|
||||||
Get all jobs (running, pending, completed) with filtering and sorting.
|
Get all jobs (running, pending, completed) with filtering and sorting.
|
||||||
|
|
||||||
@ -376,7 +389,7 @@ def get_all_jobs(
|
|||||||
InvalidCursorError on a malformed cursor.
|
InvalidCursorError on a malformed cursor.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tuple: (jobs_list, total_count, has_more, next_cursor)
|
JobsPage: (jobs, total_count, has_more, next_cursor)
|
||||||
next_cursor is non-None only for created_at sort when more rows remain.
|
next_cursor is non-None only for created_at sort when more rows remain.
|
||||||
"""
|
"""
|
||||||
jobs = []
|
jobs = []
|
||||||
@ -408,10 +421,22 @@ def get_all_jobs(
|
|||||||
total_count = len(jobs)
|
total_count = len(jobs)
|
||||||
|
|
||||||
use_cursor = after is not None and sort_by == CURSOR_SORT_FIELD
|
use_cursor = after is not None and sort_by == CURSOR_SORT_FIELD
|
||||||
if use_cursor:
|
cursor_payload = (
|
||||||
|
decode_cursor(after, [CURSOR_SORT_FIELD], expected_order=sort_order)
|
||||||
|
if use_cursor
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
# Early-out on an empty result set: nothing to page through and no cursor
|
||||||
|
# to mint, and downstream code never has to reason about indexing into an
|
||||||
|
# empty list. The cursor is still decoded above so a malformed `after` is
|
||||||
|
# rejected with INVALID_CURSOR even when there are no jobs.
|
||||||
|
if total_count == 0:
|
||||||
|
return JobsPage([], 0, False, None)
|
||||||
|
|
||||||
|
if cursor_payload is not None:
|
||||||
ascending = sort_order == 'asc'
|
ascending = sort_order == 'asc'
|
||||||
payload = decode_cursor(after, [CURSOR_SORT_FIELD], expected_order=sort_order)
|
cursor_key = (decode_cursor_int(cursor_payload), cursor_payload.id)
|
||||||
cursor_key = (decode_cursor_int(payload), payload.id)
|
|
||||||
jobs = [
|
jobs = [
|
||||||
j for j in jobs
|
j for j in jobs
|
||||||
if (_job_keyset(j) > cursor_key if ascending else _job_keyset(j) < cursor_key)
|
if (_job_keyset(j) > cursor_key if ascending else _job_keyset(j) < cursor_key)
|
||||||
@ -436,7 +461,7 @@ def get_all_jobs(
|
|||||||
order=sort_order,
|
order=sort_order,
|
||||||
)
|
)
|
||||||
|
|
||||||
return (jobs, total_count, has_more, next_cursor)
|
return JobsPage(jobs, total_count, has_more, next_cursor)
|
||||||
|
|
||||||
|
|
||||||
def _job_keyset(job: dict) -> tuple[int, str]:
|
def _job_keyset(job: dict) -> tuple[int, str]:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user