forked from 0xWheatyz/SPARC
feat: add cursor-based pagination to /jobs endpoint
Add a cursor query parameter to GET /jobs and return a next_cursor field in the response envelope. Existing clients using only limit continue to work without modification. The cursor is an opaque token encoding created_at and job_id for stable keyset pagination. Closes leeworks-agents/SPARC#25 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+39
-5
@@ -77,6 +77,13 @@ class JobStatus(BaseModel):
|
||||
error: str | None = None
|
||||
|
||||
|
||||
class PaginatedJobsResponse(BaseModel):
|
||||
"""Paginated response for job listings."""
|
||||
|
||||
items: list["JobStatus"]
|
||||
next_cursor: str | None = None
|
||||
|
||||
|
||||
class HealthResponse(BaseModel):
|
||||
"""Health check response."""
|
||||
|
||||
@@ -577,24 +584,51 @@ async def get_job_status(
|
||||
return _job_row_to_status(job_row)
|
||||
|
||||
|
||||
@app.get("/jobs", response_model=list[JobStatus], tags=["Jobs"])
|
||||
@app.get("/jobs", response_model=PaginatedJobsResponse, tags=["Jobs"])
|
||||
async def list_jobs(
|
||||
status: Annotated[
|
||||
str | None,
|
||||
Query(description="Filter by status: pending, running, completed, failed"),
|
||||
] = None,
|
||||
limit: Annotated[int, Query(ge=1, le=100)] = 10,
|
||||
cursor: Annotated[
|
||||
str | None,
|
||||
Query(description="Opaque cursor from a previous response's next_cursor field"),
|
||||
] = None,
|
||||
_: UserResponse = Depends(get_current_user),
|
||||
):
|
||||
"""List all analysis jobs.
|
||||
"""List analysis jobs with cursor-based pagination.
|
||||
|
||||
Pass ``limit`` to control page size. The response includes a ``next_cursor``
|
||||
field; pass it back as the ``cursor`` query parameter to fetch the next page.
|
||||
When ``next_cursor`` is ``null``, there are no more results.
|
||||
|
||||
Existing clients that use only ``limit`` (without ``cursor``) continue to
|
||||
work without modification.
|
||||
|
||||
Args:
|
||||
status: Optional filter by job status
|
||||
limit: Maximum number of jobs to return (default 10, max 100)
|
||||
cursor: Opaque pagination cursor from a previous response
|
||||
|
||||
Returns:
|
||||
List of job statuses
|
||||
Paginated list of job statuses
|
||||
"""
|
||||
db = _get_job_db()
|
||||
job_rows = db.list_jobs(status=status, limit=limit)
|
||||
return [_job_row_to_status(row) for row in job_rows]
|
||||
# Fetch one extra to determine if there is a next page
|
||||
job_rows = db.list_jobs(status=status, limit=limit + 1, cursor=cursor)
|
||||
|
||||
has_next = len(job_rows) > limit
|
||||
if has_next:
|
||||
job_rows = job_rows[:limit]
|
||||
|
||||
items = [_job_row_to_status(row) for row in job_rows]
|
||||
|
||||
next_cursor = None
|
||||
if has_next and job_rows:
|
||||
last = job_rows[-1]
|
||||
created = last["created_at"]
|
||||
ts = created.isoformat() if hasattr(created, "isoformat") else str(created)
|
||||
next_cursor = f"{ts}|{last['job_id']}"
|
||||
|
||||
return PaginatedJobsResponse(items=items, next_cursor=next_cursor)
|
||||
|
||||
Reference in New Issue
Block a user