ctfy.sdk.admin_resources.patches
client.admin.patches — the organiser's view of AWD+ defence.
1"""``client.admin.patches`` — the organiser's view of AWD+ defence.""" 2 3from __future__ import annotations 4 5from ctfy.sdk._helpers import _raise_for_status 6from ctfy.sdk.base import BaseHttpClient 7from ctfy.server.models import AdminPatchSubmissionDetail, AdminPatchSubmissionPage 8 9 10class AdminPatchesResource: 11 """Read the defence queue, and read one submission's code. 12 13 The two calls are split because the resource they spend differs. 14 :meth:`list` is a competition-wide page and never carries patch 15 bodies — an event accumulates those all week, so a listing that 16 shipped them would read every stored patch to render a table of 17 statuses. :meth:`get` does carry them, one row at a time, which is 18 what keeping the bytes was for: a disputed verdict, or a checker 19 bug found on day two, needs somebody to read what was submitted. 20 """ 21 22 def __init__(self, http: BaseHttpClient) -> None: 23 self._http = http 24 25 def list( 26 self, 27 *, 28 competition_id: str = "", 29 team_id: str = "", 30 challenge_id: str = "", 31 status: str = "", 32 offset: int = 0, 33 limit: int = 50, 34 ) -> AdminPatchSubmissionPage: 35 """Defence submissions across every team, newest first. 36 37 ``total`` is counted separately from the page, so 38 ``status="pending"`` answers "is the queue draining" without 39 pulling the rows to find out. 40 """ 41 params = { 42 "competition_id": competition_id, 43 "team_id": team_id, 44 "challenge_id": challenge_id, 45 "status": status, 46 "offset": offset, 47 "limit": limit, 48 } 49 resp = self._http.request( 50 "GET", "/admin/patches", params={k: v for k, v in params.items() if v not in ("", 0)} 51 ) 52 _raise_for_status(resp) 53 return AdminPatchSubmissionPage.model_validate(resp.json()) 54 55 def get(self, submission_id: str) -> AdminPatchSubmissionDetail: 56 """One submission with its code. 57 58 ``files_b64`` is base64 per path rather than text: a patch 59 target may legitimately be binary, and decoding it into a string 60 would corrupt it into something that merely looks like a bad 61 submission. 62 """ 63 resp = self._http.request("GET", f"/admin/patches/{submission_id}") 64 _raise_for_status(resp) 65 return AdminPatchSubmissionDetail.model_validate(resp.json())
11class AdminPatchesResource: 12 """Read the defence queue, and read one submission's code. 13 14 The two calls are split because the resource they spend differs. 15 :meth:`list` is a competition-wide page and never carries patch 16 bodies — an event accumulates those all week, so a listing that 17 shipped them would read every stored patch to render a table of 18 statuses. :meth:`get` does carry them, one row at a time, which is 19 what keeping the bytes was for: a disputed verdict, or a checker 20 bug found on day two, needs somebody to read what was submitted. 21 """ 22 23 def __init__(self, http: BaseHttpClient) -> None: 24 self._http = http 25 26 def list( 27 self, 28 *, 29 competition_id: str = "", 30 team_id: str = "", 31 challenge_id: str = "", 32 status: str = "", 33 offset: int = 0, 34 limit: int = 50, 35 ) -> AdminPatchSubmissionPage: 36 """Defence submissions across every team, newest first. 37 38 ``total`` is counted separately from the page, so 39 ``status="pending"`` answers "is the queue draining" without 40 pulling the rows to find out. 41 """ 42 params = { 43 "competition_id": competition_id, 44 "team_id": team_id, 45 "challenge_id": challenge_id, 46 "status": status, 47 "offset": offset, 48 "limit": limit, 49 } 50 resp = self._http.request( 51 "GET", "/admin/patches", params={k: v for k, v in params.items() if v not in ("", 0)} 52 ) 53 _raise_for_status(resp) 54 return AdminPatchSubmissionPage.model_validate(resp.json()) 55 56 def get(self, submission_id: str) -> AdminPatchSubmissionDetail: 57 """One submission with its code. 58 59 ``files_b64`` is base64 per path rather than text: a patch 60 target may legitimately be binary, and decoding it into a string 61 would corrupt it into something that merely looks like a bad 62 submission. 63 """ 64 resp = self._http.request("GET", f"/admin/patches/{submission_id}") 65 _raise_for_status(resp) 66 return AdminPatchSubmissionDetail.model_validate(resp.json())
Read the defence queue, and read one submission's code.
The two calls are split because the resource they spend differs.
list() is a competition-wide page and never carries patch
bodies — an event accumulates those all week, so a listing that
shipped them would read every stored patch to render a table of
statuses. get() does carry them, one row at a time, which is
what keeping the bytes was for: a disputed verdict, or a checker
bug found on day two, needs somebody to read what was submitted.
26 def list( 27 self, 28 *, 29 competition_id: str = "", 30 team_id: str = "", 31 challenge_id: str = "", 32 status: str = "", 33 offset: int = 0, 34 limit: int = 50, 35 ) -> AdminPatchSubmissionPage: 36 """Defence submissions across every team, newest first. 37 38 ``total`` is counted separately from the page, so 39 ``status="pending"`` answers "is the queue draining" without 40 pulling the rows to find out. 41 """ 42 params = { 43 "competition_id": competition_id, 44 "team_id": team_id, 45 "challenge_id": challenge_id, 46 "status": status, 47 "offset": offset, 48 "limit": limit, 49 } 50 resp = self._http.request( 51 "GET", "/admin/patches", params={k: v for k, v in params.items() if v not in ("", 0)} 52 ) 53 _raise_for_status(resp) 54 return AdminPatchSubmissionPage.model_validate(resp.json())
Defence submissions across every team, newest first.
total is counted separately from the page, so
status="pending" answers "is the queue draining" without
pulling the rows to find out.
56 def get(self, submission_id: str) -> AdminPatchSubmissionDetail: 57 """One submission with its code. 58 59 ``files_b64`` is base64 per path rather than text: a patch 60 target may legitimately be binary, and decoding it into a string 61 would corrupt it into something that merely looks like a bad 62 submission. 63 """ 64 resp = self._http.request("GET", f"/admin/patches/{submission_id}") 65 _raise_for_status(resp) 66 return AdminPatchSubmissionDetail.model_validate(resp.json())
One submission with its code.
files_b64 is base64 per path rather than text: a patch
target may legitimately be binary, and decoding it into a string
would corrupt it into something that merely looks like a bad
submission.