ctfy.sdk.resources.users
client.users — public per-user profile reads.
1"""``client.users`` — public per-user profile reads.""" 2 3from __future__ import annotations 4 5from ctfy.sdk._helpers import PagedList, _extract_items, _raise_for_status 6from ctfy.sdk.base import BaseHttpClient 7from ctfy.server.models import ( 8 ContributorRow, 9 CreditedChallengeRow, 10 ProfileStats, 11 UserInfo, 12 UserSolveTrend, 13) 14 15 16class UsersResource: 17 """Public profile reads for one user (privacy-scrubbed per their settings).""" 18 19 def __init__(self, http: BaseHttpClient) -> None: 20 self._http = http 21 22 def get(self, user_id: str) -> UserInfo: 23 """Public profile for one user. Privacy-scrubbed per the user's 24 ``profile_visibility`` settings for non-owner non-admin viewers.""" 25 resp = self._http.request("GET", f"/users/{user_id}") 26 _raise_for_status(resp) 27 return UserInfo.model_validate(resp.json()) 28 29 def solve_trend(self, user_id: str, days: int = 30) -> UserSolveTrend: 30 """Daily solve count over ``days`` (UTC). Privacy-aware: returns 31 an empty series when the viewer can't see the user's solve trend.""" 32 resp = self._http.request( 33 "GET", 34 f"/users/{user_id}/solve-trend", 35 params={"days": days}, 36 ) 37 _raise_for_status(resp) 38 return UserSolveTrend.model_validate(resp.json()) 39 40 def profile_stats(self, user_id: str, days: int = 30) -> ProfileStats: 41 """Public profile visualizations for one user (activity 42 calendar, per-tag radar, difficulty bar, solve trend). 43 Privacy-scrubbed for non-owner non-admin viewers.""" 44 resp = self._http.request("GET", f"/users/{user_id}/profile-stats", params={"days": days}) 45 _raise_for_status(resp) 46 return ProfileStats.model_validate(resp.json()) 47 48 def authored_challenges( 49 self, user_id: str, offset: int = 0, limit: int = 50 50 ) -> PagedList[CreditedChallengeRow]: 51 """The published uploads naming this person, and how players received 52 them: finishes across every event, the first finish, reactions. 53 54 Only uploads the author asked to be named for, and never one whose 55 event withholds their entry from you. ``solves`` / ``reactions`` 56 arrive ``None`` when withheld — a live silent exercise, or, for 57 ``reactions``, a caller without ``submissions:read`` — never ``0``. 58 """ 59 resp = self._http.request( 60 "GET", 61 f"/users/{user_id}/authored-challenges", 62 params={"offset": offset, "limit": limit}, 63 ) 64 _raise_for_status(resp) 65 return _extract_items(resp.json(), CreditedChallengeRow) 66 67 def contributors(self, offset: int = 0, limit: int = 50) -> PagedList[ContributorRow]: 68 """Everybody a published upload names, by name — the same rows as 69 :meth:`authored_challenges`, summed per person. A sum over an upload 70 whose numbers are withheld is ``None``, never a partial total.""" 71 resp = self._http.request("GET", "/contributors", params={"offset": offset, "limit": limit}) 72 _raise_for_status(resp) 73 return _extract_items(resp.json(), ContributorRow)
class
UsersResource:
17class UsersResource: 18 """Public profile reads for one user (privacy-scrubbed per their settings).""" 19 20 def __init__(self, http: BaseHttpClient) -> None: 21 self._http = http 22 23 def get(self, user_id: str) -> UserInfo: 24 """Public profile for one user. Privacy-scrubbed per the user's 25 ``profile_visibility`` settings for non-owner non-admin viewers.""" 26 resp = self._http.request("GET", f"/users/{user_id}") 27 _raise_for_status(resp) 28 return UserInfo.model_validate(resp.json()) 29 30 def solve_trend(self, user_id: str, days: int = 30) -> UserSolveTrend: 31 """Daily solve count over ``days`` (UTC). Privacy-aware: returns 32 an empty series when the viewer can't see the user's solve trend.""" 33 resp = self._http.request( 34 "GET", 35 f"/users/{user_id}/solve-trend", 36 params={"days": days}, 37 ) 38 _raise_for_status(resp) 39 return UserSolveTrend.model_validate(resp.json()) 40 41 def profile_stats(self, user_id: str, days: int = 30) -> ProfileStats: 42 """Public profile visualizations for one user (activity 43 calendar, per-tag radar, difficulty bar, solve trend). 44 Privacy-scrubbed for non-owner non-admin viewers.""" 45 resp = self._http.request("GET", f"/users/{user_id}/profile-stats", params={"days": days}) 46 _raise_for_status(resp) 47 return ProfileStats.model_validate(resp.json()) 48 49 def authored_challenges( 50 self, user_id: str, offset: int = 0, limit: int = 50 51 ) -> PagedList[CreditedChallengeRow]: 52 """The published uploads naming this person, and how players received 53 them: finishes across every event, the first finish, reactions. 54 55 Only uploads the author asked to be named for, and never one whose 56 event withholds their entry from you. ``solves`` / ``reactions`` 57 arrive ``None`` when withheld — a live silent exercise, or, for 58 ``reactions``, a caller without ``submissions:read`` — never ``0``. 59 """ 60 resp = self._http.request( 61 "GET", 62 f"/users/{user_id}/authored-challenges", 63 params={"offset": offset, "limit": limit}, 64 ) 65 _raise_for_status(resp) 66 return _extract_items(resp.json(), CreditedChallengeRow) 67 68 def contributors(self, offset: int = 0, limit: int = 50) -> PagedList[ContributorRow]: 69 """Everybody a published upload names, by name — the same rows as 70 :meth:`authored_challenges`, summed per person. A sum over an upload 71 whose numbers are withheld is ``None``, never a partial total.""" 72 resp = self._http.request("GET", "/contributors", params={"offset": offset, "limit": limit}) 73 _raise_for_status(resp) 74 return _extract_items(resp.json(), ContributorRow)
Public profile reads for one user (privacy-scrubbed per their settings).
23 def get(self, user_id: str) -> UserInfo: 24 """Public profile for one user. Privacy-scrubbed per the user's 25 ``profile_visibility`` settings for non-owner non-admin viewers.""" 26 resp = self._http.request("GET", f"/users/{user_id}") 27 _raise_for_status(resp) 28 return UserInfo.model_validate(resp.json())
Public profile for one user. Privacy-scrubbed per the user's
profile_visibility settings for non-owner non-admin viewers.
30 def solve_trend(self, user_id: str, days: int = 30) -> UserSolveTrend: 31 """Daily solve count over ``days`` (UTC). Privacy-aware: returns 32 an empty series when the viewer can't see the user's solve trend.""" 33 resp = self._http.request( 34 "GET", 35 f"/users/{user_id}/solve-trend", 36 params={"days": days}, 37 ) 38 _raise_for_status(resp) 39 return UserSolveTrend.model_validate(resp.json())
Daily solve count over days (UTC). Privacy-aware: returns
an empty series when the viewer can't see the user's solve trend.
41 def profile_stats(self, user_id: str, days: int = 30) -> ProfileStats: 42 """Public profile visualizations for one user (activity 43 calendar, per-tag radar, difficulty bar, solve trend). 44 Privacy-scrubbed for non-owner non-admin viewers.""" 45 resp = self._http.request("GET", f"/users/{user_id}/profile-stats", params={"days": days}) 46 _raise_for_status(resp) 47 return ProfileStats.model_validate(resp.json())
Public profile visualizations for one user (activity calendar, per-tag radar, difficulty bar, solve trend). Privacy-scrubbed for non-owner non-admin viewers.
def
contributors( self, offset: int = 0, limit: int = 50) -> ctfy.sdk._helpers.PagedList[ctfy.server.models.ContributorRow]:
68 def contributors(self, offset: int = 0, limit: int = 50) -> PagedList[ContributorRow]: 69 """Everybody a published upload names, by name — the same rows as 70 :meth:`authored_challenges`, summed per person. A sum over an upload 71 whose numbers are withheld is ``None``, never a partial total.""" 72 resp = self._http.request("GET", "/contributors", params={"offset": offset, "limit": limit}) 73 _raise_for_status(resp) 74 return _extract_items(resp.json(), ContributorRow)
Everybody a published upload names, by name — the same rows as
authored_challenges(), summed per person. A sum over an upload
whose numbers are withheld is None, never a partial total.