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
class RegistrationResource:
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.

RegistrationResource(http: ctfy.sdk.base.BaseHttpClient)
22    def __init__(self, http: BaseHttpClient) -> None:
23        self._http = http
def get( self, competition_id: str) -> ctfy.server.models.MyRegistrationResponse:
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.

def submit( self, competition_id: str, **fields: Any) -> ctfy.server.models.MyRegistrationResponse:
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.