ctfy.sdk.admin_resources.tasks

client.admin.tasks + client.admin.scheduled_jobs — the background-task system (admin).

 1"""``client.admin.tasks`` + ``client.admin.scheduled_jobs`` — the
 2background-task system (admin)."""
 3
 4from __future__ import annotations
 5
 6import builtins
 7from typing import Any
 8
 9from ctfy.sdk._helpers import _raise_for_status
10from ctfy.sdk.base import BaseHttpClient
11from ctfy.server.models import (
12    AdminTaskInfo,
13    AdminTaskListResponse,
14    AdminTaskLogsResponse,
15    ScheduledJobInfo,
16)
17
18
19class AdminTasksResource:
20    """One-shot background jobs: list / view / submit / cancel."""
21
22    def __init__(self, http: BaseHttpClient) -> None:
23        self._http = http
24
25    def list(
26        self, *, status: str = "", kind: str = "", offset: int = 0, limit: int = 50
27    ) -> AdminTaskListResponse:
28        """Paginated task list, newest first; filter by ``status`` / ``kind``."""
29        resp = self._http.request(
30            "GET",
31            "/admin/tasks",
32            params={"status": status, "kind": kind, "offset": offset, "limit": limit},
33        )
34        _raise_for_status(resp)
35        return AdminTaskListResponse.model_validate(resp.json())
36
37    def get(self, task_id: str) -> AdminTaskInfo:
38        resp = self._http.request("GET", f"/admin/tasks/{task_id}")
39        _raise_for_status(resp)
40        return AdminTaskInfo.model_validate(resp.json())
41
42    def logs(self, task_id: str, *, after: int = 0, limit: int = 1000) -> AdminTaskLogsResponse:
43        """Cursor page of a task's verbose logs (lines with ``seq > after``).
44        Poll with the returned ``next_after`` to tail a running task."""
45        resp = self._http.request(
46            "GET", f"/admin/tasks/{task_id}/logs", params={"after": after, "limit": limit}
47        )
48        _raise_for_status(resp)
49        return AdminTaskLogsResponse.model_validate(resp.json())
50
51    def submit(self, kind: str, params: dict[str, Any] | None = None) -> AdminTaskInfo:
52        """Queue a one-shot task (``challenge_build_all`` / ``_pull_all`` /
53        ``_rescan`` / ``scheduled_job_run``). Returns the pending row; the
54        dispatcher runs it within a tick."""
55        resp = self._http.request(
56            "POST", "/admin/tasks", json={"kind": kind, "params": params or {}}
57        )
58        _raise_for_status(resp)
59        return AdminTaskInfo.model_validate(resp.json())
60
61    def cancel(self, task_id: str) -> AdminTaskInfo:
62        """Cancel a task. Pending → cancelled immediately; running → a
63        cooperative-cancel flag the handler observes between node calls."""
64        resp = self._http.request("POST", f"/admin/tasks/{task_id}/cancel")
65        _raise_for_status(resp)
66        return AdminTaskInfo.model_validate(resp.json())
67
68
69class AdminScheduledJobsResource:
70    """Recurring jobs: list config + heartbeat, pause/resume/retune, run-now."""
71
72    def __init__(self, http: BaseHttpClient) -> None:
73        self._http = http
74
75    def list(self) -> builtins.list[ScheduledJobInfo]:
76        resp = self._http.request("GET", "/admin/scheduled-jobs")
77        _raise_for_status(resp)
78        return [ScheduledJobInfo.model_validate(row) for row in resp.json()]
79
80    def update(
81        self, name: str, *, enabled: bool | None = None, interval_s: int | None = None
82    ) -> ScheduledJobInfo:
83        """Pause / resume (``enabled``) or re-tune the cadence
84        (``interval_s``, clamped server-side to [5, 86400])."""
85        body: dict[str, Any] = {}
86        if enabled is not None:
87            body["enabled"] = enabled
88        if interval_s is not None:
89            body["interval_s"] = interval_s
90        resp = self._http.request("PATCH", f"/admin/scheduled-jobs/{name}", json=body)
91        _raise_for_status(resp)
92        return ScheduledJobInfo.model_validate(resp.json())
93
94    def run(self, name: str) -> AdminTaskInfo:
95        """Trigger an ad-hoc run now — submits a ``scheduled_job_run`` task."""
96        resp = self._http.request("POST", f"/admin/scheduled-jobs/{name}/run")
97        _raise_for_status(resp)
98        return AdminTaskInfo.model_validate(resp.json())
class AdminTasksResource:
20class AdminTasksResource:
21    """One-shot background jobs: list / view / submit / cancel."""
22
23    def __init__(self, http: BaseHttpClient) -> None:
24        self._http = http
25
26    def list(
27        self, *, status: str = "", kind: str = "", offset: int = 0, limit: int = 50
28    ) -> AdminTaskListResponse:
29        """Paginated task list, newest first; filter by ``status`` / ``kind``."""
30        resp = self._http.request(
31            "GET",
32            "/admin/tasks",
33            params={"status": status, "kind": kind, "offset": offset, "limit": limit},
34        )
35        _raise_for_status(resp)
36        return AdminTaskListResponse.model_validate(resp.json())
37
38    def get(self, task_id: str) -> AdminTaskInfo:
39        resp = self._http.request("GET", f"/admin/tasks/{task_id}")
40        _raise_for_status(resp)
41        return AdminTaskInfo.model_validate(resp.json())
42
43    def logs(self, task_id: str, *, after: int = 0, limit: int = 1000) -> AdminTaskLogsResponse:
44        """Cursor page of a task's verbose logs (lines with ``seq > after``).
45        Poll with the returned ``next_after`` to tail a running task."""
46        resp = self._http.request(
47            "GET", f"/admin/tasks/{task_id}/logs", params={"after": after, "limit": limit}
48        )
49        _raise_for_status(resp)
50        return AdminTaskLogsResponse.model_validate(resp.json())
51
52    def submit(self, kind: str, params: dict[str, Any] | None = None) -> AdminTaskInfo:
53        """Queue a one-shot task (``challenge_build_all`` / ``_pull_all`` /
54        ``_rescan`` / ``scheduled_job_run``). Returns the pending row; the
55        dispatcher runs it within a tick."""
56        resp = self._http.request(
57            "POST", "/admin/tasks", json={"kind": kind, "params": params or {}}
58        )
59        _raise_for_status(resp)
60        return AdminTaskInfo.model_validate(resp.json())
61
62    def cancel(self, task_id: str) -> AdminTaskInfo:
63        """Cancel a task. Pending → cancelled immediately; running → a
64        cooperative-cancel flag the handler observes between node calls."""
65        resp = self._http.request("POST", f"/admin/tasks/{task_id}/cancel")
66        _raise_for_status(resp)
67        return AdminTaskInfo.model_validate(resp.json())

One-shot background jobs: list / view / submit / cancel.

AdminTasksResource(http: ctfy.sdk.base.BaseHttpClient)
23    def __init__(self, http: BaseHttpClient) -> None:
24        self._http = http
def list( self, *, status: str = '', kind: str = '', offset: int = 0, limit: int = 50) -> ctfy.server.models.AdminTaskListResponse:
26    def list(
27        self, *, status: str = "", kind: str = "", offset: int = 0, limit: int = 50
28    ) -> AdminTaskListResponse:
29        """Paginated task list, newest first; filter by ``status`` / ``kind``."""
30        resp = self._http.request(
31            "GET",
32            "/admin/tasks",
33            params={"status": status, "kind": kind, "offset": offset, "limit": limit},
34        )
35        _raise_for_status(resp)
36        return AdminTaskListResponse.model_validate(resp.json())

Paginated task list, newest first; filter by status / kind.

def get(self, task_id: str) -> ctfy.server.models.AdminTaskInfo:
38    def get(self, task_id: str) -> AdminTaskInfo:
39        resp = self._http.request("GET", f"/admin/tasks/{task_id}")
40        _raise_for_status(resp)
41        return AdminTaskInfo.model_validate(resp.json())
def logs( self, task_id: str, *, after: int = 0, limit: int = 1000) -> ctfy.server.models.AdminTaskLogsResponse:
43    def logs(self, task_id: str, *, after: int = 0, limit: int = 1000) -> AdminTaskLogsResponse:
44        """Cursor page of a task's verbose logs (lines with ``seq > after``).
45        Poll with the returned ``next_after`` to tail a running task."""
46        resp = self._http.request(
47            "GET", f"/admin/tasks/{task_id}/logs", params={"after": after, "limit": limit}
48        )
49        _raise_for_status(resp)
50        return AdminTaskLogsResponse.model_validate(resp.json())

Cursor page of a task's verbose logs (lines with seq > after). Poll with the returned next_after to tail a running task.

def submit( self, kind: str, params: dict[str, typing.Any] | None = None) -> ctfy.server.models.AdminTaskInfo:
52    def submit(self, kind: str, params: dict[str, Any] | None = None) -> AdminTaskInfo:
53        """Queue a one-shot task (``challenge_build_all`` / ``_pull_all`` /
54        ``_rescan`` / ``scheduled_job_run``). Returns the pending row; the
55        dispatcher runs it within a tick."""
56        resp = self._http.request(
57            "POST", "/admin/tasks", json={"kind": kind, "params": params or {}}
58        )
59        _raise_for_status(resp)
60        return AdminTaskInfo.model_validate(resp.json())

Queue a one-shot task (challenge_build_all / _pull_all / _rescan / scheduled_job_run). Returns the pending row; the dispatcher runs it within a tick.

def cancel(self, task_id: str) -> ctfy.server.models.AdminTaskInfo:
62    def cancel(self, task_id: str) -> AdminTaskInfo:
63        """Cancel a task. Pending → cancelled immediately; running → a
64        cooperative-cancel flag the handler observes between node calls."""
65        resp = self._http.request("POST", f"/admin/tasks/{task_id}/cancel")
66        _raise_for_status(resp)
67        return AdminTaskInfo.model_validate(resp.json())

Cancel a task. Pending → cancelled immediately; running → a cooperative-cancel flag the handler observes between node calls.

class AdminScheduledJobsResource:
70class AdminScheduledJobsResource:
71    """Recurring jobs: list config + heartbeat, pause/resume/retune, run-now."""
72
73    def __init__(self, http: BaseHttpClient) -> None:
74        self._http = http
75
76    def list(self) -> builtins.list[ScheduledJobInfo]:
77        resp = self._http.request("GET", "/admin/scheduled-jobs")
78        _raise_for_status(resp)
79        return [ScheduledJobInfo.model_validate(row) for row in resp.json()]
80
81    def update(
82        self, name: str, *, enabled: bool | None = None, interval_s: int | None = None
83    ) -> ScheduledJobInfo:
84        """Pause / resume (``enabled``) or re-tune the cadence
85        (``interval_s``, clamped server-side to [5, 86400])."""
86        body: dict[str, Any] = {}
87        if enabled is not None:
88            body["enabled"] = enabled
89        if interval_s is not None:
90            body["interval_s"] = interval_s
91        resp = self._http.request("PATCH", f"/admin/scheduled-jobs/{name}", json=body)
92        _raise_for_status(resp)
93        return ScheduledJobInfo.model_validate(resp.json())
94
95    def run(self, name: str) -> AdminTaskInfo:
96        """Trigger an ad-hoc run now — submits a ``scheduled_job_run`` task."""
97        resp = self._http.request("POST", f"/admin/scheduled-jobs/{name}/run")
98        _raise_for_status(resp)
99        return AdminTaskInfo.model_validate(resp.json())

Recurring jobs: list config + heartbeat, pause/resume/retune, run-now.

AdminScheduledJobsResource(http: ctfy.sdk.base.BaseHttpClient)
73    def __init__(self, http: BaseHttpClient) -> None:
74        self._http = http
def list(self) -> list[ctfy.server.models.ScheduledJobInfo]:
76    def list(self) -> builtins.list[ScheduledJobInfo]:
77        resp = self._http.request("GET", "/admin/scheduled-jobs")
78        _raise_for_status(resp)
79        return [ScheduledJobInfo.model_validate(row) for row in resp.json()]
def update( self, name: str, *, enabled: bool | None = None, interval_s: int | None = None) -> ctfy.server.models.ScheduledJobInfo:
81    def update(
82        self, name: str, *, enabled: bool | None = None, interval_s: int | None = None
83    ) -> ScheduledJobInfo:
84        """Pause / resume (``enabled``) or re-tune the cadence
85        (``interval_s``, clamped server-side to [5, 86400])."""
86        body: dict[str, Any] = {}
87        if enabled is not None:
88            body["enabled"] = enabled
89        if interval_s is not None:
90            body["interval_s"] = interval_s
91        resp = self._http.request("PATCH", f"/admin/scheduled-jobs/{name}", json=body)
92        _raise_for_status(resp)
93        return ScheduledJobInfo.model_validate(resp.json())

Pause / resume (enabled) or re-tune the cadence (interval_s, clamped server-side to [5, 86400]).

def run(self, name: str) -> ctfy.server.models.AdminTaskInfo:
95    def run(self, name: str) -> AdminTaskInfo:
96        """Trigger an ad-hoc run now — submits a ``scheduled_job_run`` task."""
97        resp = self._http.request("POST", f"/admin/scheduled-jobs/{name}/run")
98        _raise_for_status(resp)
99        return AdminTaskInfo.model_validate(resp.json())

Trigger an ad-hoc run now — submits a scheduled_job_run task.