ctfy.sdk.resources.registration
client.registration — the entrant's own competition registration.
1"""``client.registration`` — the entrant's own competition registration.""" 2 3from __future__ import annotations 4 5from typing import Any 6 7from ctfy.sdk._helpers import _raise_for_status 8from ctfy.sdk.base import BaseHttpClient 9from ctfy.server.models import MyRegistrationResponse, TeamLogoUploadResponse 10 11 12class RegistrationResource: 13 """Read and submit the calling user's registration for one competition. 14 15 The GET carries the competition's form spec alongside the values, so 16 a client can render the right fields without hard-coding any event's 17 rules — which fields exist and which are mandatory is the organiser's 18 decision, made per competition. 19 """ 20 21 def __init__(self, http: BaseHttpClient) -> None: 22 self._http = http 23 24 def get(self, competition_id: str) -> MyRegistrationResponse: 25 """The caller's dossier plus the form spec that governs it.""" 26 resp = self._http.request("GET", f"/competitions/{competition_id}/registration") 27 _raise_for_status(resp) 28 return MyRegistrationResponse.model_validate(resp.json()) 29 30 def submit(self, competition_id: str, **fields: Any) -> MyRegistrationResponse: 31 """Create or update the caller's dossier. 32 33 An idempotent upsert — resubmitting to correct one field is the 34 normal case, not an error. Fields the competition did not ask for 35 are dropped server-side rather than rejected. 36 37 The response's ``missing_required`` names anything still 38 outstanding, so a caller never has to reimplement the server's 39 completeness rule. 40 """ 41 resp = self._http.request( 42 "PUT", f"/competitions/{competition_id}/registration", json=fields 43 ) 44 _raise_for_status(resp) 45 return MyRegistrationResponse.model_validate(resp.json()) 46 47 def upload_team_logo( 48 self, 49 competition_id: str, 50 *, 51 content: bytes, 52 filename: str = "logo.png", 53 content_type: str = "image/png", 54 ) -> TeamLogoUploadResponse: 55 """Set the team's logo. Captain only. 56 57 The server sniffs the actual bytes, so ``filename`` and 58 ``content_type`` are conveniences for the multipart envelope, not 59 a way to smuggle a non-image through. 60 """ 61 resp = self._http.request( 62 "POST", 63 f"/competitions/{competition_id}/team/logo", 64 files={"file": (filename, content, content_type)}, 65 ) 66 _raise_for_status(resp) 67 return TeamLogoUploadResponse.model_validate(resp.json()) 68 69 def team_logo(self, competition_id: str, team_id: str) -> bytes: 70 """Fetch a team's logo bytes. 71 72 Streamed through the platform rather than from a bucket URL, so 73 the object store needs no public-read policy. 74 """ 75 resp = self._http.request("GET", f"/competitions/{competition_id}/teams/{team_id}/logo") 76 _raise_for_status(resp) 77 return resp.content
13class RegistrationResource: 14 """Read and submit the calling user's registration for one competition. 15 16 The GET carries the competition's form spec alongside the values, so 17 a client can render the right fields without hard-coding any event's 18 rules — which fields exist and which are mandatory is the organiser's 19 decision, made per competition. 20 """ 21 22 def __init__(self, http: BaseHttpClient) -> None: 23 self._http = http 24 25 def get(self, competition_id: str) -> MyRegistrationResponse: 26 """The caller's dossier plus the form spec that governs it.""" 27 resp = self._http.request("GET", f"/competitions/{competition_id}/registration") 28 _raise_for_status(resp) 29 return MyRegistrationResponse.model_validate(resp.json()) 30 31 def submit(self, competition_id: str, **fields: Any) -> MyRegistrationResponse: 32 """Create or update the caller's dossier. 33 34 An idempotent upsert — resubmitting to correct one field is the 35 normal case, not an error. Fields the competition did not ask for 36 are dropped server-side rather than rejected. 37 38 The response's ``missing_required`` names anything still 39 outstanding, so a caller never has to reimplement the server's 40 completeness rule. 41 """ 42 resp = self._http.request( 43 "PUT", f"/competitions/{competition_id}/registration", json=fields 44 ) 45 _raise_for_status(resp) 46 return MyRegistrationResponse.model_validate(resp.json()) 47 48 def upload_team_logo( 49 self, 50 competition_id: str, 51 *, 52 content: bytes, 53 filename: str = "logo.png", 54 content_type: str = "image/png", 55 ) -> TeamLogoUploadResponse: 56 """Set the team's logo. Captain only. 57 58 The server sniffs the actual bytes, so ``filename`` and 59 ``content_type`` are conveniences for the multipart envelope, not 60 a way to smuggle a non-image through. 61 """ 62 resp = self._http.request( 63 "POST", 64 f"/competitions/{competition_id}/team/logo", 65 files={"file": (filename, content, content_type)}, 66 ) 67 _raise_for_status(resp) 68 return TeamLogoUploadResponse.model_validate(resp.json()) 69 70 def team_logo(self, competition_id: str, team_id: str) -> bytes: 71 """Fetch a team's logo bytes. 72 73 Streamed through the platform rather than from a bucket URL, so 74 the object store needs no public-read policy. 75 """ 76 resp = self._http.request("GET", f"/competitions/{competition_id}/teams/{team_id}/logo") 77 _raise_for_status(resp) 78 return resp.content
Read and submit the calling user's registration for one competition.
The GET carries the competition's form spec alongside the values, so a client can render the right fields without hard-coding any event's rules — which fields exist and which are mandatory is the organiser's decision, made per competition.
25 def get(self, competition_id: str) -> MyRegistrationResponse: 26 """The caller's dossier plus the form spec that governs it.""" 27 resp = self._http.request("GET", f"/competitions/{competition_id}/registration") 28 _raise_for_status(resp) 29 return MyRegistrationResponse.model_validate(resp.json())
The caller's dossier plus the form spec that governs it.
31 def submit(self, competition_id: str, **fields: Any) -> MyRegistrationResponse: 32 """Create or update the caller's dossier. 33 34 An idempotent upsert — resubmitting to correct one field is the 35 normal case, not an error. Fields the competition did not ask for 36 are dropped server-side rather than rejected. 37 38 The response's ``missing_required`` names anything still 39 outstanding, so a caller never has to reimplement the server's 40 completeness rule. 41 """ 42 resp = self._http.request( 43 "PUT", f"/competitions/{competition_id}/registration", json=fields 44 ) 45 _raise_for_status(resp) 46 return MyRegistrationResponse.model_validate(resp.json())
Create or update the caller's dossier.
An idempotent upsert — resubmitting to correct one field is the normal case, not an error. Fields the competition did not ask for are dropped server-side rather than rejected.
The response's missing_required names anything still
outstanding, so a caller never has to reimplement the server's
completeness rule.
48 def upload_team_logo( 49 self, 50 competition_id: str, 51 *, 52 content: bytes, 53 filename: str = "logo.png", 54 content_type: str = "image/png", 55 ) -> TeamLogoUploadResponse: 56 """Set the team's logo. Captain only. 57 58 The server sniffs the actual bytes, so ``filename`` and 59 ``content_type`` are conveniences for the multipart envelope, not 60 a way to smuggle a non-image through. 61 """ 62 resp = self._http.request( 63 "POST", 64 f"/competitions/{competition_id}/team/logo", 65 files={"file": (filename, content, content_type)}, 66 ) 67 _raise_for_status(resp) 68 return TeamLogoUploadResponse.model_validate(resp.json())
Set the team's logo. Captain only.
The server sniffs the actual bytes, so filename and
content_type are conveniences for the multipart envelope, not
a way to smuggle a non-image through.
70 def team_logo(self, competition_id: str, team_id: str) -> bytes: 71 """Fetch a team's logo bytes. 72 73 Streamed through the platform rather than from a bucket URL, so 74 the object store needs no public-read policy. 75 """ 76 resp = self._http.request("GET", f"/competitions/{competition_id}/teams/{team_id}/logo") 77 _raise_for_status(resp) 78 return resp.content
Fetch a team's logo bytes.
Streamed through the platform rather than from a bucket URL, so the object store needs no public-read policy.