xcpcio 0.63.6__py3-none-any.whl → 0.63.7__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/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 +69 -24
- 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 +6 -17
- xcpcio/ccs/api_server/routes/problems.py +6 -14
- xcpcio/ccs/api_server/routes/runs.py +4 -9
- xcpcio/ccs/api_server/routes/submissions.py +7 -18
- xcpcio/ccs/api_server/routes/teams.py +8 -21
- xcpcio/ccs/api_server/services/contest_service.py +147 -230
- xcpcio/ccs/contest_archiver.py +2 -3
- {xcpcio-0.63.6.dist-info → xcpcio-0.63.7.dist-info}/METADATA +1 -1
- xcpcio-0.63.7.dist-info/RECORD +34 -0
- xcpcio-0.63.6.dist-info/RECORD +0 -33
- {xcpcio-0.63.6.dist-info → xcpcio-0.63.7.dist-info}/WHEEL +0 -0
- {xcpcio-0.63.6.dist-info → xcpcio-0.63.7.dist-info}/entry_points.txt +0 -0
|
@@ -5,14 +5,14 @@ Business logic layer for Contest API operations.
|
|
|
5
5
|
Handles file reading, data validation, and business operations.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import bisect
|
|
8
9
|
import json
|
|
10
|
+
from collections import defaultdict
|
|
9
11
|
from pathlib import Path
|
|
10
12
|
from typing import Any, Dict, List, Optional, Union
|
|
11
13
|
|
|
12
14
|
from fastapi import HTTPException
|
|
13
15
|
|
|
14
|
-
from xcpcio.__version__ import __version__
|
|
15
|
-
|
|
16
16
|
|
|
17
17
|
class ContestService:
|
|
18
18
|
"""Service class for contest-related operations"""
|
|
@@ -31,26 +31,61 @@ class ContestService:
|
|
|
31
31
|
# Initialize data indexes for faster lookups
|
|
32
32
|
self._load_indexes()
|
|
33
33
|
|
|
34
|
+
def _create_index_by_id(self, data: List[Dict[str, Any]], id_name: str) -> Dict[str, List[Dict]]:
|
|
35
|
+
res = defaultdict(list)
|
|
36
|
+
for item in data:
|
|
37
|
+
res[item[id_name]].append(item)
|
|
38
|
+
return res
|
|
39
|
+
|
|
34
40
|
def _load_indexes(self) -> None:
|
|
35
41
|
"""Load and index commonly accessed data for faster lookups"""
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
self.access = self.load_json_file("access.json")
|
|
43
|
+
|
|
44
|
+
self.accounts = self.load_json_file("accounts.json")
|
|
45
|
+
self.accounts_by_id = {account["id"] for account in self.accounts}
|
|
46
|
+
|
|
47
|
+
self.api_info = self.load_json_file("api.json")
|
|
48
|
+
|
|
49
|
+
self.awards = self.load_json_file("awards.json")
|
|
50
|
+
self.awards_by_id = {award["id"] for award in self.awards}
|
|
51
|
+
|
|
52
|
+
self.clarifications = self.load_json_file("clarifications.json")
|
|
53
|
+
self.clarifications_by_id = {clarification["id"] for clarification in self.clarifications}
|
|
54
|
+
|
|
55
|
+
self.contest = self.load_json_file("contest.json")
|
|
56
|
+
self.contest_state = self.load_json_file("state.json")
|
|
57
|
+
|
|
58
|
+
self.groups = self.load_json_file("groups.json")
|
|
59
|
+
self.groups_by_id = {group["id"]: group for group in self.groups}
|
|
60
|
+
|
|
61
|
+
self.judgement_types = self.load_json_file("judgement-types.json")
|
|
62
|
+
self.judgement_types_by_id = {judgement_type["id"] for judgement_type in self.judgement_types}
|
|
63
|
+
|
|
64
|
+
self.judgements = self.load_json_file("judgements.json")
|
|
65
|
+
self.judgements_by_id = {judgement["id"] for judgement in self.judgements}
|
|
66
|
+
self.judgements_by_submission_id = self._create_index_by_id(self.judgements, "submission_id")
|
|
67
|
+
|
|
68
|
+
self.languages = self.load_json_file("languages.json")
|
|
69
|
+
self.languages_by_id = {language["id"] for language in self.languages}
|
|
38
70
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
self.organizations_by_id = {org["id"]: org for org in organizations_data}
|
|
71
|
+
self.organizations = self.load_json_file("organizations.json")
|
|
72
|
+
self.organizations_by_id = {org["id"]: org for org in self.organizations}
|
|
42
73
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
self.teams_by_id = {team["id"]: team for team in teams_data}
|
|
74
|
+
self.problems = self.load_json_file("problems.json")
|
|
75
|
+
self.problems_by_id = {problem["id"]: problem for problem in self.problems}
|
|
46
76
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
self.
|
|
77
|
+
self.runs = self.load_json_file("runs.json")
|
|
78
|
+
self.runs_by_id = {run["id"] for run in self.runs}
|
|
79
|
+
self.runs_by_judgement_id = self._create_index_by_id(self.runs, "judgement_id")
|
|
50
80
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
81
|
+
self.submissions = self.load_json_file("submissions.json")
|
|
82
|
+
self.submissions_by_id = {submission["id"]: submission for submission in self.submissions}
|
|
83
|
+
|
|
84
|
+
self.teams = self.load_json_file("teams.json")
|
|
85
|
+
self.teams_by_id = {team["id"]: team for team in self.teams}
|
|
86
|
+
|
|
87
|
+
self.event_feed = self.load_ndjson_file("event-feed.ndjson")
|
|
88
|
+
self.event_feed_tokens = [event["token"] for event in self.event_feed]
|
|
54
89
|
|
|
55
90
|
def load_json_file(self, filepath: str) -> Union[Dict[str, Any], List[Any]]:
|
|
56
91
|
"""
|
|
@@ -65,6 +100,7 @@ class ContestService:
|
|
|
65
100
|
Raises:
|
|
66
101
|
HTTPException: If file not found or invalid JSON
|
|
67
102
|
"""
|
|
103
|
+
|
|
68
104
|
full_path = self.contest_package_dir / filepath
|
|
69
105
|
try:
|
|
70
106
|
with open(full_path, "r", encoding="utf-8") as f:
|
|
@@ -74,6 +110,19 @@ class ContestService:
|
|
|
74
110
|
except json.JSONDecodeError as e:
|
|
75
111
|
raise HTTPException(status_code=500, detail=f"Invalid JSON in file {filepath}: {e}")
|
|
76
112
|
|
|
113
|
+
def load_ndjson_file(self, filepath: str) -> List[Dict[str, Any]]:
|
|
114
|
+
full_path = self.contest_package_dir / filepath
|
|
115
|
+
try:
|
|
116
|
+
data = list()
|
|
117
|
+
with open(full_path, "r", encoding="utf-8") as f:
|
|
118
|
+
for line in f.readlines():
|
|
119
|
+
data.append(json.loads(line))
|
|
120
|
+
return data
|
|
121
|
+
except FileNotFoundError:
|
|
122
|
+
raise HTTPException(status_code=404, detail=f"File not found: {filepath}")
|
|
123
|
+
except json.JSONDecodeError as e:
|
|
124
|
+
raise HTTPException(status_code=500, detail=f"Invalid JSON in file {filepath}: {e}")
|
|
125
|
+
|
|
77
126
|
def get_contest_id(self) -> str:
|
|
78
127
|
"""
|
|
79
128
|
Get contest ID from contest.json.
|
|
@@ -100,309 +149,177 @@ class ContestService:
|
|
|
100
149
|
|
|
101
150
|
# API Information
|
|
102
151
|
def get_api_info(self) -> Dict[str, Any]:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def
|
|
115
|
-
"""Get access information for current client"""
|
|
152
|
+
return self.api_info
|
|
153
|
+
|
|
154
|
+
def get_access(self, contest_id: str) -> Dict[str, Any]:
|
|
155
|
+
self.validate_contest_id(contest_id)
|
|
156
|
+
return self.access
|
|
157
|
+
|
|
158
|
+
# Account operations
|
|
159
|
+
def get_accounts(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
160
|
+
self.validate_contest_id(contest_id)
|
|
161
|
+
return self.accounts
|
|
162
|
+
|
|
163
|
+
def get_account(self, contest_id: str, account_id: str) -> Dict[str, Any]:
|
|
116
164
|
self.validate_contest_id(contest_id)
|
|
117
|
-
|
|
118
|
-
"
|
|
119
|
-
|
|
120
|
-
{
|
|
121
|
-
"type": "contest",
|
|
122
|
-
"properties": [
|
|
123
|
-
"id",
|
|
124
|
-
"name",
|
|
125
|
-
"formal_name",
|
|
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
|
-
}
|
|
165
|
+
if account_id not in self.accounts_by_id:
|
|
166
|
+
raise HTTPException(status_code=404, detail=f"Account {account_id} not found")
|
|
167
|
+
return self.accounts_by_id[account_id]
|
|
172
168
|
|
|
173
169
|
# Contest operations
|
|
174
170
|
def get_contests(self) -> List[Dict[str, Any]]:
|
|
175
|
-
|
|
176
|
-
contest_data = self.load_json_file("contest.json")
|
|
177
|
-
return [contest_data]
|
|
171
|
+
return [self.contest]
|
|
178
172
|
|
|
179
173
|
def get_contest(self, contest_id: str) -> Dict[str, Any]:
|
|
180
|
-
"""Get specific contest"""
|
|
181
174
|
self.validate_contest_id(contest_id)
|
|
182
|
-
return self.
|
|
175
|
+
return self.contest
|
|
183
176
|
|
|
184
177
|
def get_contest_state(self, contest_id: str) -> Dict[str, Any]:
|
|
185
|
-
"""Get contest state"""
|
|
186
178
|
self.validate_contest_id(contest_id)
|
|
187
|
-
return self.
|
|
179
|
+
return self.contest_state
|
|
188
180
|
|
|
189
181
|
# Problem operations
|
|
190
182
|
def get_problems(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
191
|
-
"""Get all problems"""
|
|
192
183
|
self.validate_contest_id(contest_id)
|
|
193
|
-
return self.
|
|
184
|
+
return self.problems
|
|
194
185
|
|
|
195
186
|
def get_problem(self, contest_id: str, problem_id: str) -> Dict[str, Any]:
|
|
196
|
-
"""Get specific problem"""
|
|
197
187
|
self.validate_contest_id(contest_id)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return problem
|
|
202
|
-
raise HTTPException(status_code=404, detail=f"Problem {problem_id} not found")
|
|
188
|
+
if problem_id not in self.problems_by_id:
|
|
189
|
+
raise HTTPException(status_code=404, detail=f"Problem {problem_id} not found")
|
|
190
|
+
return self.problems_by_id[problem_id]
|
|
203
191
|
|
|
204
192
|
# Team operations
|
|
205
|
-
def get_teams(self, contest_id: str
|
|
206
|
-
"""Get all teams, optionally filtered by group"""
|
|
193
|
+
def get_teams(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
207
194
|
self.validate_contest_id(contest_id)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if group_id:
|
|
211
|
-
filtered_teams = []
|
|
212
|
-
for team in teams:
|
|
213
|
-
if group_id in team.get("group_ids", []):
|
|
214
|
-
filtered_teams.append(team)
|
|
215
|
-
return filtered_teams
|
|
216
|
-
|
|
217
|
-
return teams
|
|
195
|
+
return self.teams
|
|
218
196
|
|
|
219
197
|
def get_team(self, contest_id: str, team_id: str) -> Dict[str, Any]:
|
|
220
|
-
"""Get specific team"""
|
|
221
198
|
self.validate_contest_id(contest_id)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return team
|
|
226
|
-
raise HTTPException(status_code=404, detail=f"Team {team_id} not found")
|
|
199
|
+
if team_id not in self.teams_by_id:
|
|
200
|
+
raise HTTPException(status_code=404, detail=f"Team {team_id} not found")
|
|
201
|
+
return self.teams_by_id[team_id]
|
|
227
202
|
|
|
228
203
|
# Organization operations
|
|
229
204
|
def get_organizations(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
230
|
-
"""Get all organizations"""
|
|
231
205
|
self.validate_contest_id(contest_id)
|
|
232
|
-
return self.
|
|
206
|
+
return self.organizations
|
|
233
207
|
|
|
234
208
|
def get_organization(self, contest_id: str, organization_id: str) -> Dict[str, Any]:
|
|
235
|
-
"""Get specific organization"""
|
|
236
209
|
self.validate_contest_id(contest_id)
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
return org
|
|
241
|
-
raise HTTPException(status_code=404, detail=f"Organization {organization_id} not found")
|
|
210
|
+
if organization_id not in self.organizations_by_id:
|
|
211
|
+
raise HTTPException(status_code=404, detail=f"Organization {organization_id} not found")
|
|
212
|
+
return self.organizations_by_id[organization_id]
|
|
242
213
|
|
|
243
214
|
# Group operations
|
|
244
215
|
def get_groups(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
245
|
-
"""Get all groups"""
|
|
246
216
|
self.validate_contest_id(contest_id)
|
|
247
|
-
return self.
|
|
217
|
+
return self.groups
|
|
248
218
|
|
|
249
219
|
def get_group(self, contest_id: str, group_id: str) -> Dict[str, Any]:
|
|
250
|
-
"""Get specific group"""
|
|
251
220
|
self.validate_contest_id(contest_id)
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return group
|
|
256
|
-
raise HTTPException(status_code=404, detail=f"Group {group_id} not found")
|
|
221
|
+
if group_id not in self.groups_by_id:
|
|
222
|
+
raise HTTPException(status_code=404, detail=f"Group {group_id} not found")
|
|
223
|
+
return self.groups_by_id[group_id]
|
|
257
224
|
|
|
258
225
|
# Language operations
|
|
259
226
|
def get_languages(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
260
|
-
"""Get all languages"""
|
|
261
227
|
self.validate_contest_id(contest_id)
|
|
262
|
-
return self.
|
|
228
|
+
return self.languages
|
|
263
229
|
|
|
264
230
|
def get_language(self, contest_id: str, language_id: str) -> Dict[str, Any]:
|
|
265
|
-
"""Get specific language"""
|
|
266
231
|
self.validate_contest_id(contest_id)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return lang
|
|
271
|
-
raise HTTPException(status_code=404, detail=f"Language {language_id} not found")
|
|
232
|
+
if language_id not in self.languages_by_id:
|
|
233
|
+
raise HTTPException(status_code=404, detail=f"Language {language_id} not found")
|
|
234
|
+
return self.languages_by_id[language_id]
|
|
272
235
|
|
|
273
236
|
# Judgement type operations
|
|
274
237
|
def get_judgement_types(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
275
|
-
"""Get all judgement types"""
|
|
276
238
|
self.validate_contest_id(contest_id)
|
|
277
|
-
return self.
|
|
239
|
+
return self.judgement_types
|
|
278
240
|
|
|
279
241
|
def get_judgement_type(self, contest_id: str, judgement_type_id: str) -> Dict[str, Any]:
|
|
280
|
-
"""Get specific judgement type"""
|
|
281
242
|
self.validate_contest_id(contest_id)
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
return jt
|
|
286
|
-
raise HTTPException(status_code=404, detail=f"Judgement type {judgement_type_id} not found")
|
|
243
|
+
if judgement_type_id not in self.judgement_types_by_id:
|
|
244
|
+
raise HTTPException(status_code=404, detail=f"Judgement type {judgement_type_id} not found")
|
|
245
|
+
return self.judgement_types_by_id[judgement_type_id]
|
|
287
246
|
|
|
288
247
|
# Submission operations
|
|
289
|
-
def get_submissions(
|
|
290
|
-
self, contest_id: str, team_id: Optional[str] = None, problem_id: Optional[str] = None
|
|
291
|
-
) -> List[Dict[str, Any]]:
|
|
292
|
-
"""Get all submissions, optionally filtered"""
|
|
248
|
+
def get_submissions(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
293
249
|
self.validate_contest_id(contest_id)
|
|
294
|
-
|
|
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
|
|
250
|
+
return self.submissions
|
|
303
251
|
|
|
304
252
|
def get_submission(self, contest_id: str, submission_id: str) -> Dict[str, Any]:
|
|
305
|
-
"""Get specific submission"""
|
|
306
253
|
self.validate_contest_id(contest_id)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
return submission
|
|
311
|
-
raise HTTPException(status_code=404, detail=f"Submission {submission_id} not found")
|
|
254
|
+
if submission_id not in self.submissions_by_id:
|
|
255
|
+
raise HTTPException(status_code=404, detail=f"Submission {submission_id} not found")
|
|
256
|
+
return self.submissions_by_id[submission_id]
|
|
312
257
|
|
|
313
258
|
# Judgement operations
|
|
314
259
|
def get_judgements(self, contest_id: str, submission_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
315
|
-
"""Get all judgements, optionally filtered by submission"""
|
|
316
260
|
self.validate_contest_id(contest_id)
|
|
317
|
-
judgements = self.load_json_file("judgements.json")
|
|
318
261
|
|
|
319
|
-
if submission_id:
|
|
320
|
-
|
|
262
|
+
if submission_id is not None:
|
|
263
|
+
if submission_id not in self.judgements_by_submission_id:
|
|
264
|
+
raise HTTPException(status_code=404, detail=f"Submission id not found: {submission_id}")
|
|
265
|
+
return self.judgements_by_submission_id[submission_id]
|
|
321
266
|
|
|
322
|
-
return judgements
|
|
267
|
+
return self.judgements
|
|
323
268
|
|
|
324
269
|
def get_judgement(self, contest_id: str, judgement_id: str) -> Dict[str, Any]:
|
|
325
|
-
"""Get specific judgement"""
|
|
326
270
|
self.validate_contest_id(contest_id)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
return judgement
|
|
331
|
-
raise HTTPException(status_code=404, detail=f"Judgement {judgement_id} not found")
|
|
271
|
+
if judgement_id not in self.judgements_by_id:
|
|
272
|
+
raise HTTPException(status_code=404, detail=f"Judgement {judgement_id} not found")
|
|
273
|
+
return self.judgements_by_id[judgement_id]
|
|
332
274
|
|
|
333
275
|
# Run operations
|
|
334
276
|
def get_runs(self, contest_id: str, judgement_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
335
|
-
"""Get all runs, optionally filtered by judgement"""
|
|
336
277
|
self.validate_contest_id(contest_id)
|
|
337
|
-
runs = self.load_json_file("runs.json")
|
|
338
278
|
|
|
339
|
-
if judgement_id:
|
|
340
|
-
|
|
279
|
+
if judgement_id is not None:
|
|
280
|
+
if judgement_id not in self.runs_by_judgement_id:
|
|
281
|
+
raise HTTPException(status_code=404, detail=f"Judgement id not found: {judgement_id}")
|
|
282
|
+
return self.runs_by_judgement_id[judgement_id]
|
|
341
283
|
|
|
342
|
-
return runs
|
|
284
|
+
return self.runs
|
|
343
285
|
|
|
344
286
|
def get_run(self, contest_id: str, run_id: str) -> Dict[str, Any]:
|
|
345
|
-
"""Get specific run"""
|
|
346
287
|
self.validate_contest_id(contest_id)
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
return run
|
|
351
|
-
raise HTTPException(status_code=404, detail=f"Run {run_id} not found")
|
|
288
|
+
if run_id not in self.runs_by_id:
|
|
289
|
+
raise HTTPException(status_code=404, detail=f"Run {run_id} not found")
|
|
290
|
+
return self.runs_by_id[run_id]
|
|
352
291
|
|
|
353
292
|
# Clarification operations
|
|
354
293
|
def get_clarifications(
|
|
355
294
|
self,
|
|
356
295
|
contest_id: str,
|
|
357
|
-
from_team_id: Optional[str] = None,
|
|
358
|
-
to_team_id: Optional[str] = None,
|
|
359
|
-
problem_id: Optional[str] = None,
|
|
360
296
|
) -> List[Dict[str, Any]]:
|
|
361
|
-
"""Get all clarifications, optionally filtered"""
|
|
362
297
|
self.validate_contest_id(contest_id)
|
|
363
|
-
|
|
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
|
|
298
|
+
return self.clarifications
|
|
385
299
|
|
|
386
300
|
def get_clarification(self, contest_id: str, clarification_id: str) -> Dict[str, Any]:
|
|
387
|
-
"""Get specific clarification"""
|
|
388
301
|
self.validate_contest_id(contest_id)
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
return clarification
|
|
393
|
-
raise HTTPException(status_code=404, detail=f"Clarification {clarification_id} not found")
|
|
302
|
+
if clarification_id not in self.clarifications_by_id:
|
|
303
|
+
raise HTTPException(status_code=404, detail=f"Clarification {clarification_id} not found")
|
|
304
|
+
return self.clarifications_by_id[clarification_id]
|
|
394
305
|
|
|
395
306
|
# Award operations
|
|
396
307
|
def get_awards(self, contest_id: str) -> List[Dict[str, Any]]:
|
|
397
|
-
"""Get all awards"""
|
|
398
308
|
self.validate_contest_id(contest_id)
|
|
399
|
-
return self.
|
|
309
|
+
return self.awards
|
|
400
310
|
|
|
401
311
|
def get_award(self, contest_id: str, award_id: str) -> Dict[str, Any]:
|
|
402
|
-
"""Get specific award"""
|
|
403
312
|
self.validate_contest_id(contest_id)
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
313
|
+
if award_id not in self.awards_by_id:
|
|
314
|
+
raise HTTPException(status_code=404, detail=f"Award {award_id} not found")
|
|
315
|
+
return self.awards_by_id[award_id]
|
|
316
|
+
|
|
317
|
+
# Event Feed operations
|
|
318
|
+
def get_event_feed(self, contest_id: str, since_token: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
319
|
+
self.validate_contest_id(contest_id)
|
|
320
|
+
|
|
321
|
+
if since_token is None:
|
|
322
|
+
return self.event_feed
|
|
323
|
+
|
|
324
|
+
idx = bisect.bisect_left(self.event_feed_tokens, since_token)
|
|
325
|
+
return self.event_feed[idx:]
|
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:
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
xcpcio/__init__.py,sha256=kjd6itqBRSQ-OT83qUJXHt81KQQDRUtaIuykzfaWXLM,121
|
|
2
|
+
xcpcio/__version__.py,sha256=95oCDxj_oHcOcxQzEfotvdWgY-p_3xsHZ2AaFTYVdR4,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=5nosGVwY-3Mq7eK9C5ZOMEJFozzzw_kLlpCraFbJ9wY,1225
|
|
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=o-A5wMDy5UC9wM71K3ruMmP4p4lSoU9B3XmDGPV0M7g,4203
|
|
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=608tPsK2bCUmqSalEFysC1zQf3FDDIeweT6PLLrOyLg,2515
|
|
22
|
+
xcpcio/ccs/api_server/routes/problems.py,sha256=2YFw_ODw-6L015UgpL1p-lLItgCgnqDamni6_9Qmbdk,2458
|
|
23
|
+
xcpcio/ccs/api_server/routes/runs.py,sha256=1L6fo1KPcDeKwOEkTxCwEetmyfNKY_Z0uxATqBJftIs,1046
|
|
24
|
+
xcpcio/ccs/api_server/routes/submissions.py,sha256=UKgDt9yv45Hfpr8P_8-2Po0jF4HvzG87zgkcb1YIKVc,2542
|
|
25
|
+
xcpcio/ccs/api_server/routes/teams.py,sha256=9nWz0tvMnccCNEzSN9DY-ZJtxlgaka9YSPe_5Gykbl8,2282
|
|
26
|
+
xcpcio/ccs/api_server/services/__init__.py,sha256=WQLNrLVomhtICl8HlFYaCoRewIHVZfUiiwrSBUOOWDg,171
|
|
27
|
+
xcpcio/ccs/api_server/services/contest_service.py,sha256=rMbAm7HVw5dHMTwp1tLAP0QhlCKlEMWQ65LZLZzjbfg,13210
|
|
28
|
+
xcpcio/ccs/model/__init__.py,sha256=cZE1q5JY-iHDEKZpsx0UZaMhH-23H4oAHaYOkW7dZ5s,43
|
|
29
|
+
xcpcio/ccs/model/model_2023_06/__init__.py,sha256=OmDQZqmigBpL64LXk5lIOGoQ3Uqis8-2z6qQpOO5aJc,167
|
|
30
|
+
xcpcio/ccs/model/model_2023_06/model.py,sha256=bVMDWpJTwPSpz1fHPxWrWerxCBIboH3LKVZpIZGQ2pY,15287
|
|
31
|
+
xcpcio-0.63.7.dist-info/METADATA,sha256=oWlZ8fA8EO1Ly_wvo8JUlxbeDmlV7PVKPKum4ydxzDk,1079
|
|
32
|
+
xcpcio-0.63.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
33
|
+
xcpcio-0.63.7.dist-info/entry_points.txt,sha256=qvzh8oDJxIHqTN-rg2lRN6xR99AqxbWnlAQI7uzDibI,59
|
|
34
|
+
xcpcio-0.63.7.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
|