xcpcio 0.63.6__py3-none-any.whl → 0.64.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of xcpcio might be problematic. Click here for more details.
- xcpcio/__version__.py +1 -1
- xcpcio/ccs/api_server/dependencies.py +8 -3
- xcpcio/ccs/api_server/routes/__init__.py +2 -0
- xcpcio/ccs/api_server/routes/access.py +2 -4
- xcpcio/ccs/api_server/routes/accounts.py +35 -0
- xcpcio/ccs/api_server/routes/awards.py +4 -9
- xcpcio/ccs/api_server/routes/clarifications.py +7 -15
- xcpcio/ccs/api_server/routes/contests.py +55 -39
- xcpcio/ccs/api_server/routes/general.py +5 -4
- xcpcio/ccs/api_server/routes/groups.py +6 -13
- xcpcio/ccs/api_server/routes/judgement_types.py +6 -13
- xcpcio/ccs/api_server/routes/judgements.py +4 -9
- xcpcio/ccs/api_server/routes/languages.py +6 -13
- xcpcio/ccs/api_server/routes/organizations.py +9 -41
- xcpcio/ccs/api_server/routes/problems.py +9 -36
- xcpcio/ccs/api_server/routes/runs.py +4 -9
- xcpcio/ccs/api_server/routes/submissions.py +9 -41
- xcpcio/ccs/api_server/routes/teams.py +9 -41
- xcpcio/ccs/api_server/services/contest_service.py +105 -319
- xcpcio/ccs/base/__init__.py +3 -0
- xcpcio/ccs/base/types.py +9 -0
- xcpcio/ccs/contest_archiver.py +2 -3
- xcpcio/ccs/reader/__init__.py +0 -0
- xcpcio/ccs/reader/base_ccs_reader.py +165 -0
- xcpcio/ccs/reader/contest_package_reader.py +331 -0
- {xcpcio-0.63.6.dist-info → xcpcio-0.64.0.dist-info}/METADATA +2 -1
- xcpcio-0.64.0.dist-info/RECORD +39 -0
- xcpcio-0.63.6.dist-info/RECORD +0 -33
- {xcpcio-0.63.6.dist-info → xcpcio-0.64.0.dist-info}/WHEEL +0 -0
- {xcpcio-0.63.6.dist-info → xcpcio-0.64.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
from xcpcio.ccs.base.types import FileAttr
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseCCSReader(ABC):
|
|
8
|
+
# API Information
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def get_api_info(self) -> Dict[str, Any]:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def get_access(self) -> Dict[str, Any]:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
# Account operations
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def get_accounts(self) -> List[Dict[str, Any]]:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def get_account(self, account_id: str) -> Dict[str, Any]:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
# Contest operations
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def get_contest_id(self) -> str:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def get_contest(self) -> Dict[str, Any]:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def get_contest_state(self) -> Dict[str, Any]:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def get_contest_banner(self) -> FileAttr:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def get_contest_problemset(self) -> FileAttr:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
# Problem operations
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def get_problems(self) -> List[Dict[str, Any]]:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def get_problem(self, problem_id: str) -> Dict[str, Any]:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def get_problem_statement(self, problem_id: str) -> FileAttr:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
# Team operations
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def get_teams(self) -> List[Dict[str, Any]]:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def get_team(self, team_id: str) -> Dict[str, Any]:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def get_team_photo(self, team_id: str) -> FileAttr:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
# Organization operations
|
|
74
|
+
@abstractmethod
|
|
75
|
+
def get_organizations(self) -> List[Dict[str, Any]]:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def get_organization(self, organization_id: str) -> Dict[str, Any]:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def get_organization_logo(self, organization_id: str) -> FileAttr:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
# Group operations
|
|
87
|
+
@abstractmethod
|
|
88
|
+
def get_groups(self) -> List[Dict[str, Any]]:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
def get_group(self, group_id: str) -> Dict[str, Any]:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
# Language operations
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def get_languages(self) -> List[Dict[str, Any]]:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
@abstractmethod
|
|
101
|
+
def get_language(self, language_id: str) -> Dict[str, Any]:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
# Judgement type operations
|
|
105
|
+
@abstractmethod
|
|
106
|
+
def get_judgement_types(self) -> List[Dict[str, Any]]:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
@abstractmethod
|
|
110
|
+
def get_judgement_type(self, judgement_type_id: str) -> Dict[str, Any]:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
# Submission operations
|
|
114
|
+
@abstractmethod
|
|
115
|
+
def get_submissions(self) -> List[Dict[str, Any]]:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
@abstractmethod
|
|
119
|
+
def get_submission(self, submission_id: str) -> Dict[str, Any]:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
@abstractmethod
|
|
123
|
+
def get_submission_file(self, submission_id: str) -> FileAttr:
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
# Judgement operations
|
|
127
|
+
@abstractmethod
|
|
128
|
+
def get_judgements(self, submission_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
@abstractmethod
|
|
132
|
+
def get_judgement(self, judgement_id: str) -> Dict[str, Any]:
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
# Run operations
|
|
136
|
+
@abstractmethod
|
|
137
|
+
def get_runs(self, judgement_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
@abstractmethod
|
|
141
|
+
def get_run(self, run_id: str) -> Dict[str, Any]:
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
# Clarification operations
|
|
145
|
+
@abstractmethod
|
|
146
|
+
def get_clarifications(self) -> List[Dict[str, Any]]:
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
@abstractmethod
|
|
150
|
+
def get_clarification(self, clarification_id: str) -> Dict[str, Any]:
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
# Award operations
|
|
154
|
+
@abstractmethod
|
|
155
|
+
def get_awards(self) -> List[Dict[str, Any]]:
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
@abstractmethod
|
|
159
|
+
def get_award(self, award_id: str) -> Dict[str, Any]:
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
# Event Feed operations
|
|
163
|
+
@abstractmethod
|
|
164
|
+
def get_event_feed(self, since_token: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
165
|
+
pass
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import bisect
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, List, Optional, Union
|
|
7
|
+
|
|
8
|
+
from fastapi import HTTPException
|
|
9
|
+
|
|
10
|
+
from xcpcio.ccs.base.types import FileAttr
|
|
11
|
+
from xcpcio.ccs.reader.base_ccs_reader import BaseCCSReader
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ContestPackageReader(BaseCCSReader):
|
|
17
|
+
def __init__(self, contest_package_dir: Path):
|
|
18
|
+
self.contest_package_dir = contest_package_dir
|
|
19
|
+
if not self.contest_package_dir.exists():
|
|
20
|
+
raise ValueError(f"Contest package directory does not exist: {contest_package_dir}")
|
|
21
|
+
|
|
22
|
+
self._load_indexes()
|
|
23
|
+
|
|
24
|
+
def _create_index_by_id(self, data: List[Dict[str, Any]], id_name: str) -> Dict[str, List[Dict]]:
|
|
25
|
+
res = defaultdict(list)
|
|
26
|
+
for item in data:
|
|
27
|
+
res[item[id_name]].append(item)
|
|
28
|
+
return res
|
|
29
|
+
|
|
30
|
+
def _load_json_file(self, filepath: str) -> Union[Dict[str, Any], List[Any]]:
|
|
31
|
+
full_path = self.contest_package_dir / filepath
|
|
32
|
+
try:
|
|
33
|
+
with open(full_path, "r", encoding="utf-8") as f:
|
|
34
|
+
return json.load(f)
|
|
35
|
+
except FileNotFoundError:
|
|
36
|
+
raise HTTPException(status_code=404, detail=f"File not found: {filepath}")
|
|
37
|
+
except json.JSONDecodeError as e:
|
|
38
|
+
raise HTTPException(status_code=500, detail=f"Invalid JSON in file {filepath}: {e}")
|
|
39
|
+
|
|
40
|
+
def _load_ndjson_file(self, filepath: str) -> List[Dict[str, Any]]:
|
|
41
|
+
full_path = self.contest_package_dir / filepath
|
|
42
|
+
try:
|
|
43
|
+
data = list()
|
|
44
|
+
with open(full_path, "r", encoding="utf-8") as f:
|
|
45
|
+
for line in f.readlines():
|
|
46
|
+
data.append(json.loads(line))
|
|
47
|
+
return data
|
|
48
|
+
except FileNotFoundError:
|
|
49
|
+
raise HTTPException(status_code=404, detail=f"File not found: {filepath}")
|
|
50
|
+
except json.JSONDecodeError as e:
|
|
51
|
+
raise HTTPException(status_code=500, detail=f"Invalid JSON in file {filepath}: {e}")
|
|
52
|
+
|
|
53
|
+
def _load_indexes(self) -> None:
|
|
54
|
+
self.access = self._load_json_file("access.json")
|
|
55
|
+
|
|
56
|
+
self.accounts = self._load_json_file("accounts.json")
|
|
57
|
+
self.accounts_by_id = {account["id"] for account in self.accounts}
|
|
58
|
+
|
|
59
|
+
self.api_info = self._load_json_file("api.json")
|
|
60
|
+
|
|
61
|
+
self.awards = self._load_json_file("awards.json")
|
|
62
|
+
self.awards_by_id = {award["id"] for award in self.awards}
|
|
63
|
+
|
|
64
|
+
self.clarifications = self._load_json_file("clarifications.json")
|
|
65
|
+
self.clarifications_by_id = {clarification["id"] for clarification in self.clarifications}
|
|
66
|
+
|
|
67
|
+
self.contest = self._load_json_file("contest.json")
|
|
68
|
+
self.contest_state = self._load_json_file("state.json")
|
|
69
|
+
|
|
70
|
+
self.groups = self._load_json_file("groups.json")
|
|
71
|
+
self.groups_by_id = {group["id"]: group for group in self.groups}
|
|
72
|
+
|
|
73
|
+
self.judgement_types = self._load_json_file("judgement-types.json")
|
|
74
|
+
self.judgement_types_by_id = {judgement_type["id"] for judgement_type in self.judgement_types}
|
|
75
|
+
|
|
76
|
+
self.judgements = self._load_json_file("judgements.json")
|
|
77
|
+
self.judgements_by_id = {judgement["id"] for judgement in self.judgements}
|
|
78
|
+
self.judgements_by_submission_id = self._create_index_by_id(self.judgements, "submission_id")
|
|
79
|
+
|
|
80
|
+
self.languages = self._load_json_file("languages.json")
|
|
81
|
+
self.languages_by_id = {language["id"] for language in self.languages}
|
|
82
|
+
|
|
83
|
+
self.organizations = self._load_json_file("organizations.json")
|
|
84
|
+
self.organizations_by_id = {org["id"]: org for org in self.organizations}
|
|
85
|
+
|
|
86
|
+
self.problems = self._load_json_file("problems.json")
|
|
87
|
+
self.problems_by_id = {problem["id"]: problem for problem in self.problems}
|
|
88
|
+
|
|
89
|
+
self.runs = self._load_json_file("runs.json")
|
|
90
|
+
self.runs_by_id = {run["id"] for run in self.runs}
|
|
91
|
+
self.runs_by_judgement_id = self._create_index_by_id(self.runs, "judgement_id")
|
|
92
|
+
|
|
93
|
+
self.submissions = self._load_json_file("submissions.json")
|
|
94
|
+
self.submissions_by_id = {submission["id"]: submission for submission in self.submissions}
|
|
95
|
+
|
|
96
|
+
self.teams = self._load_json_file("teams.json")
|
|
97
|
+
self.teams_by_id = {team["id"]: team for team in self.teams}
|
|
98
|
+
|
|
99
|
+
self.event_feed = self._load_ndjson_file("event-feed.ndjson")
|
|
100
|
+
self.event_feed_tokens = [event["token"] for event in self.event_feed]
|
|
101
|
+
|
|
102
|
+
self.contest_id = self.contest["id"]
|
|
103
|
+
|
|
104
|
+
def _get_file_attr(self, expected_href: str, base_path: Path, files: List[Dict]) -> FileAttr:
|
|
105
|
+
for file in files:
|
|
106
|
+
href = file["href"]
|
|
107
|
+
if href == expected_href:
|
|
108
|
+
filename = file["filename"]
|
|
109
|
+
mime_type = file["mime"]
|
|
110
|
+
filepath: Path = base_path / filename
|
|
111
|
+
if not filepath.exists():
|
|
112
|
+
raise FileNotFoundError(f"File not found: {filepath}")
|
|
113
|
+
return FileAttr(path=filepath, media_type=mime_type, name=filename)
|
|
114
|
+
raise KeyError(f"Href not found: {expected_href}")
|
|
115
|
+
|
|
116
|
+
# API Information
|
|
117
|
+
def get_api_info(self) -> Dict[str, Any]:
|
|
118
|
+
return self.api_info
|
|
119
|
+
|
|
120
|
+
def get_access(self) -> Dict[str, Any]:
|
|
121
|
+
return self.access
|
|
122
|
+
|
|
123
|
+
# Account operations
|
|
124
|
+
def get_accounts(self) -> List[Dict[str, Any]]:
|
|
125
|
+
return self.accounts
|
|
126
|
+
|
|
127
|
+
def get_account(self, account_id: str) -> Dict[str, Any]:
|
|
128
|
+
if account_id not in self.accounts_by_id:
|
|
129
|
+
raise HTTPException(status_code=404, detail=f"Account {account_id} not found")
|
|
130
|
+
return self.accounts_by_id[account_id]
|
|
131
|
+
|
|
132
|
+
# Contest operations
|
|
133
|
+
def get_contest_id(self) -> str:
|
|
134
|
+
return self.contest["id"]
|
|
135
|
+
|
|
136
|
+
def get_contest(self) -> Dict[str, Any]:
|
|
137
|
+
return self.contest
|
|
138
|
+
|
|
139
|
+
def get_contest_state(self) -> Dict[str, Any]:
|
|
140
|
+
return self.contest_state
|
|
141
|
+
|
|
142
|
+
def get_contest_banner(self) -> FileAttr:
|
|
143
|
+
expected_href = f"contests/{self.contest_id}/banner"
|
|
144
|
+
base_path = self.contest_package_dir / "contest"
|
|
145
|
+
files = self.contest.get("banner", [])
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
return self._get_file_attr(expected_href, base_path, files)
|
|
149
|
+
except Exception as e:
|
|
150
|
+
raise HTTPException(status_code=404, detail=f"Banner not found. [contest_id={self.contest_id}] [err={e}]")
|
|
151
|
+
|
|
152
|
+
def get_contest_problemset(self) -> FileAttr:
|
|
153
|
+
expected_href = f"contests/{self.contest_id}/problemset"
|
|
154
|
+
base_path = self.contest_package_dir / "contest"
|
|
155
|
+
files = self.contest.get("problemset", [])
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
return self._get_file_attr(expected_href, base_path, files)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
raise HTTPException(
|
|
161
|
+
status_code=404, detail=f"Problemset not found. [contest_id={self.contest_id}] [err={e}]"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Problem operations
|
|
165
|
+
def get_problems(self) -> List[Dict[str, Any]]:
|
|
166
|
+
return self.problems
|
|
167
|
+
|
|
168
|
+
def get_problem(self, problem_id: str) -> Dict[str, Any]:
|
|
169
|
+
if problem_id not in self.problems_by_id:
|
|
170
|
+
raise HTTPException(status_code=404, detail=f"Problem {problem_id} not found")
|
|
171
|
+
return self.problems_by_id[problem_id]
|
|
172
|
+
|
|
173
|
+
def get_problem_statement(self, problem_id: str) -> FileAttr:
|
|
174
|
+
expected_href = f"contests/{self.contest_id}/problems/{problem_id}/statement"
|
|
175
|
+
base_path = self.contest_package_dir / "problems" / problem_id
|
|
176
|
+
files = self.get_problem(problem_id).get("statement", [])
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
return self._get_file_attr(expected_href, base_path, files)
|
|
180
|
+
except Exception as e:
|
|
181
|
+
raise HTTPException(
|
|
182
|
+
status_code=404,
|
|
183
|
+
detail=f"Problem statement not found. [contest_id={self.contest_id}] [problem_id={problem_id}] [err={e}]",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Team operations
|
|
187
|
+
def get_teams(self) -> List[Dict[str, Any]]:
|
|
188
|
+
return self.teams
|
|
189
|
+
|
|
190
|
+
def get_team(self, team_id: str) -> Dict[str, Any]:
|
|
191
|
+
if team_id not in self.teams_by_id:
|
|
192
|
+
raise HTTPException(status_code=404, detail=f"Team {team_id} not found")
|
|
193
|
+
return self.teams_by_id[team_id]
|
|
194
|
+
|
|
195
|
+
def get_team_photo(self, team_id: str) -> FileAttr:
|
|
196
|
+
expected_href = f"contests/{self.contest_id}/teams/{team_id}/photo"
|
|
197
|
+
base_path = self.contest_package_dir / "teams" / team_id
|
|
198
|
+
files = self.get_team(team_id).get("photo", [])
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
return self._get_file_attr(expected_href, base_path, files)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
raise HTTPException(
|
|
204
|
+
status_code=404,
|
|
205
|
+
detail=f"Team photo not found. [contest_id={self.contest_id}] [team_id={team_id}] [err={e}]",
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Organization operations
|
|
209
|
+
def get_organizations(self) -> List[Dict[str, Any]]:
|
|
210
|
+
return self.organizations
|
|
211
|
+
|
|
212
|
+
def get_organization(self, organization_id: str) -> Dict[str, Any]:
|
|
213
|
+
if organization_id not in self.organizations_by_id:
|
|
214
|
+
raise HTTPException(status_code=404, detail=f"Organization {organization_id} not found")
|
|
215
|
+
return self.organizations_by_id[organization_id]
|
|
216
|
+
|
|
217
|
+
def get_organization_logo(self, organization_id: str) -> FileAttr:
|
|
218
|
+
expected_href = f"contests/{self.contest_id}/organizations/{organization_id}/logo"
|
|
219
|
+
base_path = self.contest_package_dir / "organizations" / organization_id
|
|
220
|
+
files = self.get_organization(organization_id).get("logo", [])
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
return self._get_file_attr(expected_href, base_path, files)
|
|
224
|
+
except Exception as e:
|
|
225
|
+
raise HTTPException(
|
|
226
|
+
status_code=404,
|
|
227
|
+
detail=f"Organization logo not found. [contest_id={self.contest_id}] [organization_id={organization_id}] [err={e}]",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Group operations
|
|
231
|
+
def get_groups(self) -> List[Dict[str, Any]]:
|
|
232
|
+
return self.groups
|
|
233
|
+
|
|
234
|
+
def get_group(self, group_id: str) -> Dict[str, Any]:
|
|
235
|
+
if group_id not in self.groups_by_id:
|
|
236
|
+
raise HTTPException(status_code=404, detail=f"Group {group_id} not found")
|
|
237
|
+
return self.groups_by_id[group_id]
|
|
238
|
+
|
|
239
|
+
# Language operations
|
|
240
|
+
def get_languages(self) -> List[Dict[str, Any]]:
|
|
241
|
+
return self.languages
|
|
242
|
+
|
|
243
|
+
def get_language(self, language_id: str) -> Dict[str, Any]:
|
|
244
|
+
if language_id not in self.languages_by_id:
|
|
245
|
+
raise HTTPException(status_code=404, detail=f"Language {language_id} not found")
|
|
246
|
+
return self.languages_by_id[language_id]
|
|
247
|
+
|
|
248
|
+
# Judgement type operations
|
|
249
|
+
def get_judgement_types(self) -> List[Dict[str, Any]]:
|
|
250
|
+
return self.judgement_types
|
|
251
|
+
|
|
252
|
+
def get_judgement_type(self, judgement_type_id: str) -> Dict[str, Any]:
|
|
253
|
+
if judgement_type_id not in self.judgement_types_by_id:
|
|
254
|
+
raise HTTPException(status_code=404, detail=f"Judgement type {judgement_type_id} not found")
|
|
255
|
+
return self.judgement_types_by_id[judgement_type_id]
|
|
256
|
+
|
|
257
|
+
# Submission operations
|
|
258
|
+
def get_submissions(self) -> List[Dict[str, Any]]:
|
|
259
|
+
return self.submissions
|
|
260
|
+
|
|
261
|
+
def get_submission(self, submission_id: str) -> Dict[str, Any]:
|
|
262
|
+
if submission_id not in self.submissions_by_id:
|
|
263
|
+
raise HTTPException(status_code=404, detail=f"Submission {submission_id} not found")
|
|
264
|
+
return self.submissions_by_id[submission_id]
|
|
265
|
+
|
|
266
|
+
def get_submission_file(self, submission_id: str) -> FileAttr:
|
|
267
|
+
expected_href = f"contests/{self.contest_id}/submissions/{submission_id}/files"
|
|
268
|
+
base_path = self.contest_package_dir / "submissions" / submission_id
|
|
269
|
+
files = self.get_submission(submission_id).get("files", [])
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
return self._get_file_attr(expected_href, base_path, files)
|
|
273
|
+
except Exception as e:
|
|
274
|
+
raise HTTPException(
|
|
275
|
+
status_code=404,
|
|
276
|
+
detail=f"Submission file not found. [contest_id={self.contest_id}] [submission_id={submission_id}] [err={e}]",
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Judgement operations
|
|
280
|
+
def get_judgements(self, submission_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
281
|
+
if submission_id is not None:
|
|
282
|
+
if submission_id not in self.judgements_by_submission_id:
|
|
283
|
+
raise HTTPException(status_code=404, detail=f"Submission id not found: {submission_id}")
|
|
284
|
+
return self.judgements_by_submission_id[submission_id]
|
|
285
|
+
|
|
286
|
+
return self.judgements
|
|
287
|
+
|
|
288
|
+
def get_judgement(self, judgement_id: str) -> Dict[str, Any]:
|
|
289
|
+
if judgement_id not in self.judgements_by_id:
|
|
290
|
+
raise HTTPException(status_code=404, detail=f"Judgement {judgement_id} not found")
|
|
291
|
+
return self.judgements_by_id[judgement_id]
|
|
292
|
+
|
|
293
|
+
# Run operations
|
|
294
|
+
def get_runs(self, judgement_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
295
|
+
if judgement_id is not None:
|
|
296
|
+
if judgement_id not in self.runs_by_judgement_id:
|
|
297
|
+
raise HTTPException(status_code=404, detail=f"Judgement id not found: {judgement_id}")
|
|
298
|
+
return self.runs_by_judgement_id[judgement_id]
|
|
299
|
+
|
|
300
|
+
return self.runs
|
|
301
|
+
|
|
302
|
+
def get_run(self, run_id: str) -> Dict[str, Any]:
|
|
303
|
+
if run_id not in self.runs_by_id:
|
|
304
|
+
raise HTTPException(status_code=404, detail=f"Run {run_id} not found")
|
|
305
|
+
return self.runs_by_id[run_id]
|
|
306
|
+
|
|
307
|
+
# Clarification operations
|
|
308
|
+
def get_clarifications(self) -> List[Dict[str, Any]]:
|
|
309
|
+
return self.clarifications
|
|
310
|
+
|
|
311
|
+
def get_clarification(self, clarification_id: str) -> Dict[str, Any]:
|
|
312
|
+
if clarification_id not in self.clarifications_by_id:
|
|
313
|
+
raise HTTPException(status_code=404, detail=f"Clarification {clarification_id} not found")
|
|
314
|
+
return self.clarifications_by_id[clarification_id]
|
|
315
|
+
|
|
316
|
+
# Award operations
|
|
317
|
+
def get_awards(self) -> List[Dict[str, Any]]:
|
|
318
|
+
return self.awards
|
|
319
|
+
|
|
320
|
+
def get_award(self, award_id: str) -> Dict[str, Any]:
|
|
321
|
+
if award_id not in self.awards_by_id:
|
|
322
|
+
raise HTTPException(status_code=404, detail=f"Award {award_id} not found")
|
|
323
|
+
return self.awards_by_id[award_id]
|
|
324
|
+
|
|
325
|
+
# Event Feed operations
|
|
326
|
+
def get_event_feed(self, since_token: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
327
|
+
if since_token is None:
|
|
328
|
+
return self.event_feed
|
|
329
|
+
|
|
330
|
+
idx = bisect.bisect_left(self.event_feed_tokens, since_token)
|
|
331
|
+
return self.event_feed[idx:]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xcpcio
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.64.0
|
|
4
4
|
Summary: xcpcio python lib
|
|
5
5
|
Project-URL: homepage, https://github.com/xcpcio/xcpcio
|
|
6
6
|
Project-URL: documentation, https://github.com/xcpcio/xcpcio
|
|
@@ -25,6 +25,7 @@ Requires-Dist: pyyaml>=6.0.0
|
|
|
25
25
|
Requires-Dist: semver>=3.0.0
|
|
26
26
|
Requires-Dist: tenacity>=8.0.0
|
|
27
27
|
Requires-Dist: uvicorn>=0.36.0
|
|
28
|
+
Requires-Dist: zstandard>=0.25.0
|
|
28
29
|
Description-Content-Type: text/markdown
|
|
29
30
|
|
|
30
31
|
# xcpcio-python
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
xcpcio/__init__.py,sha256=kjd6itqBRSQ-OT83qUJXHt81KQQDRUtaIuykzfaWXLM,121
|
|
2
|
+
xcpcio/__version__.py,sha256=klqfiPl7r99QoDZy8_HzKScRlEEWVa3Svby8c3yeAsY,172
|
|
3
|
+
xcpcio/constants.py,sha256=MjpAgNXiBlUsx1S09m7JNT-nekNDR-aE6ggvGL3fg0I,2297
|
|
4
|
+
xcpcio/types.py,sha256=AkYby2haJgxwtozlgaPMG2ryAZdvsSc3sH-p6qXcM4g,6575
|
|
5
|
+
xcpcio/ccs/__init__.py,sha256=LSoKFblEuSoIVBYcUxOFF8fn2bH2R6kSg9xNrBfzC0g,99
|
|
6
|
+
xcpcio/ccs/contest_archiver.py,sha256=ICogyPzKfFRoO7J5D2Eu-3JwIirC3etMev7hyTjn4z8,16198
|
|
7
|
+
xcpcio/ccs/api_server/__init__.py,sha256=ASvVJ_ibGkXFDSNmy05eb9gESXRS8hjYHCrBecSnaS0,174
|
|
8
|
+
xcpcio/ccs/api_server/dependencies.py,sha256=cbLHcP91SaRBj2W9OfC0yCQ1fasI2DofxGUhPPNs3F8,1518
|
|
9
|
+
xcpcio/ccs/api_server/server.py,sha256=3gft3MqDXvKWug5UCLhdV681bcQMRrewDkwQ7qSPtRU,2598
|
|
10
|
+
xcpcio/ccs/api_server/routes/__init__.py,sha256=uz65H4L5Wzef7QPi5PsLQM1xbMdG6FoZ0Np0y039_2k,1537
|
|
11
|
+
xcpcio/ccs/api_server/routes/access.py,sha256=O-RGLmgLNBQ-ccu8rlHOgonTjl02fYOdi3VTsUa-T0w,434
|
|
12
|
+
xcpcio/ccs/api_server/routes/accounts.py,sha256=nAwiIz-y4fGmqHBniMuQifk9jVt4i9YPZWBB7w40q5Q,994
|
|
13
|
+
xcpcio/ccs/api_server/routes/awards.py,sha256=gBPSFlDj6PM6Poys6JbO7VMsfCljKz6QrTjKEqQcS8w,957
|
|
14
|
+
xcpcio/ccs/api_server/routes/clarifications.py,sha256=vvMNMvQkZTOn1VJ8C5U86gpIkN3yNrxBll1VMTJjzQg,1046
|
|
15
|
+
xcpcio/ccs/api_server/routes/contests.py,sha256=VbvegfP5ZnVNKdqoHmy7_Wd2L6xEBqON7UD2OQ6sQ8A,2979
|
|
16
|
+
xcpcio/ccs/api_server/routes/general.py,sha256=xLH-sqyeC4dSd6SUYpjg-w-1ZtmSqZAgIupoVOyYwD4,763
|
|
17
|
+
xcpcio/ccs/api_server/routes/groups.py,sha256=kWKFFty2iWckMN-j6G3NbgMVKCPQ478_mYKrYsHXgMA,949
|
|
18
|
+
xcpcio/ccs/api_server/routes/judgement_types.py,sha256=S3Dt0VYjntVBQBvLYhJGcDNSy7O8Zh7b7NSfrronZSU,1057
|
|
19
|
+
xcpcio/ccs/api_server/routes/judgements.py,sha256=3w0LHYbZazli_v587l98U23r5PuolRs_vtbNLMwgjTU,1127
|
|
20
|
+
xcpcio/ccs/api_server/routes/languages.py,sha256=2BYqTaSBWx0E5XlaTjofElLb7z4HTsVs3rrozdyqz0s,985
|
|
21
|
+
xcpcio/ccs/api_server/routes/organizations.py,sha256=6xMl0Iqo7pjLaJbR7L1NWwo8JXwnXhH2O8hJKiLoRa0,1712
|
|
22
|
+
xcpcio/ccs/api_server/routes/problems.py,sha256=XAhXkShHwewZdugsffLj1UxvFNSorrxc-PyZymdBQTY,1632
|
|
23
|
+
xcpcio/ccs/api_server/routes/runs.py,sha256=1L6fo1KPcDeKwOEkTxCwEetmyfNKY_Z0uxATqBJftIs,1046
|
|
24
|
+
xcpcio/ccs/api_server/routes/submissions.py,sha256=ByVX_wzab0Q9EHFmuE8JQtikvboMXtt_kXecEy6yiRc,1675
|
|
25
|
+
xcpcio/ccs/api_server/routes/teams.py,sha256=euDs-GDHLqmJS3zPTNxm9LfKXQYEuhwAlX-BF2tcrk4,1556
|
|
26
|
+
xcpcio/ccs/api_server/services/__init__.py,sha256=WQLNrLVomhtICl8HlFYaCoRewIHVZfUiiwrSBUOOWDg,171
|
|
27
|
+
xcpcio/ccs/api_server/services/contest_service.py,sha256=Jmt7h4rQoZLXPAkHx3FdQNsRtYOPpdSOlh7QA41nIm4,7467
|
|
28
|
+
xcpcio/ccs/base/__init__.py,sha256=JYKVtcQG-VGM2OfCg6VhxcXCeCp7r2zxpg_7s9AThS0,39
|
|
29
|
+
xcpcio/ccs/base/types.py,sha256=NfAG-XJjex7p2DfFTRecLWhmwpeO8Ul42nNMnZH39sM,137
|
|
30
|
+
xcpcio/ccs/model/__init__.py,sha256=cZE1q5JY-iHDEKZpsx0UZaMhH-23H4oAHaYOkW7dZ5s,43
|
|
31
|
+
xcpcio/ccs/model/model_2023_06/__init__.py,sha256=OmDQZqmigBpL64LXk5lIOGoQ3Uqis8-2z6qQpOO5aJc,167
|
|
32
|
+
xcpcio/ccs/model/model_2023_06/model.py,sha256=bVMDWpJTwPSpz1fHPxWrWerxCBIboH3LKVZpIZGQ2pY,15287
|
|
33
|
+
xcpcio/ccs/reader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
+
xcpcio/ccs/reader/base_ccs_reader.py,sha256=fS7M0hD3-3PAEV7EYyorVZsBhD4HtABkQeV4fXNldhA,3912
|
|
35
|
+
xcpcio/ccs/reader/contest_package_reader.py,sha256=DPuKp3eptRNMi0-Ssx_K2roN5_0xXILMltucWy1NTPI,14173
|
|
36
|
+
xcpcio-0.64.0.dist-info/METADATA,sha256=1hiCdfGBJXTXTNnDe0AGnS-BoFWv4Q_W8LmbNjSMPvU,1112
|
|
37
|
+
xcpcio-0.64.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
38
|
+
xcpcio-0.64.0.dist-info/entry_points.txt,sha256=qvzh8oDJxIHqTN-rg2lRN6xR99AqxbWnlAQI7uzDibI,59
|
|
39
|
+
xcpcio-0.64.0.dist-info/RECORD,,
|
xcpcio-0.63.6.dist-info/RECORD
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
xcpcio/__init__.py,sha256=kjd6itqBRSQ-OT83qUJXHt81KQQDRUtaIuykzfaWXLM,121
|
|
2
|
-
xcpcio/__version__.py,sha256=rmZgql2iMibrRXQ70oij5TmONsjXyGZIxE3EUzW1_1E,172
|
|
3
|
-
xcpcio/constants.py,sha256=MjpAgNXiBlUsx1S09m7JNT-nekNDR-aE6ggvGL3fg0I,2297
|
|
4
|
-
xcpcio/types.py,sha256=AkYby2haJgxwtozlgaPMG2ryAZdvsSc3sH-p6qXcM4g,6575
|
|
5
|
-
xcpcio/ccs/__init__.py,sha256=LSoKFblEuSoIVBYcUxOFF8fn2bH2R6kSg9xNrBfzC0g,99
|
|
6
|
-
xcpcio/ccs/contest_archiver.py,sha256=FKpUn1IGfa-UNf63OJ5eff7rxOEqXCvFYRLsvkMbUJc,16203
|
|
7
|
-
xcpcio/ccs/api_server/__init__.py,sha256=ASvVJ_ibGkXFDSNmy05eb9gESXRS8hjYHCrBecSnaS0,174
|
|
8
|
-
xcpcio/ccs/api_server/dependencies.py,sha256=5nosGVwY-3Mq7eK9C5ZOMEJFozzzw_kLlpCraFbJ9wY,1225
|
|
9
|
-
xcpcio/ccs/api_server/server.py,sha256=3gft3MqDXvKWug5UCLhdV681bcQMRrewDkwQ7qSPtRU,2598
|
|
10
|
-
xcpcio/ccs/api_server/routes/__init__.py,sha256=imTyDjc9Md3OIr7GniHi3i1vmfzSTKppuUjVREtd8b4,1461
|
|
11
|
-
xcpcio/ccs/api_server/routes/access.py,sha256=8f21NmvVLaFopb5q4MnifOHbhadDZuNHr0BIYc9nmqY,579
|
|
12
|
-
xcpcio/ccs/api_server/routes/awards.py,sha256=uj56ty2k7N5KXfQuWTvvTXSa7HO29CUfEhO8S85hlM0,1069
|
|
13
|
-
xcpcio/ccs/api_server/routes/clarifications.py,sha256=JX7FNbv_4mRdpuV0aSzGwjjmW-4SwTbmAOlm51MNpf4,1644
|
|
14
|
-
xcpcio/ccs/api_server/routes/contests.py,sha256=bJeMaGegei59rD-5YE0ptYcNsPdJqc73KnntRpShzZk,2560
|
|
15
|
-
xcpcio/ccs/api_server/routes/general.py,sha256=9WsnjlomsU13vvozOVHSRIzgReuLGzPln3xwDb8i-gY,933
|
|
16
|
-
xcpcio/ccs/api_server/routes/groups.py,sha256=8hOT_2rz329M_7hcfggRxLU-g8aV2xp6w3aMYlfOPNs,1087
|
|
17
|
-
xcpcio/ccs/api_server/routes/judgement_types.py,sha256=pMOFBQ1LqKhCTLNBGzopCOEmTnWsuVg67dTuItJaCsM,1268
|
|
18
|
-
xcpcio/ccs/api_server/routes/judgements.py,sha256=9GMaEkD8erhqaSItlZQdLzKz-1XL7MD2SQinnD_CZFg,1339
|
|
19
|
-
xcpcio/ccs/api_server/routes/languages.py,sha256=DUNRd3q_Hm5F8-3nCiAMpqqxzG_9zcU5_N8ACefqjOE,1176
|
|
20
|
-
xcpcio/ccs/api_server/routes/organizations.py,sha256=fWnKTCmxd3c1leYsRuJHuuTs0CZr-l_-Jl-8_wY_Ojk,2889
|
|
21
|
-
xcpcio/ccs/api_server/routes/problems.py,sha256=0Ba3fz09UfTGYmYYYJYFGmIpgIvJaa1Fg-5pcLcxEMY,2774
|
|
22
|
-
xcpcio/ccs/api_server/routes/runs.py,sha256=uBPeM5ChI3LxR__pBSyDxsbcE4NyL6eKVdvF4TEOk5g,1248
|
|
23
|
-
xcpcio/ccs/api_server/routes/submissions.py,sha256=81-KbQLUKCqgGE-aCTO7-Pg2QyTJc_Mu_Ys8N0oF1tU,3161
|
|
24
|
-
xcpcio/ccs/api_server/routes/teams.py,sha256=y_aJykzgewmOQtXOJe2s0s7DQmnKjD3805Q5VTcdlIQ,2725
|
|
25
|
-
xcpcio/ccs/api_server/services/__init__.py,sha256=WQLNrLVomhtICl8HlFYaCoRewIHVZfUiiwrSBUOOWDg,171
|
|
26
|
-
xcpcio/ccs/api_server/services/contest_service.py,sha256=EbSDrkVg18Th9kAuYAin4InHCOI7hWkkDJR1u1xlEtk,16186
|
|
27
|
-
xcpcio/ccs/model/__init__.py,sha256=cZE1q5JY-iHDEKZpsx0UZaMhH-23H4oAHaYOkW7dZ5s,43
|
|
28
|
-
xcpcio/ccs/model/model_2023_06/__init__.py,sha256=OmDQZqmigBpL64LXk5lIOGoQ3Uqis8-2z6qQpOO5aJc,167
|
|
29
|
-
xcpcio/ccs/model/model_2023_06/model.py,sha256=bVMDWpJTwPSpz1fHPxWrWerxCBIboH3LKVZpIZGQ2pY,15287
|
|
30
|
-
xcpcio-0.63.6.dist-info/METADATA,sha256=uEBCmgoTkMCCrO-zLZGdzDjhYb9n51mabFs4lwEV0II,1079
|
|
31
|
-
xcpcio-0.63.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
32
|
-
xcpcio-0.63.6.dist-info/entry_points.txt,sha256=qvzh8oDJxIHqTN-rg2lRN6xR99AqxbWnlAQI7uzDibI,59
|
|
33
|
-
xcpcio-0.63.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|