Add public SDK cancel endpoint and CloudClient method
- POST /v1/tasks/{task_id}/cancel: tasks:submit scoped, 200 for
immediate/idempotent cancellation, 202 for newly recorded pending
cancellation, 404 for unknown task, 409 for terminal task.
- TaskCancellationResponse{task_id, status} model.
- Widen list_tasks status_filter Literal to include "cancelled".
- CloudClient.cancel_task(task_id).
- SDK-level tests covering queued/assigned/idempotent/404/409/scope
cases for both the router and CloudClient.
Task 5/9 of task-cancellation change.
This commit is contained in:
@@ -11,6 +11,7 @@ authentication can be added later without changing route signatures.
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import UTC, datetime
|
||||
from typing import TYPE_CHECKING, Callable, Literal
|
||||
|
||||
from cloud.auth import (
|
||||
@@ -31,6 +32,7 @@ from cloud.sdk.models import (
|
||||
PluginRegistrationRequest,
|
||||
PluginResponse,
|
||||
TaskAttemptResponse,
|
||||
TaskCancellationResponse,
|
||||
TaskListItem,
|
||||
TaskListResponse,
|
||||
TaskPlannerDecisionItem,
|
||||
@@ -39,7 +41,7 @@ from cloud.sdk.models import (
|
||||
TaskSubmissionRequest,
|
||||
TaskSubmissionResponse,
|
||||
)
|
||||
from fastapi import APIRouter, HTTPException, Query, Request, status
|
||||
from fastapi import APIRouter, HTTPException, Query, Request, Response, status
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cloud.plugins import PluginRegistry
|
||||
@@ -156,7 +158,9 @@ def create_cloud_router(
|
||||
@router.get("/tasks", response_model=TaskListResponse)
|
||||
def list_tasks(
|
||||
request: Request,
|
||||
status_filter: Literal["queued", "assigned", "dispatched", "done", "failed"]
|
||||
status_filter: Literal[
|
||||
"queued", "assigned", "dispatched", "done", "failed", "cancelled"
|
||||
]
|
||||
| None = Query(default=None, alias="status"),
|
||||
limit: int = Query(default=50, ge=1, le=100),
|
||||
offset: int = Query(default=0, ge=0),
|
||||
@@ -268,6 +272,36 @@ def create_cloud_router(
|
||||
)
|
||||
return TaskPlannerDecisionListResponse(items=items)
|
||||
|
||||
@router.post(
|
||||
"/tasks/{task_id}/cancel",
|
||||
response_model=TaskCancellationResponse,
|
||||
responses={
|
||||
status.HTTP_202_ACCEPTED: {"model": TaskCancellationResponse},
|
||||
},
|
||||
)
|
||||
def cancel_task(
|
||||
task_id: str, request: Request, response: Response
|
||||
) -> TaskCancellationResponse:
|
||||
_authorize(request, TASKS_SUBMIT_SCOPE)
|
||||
result = scheduler.store.request_task_cancellation(
|
||||
task_id, requested_at=datetime.now(UTC)
|
||||
)
|
||||
if result == "not_found":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"task {task_id!r} not found",
|
||||
)
|
||||
if result == "already_terminal":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"task {task_id!r} has already reached a terminal state",
|
||||
)
|
||||
task = scheduler.store.get_task(task_id)
|
||||
assert task is not None
|
||||
if result == "requested" and task.status != "cancelled":
|
||||
response.status_code = status.HTTP_202_ACCEPTED
|
||||
return TaskCancellationResponse(task_id=task_id, status=task.status)
|
||||
|
||||
@router.get("/devices", response_model=list[DeviceResponse])
|
||||
def list_devices(request: Request) -> list[DeviceResponse]:
|
||||
_authorize(request, POOL_READ_SCOPE)
|
||||
|
||||
@@ -115,6 +115,10 @@ class CloudClient:
|
||||
resp = self._request("GET", f"/tasks/{task_id}/attempts")
|
||||
return resp.json()
|
||||
|
||||
def cancel_task(self, task_id: str) -> dict[str, Any]:
|
||||
resp = self._request("POST", f"/tasks/{task_id}/cancel")
|
||||
return resp.json()
|
||||
|
||||
# ----------------------------------------------------------------- devices
|
||||
|
||||
def list_devices(self) -> list[dict[str, Any]]:
|
||||
|
||||
@@ -25,6 +25,11 @@ class TaskSubmissionResponse(BaseModel):
|
||||
task_id: str
|
||||
|
||||
|
||||
class TaskCancellationResponse(BaseModel):
|
||||
task_id: str
|
||||
status: str
|
||||
|
||||
|
||||
class TaskStatusResponse(BaseModel):
|
||||
id: str
|
||||
status: str
|
||||
|
||||
Reference in New Issue
Block a user