ctfy.sdk.admin_resources.competition_attachments

client.admin.competition_attachments — the files an organiser hangs on a competition: its rules, handbook or notice (#1808).

 1"""``client.admin.competition_attachments`` — the files an organiser hangs
 2on a competition: its rules, handbook or notice (#1808)."""
 3
 4from __future__ import annotations
 5
 6from typing import Any
 7
 8from ctfy.core.localized import LocalizedText
 9from ctfy.sdk._helpers import _raise_for_status
10from ctfy.sdk.base import BaseHttpClient
11from ctfy.server.models import (
12    AdminCompetitionAttachmentsResponse,
13    CompetitionAttachmentInfo,
14)
15
16
17class AdminCompetitionAttachmentsResource:
18    """Upload, list, edit and delete one competition's files. For its
19    organisers (global admins and the event's own competition admins)."""
20
21    def __init__(self, http: BaseHttpClient) -> None:
22        self._http = http
23
24    def list(self, competition_id: str) -> AdminCompetitionAttachmentsResponse:
25        """The files in display order, plus what the next upload may be:
26        ``uploads_enabled``, ``remaining`` (``None`` = no cap), ``limit``
27        and ``max_bytes``."""
28        resp = self._http.request("GET", f"/admin/competitions/{competition_id}/attachments")
29        _raise_for_status(resp)
30        return AdminCompetitionAttachmentsResponse.model_validate(resp.json())
31
32    def upload(
33        self,
34        competition_id: str,
35        filename: str,
36        content: bytes,
37        *,
38        locale: str = "",
39    ) -> CompetitionAttachmentInfo:
40        """Attach one file. ``locale`` ``""`` shows it to every reader; a
41        tag (``en`` / ``zh-CN``) only to readers of that language.
42
43        The type is decided from the bytes: PDF, Word / Excel / PowerPoint
44        (OOXML, not macro-enabled), PNG / JPEG / GIF / WebP, or UTF-8 text.
45        Anything else is ``415 competition_attachment_type_refused``; over
46        the size setting is ``413 competition_attachment_too_large``; over
47        the count is ``409 competition_attachment_limit``; no object
48        storage is ``503 storage_unavailable``.
49        """
50        data: dict[str, Any] = {"locale": locale} if locale else {}
51        resp = self._http.request(
52            "POST",
53            f"/admin/competitions/{competition_id}/attachments",
54            files={"file": (filename, content, "application/octet-stream")},
55            data=data,
56        )
57        _raise_for_status(resp)
58        return CompetitionAttachmentInfo.model_validate(resp.json())
59
60    def update(
61        self,
62        competition_id: str,
63        attachment_id: str,
64        *,
65        title: LocalizedText | None = None,
66        locale: str | None = None,
67        position: int | None = None,
68    ) -> CompetitionAttachmentInfo:
69        """Edit one file's title, language or position. Partial: only the
70        arguments you pass are sent, so the rest are left alone."""
71        body: dict[str, Any] = {}
72        if title is not None:
73            body["title"] = title
74        if locale is not None:
75            body["locale"] = locale
76        if position is not None:
77            body["position"] = position
78        resp = self._http.request(
79            "PATCH",
80            f"/admin/competitions/{competition_id}/attachments/{attachment_id}",
81            json=body,
82        )
83        _raise_for_status(resp)
84        return CompetitionAttachmentInfo.model_validate(resp.json())
85
86    def delete(
87        self, competition_id: str, attachment_id: str
88    ) -> AdminCompetitionAttachmentsResponse:
89        """Remove one file, bytes and all; returns the list as it now stands."""
90        resp = self._http.request(
91            "DELETE", f"/admin/competitions/{competition_id}/attachments/{attachment_id}"
92        )
93        _raise_for_status(resp)
94        return AdminCompetitionAttachmentsResponse.model_validate(resp.json())
class AdminCompetitionAttachmentsResource:
18class AdminCompetitionAttachmentsResource:
19    """Upload, list, edit and delete one competition's files. For its
20    organisers (global admins and the event's own competition admins)."""
21
22    def __init__(self, http: BaseHttpClient) -> None:
23        self._http = http
24
25    def list(self, competition_id: str) -> AdminCompetitionAttachmentsResponse:
26        """The files in display order, plus what the next upload may be:
27        ``uploads_enabled``, ``remaining`` (``None`` = no cap), ``limit``
28        and ``max_bytes``."""
29        resp = self._http.request("GET", f"/admin/competitions/{competition_id}/attachments")
30        _raise_for_status(resp)
31        return AdminCompetitionAttachmentsResponse.model_validate(resp.json())
32
33    def upload(
34        self,
35        competition_id: str,
36        filename: str,
37        content: bytes,
38        *,
39        locale: str = "",
40    ) -> CompetitionAttachmentInfo:
41        """Attach one file. ``locale`` ``""`` shows it to every reader; a
42        tag (``en`` / ``zh-CN``) only to readers of that language.
43
44        The type is decided from the bytes: PDF, Word / Excel / PowerPoint
45        (OOXML, not macro-enabled), PNG / JPEG / GIF / WebP, or UTF-8 text.
46        Anything else is ``415 competition_attachment_type_refused``; over
47        the size setting is ``413 competition_attachment_too_large``; over
48        the count is ``409 competition_attachment_limit``; no object
49        storage is ``503 storage_unavailable``.
50        """
51        data: dict[str, Any] = {"locale": locale} if locale else {}
52        resp = self._http.request(
53            "POST",
54            f"/admin/competitions/{competition_id}/attachments",
55            files={"file": (filename, content, "application/octet-stream")},
56            data=data,
57        )
58        _raise_for_status(resp)
59        return CompetitionAttachmentInfo.model_validate(resp.json())
60
61    def update(
62        self,
63        competition_id: str,
64        attachment_id: str,
65        *,
66        title: LocalizedText | None = None,
67        locale: str | None = None,
68        position: int | None = None,
69    ) -> CompetitionAttachmentInfo:
70        """Edit one file's title, language or position. Partial: only the
71        arguments you pass are sent, so the rest are left alone."""
72        body: dict[str, Any] = {}
73        if title is not None:
74            body["title"] = title
75        if locale is not None:
76            body["locale"] = locale
77        if position is not None:
78            body["position"] = position
79        resp = self._http.request(
80            "PATCH",
81            f"/admin/competitions/{competition_id}/attachments/{attachment_id}",
82            json=body,
83        )
84        _raise_for_status(resp)
85        return CompetitionAttachmentInfo.model_validate(resp.json())
86
87    def delete(
88        self, competition_id: str, attachment_id: str
89    ) -> AdminCompetitionAttachmentsResponse:
90        """Remove one file, bytes and all; returns the list as it now stands."""
91        resp = self._http.request(
92            "DELETE", f"/admin/competitions/{competition_id}/attachments/{attachment_id}"
93        )
94        _raise_for_status(resp)
95        return AdminCompetitionAttachmentsResponse.model_validate(resp.json())

Upload, list, edit and delete one competition's files. For its organisers (global admins and the event's own competition admins).

AdminCompetitionAttachmentsResource(http: ctfy.sdk.base.BaseHttpClient)
22    def __init__(self, http: BaseHttpClient) -> None:
23        self._http = http
def list( self, competition_id: str) -> ctfy.server.models.AdminCompetitionAttachmentsResponse:
25    def list(self, competition_id: str) -> AdminCompetitionAttachmentsResponse:
26        """The files in display order, plus what the next upload may be:
27        ``uploads_enabled``, ``remaining`` (``None`` = no cap), ``limit``
28        and ``max_bytes``."""
29        resp = self._http.request("GET", f"/admin/competitions/{competition_id}/attachments")
30        _raise_for_status(resp)
31        return AdminCompetitionAttachmentsResponse.model_validate(resp.json())

The files in display order, plus what the next upload may be: uploads_enabled, remaining (None = no cap), limit and max_bytes.

def upload( self, competition_id: str, filename: str, content: bytes, *, locale: str = '') -> ctfy.server.models.CompetitionAttachmentInfo:
33    def upload(
34        self,
35        competition_id: str,
36        filename: str,
37        content: bytes,
38        *,
39        locale: str = "",
40    ) -> CompetitionAttachmentInfo:
41        """Attach one file. ``locale`` ``""`` shows it to every reader; a
42        tag (``en`` / ``zh-CN``) only to readers of that language.
43
44        The type is decided from the bytes: PDF, Word / Excel / PowerPoint
45        (OOXML, not macro-enabled), PNG / JPEG / GIF / WebP, or UTF-8 text.
46        Anything else is ``415 competition_attachment_type_refused``; over
47        the size setting is ``413 competition_attachment_too_large``; over
48        the count is ``409 competition_attachment_limit``; no object
49        storage is ``503 storage_unavailable``.
50        """
51        data: dict[str, Any] = {"locale": locale} if locale else {}
52        resp = self._http.request(
53            "POST",
54            f"/admin/competitions/{competition_id}/attachments",
55            files={"file": (filename, content, "application/octet-stream")},
56            data=data,
57        )
58        _raise_for_status(resp)
59        return CompetitionAttachmentInfo.model_validate(resp.json())

Attach one file. locale "" shows it to every reader; a tag (en / zh-CN) only to readers of that language.

The type is decided from the bytes: PDF, Word / Excel / PowerPoint (OOXML, not macro-enabled), PNG / JPEG / GIF / WebP, or UTF-8 text. Anything else is 415 competition_attachment_type_refused; over the size setting is 413 competition_attachment_too_large; over the count is 409 competition_attachment_limit; no object storage is 503 storage_unavailable.

def update( self, competition_id: str, attachment_id: str, *, title: dict[str, str] | None = None, locale: str | None = None, position: int | None = None) -> ctfy.server.models.CompetitionAttachmentInfo:
61    def update(
62        self,
63        competition_id: str,
64        attachment_id: str,
65        *,
66        title: LocalizedText | None = None,
67        locale: str | None = None,
68        position: int | None = None,
69    ) -> CompetitionAttachmentInfo:
70        """Edit one file's title, language or position. Partial: only the
71        arguments you pass are sent, so the rest are left alone."""
72        body: dict[str, Any] = {}
73        if title is not None:
74            body["title"] = title
75        if locale is not None:
76            body["locale"] = locale
77        if position is not None:
78            body["position"] = position
79        resp = self._http.request(
80            "PATCH",
81            f"/admin/competitions/{competition_id}/attachments/{attachment_id}",
82            json=body,
83        )
84        _raise_for_status(resp)
85        return CompetitionAttachmentInfo.model_validate(resp.json())

Edit one file's title, language or position. Partial: only the arguments you pass are sent, so the rest are left alone.

def delete( self, competition_id: str, attachment_id: str) -> ctfy.server.models.AdminCompetitionAttachmentsResponse:
87    def delete(
88        self, competition_id: str, attachment_id: str
89    ) -> AdminCompetitionAttachmentsResponse:
90        """Remove one file, bytes and all; returns the list as it now stands."""
91        resp = self._http.request(
92            "DELETE", f"/admin/competitions/{competition_id}/attachments/{attachment_id}"
93        )
94        _raise_for_status(resp)
95        return AdminCompetitionAttachmentsResponse.model_validate(resp.json())

Remove one file, bytes and all; returns the list as it now stands.