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