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.
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.
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.