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.challenges import AdminChallengesResource
 41from ctfy.sdk.admin_resources.competition_admins import AdminCompetitionAdminsResource
 42from ctfy.sdk.admin_resources.competition_invites import AdminCompetitionInvitesResource
 43from ctfy.sdk.admin_resources.competitions import AdminCompetitionsResource
 44from ctfy.sdk.admin_resources.instances import AdminInstancesResource
 45from ctfy.sdk.admin_resources.nodes import AdminNodesResource
 46from ctfy.sdk.admin_resources.observability import AdminObservabilityResource
 47from ctfy.sdk.admin_resources.records import AdminRecordsResource
 48from ctfy.sdk.admin_resources.settings import AdminSettingsResource
 49from ctfy.sdk.admin_resources.users import AdminUsersResource
 50from ctfy.sdk.base import BaseHttpClient
 51
 52__all__ = ["AdminClient"]
 53
 54
 55class AdminClient:
 56    """Admin / operator surface, grouped into resource namespaces.
 57
 58    Namespaces: :attr:`users`, :attr:`competitions`, :attr:`announcements`,
 59    :attr:`achievements`, :attr:`challenges`, :attr:`instances`,
 60    :attr:`records`, :attr:`nodes`, :attr:`observability`, :attr:`settings`,
 61    :attr:`competition_admins`.
 62    """
 63
 64    def __init__(self, http: BaseHttpClient) -> None:
 65        #: Shared transport (the parent PlatformClient when reached via
 66        #: ``client.admin``, or an owned client when built via ``connect``).
 67        self._http = http
 68
 69    @classmethod
 70    def connect(
 71        cls,
 72        server_url: str,
 73        token: str = "",
 74        *,
 75        max_retries: int = 3,
 76        timeout: int = DEFAULT_CLIENT_TIMEOUT,
 77    ) -> AdminClient:
 78        """Build an ``AdminClient`` that owns its own transport.
 79
 80        For operator tooling that doesn't already hold a
 81        :class:`PlatformClient`. The returned client is a context manager
 82        that closes its transport on exit.
 83        """
 84        base = server_url.rstrip("/")
 85        http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries)
 86        return cls(http)
 87
 88    @cached_property
 89    def users(self) -> AdminUsersResource:
 90        return AdminUsersResource(self._http)
 91
 92    @cached_property
 93    def competitions(self) -> AdminCompetitionsResource:
 94        return AdminCompetitionsResource(self._http)
 95
 96    @cached_property
 97    def announcements(self) -> AdminAnnouncementsResource:
 98        return AdminAnnouncementsResource(self._http)
 99
100    @cached_property
101    def achievements(self) -> AdminAchievementsResource:
102        return AdminAchievementsResource(self._http)
103
104    @cached_property
105    def challenges(self) -> AdminChallengesResource:
106        return AdminChallengesResource(self._http)
107
108    @cached_property
109    def instances(self) -> AdminInstancesResource:
110        return AdminInstancesResource(self._http)
111
112    @cached_property
113    def records(self) -> AdminRecordsResource:
114        return AdminRecordsResource(self._http)
115
116    @cached_property
117    def nodes(self) -> AdminNodesResource:
118        return AdminNodesResource(self._http)
119
120    @cached_property
121    def observability(self) -> AdminObservabilityResource:
122        return AdminObservabilityResource(self._http)
123
124    @cached_property
125    def settings(self) -> AdminSettingsResource:
126        return AdminSettingsResource(self._http)
127
128    @cached_property
129    def competition_admins(self) -> AdminCompetitionAdminsResource:
130        return AdminCompetitionAdminsResource(self._http)
131
132    @cached_property
133    def competition_invites(self) -> AdminCompetitionInvitesResource:
134        return AdminCompetitionInvitesResource(self._http)
135
136    def close(self) -> None:
137        """Close the underlying transport.
138
139        Only call this on a standalone client built via :meth:`connect`;
140        when reached via ``PlatformClient.admin`` the transport is shared
141        with — and closed by — the parent player client.
142        """
143        self._http.close()
144
145    def __enter__(self) -> Self:
146        return self
147
148    def __exit__(
149        self,
150        exc_type: type[BaseException] | None,
151        exc: BaseException | None,
152        tb: TracebackType | None,
153    ) -> None:
154        self.close()
class AdminClient:
 56class AdminClient:
 57    """Admin / operator surface, grouped into resource namespaces.
 58
 59    Namespaces: :attr:`users`, :attr:`competitions`, :attr:`announcements`,
 60    :attr:`achievements`, :attr:`challenges`, :attr:`instances`,
 61    :attr:`records`, :attr:`nodes`, :attr:`observability`, :attr:`settings`,
 62    :attr:`competition_admins`.
 63    """
 64
 65    def __init__(self, http: BaseHttpClient) -> None:
 66        #: Shared transport (the parent PlatformClient when reached via
 67        #: ``client.admin``, or an owned client when built via ``connect``).
 68        self._http = http
 69
 70    @classmethod
 71    def connect(
 72        cls,
 73        server_url: str,
 74        token: str = "",
 75        *,
 76        max_retries: int = 3,
 77        timeout: int = DEFAULT_CLIENT_TIMEOUT,
 78    ) -> AdminClient:
 79        """Build an ``AdminClient`` that owns its own transport.
 80
 81        For operator tooling that doesn't already hold a
 82        :class:`PlatformClient`. The returned client is a context manager
 83        that closes its transport on exit.
 84        """
 85        base = server_url.rstrip("/")
 86        http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries)
 87        return cls(http)
 88
 89    @cached_property
 90    def users(self) -> AdminUsersResource:
 91        return AdminUsersResource(self._http)
 92
 93    @cached_property
 94    def competitions(self) -> AdminCompetitionsResource:
 95        return AdminCompetitionsResource(self._http)
 96
 97    @cached_property
 98    def announcements(self) -> AdminAnnouncementsResource:
 99        return AdminAnnouncementsResource(self._http)
100
101    @cached_property
102    def achievements(self) -> AdminAchievementsResource:
103        return AdminAchievementsResource(self._http)
104
105    @cached_property
106    def challenges(self) -> AdminChallengesResource:
107        return AdminChallengesResource(self._http)
108
109    @cached_property
110    def instances(self) -> AdminInstancesResource:
111        return AdminInstancesResource(self._http)
112
113    @cached_property
114    def records(self) -> AdminRecordsResource:
115        return AdminRecordsResource(self._http)
116
117    @cached_property
118    def nodes(self) -> AdminNodesResource:
119        return AdminNodesResource(self._http)
120
121    @cached_property
122    def observability(self) -> AdminObservabilityResource:
123        return AdminObservabilityResource(self._http)
124
125    @cached_property
126    def settings(self) -> AdminSettingsResource:
127        return AdminSettingsResource(self._http)
128
129    @cached_property
130    def competition_admins(self) -> AdminCompetitionAdminsResource:
131        return AdminCompetitionAdminsResource(self._http)
132
133    @cached_property
134    def competition_invites(self) -> AdminCompetitionInvitesResource:
135        return AdminCompetitionInvitesResource(self._http)
136
137    def close(self) -> None:
138        """Close the underlying transport.
139
140        Only call this on a standalone client built via :meth:`connect`;
141        when reached via ``PlatformClient.admin`` the transport is shared
142        with — and closed by — the parent player client.
143        """
144        self._http.close()
145
146    def __enter__(self) -> Self:
147        return self
148
149    def __exit__(
150        self,
151        exc_type: type[BaseException] | None,
152        exc: BaseException | None,
153        tb: TracebackType | None,
154    ) -> None:
155        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)
65    def __init__(self, http: BaseHttpClient) -> None:
66        #: Shared transport (the parent PlatformClient when reached via
67        #: ``client.admin``, or an owned client when built via ``connect``).
68        self._http = http
@classmethod
def connect( cls, server_url: str, token: str = '', *, max_retries: int = 3, timeout: int = 600) -> AdminClient:
70    @classmethod
71    def connect(
72        cls,
73        server_url: str,
74        token: str = "",
75        *,
76        max_retries: int = 3,
77        timeout: int = DEFAULT_CLIENT_TIMEOUT,
78    ) -> AdminClient:
79        """Build an ``AdminClient`` that owns its own transport.
80
81        For operator tooling that doesn't already hold a
82        :class:`PlatformClient`. The returned client is a context manager
83        that closes its transport on exit.
84        """
85        base = server_url.rstrip("/")
86        http = BaseHttpClient(f"{base}/api/v1", token, timeout=timeout, max_retries=max_retries)
87        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.

89    @cached_property
90    def users(self) -> AdminUsersResource:
91        return AdminUsersResource(self._http)
93    @cached_property
94    def competitions(self) -> AdminCompetitionsResource:
95        return AdminCompetitionsResource(self._http)
97    @cached_property
98    def announcements(self) -> AdminAnnouncementsResource:
99        return AdminAnnouncementsResource(self._http)
101    @cached_property
102    def achievements(self) -> AdminAchievementsResource:
103        return AdminAchievementsResource(self._http)
105    @cached_property
106    def challenges(self) -> AdminChallengesResource:
107        return AdminChallengesResource(self._http)
109    @cached_property
110    def instances(self) -> AdminInstancesResource:
111        return AdminInstancesResource(self._http)
113    @cached_property
114    def records(self) -> AdminRecordsResource:
115        return AdminRecordsResource(self._http)
117    @cached_property
118    def nodes(self) -> AdminNodesResource:
119        return AdminNodesResource(self._http)
121    @cached_property
122    def observability(self) -> AdminObservabilityResource:
123        return AdminObservabilityResource(self._http)
125    @cached_property
126    def settings(self) -> AdminSettingsResource:
127        return AdminSettingsResource(self._http)
129    @cached_property
130    def competition_admins(self) -> AdminCompetitionAdminsResource:
131        return AdminCompetitionAdminsResource(self._http)
133    @cached_property
134    def competition_invites(self) -> AdminCompetitionInvitesResource:
135        return AdminCompetitionInvitesResource(self._http)
def close(self) -> None:
137    def close(self) -> None:
138        """Close the underlying transport.
139
140        Only call this on a standalone client built via :meth:`connect`;
141        when reached via ``PlatformClient.admin`` the transport is shared
142        with — and closed by — the parent player client.
143        """
144        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.