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

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.

88    @cached_property
89    def users(self) -> AdminUsersResource:
90        return AdminUsersResource(self._http)
92    @cached_property
93    def competitions(self) -> AdminCompetitionsResource:
94        return AdminCompetitionsResource(self._http)
96    @cached_property
97    def announcements(self) -> AdminAnnouncementsResource:
98        return AdminAnnouncementsResource(self._http)
100    @cached_property
101    def achievements(self) -> AdminAchievementsResource:
102        return AdminAchievementsResource(self._http)
104    @cached_property
105    def challenges(self) -> AdminChallengesResource:
106        return AdminChallengesResource(self._http)
108    @cached_property
109    def instances(self) -> AdminInstancesResource:
110        return AdminInstancesResource(self._http)
112    @cached_property
113    def records(self) -> AdminRecordsResource:
114        return AdminRecordsResource(self._http)
116    @cached_property
117    def nodes(self) -> AdminNodesResource:
118        return AdminNodesResource(self._http)
120    @cached_property
121    def observability(self) -> AdminObservabilityResource:
122        return AdminObservabilityResource(self._http)
124    @cached_property
125    def settings(self) -> AdminSettingsResource:
126        return AdminSettingsResource(self._http)
128    @cached_property
129    def competition_admins(self) -> AdminCompetitionAdminsResource:
130        return AdminCompetitionAdminsResource(self._http)
def close(self) -> None:
132    def close(self) -> None:
133        """Close the underlying transport.
134
135        Only call this on a standalone client built via :meth:`connect`;
136        when reached via ``PlatformClient.admin`` the transport is shared
137        with — and closed by — the parent player client.
138        """
139        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.