xcpcio 0.63.7__py3-none-any.whl → 0.64.1__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.

@@ -5,321 +5,190 @@ Business logic layer for Contest API operations.
5
5
  Handles file reading, data validation, and business operations.
6
6
  """
7
7
 
8
- import bisect
9
- import json
10
- from collections import defaultdict
11
- from pathlib import Path
12
- from typing import Any, Dict, List, Optional, Union
8
+ import logging
9
+ from typing import Any, Dict, List, Optional
13
10
 
14
11
  from fastapi import HTTPException
15
12
 
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
16
 
17
- class ContestService:
18
- """Service class for contest-related operations"""
19
-
20
- def __init__(self, contest_package_dir: Path):
21
- """
22
- Initialize the contest service.
23
-
24
- Args:
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 _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
17
+ logger = logging.getLogger(__name__)
39
18
 
40
- def _load_indexes(self) -> None:
41
- """Load and index commonly accessed data for faster lookups"""
42
- self.access = self.load_json_file("access.json")
43
19
 
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}
70
-
71
- self.organizations = self.load_json_file("organizations.json")
72
- self.organizations_by_id = {org["id"]: org for org in self.organizations}
73
-
74
- self.problems = self.load_json_file("problems.json")
75
- self.problems_by_id = {problem["id"]: problem for problem in self.problems}
76
-
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")
80
-
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]
89
-
90
- def load_json_file(self, filepath: str) -> Union[Dict[str, Any], List[Any]]:
91
- """
92
- Load JSON data from contest package directory.
93
-
94
- Args:
95
- filepath: Relative path to JSON file within contest package
96
-
97
- Returns:
98
- Parsed JSON data
99
-
100
- Raises:
101
- HTTPException: If file not found or invalid JSON
102
- """
103
-
104
- full_path = self.contest_package_dir / filepath
105
- try:
106
- with open(full_path, "r", encoding="utf-8") as f:
107
- return json.load(f)
108
- except FileNotFoundError:
109
- raise HTTPException(status_code=404, detail=f"File not found: {filepath}")
110
- except json.JSONDecodeError as e:
111
- raise HTTPException(status_code=500, detail=f"Invalid JSON in file {filepath}: {e}")
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
-
126
- def get_contest_id(self) -> str:
127
- """
128
- Get contest ID from contest.json.
129
-
130
- Returns:
131
- Contest ID string
132
- """
133
- contest_data = self.load_json_file("contest.json")
134
- return contest_data.get("id", "unknown")
135
-
136
- def validate_contest_id(self, contest_id: str) -> None:
137
- """
138
- Validate that the provided contest ID matches the expected one.
20
+ class ContestService:
21
+ """Service class for contest-related operations"""
139
22
 
140
- Args:
141
- contest_id: Contest ID to validate
23
+ def __init__(self, reader_dict: Dict[str, BaseCCSReader]):
24
+ self.reader_dict = reader_dict
142
25
 
143
- Raises:
144
- HTTPException: If contest ID doesn't match
145
- """
146
- expected_id = self.get_contest_id()
147
- if contest_id != expected_id:
26
+ def _get_reader(self, contest_id: str) -> BaseCCSReader:
27
+ if contest_id not in self.reader_dict:
148
28
  raise HTTPException(status_code=404, detail=f"Contest {contest_id} not found")
29
+ return self.reader_dict[contest_id]
149
30
 
150
31
  # API Information
151
32
  def get_api_info(self) -> Dict[str, Any]:
152
- return self.api_info
33
+ return {
34
+ "version": "2023-06",
35
+ "version_url": "https://ccs-specs.icpc.io/2023-06/contest_api",
36
+ "name": "XCPCIO",
37
+ "provider": {
38
+ "name": "XCPCIO",
39
+ "version": __version__,
40
+ },
41
+ }
153
42
 
154
43
  def get_access(self, contest_id: str) -> Dict[str, Any]:
155
- self.validate_contest_id(contest_id)
156
- return self.access
44
+ reader = self._get_reader(contest_id)
45
+ return reader.get_access()
157
46
 
158
47
  # Account operations
159
48
  def get_accounts(self, contest_id: str) -> List[Dict[str, Any]]:
160
- self.validate_contest_id(contest_id)
161
- return self.accounts
49
+ reader = self._get_reader(contest_id)
50
+ return reader.get_accounts()
162
51
 
163
52
  def get_account(self, contest_id: str, account_id: str) -> Dict[str, Any]:
164
- self.validate_contest_id(contest_id)
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]
53
+ reader = self._get_reader(contest_id)
54
+ return reader.get_account(account_id)
168
55
 
169
56
  # Contest operations
170
57
  def get_contests(self) -> List[Dict[str, Any]]:
171
- return [self.contest]
58
+ return [reader.get_contest() for reader in self.reader_dict.values()]
172
59
 
173
60
  def get_contest(self, contest_id: str) -> Dict[str, Any]:
174
- self.validate_contest_id(contest_id)
175
- return self.contest
61
+ reader = self._get_reader(contest_id)
62
+ return reader.get_contest()
176
63
 
177
64
  def get_contest_state(self, contest_id: str) -> Dict[str, Any]:
178
- self.validate_contest_id(contest_id)
179
- return self.contest_state
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()
180
75
 
181
76
  # Problem operations
182
77
  def get_problems(self, contest_id: str) -> List[Dict[str, Any]]:
183
- self.validate_contest_id(contest_id)
184
- return self.problems
78
+ reader = self._get_reader(contest_id)
79
+ return reader.get_problems()
185
80
 
186
81
  def get_problem(self, contest_id: str, problem_id: str) -> Dict[str, Any]:
187
- self.validate_contest_id(contest_id)
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]
82
+ reader = self._get_reader(contest_id)
83
+ return reader.get_problem(problem_id)
84
+
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)
191
88
 
192
89
  # Team operations
193
90
  def get_teams(self, contest_id: str) -> List[Dict[str, Any]]:
194
- self.validate_contest_id(contest_id)
195
- return self.teams
91
+ reader = self._get_reader(contest_id)
92
+ return reader.get_teams()
196
93
 
197
94
  def get_team(self, contest_id: str, team_id: str) -> Dict[str, Any]:
198
- self.validate_contest_id(contest_id)
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]
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)
202
101
 
203
102
  # Organization operations
204
103
  def get_organizations(self, contest_id: str) -> List[Dict[str, Any]]:
205
- self.validate_contest_id(contest_id)
206
- return self.organizations
104
+ reader = self._get_reader(contest_id)
105
+ return reader.get_organizations()
207
106
 
208
107
  def get_organization(self, contest_id: str, organization_id: str) -> Dict[str, Any]:
209
- self.validate_contest_id(contest_id)
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]
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)
213
114
 
214
115
  # Group operations
215
116
  def get_groups(self, contest_id: str) -> List[Dict[str, Any]]:
216
- self.validate_contest_id(contest_id)
217
- return self.groups
117
+ reader = self._get_reader(contest_id)
118
+ return reader.get_groups()
218
119
 
219
120
  def get_group(self, contest_id: str, group_id: str) -> Dict[str, Any]:
220
- self.validate_contest_id(contest_id)
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]
121
+ reader = self._get_reader(contest_id)
122
+ return reader.get_group(group_id)
224
123
 
225
124
  # Language operations
226
125
  def get_languages(self, contest_id: str) -> List[Dict[str, Any]]:
227
- self.validate_contest_id(contest_id)
228
- return self.languages
126
+ reader = self._get_reader(contest_id)
127
+ return reader.get_languages()
229
128
 
230
129
  def get_language(self, contest_id: str, language_id: str) -> Dict[str, Any]:
231
- self.validate_contest_id(contest_id)
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]
130
+ reader = self._get_reader(contest_id)
131
+ return reader.get_language(language_id)
235
132
 
236
133
  # Judgement type operations
237
134
  def get_judgement_types(self, contest_id: str) -> List[Dict[str, Any]]:
238
- self.validate_contest_id(contest_id)
239
- return self.judgement_types
135
+ reader = self._get_reader(contest_id)
136
+ return reader.get_judgement_types()
240
137
 
241
138
  def get_judgement_type(self, contest_id: str, judgement_type_id: str) -> Dict[str, Any]:
242
- self.validate_contest_id(contest_id)
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]
139
+ reader = self._get_reader(contest_id)
140
+ return reader.get_judgement_type(judgement_type_id)
246
141
 
247
142
  # Submission operations
248
143
  def get_submissions(self, contest_id: str) -> List[Dict[str, Any]]:
249
- self.validate_contest_id(contest_id)
250
- return self.submissions
144
+ reader = self._get_reader(contest_id)
145
+ return reader.get_submissions()
251
146
 
252
147
  def get_submission(self, contest_id: str, submission_id: str) -> Dict[str, Any]:
253
- self.validate_contest_id(contest_id)
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]
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)
257
154
 
258
155
  # Judgement operations
259
156
  def get_judgements(self, contest_id: str, submission_id: Optional[str] = None) -> List[Dict[str, Any]]:
260
- self.validate_contest_id(contest_id)
261
-
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]
266
-
267
- return self.judgements
157
+ reader = self._get_reader(contest_id)
158
+ return reader.get_judgements(submission_id)
268
159
 
269
160
  def get_judgement(self, contest_id: str, judgement_id: str) -> Dict[str, Any]:
270
- self.validate_contest_id(contest_id)
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]
161
+ reader = self._get_reader(contest_id)
162
+ return reader.get_judgement(judgement_id)
274
163
 
275
164
  # Run operations
276
165
  def get_runs(self, contest_id: str, judgement_id: Optional[str] = None) -> List[Dict[str, Any]]:
277
- self.validate_contest_id(contest_id)
278
-
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]
283
-
284
- return self.runs
166
+ reader = self._get_reader(contest_id)
167
+ return reader.get_runs(judgement_id)
285
168
 
286
169
  def get_run(self, contest_id: str, run_id: str) -> Dict[str, Any]:
287
- self.validate_contest_id(contest_id)
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]
170
+ reader = self._get_reader(contest_id)
171
+ return reader.get_run(run_id)
291
172
 
292
173
  # Clarification operations
293
- def get_clarifications(
294
- self,
295
- contest_id: str,
296
- ) -> List[Dict[str, Any]]:
297
- self.validate_contest_id(contest_id)
298
- return self.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()
299
177
 
300
178
  def get_clarification(self, contest_id: str, clarification_id: str) -> Dict[str, Any]:
301
- self.validate_contest_id(contest_id)
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]
179
+ reader = self._get_reader(contest_id)
180
+ return reader.get_clarification(clarification_id)
305
181
 
306
182
  # Award operations
307
183
  def get_awards(self, contest_id: str) -> List[Dict[str, Any]]:
308
- self.validate_contest_id(contest_id)
309
- return self.awards
184
+ reader = self._get_reader(contest_id)
185
+ return reader.get_awards()
310
186
 
311
187
  def get_award(self, contest_id: str, award_id: str) -> Dict[str, Any]:
312
- self.validate_contest_id(contest_id)
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]
188
+ reader = self._get_reader(contest_id)
189
+ return reader.get_award(award_id)
316
190
 
317
191
  # Event Feed operations
318
192
  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:]
193
+ reader = self._get_reader(contest_id)
194
+ return reader.get_event_feed(since_token)
@@ -0,0 +1,3 @@
1
+ from . import types
2
+
3
+ __all__ = [types]
@@ -0,0 +1,9 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+
4
+
5
+ @dataclass
6
+ class FileAttr:
7
+ path: Path
8
+ media_type: str
9
+ name: str
File without changes
@@ -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