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.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()
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.
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.
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.