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
|
@@ -5,102 +5,31 @@ Business logic layer for Contest API operations.
|
|
|
5
5
|
Handles file reading, data validation, and business operations.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
from
|
|
10
|
-
from typing import Any, Dict, List, Optional, Union
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
11
10
|
|
|
12
11
|
from fastapi import HTTPException
|
|
13
12
|
|
|
14
13
|
from xcpcio.__version__ import __version__
|
|
14
|
+
from xcpcio.ccs.base.types import FileAttr
|
|
15
|
+
from xcpcio.ccs.reader.base_ccs_reader import BaseCCSReader
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
15
18
|
|
|
16
19
|
|
|
17
20
|
class ContestService:
|
|
18
21
|
"""Service class for contest-related operations"""
|
|
19
22
|
|
|
20
|
-
def __init__(self,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
contest_package_dir: Path to the contest package directory
|
|
26
|
-
"""
|
|
27
|
-
self.contest_package_dir = contest_package_dir
|
|
28
|
-
if not self.contest_package_dir.exists():
|
|
29
|
-
raise ValueError(f"Contest package directory does not exist: {contest_package_dir}")
|
|
30
|
-
|
|
31
|
-
# Initialize data indexes for faster lookups
|
|
32
|
-
self._load_indexes()
|
|
33
|
-
|
|
34
|
-
def _load_indexes(self) -> None:
|
|
35
|
-
"""Load and index commonly accessed data for faster lookups"""
|
|
36
|
-
# Load contest data
|
|
37
|
-
self.contest_data = self.load_json_file("contest.json")
|
|
38
|
-
|
|
39
|
-
# Load organizations and create index
|
|
40
|
-
organizations_data = self.load_json_file("organizations.json")
|
|
41
|
-
self.organizations_by_id = {org["id"]: org for org in organizations_data}
|
|
42
|
-
|
|
43
|
-
# Load teams and create index
|
|
44
|
-
teams_data = self.load_json_file("teams.json")
|
|
45
|
-
self.teams_by_id = {team["id"]: team for team in teams_data}
|
|
46
|
-
|
|
47
|
-
# Load problems and create index
|
|
48
|
-
problems_data = self.load_json_file("problems.json")
|
|
49
|
-
self.problems_by_id = {problem["id"]: problem for problem in problems_data}
|
|
50
|
-
|
|
51
|
-
# Load submissions and create index
|
|
52
|
-
submissions_data = self.load_json_file("submissions.json")
|
|
53
|
-
self.submissions_by_id = {submission["id"]: submission for submission in submissions_data}
|
|
54
|
-
|
|
55
|
-
def load_json_file(self, filepath: str) -> Union[Dict[str, Any], List[Any]]:
|
|
56
|
-
"""
|
|
57
|
-
Load JSON data from contest package directory.
|
|
58
|
-
|
|
59
|
-
Args:
|
|
60
|
-
filepath: Relative path to JSON file within contest package
|
|
61
|
-
|
|
62
|
-
Returns:
|
|
63
|
-
Parsed JSON data
|
|
64
|
-
|
|
65
|
-
Raises:
|
|
66
|
-
HTTPException: If file not found or invalid JSON
|
|
67
|
-
"""
|
|
68
|
-
full_path = self.contest_package_dir / filepath
|
|
69
|
-
try:
|
|
70
|
-
with open(full_path, "r", encoding="utf-8") as f:
|
|
71
|
-
return json.load(f)
|
|
72
|
-
except FileNotFoundError:
|
|
73
|
-
raise HTTPException(status_code=404, detail=f"File not found: {filepath}")
|
|
74
|
-
except json.JSONDecodeError as e:
|
|
75
|
-
raise HTTPException(status_code=500, detail=f"Invalid JSON in file {filepath}: {e}")
|
|
76
|
-
|
|
77
|
-
def get_contest_id(self) -> str:
|
|
78
|
-
"""
|
|
79
|
-
Get contest ID from contest.json.
|
|
80
|
-
|
|
81
|
-
Returns:
|
|
82
|
-
Contest ID string
|
|
83
|
-
"""
|
|
84
|
-
contest_data = self.load_json_file("contest.json")
|
|
85
|
-
return contest_data.get("id", "unknown")
|
|
86
|
-
|
|
87
|
-
def validate_contest_id(self, contest_id: str) -> None:
|
|
88
|
-
"""
|
|
89
|
-
Validate that the provided contest ID matches the expected one.
|
|
90
|
-
|
|
91
|
-
Args:
|
|
92
|
-
contest_id: Contest ID to validate
|
|
93
|
-
|
|
94
|
-
Raises:
|
|
95
|
-
HTTPException: If contest ID doesn't match
|
|
96
|
-
"""
|
|
97
|
-
expected_id = self.get_contest_id()
|
|
98
|
-
if contest_id != expected_id:
|
|
23
|
+
def __init__(self, reader_dict: Dict[str, BaseCCSReader]):
|
|
24
|
+
self.reader_dict = reader_dict
|
|
25
|
+
|
|
26
|
+
def _get_reader(self, contest_id: str) -> BaseCCSReader:
|
|
27
|
+
if contest_id not in self.reader_dict:
|
|
99
28
|
raise HTTPException(status_code=404, detail=f"Contest {contest_id} not found")
|
|
29
|
+
return self.reader_dict[contest_id]
|
|
100
30
|
|
|
101
31
|
# API Information
|
|
102
32
|
def get_api_info(self) -> Dict[str, Any]:
|
|
103
|
-
"""Get API information"""
|
|
104
33
|
return {
|
|
105
34
|
"version": "2023-06",
|
|
106
35
|
"version_url": "https://ccs-specs.icpc.io/2023-06/contest_api",
|
|
@@ -111,298 +40,155 @@ class ContestService:
|
|
|
111
40
|
},
|
|
112
41
|
}
|
|
113
42
|
|
|
114
|
-
def
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
"start_time",
|
|
127
|
-
"duration",
|
|
128
|
-
"scoreboard_type",
|
|
129
|
-
"penalty_time",
|
|
130
|
-
],
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
"type": "problems",
|
|
134
|
-
"properties": ["id", "label", "name", "ordinal", "color", "rgb", "time_limit", "test_data_count"],
|
|
135
|
-
},
|
|
136
|
-
{"type": "teams", "properties": ["id", "name", "label", "organization_id", "group_ids", "hidden"]},
|
|
137
|
-
{"type": "organizations", "properties": ["id", "name", "formal_name"]},
|
|
138
|
-
{"type": "groups", "properties": ["id", "name"]},
|
|
139
|
-
{"type": "judgement-types", "properties": ["id", "name", "penalty", "solved"]},
|
|
140
|
-
{"type": "languages", "properties": ["id", "name", "extensions"]},
|
|
141
|
-
{
|
|
142
|
-
"type": "state",
|
|
143
|
-
"properties": ["started", "ended", "frozen", "thawed", "finalized", "end_of_updates"],
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
"type": "submissions",
|
|
147
|
-
"properties": ["id", "team_id", "problem_id", "language_id", "time", "contest_time"],
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
"type": "judgements",
|
|
151
|
-
"properties": ["id", "submission_id", "judgement_type_id", "start_time", "start_contest_time"],
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
"type": "runs",
|
|
155
|
-
"properties": [
|
|
156
|
-
"id",
|
|
157
|
-
"judgement_id",
|
|
158
|
-
"ordinal",
|
|
159
|
-
"judgement_type_id",
|
|
160
|
-
"time",
|
|
161
|
-
"contest_time",
|
|
162
|
-
"run_time",
|
|
163
|
-
],
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
"type": "clarifications",
|
|
167
|
-
"properties": ["id", "from_team_id", "to_team_id", "problem_id", "text", "time", "contest_time"],
|
|
168
|
-
},
|
|
169
|
-
{"type": "awards", "properties": ["id", "citation", "team_ids"]},
|
|
170
|
-
],
|
|
171
|
-
}
|
|
43
|
+
def get_access(self, contest_id: str) -> Dict[str, Any]:
|
|
44
|
+
reader = self._get_reader(contest_id)
|
|
45
|
+
return reader.get_access()
|
|
46
|
+
|
|
47
|
+
# Account operations
|
|
48
|
+
def get_accounts(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
49
|
+
reader = self._get_reader(contest_id)
|
|
50
|
+
return reader.get_accounts()
|
|
51
|
+
|
|
52
|
+
def get_account(self, contest_id: str, account_id: str) -> Dict[str, Any]:
|
|
53
|
+
reader = self._get_reader(contest_id)
|
|
54
|
+
return reader.get_account(account_id)
|
|
172
55
|
|
|
173
56
|
# Contest operations
|
|
174
57
|
def get_contests(self) -> List[Dict[str, Any]]:
|
|
175
|
-
|
|
176
|
-
contest_data = self.load_json_file("contest.json")
|
|
177
|
-
return [contest_data]
|
|
58
|
+
return [reader.get_contest() for reader in self.reader_dict.values()]
|
|
178
59
|
|
|
179
60
|
def get_contest(self, contest_id: str) -> Dict[str, Any]:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return self.load_json_file("contest.json")
|
|
61
|
+
reader = self._get_reader(contest_id)
|
|
62
|
+
return reader.get_contest()
|
|
183
63
|
|
|
184
64
|
def get_contest_state(self, contest_id: str) -> Dict[str, Any]:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
65
|
+
reader = self._get_reader(contest_id)
|
|
66
|
+
return reader.get_contest_state()
|
|
67
|
+
|
|
68
|
+
def get_contest_banner(self, contest_id: str) -> FileAttr:
|
|
69
|
+
reader = self._get_reader(contest_id)
|
|
70
|
+
return reader.get_contest_banner()
|
|
71
|
+
|
|
72
|
+
def get_contest_problemset(self, contest_id: str) -> FileAttr:
|
|
73
|
+
reader = self._get_reader(contest_id)
|
|
74
|
+
return reader.get_contest_problemset()
|
|
188
75
|
|
|
189
76
|
# Problem operations
|
|
190
77
|
def get_problems(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return self.load_json_file("problems.json")
|
|
78
|
+
reader = self._get_reader(contest_id)
|
|
79
|
+
return reader.get_problems()
|
|
194
80
|
|
|
195
81
|
def get_problem(self, contest_id: str, problem_id: str) -> Dict[str, Any]:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
problems = self.load_json_file("problems.json")
|
|
199
|
-
for problem in problems:
|
|
200
|
-
if problem["id"] == problem_id:
|
|
201
|
-
return problem
|
|
202
|
-
raise HTTPException(status_code=404, detail=f"Problem {problem_id} not found")
|
|
82
|
+
reader = self._get_reader(contest_id)
|
|
83
|
+
return reader.get_problem(problem_id)
|
|
203
84
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
self.validate_contest_id(contest_id)
|
|
208
|
-
teams = self.load_json_file("teams.json")
|
|
85
|
+
def get_problem_statement(self, contest_id: str, problem_id: str) -> FileAttr:
|
|
86
|
+
reader = self._get_reader(contest_id)
|
|
87
|
+
return reader.get_problem_statement(problem_id)
|
|
209
88
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
filtered_teams.append(team)
|
|
215
|
-
return filtered_teams
|
|
216
|
-
|
|
217
|
-
return teams
|
|
89
|
+
# Team operations
|
|
90
|
+
def get_teams(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
91
|
+
reader = self._get_reader(contest_id)
|
|
92
|
+
return reader.get_teams()
|
|
218
93
|
|
|
219
94
|
def get_team(self, contest_id: str, team_id: str) -> Dict[str, Any]:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
raise HTTPException(status_code=404, detail=f"Team {team_id} not found")
|
|
95
|
+
reader = self._get_reader(contest_id)
|
|
96
|
+
return reader.get_team(team_id)
|
|
97
|
+
|
|
98
|
+
def get_team_photo(self, contest_id: str, team_id: str) -> FileAttr:
|
|
99
|
+
reader = self._get_reader(contest_id)
|
|
100
|
+
return reader.get_team_photo(team_id)
|
|
227
101
|
|
|
228
102
|
# Organization operations
|
|
229
103
|
def get_organizations(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return self.load_json_file("organizations.json")
|
|
104
|
+
reader = self._get_reader(contest_id)
|
|
105
|
+
return reader.get_organizations()
|
|
233
106
|
|
|
234
107
|
def get_organization(self, contest_id: str, organization_id: str) -> Dict[str, Any]:
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
raise HTTPException(status_code=404, detail=f"Organization {organization_id} not found")
|
|
108
|
+
reader = self._get_reader(contest_id)
|
|
109
|
+
return reader.get_organization(organization_id)
|
|
110
|
+
|
|
111
|
+
def get_organization_logo(self, contest_id: str, organization_id: str) -> FileAttr:
|
|
112
|
+
reader = self._get_reader(contest_id)
|
|
113
|
+
return reader.get_organization_logo(organization_id)
|
|
242
114
|
|
|
243
115
|
# Group operations
|
|
244
116
|
def get_groups(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return self.load_json_file("groups.json")
|
|
117
|
+
reader = self._get_reader(contest_id)
|
|
118
|
+
return reader.get_groups()
|
|
248
119
|
|
|
249
120
|
def get_group(self, contest_id: str, group_id: str) -> Dict[str, Any]:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
groups = self.load_json_file("groups.json")
|
|
253
|
-
for group in groups:
|
|
254
|
-
if group["id"] == group_id:
|
|
255
|
-
return group
|
|
256
|
-
raise HTTPException(status_code=404, detail=f"Group {group_id} not found")
|
|
121
|
+
reader = self._get_reader(contest_id)
|
|
122
|
+
return reader.get_group(group_id)
|
|
257
123
|
|
|
258
124
|
# Language operations
|
|
259
125
|
def get_languages(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return self.load_json_file("languages.json")
|
|
126
|
+
reader = self._get_reader(contest_id)
|
|
127
|
+
return reader.get_languages()
|
|
263
128
|
|
|
264
129
|
def get_language(self, contest_id: str, language_id: str) -> Dict[str, Any]:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
languages = self.load_json_file("languages.json")
|
|
268
|
-
for lang in languages:
|
|
269
|
-
if lang["id"] == language_id:
|
|
270
|
-
return lang
|
|
271
|
-
raise HTTPException(status_code=404, detail=f"Language {language_id} not found")
|
|
130
|
+
reader = self._get_reader(contest_id)
|
|
131
|
+
return reader.get_language(language_id)
|
|
272
132
|
|
|
273
133
|
# Judgement type operations
|
|
274
134
|
def get_judgement_types(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return self.load_json_file("judgement-types.json")
|
|
135
|
+
reader = self._get_reader(contest_id)
|
|
136
|
+
return reader.get_judgement_types()
|
|
278
137
|
|
|
279
138
|
def get_judgement_type(self, contest_id: str, judgement_type_id: str) -> Dict[str, Any]:
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
judgement_types = self.load_json_file("judgement-types.json")
|
|
283
|
-
for jt in judgement_types:
|
|
284
|
-
if jt["id"] == judgement_type_id:
|
|
285
|
-
return jt
|
|
286
|
-
raise HTTPException(status_code=404, detail=f"Judgement type {judgement_type_id} not found")
|
|
139
|
+
reader = self._get_reader(contest_id)
|
|
140
|
+
return reader.get_judgement_type(judgement_type_id)
|
|
287
141
|
|
|
288
142
|
# Submission operations
|
|
289
|
-
def get_submissions(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
"""Get all submissions, optionally filtered"""
|
|
293
|
-
self.validate_contest_id(contest_id)
|
|
294
|
-
submissions = self.load_json_file("submissions.json")
|
|
295
|
-
|
|
296
|
-
# Apply filters
|
|
297
|
-
if team_id:
|
|
298
|
-
submissions = [s for s in submissions if s.get("team_id") == team_id]
|
|
299
|
-
if problem_id:
|
|
300
|
-
submissions = [s for s in submissions if s.get("problem_id") == problem_id]
|
|
301
|
-
|
|
302
|
-
return submissions
|
|
143
|
+
def get_submissions(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
144
|
+
reader = self._get_reader(contest_id)
|
|
145
|
+
return reader.get_submissions()
|
|
303
146
|
|
|
304
147
|
def get_submission(self, contest_id: str, submission_id: str) -> Dict[str, Any]:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
raise HTTPException(status_code=404, detail=f"Submission {submission_id} not found")
|
|
148
|
+
reader = self._get_reader(contest_id)
|
|
149
|
+
return reader.get_submission(submission_id)
|
|
150
|
+
|
|
151
|
+
def get_submission_file(self, contest_id: str, submission_id: str) -> FileAttr:
|
|
152
|
+
reader = self._get_reader(contest_id)
|
|
153
|
+
return reader.get_submission_file(submission_id)
|
|
312
154
|
|
|
313
155
|
# Judgement operations
|
|
314
156
|
def get_judgements(self, contest_id: str, submission_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
judgements = self.load_json_file("judgements.json")
|
|
318
|
-
|
|
319
|
-
if submission_id:
|
|
320
|
-
judgements = [j for j in judgements if j.get("submission_id") == submission_id]
|
|
321
|
-
|
|
322
|
-
return judgements
|
|
157
|
+
reader = self._get_reader(contest_id)
|
|
158
|
+
return reader.get_judgements(submission_id)
|
|
323
159
|
|
|
324
160
|
def get_judgement(self, contest_id: str, judgement_id: str) -> Dict[str, Any]:
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
judgements = self.load_json_file("judgements.json")
|
|
328
|
-
for judgement in judgements:
|
|
329
|
-
if judgement["id"] == judgement_id:
|
|
330
|
-
return judgement
|
|
331
|
-
raise HTTPException(status_code=404, detail=f"Judgement {judgement_id} not found")
|
|
161
|
+
reader = self._get_reader(contest_id)
|
|
162
|
+
return reader.get_judgement(judgement_id)
|
|
332
163
|
|
|
333
164
|
# Run operations
|
|
334
165
|
def get_runs(self, contest_id: str, judgement_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
runs = self.load_json_file("runs.json")
|
|
338
|
-
|
|
339
|
-
if judgement_id:
|
|
340
|
-
runs = [r for r in runs if r.get("judgement_id") == judgement_id]
|
|
341
|
-
|
|
342
|
-
return runs
|
|
166
|
+
reader = self._get_reader(contest_id)
|
|
167
|
+
return reader.get_runs(judgement_id)
|
|
343
168
|
|
|
344
169
|
def get_run(self, contest_id: str, run_id: str) -> Dict[str, Any]:
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
runs = self.load_json_file("runs.json")
|
|
348
|
-
for run in runs:
|
|
349
|
-
if run["id"] == run_id:
|
|
350
|
-
return run
|
|
351
|
-
raise HTTPException(status_code=404, detail=f"Run {run_id} not found")
|
|
170
|
+
reader = self._get_reader(contest_id)
|
|
171
|
+
return reader.get_run(run_id)
|
|
352
172
|
|
|
353
173
|
# Clarification operations
|
|
354
|
-
def get_clarifications(
|
|
355
|
-
self
|
|
356
|
-
|
|
357
|
-
from_team_id: Optional[str] = None,
|
|
358
|
-
to_team_id: Optional[str] = None,
|
|
359
|
-
problem_id: Optional[str] = None,
|
|
360
|
-
) -> List[Dict[str, Any]]:
|
|
361
|
-
"""Get all clarifications, optionally filtered"""
|
|
362
|
-
self.validate_contest_id(contest_id)
|
|
363
|
-
clarifications = self.load_json_file("clarifications.json")
|
|
364
|
-
|
|
365
|
-
# Apply filters (empty string means null)
|
|
366
|
-
if from_team_id is not None:
|
|
367
|
-
if from_team_id == "":
|
|
368
|
-
clarifications = [c for c in clarifications if c.get("from_team_id") is None]
|
|
369
|
-
else:
|
|
370
|
-
clarifications = [c for c in clarifications if c.get("from_team_id") == from_team_id]
|
|
371
|
-
|
|
372
|
-
if to_team_id is not None:
|
|
373
|
-
if to_team_id == "":
|
|
374
|
-
clarifications = [c for c in clarifications if c.get("to_team_id") is None]
|
|
375
|
-
else:
|
|
376
|
-
clarifications = [c for c in clarifications if c.get("to_team_id") == to_team_id]
|
|
377
|
-
|
|
378
|
-
if problem_id is not None:
|
|
379
|
-
if problem_id == "":
|
|
380
|
-
clarifications = [c for c in clarifications if c.get("problem_id") is None]
|
|
381
|
-
else:
|
|
382
|
-
clarifications = [c for c in clarifications if c.get("problem_id") == problem_id]
|
|
383
|
-
|
|
384
|
-
return clarifications
|
|
174
|
+
def get_clarifications(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
175
|
+
reader = self._get_reader(contest_id)
|
|
176
|
+
return reader.get_clarifications()
|
|
385
177
|
|
|
386
178
|
def get_clarification(self, contest_id: str, clarification_id: str) -> Dict[str, Any]:
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
clarifications = self.load_json_file("clarifications.json")
|
|
390
|
-
for clarification in clarifications:
|
|
391
|
-
if clarification["id"] == clarification_id:
|
|
392
|
-
return clarification
|
|
393
|
-
raise HTTPException(status_code=404, detail=f"Clarification {clarification_id} not found")
|
|
179
|
+
reader = self._get_reader(contest_id)
|
|
180
|
+
return reader.get_clarification(clarification_id)
|
|
394
181
|
|
|
395
182
|
# Award operations
|
|
396
183
|
def get_awards(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
return self.load_json_file("awards.json")
|
|
184
|
+
reader = self._get_reader(contest_id)
|
|
185
|
+
return reader.get_awards()
|
|
400
186
|
|
|
401
187
|
def get_award(self, contest_id: str, award_id: str) -> Dict[str, Any]:
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
188
|
+
reader = self._get_reader(contest_id)
|
|
189
|
+
return reader.get_award(award_id)
|
|
190
|
+
|
|
191
|
+
# Event Feed operations
|
|
192
|
+
def get_event_feed(self, contest_id: str, since_token: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
193
|
+
reader = self._get_reader(contest_id)
|
|
194
|
+
return reader.get_event_feed(since_token)
|
xcpcio/ccs/base/types.py
ADDED
xcpcio/ccs/contest_archiver.py
CHANGED
|
@@ -59,7 +59,6 @@ class ContestArchiver:
|
|
|
59
59
|
|
|
60
60
|
# Known endpoints that can be fetched
|
|
61
61
|
KNOWN_ENDPOINTS = [
|
|
62
|
-
"access",
|
|
63
62
|
"contests",
|
|
64
63
|
"judgement-types",
|
|
65
64
|
"languages",
|
|
@@ -80,7 +79,6 @@ class ContestArchiver:
|
|
|
80
79
|
]
|
|
81
80
|
|
|
82
81
|
DOMJUDGE_KNOWN_ENDPOINTS = [
|
|
83
|
-
"access",
|
|
84
82
|
"contests",
|
|
85
83
|
"judgement-types",
|
|
86
84
|
"languages",
|
|
@@ -289,7 +287,7 @@ class ContestArchiver:
|
|
|
289
287
|
|
|
290
288
|
data = await self.fetch_json("/")
|
|
291
289
|
if not data:
|
|
292
|
-
raise RuntimeError("Failed to fetch API information
|
|
290
|
+
raise RuntimeError("Failed to fetch API information")
|
|
293
291
|
|
|
294
292
|
self._api_info = data # Store API info for later use
|
|
295
293
|
|
|
@@ -401,6 +399,7 @@ class ContestArchiver:
|
|
|
401
399
|
# Always dump API and contest info
|
|
402
400
|
await self.dump_api_info()
|
|
403
401
|
await self.dump_contest_info()
|
|
402
|
+
await self.dump_endpoint_single("access")
|
|
404
403
|
|
|
405
404
|
# Get list of endpoints to dump
|
|
406
405
|
if self._config.endpoints:
|
|
File without changes
|