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.admin on an existing PlatformClient — 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()
class AdminClient:
 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.

AdminClient(http: ctfy.sdk.base.BaseHttpClient)
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
@classmethod
def connect( cls, server_url: str, token: str = '', *, max_retries: int = 3, timeout: int = 600) -> AdminClient:
 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.

106    @cached_property
107    def users(self) -> AdminUsersResource:
108        return AdminUsersResource(self._http)
110    @cached_property
111    def competitions(self) -> AdminCompetitionsResource:
112        return AdminCompetitionsResource(self._http)
114    @cached_property
115    def announcements(self) -> AdminAnnouncementsResource:
116        return AdminAnnouncementsResource(self._http)
118    @cached_property
119    def achievements(self) -> AdminAchievementsResource:
120        return AdminAchievementsResource(self._http)
122    @cached_property
123    def challenges(self) -> AdminChallengesResource:
124        return AdminChallengesResource(self._http)
126    @cached_property
127    def email(self) -> AdminEmailResource:
128        return AdminEmailResource(self._http)
130    @cached_property
131    def patches(self) -> AdminPatchesResource:
132        return AdminPatchesResource(self._http)
134    @cached_property
135    def awd(self) -> AdminAwdResource:
136        return AdminAwdResource(self._http)
138    @cached_property
139    def series(self) -> AdminSeriesResource:
140        return AdminSeriesResource(self._http)
142    @cached_property
143    def instances(self) -> AdminInstancesResource:
144        return AdminInstancesResource(self._http)
146    @cached_property
147    def records(self) -> AdminRecordsResource:
148        return AdminRecordsResource(self._http)
150    @cached_property
151    def nodes(self) -> AdminNodesResource:
152        return AdminNodesResource(self._http)
154    @cached_property
155    def observability(self) -> AdminObservabilityResource:
156        return AdminObservabilityResource(self._http)
158    @cached_property
159    def settings(self) -> AdminSettingsResource:
160        return AdminSettingsResource(self._http)
162    @cached_property
163    def competition_admins(self) -> AdminCompetitionAdminsResource:
164        return AdminCompetitionAdminsResource(self._http)
166    @cached_property
167    def competition_invites(self) -> AdminCompetitionInvitesResource:
168        return AdminCompetitionInvitesResource(self._http)
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.

185    @cached_property
186    def tasks(self) -> AdminTasksResource:
187        return AdminTasksResource(self._http)
189    @cached_property
190    def scheduled_jobs(self) -> AdminScheduledJobsResource:
191        return AdminScheduledJobsResource(self._http)
193    @cached_property
194    def eval_models(self) -> AdminEvalModelsResource:
195        return AdminEvalModelsResource(self._http)
197    @cached_property
198    def eval_vendors(self) -> AdminEvalVendorsResource:
199        return AdminEvalVendorsResource(self._http)
201    @cached_property
202    def eval_harnesses(self) -> AdminEvalHarnessesResource:
203        return AdminEvalHarnessesResource(self._http)
205    @cached_property
206    def eval_runs(self) -> AdminEvalRunsResource:
207        return AdminEvalRunsResource(self._http)
209    @cached_property
210    def eval_campaigns(self) -> AdminEvalCampaignsResource:
211        return AdminEvalCampaignsResource(self._http)
def close(self) -> None:
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.