ctfy.sdk.admin_resources.competitions

client.admin.competitions — competition CRUD + roster (admin).

 1"""``client.admin.competitions`` — competition CRUD + roster (admin)."""
 2
 3from __future__ import annotations
 4
 5import builtins
 6from datetime import datetime
 7from typing import Any
 8
 9from ctfy.sdk._helpers import _raise_for_status
10from ctfy.sdk.base import BaseHttpClient
11from ctfy.server.models import CompetitionInfo, CompetitionRegistrationInfo
12
13
14class AdminCompetitionsResource:
15    """Create / update / delete competitions and inspect their roster."""
16
17    def __init__(self, http: BaseHttpClient) -> None:
18        self._http = http
19
20    def list(self, offset: int = 0, limit: int = 50) -> builtins.list[CompetitionInfo]:
21        """Every competition — past, current, scheduled."""
22        resp = self._http.request(
23            "GET", "/admin/competitions", params={"offset": offset, "limit": limit}
24        )
25        _raise_for_status(resp)
26        return [CompetitionInfo.model_validate(c) for c in resp.json()["items"]]
27
28    def create(
29        self,
30        *,
31        title: str,
32        description: str = "",
33        starts_at: datetime | None = None,
34        ends_at: datetime | None = None,
35        challenge_ids: builtins.list[str] | None = None,
36        status: str = "draft",
37    ) -> CompetitionInfo:
38        """Create a new competition. ``None`` time fields = no bound
39        (sent as JSON ``null``). Unknown ``challenge_ids`` → 422.
40        ``status`` defaults to ``draft`` (hidden from players); pass
41        ``"published"`` to make it immediately visible."""
42        resp = self._http.request(
43            "POST",
44            "/admin/competitions",
45            json={
46                "title": title,
47                "description": description,
48                "starts_at": starts_at.isoformat() if starts_at else None,
49                "ends_at": ends_at.isoformat() if ends_at else None,
50                "challenge_ids": challenge_ids or [],
51                "status": status,
52            },
53        )
54        _raise_for_status(resp)
55        return CompetitionInfo.model_validate(resp.json())
56
57    def update(self, competition_id: str, **fields: Any) -> CompetitionInfo:
58        """PATCH any subset of ``title`` / ``description`` / ``starts_at`` /
59        ``ends_at`` / ``challenge_ids``. Omit a field to leave it; pass
60        ``None`` to clear."""
61        resp = self._http.request("PATCH", f"/admin/competitions/{competition_id}", json=fields)
62        _raise_for_status(resp)
63        return CompetitionInfo.model_validate(resp.json())
64
65    def delete(self, competition_id: str) -> None:
66        """Hard-delete a competition. Cascades against per-comp teams,
67        memberships, invites, solve / submission rows."""
68        resp = self._http.request("DELETE", f"/admin/competitions/{competition_id}")
69        _raise_for_status(resp)
70
71    def registrations(
72        self,
73        competition_id: str,
74        offset: int = 0,
75        limit: int = 50,
76    ) -> builtins.list[CompetitionRegistrationInfo]:
77        """Roster for ``competition_id``. Per the per-comp refactor, a
78        team's existence with the comp scope IS the registration."""
79        resp = self._http.request(
80            "GET",
81            f"/admin/competitions/{competition_id}/registrations",
82            params={"offset": offset, "limit": limit},
83        )
84        _raise_for_status(resp)
85        return [CompetitionRegistrationInfo.model_validate(r) for r in resp.json()["items"]]
class AdminCompetitionsResource:
15class AdminCompetitionsResource:
16    """Create / update / delete competitions and inspect their roster."""
17
18    def __init__(self, http: BaseHttpClient) -> None:
19        self._http = http
20
21    def list(self, offset: int = 0, limit: int = 50) -> builtins.list[CompetitionInfo]:
22        """Every competition — past, current, scheduled."""
23        resp = self._http.request(
24            "GET", "/admin/competitions", params={"offset": offset, "limit": limit}
25        )
26        _raise_for_status(resp)
27        return [CompetitionInfo.model_validate(c) for c in resp.json()["items"]]
28
29    def create(
30        self,
31        *,
32        title: str,
33        description: str = "",
34        starts_at: datetime | None = None,
35        ends_at: datetime | None = None,
36        challenge_ids: builtins.list[str] | None = None,
37        status: str = "draft",
38    ) -> CompetitionInfo:
39        """Create a new competition. ``None`` time fields = no bound
40        (sent as JSON ``null``). Unknown ``challenge_ids`` → 422.
41        ``status`` defaults to ``draft`` (hidden from players); pass
42        ``"published"`` to make it immediately visible."""
43        resp = self._http.request(
44            "POST",
45            "/admin/competitions",
46            json={
47                "title": title,
48                "description": description,
49                "starts_at": starts_at.isoformat() if starts_at else None,
50                "ends_at": ends_at.isoformat() if ends_at else None,
51                "challenge_ids": challenge_ids or [],
52                "status": status,
53            },
54        )
55        _raise_for_status(resp)
56        return CompetitionInfo.model_validate(resp.json())
57
58    def update(self, competition_id: str, **fields: Any) -> CompetitionInfo:
59        """PATCH any subset of ``title`` / ``description`` / ``starts_at`` /
60        ``ends_at`` / ``challenge_ids``. Omit a field to leave it; pass
61        ``None`` to clear."""
62        resp = self._http.request("PATCH", f"/admin/competitions/{competition_id}", json=fields)
63        _raise_for_status(resp)
64        return CompetitionInfo.model_validate(resp.json())
65
66    def delete(self, competition_id: str) -> None:
67        """Hard-delete a competition. Cascades against per-comp teams,
68        memberships, invites, solve / submission rows."""
69        resp = self._http.request("DELETE", f"/admin/competitions/{competition_id}")
70        _raise_for_status(resp)
71
72    def registrations(
73        self,
74        competition_id: str,
75        offset: int = 0,
76        limit: int = 50,
77    ) -> builtins.list[CompetitionRegistrationInfo]:
78        """Roster for ``competition_id``. Per the per-comp refactor, a
79        team's existence with the comp scope IS the registration."""
80        resp = self._http.request(
81            "GET",
82            f"/admin/competitions/{competition_id}/registrations",
83            params={"offset": offset, "limit": limit},
84        )
85        _raise_for_status(resp)
86        return [CompetitionRegistrationInfo.model_validate(r) for r in resp.json()["items"]]

Create / update / delete competitions and inspect their roster.

AdminCompetitionsResource(http: ctfy.sdk.base.BaseHttpClient)
18    def __init__(self, http: BaseHttpClient) -> None:
19        self._http = http
def list( self, offset: int = 0, limit: int = 50) -> list[ctfy.server.models.CompetitionInfo]:
21    def list(self, offset: int = 0, limit: int = 50) -> builtins.list[CompetitionInfo]:
22        """Every competition — past, current, scheduled."""
23        resp = self._http.request(
24            "GET", "/admin/competitions", params={"offset": offset, "limit": limit}
25        )
26        _raise_for_status(resp)
27        return [CompetitionInfo.model_validate(c) for c in resp.json()["items"]]

Every competition — past, current, scheduled.

def create( self, *, title: str, description: str = '', starts_at: datetime.datetime | None = None, ends_at: datetime.datetime | None = None, challenge_ids: list[str] | None = None, status: str = 'draft') -> ctfy.server.models.CompetitionInfo:
29    def create(
30        self,
31        *,
32        title: str,
33        description: str = "",
34        starts_at: datetime | None = None,
35        ends_at: datetime | None = None,
36        challenge_ids: builtins.list[str] | None = None,
37        status: str = "draft",
38    ) -> CompetitionInfo:
39        """Create a new competition. ``None`` time fields = no bound
40        (sent as JSON ``null``). Unknown ``challenge_ids`` → 422.
41        ``status`` defaults to ``draft`` (hidden from players); pass
42        ``"published"`` to make it immediately visible."""
43        resp = self._http.request(
44            "POST",
45            "/admin/competitions",
46            json={
47                "title": title,
48                "description": description,
49                "starts_at": starts_at.isoformat() if starts_at else None,
50                "ends_at": ends_at.isoformat() if ends_at else None,
51                "challenge_ids": challenge_ids or [],
52                "status": status,
53            },
54        )
55        _raise_for_status(resp)
56        return CompetitionInfo.model_validate(resp.json())

Create a new competition. None time fields = no bound (sent as JSON null). Unknown challenge_ids → 422. status defaults to draft (hidden from players); pass "published" to make it immediately visible.

def update( self, competition_id: str, **fields: Any) -> ctfy.server.models.CompetitionInfo:
58    def update(self, competition_id: str, **fields: Any) -> CompetitionInfo:
59        """PATCH any subset of ``title`` / ``description`` / ``starts_at`` /
60        ``ends_at`` / ``challenge_ids``. Omit a field to leave it; pass
61        ``None`` to clear."""
62        resp = self._http.request("PATCH", f"/admin/competitions/{competition_id}", json=fields)
63        _raise_for_status(resp)
64        return CompetitionInfo.model_validate(resp.json())

PATCH any subset of title / description / starts_at / ends_at / challenge_ids. Omit a field to leave it; pass None to clear.

def delete(self, competition_id: str) -> None:
66    def delete(self, competition_id: str) -> None:
67        """Hard-delete a competition. Cascades against per-comp teams,
68        memberships, invites, solve / submission rows."""
69        resp = self._http.request("DELETE", f"/admin/competitions/{competition_id}")
70        _raise_for_status(resp)

Hard-delete a competition. Cascades against per-comp teams, memberships, invites, solve / submission rows.

def registrations( self, competition_id: str, offset: int = 0, limit: int = 50) -> list[ctfy.server.models.CompetitionRegistrationInfo]:
72    def registrations(
73        self,
74        competition_id: str,
75        offset: int = 0,
76        limit: int = 50,
77    ) -> builtins.list[CompetitionRegistrationInfo]:
78        """Roster for ``competition_id``. Per the per-comp refactor, a
79        team's existence with the comp scope IS the registration."""
80        resp = self._http.request(
81            "GET",
82            f"/admin/competitions/{competition_id}/registrations",
83            params={"offset": offset, "limit": limit},
84        )
85        _raise_for_status(resp)
86        return [CompetitionRegistrationInfo.model_validate(r) for r in resp.json()["items"]]

Roster for competition_id. Per the per-comp refactor, a team's existence with the comp scope IS the registration.