ctfy.sdk.admin
Operator / admin client for the ctfy platform.
AdminClient is the operator-facing counterpart to
~ctfy.sdk.client.PlatformClient, mirroring the ctfy /
ctfy-admin CLI split: the player client never surfaces admin methods, so
agents and players reading the SDK reference aren't distracted by operator
endpoints they can't call.
Reach it two ways:
client.adminon an existingPlatformClient— shares that client's transport (connection pool + bearer)::from ctfy.sdk import PlatformClient client = PlatformClient("https://ctfy.example", token="pf_xxx") client.admin.users.set_role(user_id, "admin")AdminClient.connect()for operator tooling that doesn't already hold a player client::from ctfy.sdk import AdminClient with AdminClient.connect("https://ctfy.example", token="pf_xxx") as admin: admin.observability.overview()
Every endpoint here is role-gated server-side (admin / super-admin); the SDK only relays — the token's role decides access.
1"""Operator / admin client for the ctfy platform. 2 3``AdminClient`` is the operator-facing counterpart to 4:class:`~ctfy.sdk.client.PlatformClient`, mirroring the ``ctfy`` / 5``ctfy-admin`` CLI split: the player client never surfaces admin methods, so 6agents and players reading the SDK reference aren't distracted by operator 7endpoints they can't call. 8 9Reach it two ways: 10 11* ``client.admin`` on an existing :class:`PlatformClient` — shares that 12 client's transport (connection pool + bearer):: 13 14 from ctfy.sdk import PlatformClient 15 16 client = PlatformClient("https://ctfy.example", token="pf_xxx") 17 client.admin.users.set_role(user_id, "admin") 18 19* :meth:`AdminClient.connect` for operator tooling that doesn't already hold 20 a player client:: 21 22 from ctfy.sdk import AdminClient 23 24 with AdminClient.connect("https://ctfy.example", token="pf_xxx") as admin: 25 admin.observability.overview() 26 27Every endpoint here is role-gated server-side (admin / super-admin); the SDK 28only relays — the token's role decides access. 29""" 30 31from __future__ import annotations 32 33from functools import cached_property 34from types import TracebackType 35from typing import Self 36 37from ctfy.core.constants import DEFAULT_CLIENT_TIMEOUT 38from ctfy.sdk.admin_resources.achievements import AdminAchievementsResource 39from ctfy.sdk.admin_resources.announcements import AdminAnnouncementsResource 40from ctfy.sdk.admin_resources.awd import AdminAwdResource 41from ctfy.sdk.admin_resources.challenges import AdminChallengesResource 42from ctfy.sdk.admin_resources.competition_admins import AdminCompetitionAdminsResource 43from ctfy.sdk.admin_resources.competition_invites import AdminCompetitionInvitesResource 44from ctfy.sdk.admin_resources.competitions import AdminCompetitionsResource 45from ctfy.sdk.admin_resources.email import AdminEmailResource 46from ctfy.sdk.admin_resources.eval import ( 47 AdminEvalCampaignsResource, 48 AdminEvalHarnessesResource, 49 AdminEvalModelsResource, 50 AdminEvalRunsResource, 51 AdminEvalVendorsResource, 52) 53from ctfy.sdk.admin_resources.instances import AdminInstancesResource 54from ctfy.sdk.admin_resources.nodes import AdminNodesResource 55from ctfy.sdk.admin_resources.observability import AdminObservabilityResource 56from ctfy.sdk.admin_resources.patches import AdminPatchesResource 57from ctfy.sdk.admin_resources.records import AdminRecordsResource 58from ctfy.sdk.admin_resources.registrations import AdminRegistrationsResource 59from ctfy.sdk.admin_resources.series import AdminSeriesResource 60from ctfy.sdk.admin_resources.settings import AdminSettingsResource 61from ctfy.sdk.admin_resources.tasks import ( 62 AdminScheduledJobsResource, 63 AdminTasksResource, 64) 65from ctfy.sdk.admin_resources.teams import AdminTeamsResource 66from ctfy.sdk.admin_resources.users import AdminUsersResource 67from ctfy.sdk.base import BaseHttpClient 68 69__all__ = ["AdminClient"] 70 71 72class AdminClient: 73 """Admin / operator surface, grouped into resource namespaces. 74 75 Namespaces: :attr:`users`, :attr:`competitions`, :attr:`announcements`, 76 :attr:`achievements`, :attr:`challenges`, :attr:`instances`, 77 :attr:`records`, :attr:`nodes`, :attr:`observability`, :attr:`settings`, 78 :attr:`competition_admins`. 79 """ 80 81 def __init__(self, http: BaseHttpClient) -> None: 82 #: Shared transport (the parent PlatformClient when reached via 83 #: ``client.admin``, or an owned client when built via ``connect``). 84 self._http = http 85 86 @classmethod 87 def connect( 88 cls, 89 server_url: str, 90 token: str = "", 91 *, 92 max_retries: int = 3, 93 timeout: int = DEFAULT_CLIENT_TIMEOUT, 94 ) -> AdminClient: 95 """Build an ``AdminClient`` that owns its own transport. 96 97 For operator tooling that doesn't already hold a 98 :class:`PlatformClient`. The returned client is a context manager 99 that closes its transport on exit. 100 """ 101 base = server_url.rstrip("/") 102 http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries) 103 return cls(http) 104 105 @cached_property 106 def users(self) -> AdminUsersResource: 107 return AdminUsersResource(self._http) 108 109 @cached_property 110 def competitions(self) -> AdminCompetitionsResource: 111 return AdminCompetitionsResource(self._http) 112 113 @cached_property 114 def announcements(self) -> AdminAnnouncementsResource: 115 return AdminAnnouncementsResource(self._http) 116 117 @cached_property 118 def achievements(self) -> AdminAchievementsResource: 119 return AdminAchievementsResource(self._http) 120 121 @cached_property 122 def challenges(self) -> AdminChallengesResource: 123 return AdminChallengesResource(self._http) 124 125 @cached_property 126 def email(self) -> AdminEmailResource: 127 return AdminEmailResource(self._http) 128 129 @cached_property 130 def patches(self) -> AdminPatchesResource: 131 return AdminPatchesResource(self._http) 132 133 @cached_property 134 def awd(self) -> AdminAwdResource: 135 return AdminAwdResource(self._http) 136 137 @cached_property 138 def series(self) -> AdminSeriesResource: 139 return AdminSeriesResource(self._http) 140 141 @cached_property 142 def instances(self) -> AdminInstancesResource: 143 return AdminInstancesResource(self._http) 144 145 @cached_property 146 def records(self) -> AdminRecordsResource: 147 return AdminRecordsResource(self._http) 148 149 @cached_property 150 def nodes(self) -> AdminNodesResource: 151 return AdminNodesResource(self._http) 152 153 @cached_property 154 def observability(self) -> AdminObservabilityResource: 155 return AdminObservabilityResource(self._http) 156 157 @cached_property 158 def settings(self) -> AdminSettingsResource: 159 return AdminSettingsResource(self._http) 160 161 @cached_property 162 def competition_admins(self) -> AdminCompetitionAdminsResource: 163 return AdminCompetitionAdminsResource(self._http) 164 165 @cached_property 166 def competition_invites(self) -> AdminCompetitionInvitesResource: 167 return AdminCompetitionInvitesResource(self._http) 168 169 @cached_property 170 def registrations(self) -> AdminRegistrationsResource: 171 """Entrant roster, eligibility review, and CSV export.""" 172 return AdminRegistrationsResource(self._http) 173 174 @cached_property 175 def teams(self) -> AdminTeamsResource: 176 """Enforcement against a squad — disqualify and reinstate. 177 178 Separate from ``registrations`` on purpose: that one rules on 179 eligibility (which decides prizes), this one decides whether the 180 team is in the event at all. 181 """ 182 return AdminTeamsResource(self._http) 183 184 @cached_property 185 def tasks(self) -> AdminTasksResource: 186 return AdminTasksResource(self._http) 187 188 @cached_property 189 def scheduled_jobs(self) -> AdminScheduledJobsResource: 190 return AdminScheduledJobsResource(self._http) 191 192 @cached_property 193 def eval_models(self) -> AdminEvalModelsResource: 194 return AdminEvalModelsResource(self._http) 195 196 @cached_property 197 def eval_vendors(self) -> AdminEvalVendorsResource: 198 return AdminEvalVendorsResource(self._http) 199 200 @cached_property 201 def eval_harnesses(self) -> AdminEvalHarnessesResource: 202 return AdminEvalHarnessesResource(self._http) 203 204 @cached_property 205 def eval_runs(self) -> AdminEvalRunsResource: 206 return AdminEvalRunsResource(self._http) 207 208 @cached_property 209 def eval_campaigns(self) -> AdminEvalCampaignsResource: 210 return AdminEvalCampaignsResource(self._http) 211 212 def close(self) -> None: 213 """Close the underlying transport. 214 215 Only call this on a standalone client built via :meth:`connect`; 216 when reached via ``PlatformClient.admin`` the transport is shared 217 with — and closed by — the parent player client. 218 """ 219 self._http.close() 220 221 def __enter__(self) -> Self: 222 return self 223 224 def __exit__( 225 self, 226 exc_type: type[BaseException] | None, 227 exc: BaseException | None, 228 tb: TracebackType | None, 229 ) -> None: 230 self.close()
73class AdminClient: 74 """Admin / operator surface, grouped into resource namespaces. 75 76 Namespaces: :attr:`users`, :attr:`competitions`, :attr:`announcements`, 77 :attr:`achievements`, :attr:`challenges`, :attr:`instances`, 78 :attr:`records`, :attr:`nodes`, :attr:`observability`, :attr:`settings`, 79 :attr:`competition_admins`. 80 """ 81 82 def __init__(self, http: BaseHttpClient) -> None: 83 #: Shared transport (the parent PlatformClient when reached via 84 #: ``client.admin``, or an owned client when built via ``connect``). 85 self._http = http 86 87 @classmethod 88 def connect( 89 cls, 90 server_url: str, 91 token: str = "", 92 *, 93 max_retries: int = 3, 94 timeout: int = DEFAULT_CLIENT_TIMEOUT, 95 ) -> AdminClient: 96 """Build an ``AdminClient`` that owns its own transport. 97 98 For operator tooling that doesn't already hold a 99 :class:`PlatformClient`. The returned client is a context manager 100 that closes its transport on exit. 101 """ 102 base = server_url.rstrip("/") 103 http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries) 104 return cls(http) 105 106 @cached_property 107 def users(self) -> AdminUsersResource: 108 return AdminUsersResource(self._http) 109 110 @cached_property 111 def competitions(self) -> AdminCompetitionsResource: 112 return AdminCompetitionsResource(self._http) 113 114 @cached_property 115 def announcements(self) -> AdminAnnouncementsResource: 116 return AdminAnnouncementsResource(self._http) 117 118 @cached_property 119 def achievements(self) -> AdminAchievementsResource: 120 return AdminAchievementsResource(self._http) 121 122 @cached_property 123 def challenges(self) -> AdminChallengesResource: 124 return AdminChallengesResource(self._http) 125 126 @cached_property 127 def email(self) -> AdminEmailResource: 128 return AdminEmailResource(self._http) 129 130 @cached_property 131 def patches(self) -> AdminPatchesResource: 132 return AdminPatchesResource(self._http) 133 134 @cached_property 135 def awd(self) -> AdminAwdResource: 136 return AdminAwdResource(self._http) 137 138 @cached_property 139 def series(self) -> AdminSeriesResource: 140 return AdminSeriesResource(self._http) 141 142 @cached_property 143 def instances(self) -> AdminInstancesResource: 144 return AdminInstancesResource(self._http) 145 146 @cached_property 147 def records(self) -> AdminRecordsResource: 148 return AdminRecordsResource(self._http) 149 150 @cached_property 151 def nodes(self) -> AdminNodesResource: 152 return AdminNodesResource(self._http) 153 154 @cached_property 155 def observability(self) -> AdminObservabilityResource: 156 return AdminObservabilityResource(self._http) 157 158 @cached_property 159 def settings(self) -> AdminSettingsResource: 160 return AdminSettingsResource(self._http) 161 162 @cached_property 163 def competition_admins(self) -> AdminCompetitionAdminsResource: 164 return AdminCompetitionAdminsResource(self._http) 165 166 @cached_property 167 def competition_invites(self) -> AdminCompetitionInvitesResource: 168 return AdminCompetitionInvitesResource(self._http) 169 170 @cached_property 171 def registrations(self) -> AdminRegistrationsResource: 172 """Entrant roster, eligibility review, and CSV export.""" 173 return AdminRegistrationsResource(self._http) 174 175 @cached_property 176 def teams(self) -> AdminTeamsResource: 177 """Enforcement against a squad — disqualify and reinstate. 178 179 Separate from ``registrations`` on purpose: that one rules on 180 eligibility (which decides prizes), this one decides whether the 181 team is in the event at all. 182 """ 183 return AdminTeamsResource(self._http) 184 185 @cached_property 186 def tasks(self) -> AdminTasksResource: 187 return AdminTasksResource(self._http) 188 189 @cached_property 190 def scheduled_jobs(self) -> AdminScheduledJobsResource: 191 return AdminScheduledJobsResource(self._http) 192 193 @cached_property 194 def eval_models(self) -> AdminEvalModelsResource: 195 return AdminEvalModelsResource(self._http) 196 197 @cached_property 198 def eval_vendors(self) -> AdminEvalVendorsResource: 199 return AdminEvalVendorsResource(self._http) 200 201 @cached_property 202 def eval_harnesses(self) -> AdminEvalHarnessesResource: 203 return AdminEvalHarnessesResource(self._http) 204 205 @cached_property 206 def eval_runs(self) -> AdminEvalRunsResource: 207 return AdminEvalRunsResource(self._http) 208 209 @cached_property 210 def eval_campaigns(self) -> AdminEvalCampaignsResource: 211 return AdminEvalCampaignsResource(self._http) 212 213 def close(self) -> None: 214 """Close the underlying transport. 215 216 Only call this on a standalone client built via :meth:`connect`; 217 when reached via ``PlatformClient.admin`` the transport is shared 218 with — and closed by — the parent player client. 219 """ 220 self._http.close() 221 222 def __enter__(self) -> Self: 223 return self 224 225 def __exit__( 226 self, 227 exc_type: type[BaseException] | None, 228 exc: BaseException | None, 229 tb: TracebackType | None, 230 ) -> None: 231 self.close()
Admin / operator surface, grouped into resource namespaces.
Namespaces: users, competitions, announcements,
achievements, challenges, instances,
records, nodes, observability, settings,
competition_admins.
87 @classmethod 88 def connect( 89 cls, 90 server_url: str, 91 token: str = "", 92 *, 93 max_retries: int = 3, 94 timeout: int = DEFAULT_CLIENT_TIMEOUT, 95 ) -> AdminClient: 96 """Build an ``AdminClient`` that owns its own transport. 97 98 For operator tooling that doesn't already hold a 99 :class:`PlatformClient`. The returned client is a context manager 100 that closes its transport on exit. 101 """ 102 base = server_url.rstrip("/") 103 http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries) 104 return cls(http)
Build an AdminClient that owns its own transport.
For operator tooling that doesn't already hold a
PlatformClient. The returned client is a context manager
that closes its transport on exit.
170 @cached_property 171 def registrations(self) -> AdminRegistrationsResource: 172 """Entrant roster, eligibility review, and CSV export.""" 173 return AdminRegistrationsResource(self._http)
Entrant roster, eligibility review, and CSV export.
175 @cached_property 176 def teams(self) -> AdminTeamsResource: 177 """Enforcement against a squad — disqualify and reinstate. 178 179 Separate from ``registrations`` on purpose: that one rules on 180 eligibility (which decides prizes), this one decides whether the 181 team is in the event at all. 182 """ 183 return AdminTeamsResource(self._http)
Enforcement against a squad — disqualify and reinstate.
Separate from registrations on purpose: that one rules on
eligibility (which decides prizes), this one decides whether the
team is in the event at all.
213 def close(self) -> None: 214 """Close the underlying transport. 215 216 Only call this on a standalone client built via :meth:`connect`; 217 when reached via ``PlatformClient.admin`` the transport is shared 218 with — and closed by — the parent player client. 219 """ 220 self._http.close()
Close the underlying transport.
Only call this on a standalone client built via connect();
when reached via PlatformClient.admin the transport is shared
with — and closed by — the parent player client.