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.llm_providers import AdminLlmProvidersResource 55from ctfy.sdk.admin_resources.nodes import AdminNodesResource 56from ctfy.sdk.admin_resources.observability import AdminObservabilityResource 57from ctfy.sdk.admin_resources.patches import AdminPatchesResource 58from ctfy.sdk.admin_resources.records import AdminRecordsResource 59from ctfy.sdk.admin_resources.registrations import AdminRegistrationsResource 60from ctfy.sdk.admin_resources.reports import AdminReportsResource 61from ctfy.sdk.admin_resources.series import AdminSeriesResource 62from ctfy.sdk.admin_resources.settings import AdminSettingsResource 63from ctfy.sdk.admin_resources.tasks import ( 64 AdminScheduledJobsResource, 65 AdminTasksResource, 66) 67from ctfy.sdk.admin_resources.teams import AdminTeamsResource 68from ctfy.sdk.admin_resources.users import AdminUsersResource 69from ctfy.sdk.base import BaseHttpClient 70 71__all__ = ["AdminClient"] 72 73 74class AdminClient: 75 """Admin / operator surface, grouped into resource namespaces. 76 77 Namespaces: :attr:`users`, :attr:`competitions`, :attr:`announcements`, 78 :attr:`achievements`, :attr:`challenges`, :attr:`instances`, 79 :attr:`records`, :attr:`nodes`, :attr:`observability`, :attr:`settings`, 80 :attr:`competition_admins`. 81 """ 82 83 def __init__(self, http: BaseHttpClient) -> None: 84 #: Shared transport (the parent PlatformClient when reached via 85 #: ``client.admin``, or an owned client when built via ``connect``). 86 self._http = http 87 88 @classmethod 89 def connect( 90 cls, 91 server_url: str, 92 token: str = "", 93 *, 94 max_retries: int = 3, 95 timeout: int = DEFAULT_CLIENT_TIMEOUT, 96 ) -> AdminClient: 97 """Build an ``AdminClient`` that owns its own transport. 98 99 For operator tooling that doesn't already hold a 100 :class:`PlatformClient`. The returned client is a context manager 101 that closes its transport on exit. 102 """ 103 base = server_url.rstrip("/") 104 http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries) 105 return cls(http) 106 107 @cached_property 108 def users(self) -> AdminUsersResource: 109 return AdminUsersResource(self._http) 110 111 @cached_property 112 def competitions(self) -> AdminCompetitionsResource: 113 return AdminCompetitionsResource(self._http) 114 115 @cached_property 116 def announcements(self) -> AdminAnnouncementsResource: 117 return AdminAnnouncementsResource(self._http) 118 119 @cached_property 120 def achievements(self) -> AdminAchievementsResource: 121 return AdminAchievementsResource(self._http) 122 123 @cached_property 124 def challenges(self) -> AdminChallengesResource: 125 return AdminChallengesResource(self._http) 126 127 @cached_property 128 def email(self) -> AdminEmailResource: 129 return AdminEmailResource(self._http) 130 131 @cached_property 132 def llm_providers(self) -> AdminLlmProvidersResource: 133 return AdminLlmProvidersResource(self._http) 134 135 @cached_property 136 def patches(self) -> AdminPatchesResource: 137 return AdminPatchesResource(self._http) 138 139 @cached_property 140 def reports(self) -> AdminReportsResource: 141 return AdminReportsResource(self._http) 142 143 @cached_property 144 def awd(self) -> AdminAwdResource: 145 return AdminAwdResource(self._http) 146 147 @cached_property 148 def series(self) -> AdminSeriesResource: 149 return AdminSeriesResource(self._http) 150 151 @cached_property 152 def instances(self) -> AdminInstancesResource: 153 return AdminInstancesResource(self._http) 154 155 @cached_property 156 def records(self) -> AdminRecordsResource: 157 return AdminRecordsResource(self._http) 158 159 @cached_property 160 def nodes(self) -> AdminNodesResource: 161 return AdminNodesResource(self._http) 162 163 @cached_property 164 def observability(self) -> AdminObservabilityResource: 165 return AdminObservabilityResource(self._http) 166 167 @cached_property 168 def settings(self) -> AdminSettingsResource: 169 return AdminSettingsResource(self._http) 170 171 @cached_property 172 def competition_admins(self) -> AdminCompetitionAdminsResource: 173 return AdminCompetitionAdminsResource(self._http) 174 175 @cached_property 176 def competition_invites(self) -> AdminCompetitionInvitesResource: 177 return AdminCompetitionInvitesResource(self._http) 178 179 @cached_property 180 def registrations(self) -> AdminRegistrationsResource: 181 """Entrant roster, eligibility review, and CSV export.""" 182 return AdminRegistrationsResource(self._http) 183 184 @cached_property 185 def teams(self) -> AdminTeamsResource: 186 """Enforcement against a squad — disqualify and reinstate. 187 188 Separate from ``registrations`` on purpose: that one rules on 189 eligibility (which decides prizes), this one decides whether the 190 team is in the event at all. 191 """ 192 return AdminTeamsResource(self._http) 193 194 @cached_property 195 def tasks(self) -> AdminTasksResource: 196 return AdminTasksResource(self._http) 197 198 @cached_property 199 def scheduled_jobs(self) -> AdminScheduledJobsResource: 200 return AdminScheduledJobsResource(self._http) 201 202 @cached_property 203 def eval_models(self) -> AdminEvalModelsResource: 204 return AdminEvalModelsResource(self._http) 205 206 @cached_property 207 def eval_vendors(self) -> AdminEvalVendorsResource: 208 return AdminEvalVendorsResource(self._http) 209 210 @cached_property 211 def eval_harnesses(self) -> AdminEvalHarnessesResource: 212 return AdminEvalHarnessesResource(self._http) 213 214 @cached_property 215 def eval_runs(self) -> AdminEvalRunsResource: 216 return AdminEvalRunsResource(self._http) 217 218 @cached_property 219 def eval_campaigns(self) -> AdminEvalCampaignsResource: 220 return AdminEvalCampaignsResource(self._http) 221 222 def close(self) -> None: 223 """Close the underlying transport. 224 225 Only call this on a standalone client built via :meth:`connect`; 226 when reached via ``PlatformClient.admin`` the transport is shared 227 with — and closed by — the parent player client. 228 """ 229 self._http.close() 230 231 def __enter__(self) -> Self: 232 return self 233 234 def __exit__( 235 self, 236 exc_type: type[BaseException] | None, 237 exc: BaseException | None, 238 tb: TracebackType | None, 239 ) -> None: 240 self.close()
75class AdminClient: 76 """Admin / operator surface, grouped into resource namespaces. 77 78 Namespaces: :attr:`users`, :attr:`competitions`, :attr:`announcements`, 79 :attr:`achievements`, :attr:`challenges`, :attr:`instances`, 80 :attr:`records`, :attr:`nodes`, :attr:`observability`, :attr:`settings`, 81 :attr:`competition_admins`. 82 """ 83 84 def __init__(self, http: BaseHttpClient) -> None: 85 #: Shared transport (the parent PlatformClient when reached via 86 #: ``client.admin``, or an owned client when built via ``connect``). 87 self._http = http 88 89 @classmethod 90 def connect( 91 cls, 92 server_url: str, 93 token: str = "", 94 *, 95 max_retries: int = 3, 96 timeout: int = DEFAULT_CLIENT_TIMEOUT, 97 ) -> AdminClient: 98 """Build an ``AdminClient`` that owns its own transport. 99 100 For operator tooling that doesn't already hold a 101 :class:`PlatformClient`. The returned client is a context manager 102 that closes its transport on exit. 103 """ 104 base = server_url.rstrip("/") 105 http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries) 106 return cls(http) 107 108 @cached_property 109 def users(self) -> AdminUsersResource: 110 return AdminUsersResource(self._http) 111 112 @cached_property 113 def competitions(self) -> AdminCompetitionsResource: 114 return AdminCompetitionsResource(self._http) 115 116 @cached_property 117 def announcements(self) -> AdminAnnouncementsResource: 118 return AdminAnnouncementsResource(self._http) 119 120 @cached_property 121 def achievements(self) -> AdminAchievementsResource: 122 return AdminAchievementsResource(self._http) 123 124 @cached_property 125 def challenges(self) -> AdminChallengesResource: 126 return AdminChallengesResource(self._http) 127 128 @cached_property 129 def email(self) -> AdminEmailResource: 130 return AdminEmailResource(self._http) 131 132 @cached_property 133 def llm_providers(self) -> AdminLlmProvidersResource: 134 return AdminLlmProvidersResource(self._http) 135 136 @cached_property 137 def patches(self) -> AdminPatchesResource: 138 return AdminPatchesResource(self._http) 139 140 @cached_property 141 def reports(self) -> AdminReportsResource: 142 return AdminReportsResource(self._http) 143 144 @cached_property 145 def awd(self) -> AdminAwdResource: 146 return AdminAwdResource(self._http) 147 148 @cached_property 149 def series(self) -> AdminSeriesResource: 150 return AdminSeriesResource(self._http) 151 152 @cached_property 153 def instances(self) -> AdminInstancesResource: 154 return AdminInstancesResource(self._http) 155 156 @cached_property 157 def records(self) -> AdminRecordsResource: 158 return AdminRecordsResource(self._http) 159 160 @cached_property 161 def nodes(self) -> AdminNodesResource: 162 return AdminNodesResource(self._http) 163 164 @cached_property 165 def observability(self) -> AdminObservabilityResource: 166 return AdminObservabilityResource(self._http) 167 168 @cached_property 169 def settings(self) -> AdminSettingsResource: 170 return AdminSettingsResource(self._http) 171 172 @cached_property 173 def competition_admins(self) -> AdminCompetitionAdminsResource: 174 return AdminCompetitionAdminsResource(self._http) 175 176 @cached_property 177 def competition_invites(self) -> AdminCompetitionInvitesResource: 178 return AdminCompetitionInvitesResource(self._http) 179 180 @cached_property 181 def registrations(self) -> AdminRegistrationsResource: 182 """Entrant roster, eligibility review, and CSV export.""" 183 return AdminRegistrationsResource(self._http) 184 185 @cached_property 186 def teams(self) -> AdminTeamsResource: 187 """Enforcement against a squad — disqualify and reinstate. 188 189 Separate from ``registrations`` on purpose: that one rules on 190 eligibility (which decides prizes), this one decides whether the 191 team is in the event at all. 192 """ 193 return AdminTeamsResource(self._http) 194 195 @cached_property 196 def tasks(self) -> AdminTasksResource: 197 return AdminTasksResource(self._http) 198 199 @cached_property 200 def scheduled_jobs(self) -> AdminScheduledJobsResource: 201 return AdminScheduledJobsResource(self._http) 202 203 @cached_property 204 def eval_models(self) -> AdminEvalModelsResource: 205 return AdminEvalModelsResource(self._http) 206 207 @cached_property 208 def eval_vendors(self) -> AdminEvalVendorsResource: 209 return AdminEvalVendorsResource(self._http) 210 211 @cached_property 212 def eval_harnesses(self) -> AdminEvalHarnessesResource: 213 return AdminEvalHarnessesResource(self._http) 214 215 @cached_property 216 def eval_runs(self) -> AdminEvalRunsResource: 217 return AdminEvalRunsResource(self._http) 218 219 @cached_property 220 def eval_campaigns(self) -> AdminEvalCampaignsResource: 221 return AdminEvalCampaignsResource(self._http) 222 223 def close(self) -> None: 224 """Close the underlying transport. 225 226 Only call this on a standalone client built via :meth:`connect`; 227 when reached via ``PlatformClient.admin`` the transport is shared 228 with — and closed by — the parent player client. 229 """ 230 self._http.close() 231 232 def __enter__(self) -> Self: 233 return self 234 235 def __exit__( 236 self, 237 exc_type: type[BaseException] | None, 238 exc: BaseException | None, 239 tb: TracebackType | None, 240 ) -> None: 241 self.close()
Admin / operator surface, grouped into resource namespaces.
Namespaces: users, competitions, announcements,
achievements, challenges, instances,
records, nodes, observability, settings,
competition_admins.
89 @classmethod 90 def connect( 91 cls, 92 server_url: str, 93 token: str = "", 94 *, 95 max_retries: int = 3, 96 timeout: int = DEFAULT_CLIENT_TIMEOUT, 97 ) -> AdminClient: 98 """Build an ``AdminClient`` that owns its own transport. 99 100 For operator tooling that doesn't already hold a 101 :class:`PlatformClient`. The returned client is a context manager 102 that closes its transport on exit. 103 """ 104 base = server_url.rstrip("/") 105 http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries) 106 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.
180 @cached_property 181 def registrations(self) -> AdminRegistrationsResource: 182 """Entrant roster, eligibility review, and CSV export.""" 183 return AdminRegistrationsResource(self._http)
Entrant roster, eligibility review, and CSV export.
185 @cached_property 186 def teams(self) -> AdminTeamsResource: 187 """Enforcement against a squad — disqualify and reinstate. 188 189 Separate from ``registrations`` on purpose: that one rules on 190 eligibility (which decides prizes), this one decides whether the 191 team is in the event at all. 192 """ 193 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.
223 def close(self) -> None: 224 """Close the underlying transport. 225 226 Only call this on a standalone client built via :meth:`connect`; 227 when reached via ``PlatformClient.admin`` the transport is shared 228 with — and closed by — the parent player client. 229 """ 230 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.