xcpcio 0.64.4__py3-none-any.whl → 0.65.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 CHANGED
@@ -1,4 +1,4 @@
1
1
  # This file is auto-generated by Hatchling. As such, do not:
2
2
  # - modify
3
3
  # - track in version control e.g. be sure to add to .gitignore
4
- __version__ = VERSION = '0.64.4'
4
+ __version__ = VERSION = '0.65.0'
@@ -0,0 +1,223 @@
1
+ import asyncio
2
+ import atexit
3
+ import hashlib
4
+ import logging
5
+ import shutil
6
+ import tarfile
7
+ import tempfile
8
+ import zipfile
9
+ from pathlib import Path
10
+ from typing import Optional
11
+
12
+ import click
13
+ import zstandard as zstd
14
+
15
+ from xcpcio import __version__
16
+ from xcpcio.clics.contest_archiver import APICredentials, ArchiveConfig, ContestArchiver
17
+
18
+
19
+ def setup_logging(level: str = "INFO"):
20
+ """Setup logging configuration"""
21
+ logging.basicConfig(
22
+ level=getattr(logging, level.upper()),
23
+ format="%(asctime)s [%(name)s] %(filename)s:%(lineno)d %(levelname)s: %(message)s",
24
+ )
25
+
26
+
27
+ def calculate_checksums(file_path: Path) -> dict:
28
+ """Calculate MD5, SHA1, SHA256, SHA512 checksums for a file"""
29
+ md5 = hashlib.md5()
30
+ sha1 = hashlib.sha1()
31
+ sha256 = hashlib.sha256()
32
+ sha512 = hashlib.sha512()
33
+
34
+ with open(file_path, "rb") as f:
35
+ while chunk := f.read(8192):
36
+ md5.update(chunk)
37
+ sha1.update(chunk)
38
+ sha256.update(chunk)
39
+ sha512.update(chunk)
40
+
41
+ return {
42
+ "md5": md5.hexdigest(),
43
+ "sha1": sha1.hexdigest(),
44
+ "sha256": sha256.hexdigest(),
45
+ "sha512": sha512.hexdigest(),
46
+ }
47
+
48
+
49
+ @click.command()
50
+ @click.version_option(__version__)
51
+ @click.option("--base-url", required=True, help="Base URL of the CCS API (e.g., https://example.com/api)")
52
+ @click.option("--contest-id", required=True, help="Contest ID to dump")
53
+ @click.option(
54
+ "--output",
55
+ "-o",
56
+ required=True,
57
+ type=click.Path(path_type=Path),
58
+ help="Output path: directory, or archive file (.zip, .tar.gz, .tar.zst)",
59
+ )
60
+ @click.option("--username", "-u", help="HTTP Basic Auth username")
61
+ @click.option("--password", "-p", help="HTTP Basic Auth password")
62
+ @click.option("--token", "-t", help="Bearer token for authentication")
63
+ @click.option("--no-files", is_flag=True, help="Skip downloading files")
64
+ @click.option("--no-event-feed", is_flag=True, help="Skip dump event-feed(large aggregated data)")
65
+ @click.option("--endpoints", "-e", multiple=True, help="Specific endpoints to dump (can be used multiple times)")
66
+ @click.option("--timeout", default=30, type=int, help="Request timeout in seconds")
67
+ @click.option("--max-concurrent", default=10, type=int, help="Max concurrent requests")
68
+ @click.option(
69
+ "--log-level",
70
+ default="INFO",
71
+ type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR"], case_sensitive=False),
72
+ help="Log level",
73
+ )
74
+ @click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging (same as --log-level DEBUG)")
75
+ def main(
76
+ base_url: str,
77
+ contest_id: str,
78
+ output: Path,
79
+ username: Optional[str],
80
+ password: Optional[str],
81
+ token: Optional[str],
82
+ no_files: bool,
83
+ no_event_feed: bool,
84
+ endpoints: tuple,
85
+ timeout: int,
86
+ max_concurrent: int,
87
+ log_level: str,
88
+ verbose: bool,
89
+ ):
90
+ """
91
+ Archive CCS Contest API data to contest package format.
92
+
93
+ This tool fetches contest data from a CCS API and organizes it into the standard
94
+ contest package format as specified by the CCS Contest Package specification.
95
+
96
+ Examples:
97
+
98
+ # Output to directory
99
+ clics-archiver --base-url https://domjudge/api --contest-id contest123 -o ./output -u admin -p secret
100
+
101
+ # Output to ZIP archive
102
+ clics-archiver --base-url https://domjudge/api --contest-id contest123 -o contest.zip --token abc123
103
+
104
+ # Output to tar.gz archive
105
+ clics-archiver --base-url https://domjudge/api --contest-id contest123 -o contest.tar.gz -u admin -p secret
106
+
107
+ # Output to tar.zst archive
108
+ clics-archiver --base-url https://domjudge/api --contest-id contest123 -o contest.tar.zst -u admin -p secret
109
+
110
+ # Only archive specific endpoints
111
+ clics-archiver --base-url https://domjudge/api --contest-id contest123 -o ./output -u admin -p secret -e teams -e problems
112
+
113
+ # Skip file downloads
114
+ clics-archiver --base-url https://domjudge/api --contest-id contest123 -o ./output --no-files
115
+ """
116
+
117
+ if verbose:
118
+ log_level = "DEBUG"
119
+
120
+ setup_logging(log_level)
121
+
122
+ if not (username and password) and not token:
123
+ click.echo("Warning: No authentication provided. Some endpoints may not be accessible.", err=True)
124
+
125
+ output_str = str(output)
126
+ is_archive = output_str.endswith((".zip", ".tar.gz", ".tar.zst"))
127
+ archive_format = None
128
+ temp_dir: Optional[Path] = None
129
+
130
+ def cleanup_temp_dir():
131
+ if temp_dir and temp_dir.exists():
132
+ click.echo(f"Cleaning up temporary directory: {temp_dir}")
133
+ try:
134
+ shutil.rmtree(temp_dir)
135
+ except Exception as e:
136
+ click.echo(f"Warning: Failed to cleanup temporary directory: {e}", err=True)
137
+
138
+ atexit.register(cleanup_temp_dir)
139
+
140
+ if is_archive:
141
+ if output_str.endswith(".zip"):
142
+ archive_format = "zip"
143
+ elif output_str.endswith(".tar.gz"):
144
+ archive_format = "tar.gz"
145
+ elif output_str.endswith(".tar.zst"):
146
+ archive_format = "tar.zst"
147
+
148
+ credentials = APICredentials(username=username, password=password, token=token)
149
+
150
+ if is_archive:
151
+ temp_dir = Path(tempfile.mkdtemp(prefix="clics_archive_"))
152
+ output_dir = temp_dir
153
+ else:
154
+ output_dir = output
155
+
156
+ config = ArchiveConfig(
157
+ base_url=base_url.rstrip("/"),
158
+ contest_id=contest_id,
159
+ credentials=credentials,
160
+ output_dir=output_dir,
161
+ include_files=not no_files,
162
+ endpoints=list(endpoints) if endpoints else None,
163
+ timeout=timeout,
164
+ max_concurrent=max_concurrent,
165
+ include_event_feed=not no_event_feed,
166
+ )
167
+
168
+ click.echo(f"Archiving contest '{contest_id}' from {base_url}")
169
+ if is_archive:
170
+ click.echo(f"Output archive: {output} (format: {archive_format})")
171
+ else:
172
+ click.echo(f"Output directory: {output}")
173
+ if config.endpoints:
174
+ click.echo(f"Endpoints: {', '.join(config.endpoints)}")
175
+
176
+ async def run_archive():
177
+ async with ContestArchiver(config) as archiver:
178
+ await archiver.dump_all()
179
+
180
+ try:
181
+ asyncio.run(run_archive())
182
+
183
+ if is_archive:
184
+ click.echo(f"Creating {archive_format} archive...")
185
+ output.parent.mkdir(parents=True, exist_ok=True)
186
+
187
+ if archive_format == "zip":
188
+ with zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED) as zipf:
189
+ for file_path in output_dir.rglob("*"):
190
+ if file_path.is_file():
191
+ arcname = file_path.relative_to(output_dir)
192
+ zipf.write(file_path, arcname)
193
+
194
+ elif archive_format == "tar.gz":
195
+ with tarfile.open(output, "w:gz") as tarf:
196
+ tarf.add(output_dir, arcname=".")
197
+
198
+ elif archive_format == "tar.zst":
199
+ with open(output, "wb") as f:
200
+ cctx = zstd.ZstdCompressor()
201
+ with cctx.stream_writer(f) as compressor:
202
+ with tarfile.open(fileobj=compressor, mode="w") as tarf:
203
+ tarf.add(output_dir, arcname=".")
204
+
205
+ click.echo(click.style(f"Contest package created successfully: {output}", fg="green"))
206
+ click.echo("\nChecksums:")
207
+ checksums = calculate_checksums(output)
208
+ click.echo(f" MD5: {checksums['md5']}")
209
+ click.echo(f" SHA1: {checksums['sha1']}")
210
+ click.echo(f" SHA256: {checksums['sha256']}")
211
+ click.echo(f" SHA512: {checksums['sha512']}")
212
+ else:
213
+ click.echo(click.style(f"Contest package created successfully in: {config.output_dir}", fg="green"))
214
+ except KeyboardInterrupt:
215
+ click.echo(click.style("Archive interrupted by user", fg="yellow"), err=True)
216
+ raise click.Abort()
217
+ except Exception as e:
218
+ click.echo(click.style(f"Error during archive: {e}", fg="red"), err=True)
219
+ raise click.ClickException(str(e))
220
+
221
+
222
+ if __name__ == "__main__":
223
+ main()
@@ -0,0 +1,138 @@
1
+ import atexit
2
+ import logging
3
+ import shutil
4
+ import tarfile
5
+ import tempfile
6
+ import zipfile
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import click
11
+ import zstandard as zstd
12
+
13
+ from xcpcio import __version__
14
+ from xcpcio.clics.api_server.server import ContestAPIServer
15
+
16
+
17
+ def setup_logging(level: str = "INFO"):
18
+ """Setup logging configuration"""
19
+ logging.basicConfig(
20
+ level=getattr(logging, level.upper()),
21
+ format="%(asctime)s [%(name)s] %(filename)s:%(lineno)d %(levelname)s: %(message)s",
22
+ )
23
+
24
+
25
+ def extract_archive(archive_path: Path, dest_dir: Path) -> None:
26
+ """Extract archive to destination directory"""
27
+ archive_str = str(archive_path)
28
+
29
+ if archive_str.endswith(".zip"):
30
+ with zipfile.ZipFile(archive_path, "r") as zipf:
31
+ zipf.extractall(dest_dir)
32
+ elif archive_str.endswith(".tar.gz"):
33
+ with tarfile.open(archive_path, "r:gz") as tarf:
34
+ tarf.extractall(dest_dir, filter="data")
35
+ elif archive_str.endswith(".tar.zst"):
36
+ with tempfile.NamedTemporaryFile(suffix=".tar") as tmp_tar:
37
+ with open(archive_path, "rb") as f:
38
+ dctx = zstd.ZstdDecompressor()
39
+ dctx.copy_stream(f, tmp_tar)
40
+ tmp_tar.seek(0)
41
+ with tarfile.open(fileobj=tmp_tar, mode="r") as tarf:
42
+ tarf.extractall(dest_dir, filter="data")
43
+ else:
44
+ raise ValueError(f"Unsupported archive format: {archive_path}")
45
+
46
+
47
+ @click.command()
48
+ @click.version_option(__version__)
49
+ @click.option(
50
+ "--contest-package",
51
+ "-p",
52
+ required=True,
53
+ type=click.Path(exists=True, path_type=Path),
54
+ help="Contest package directory or archive file (.zip, .tar.gz, .tar.zst)",
55
+ )
56
+ @click.option("--host", default="0.0.0.0", help="Host to bind to")
57
+ @click.option("--port", default=8000, type=int, help="Port to bind to")
58
+ @click.option(
59
+ "--log-level",
60
+ default="info",
61
+ type=click.Choice(["debug", "info", "warning", "error", "critical"], case_sensitive=False),
62
+ help="Log level",
63
+ )
64
+ @click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging (same as --log-level debug)")
65
+ def main(contest_package: Path, host: str, port: int, log_level: str, verbose: bool):
66
+ """
67
+ Start the Contest API Server.
68
+
69
+ Examples:
70
+
71
+ # Start server with contest directory
72
+ clics-server -p /path/to/contest
73
+
74
+ # Start server with archive file
75
+ clics-server -p /path/to/contest.zip
76
+ clics-server -p /path/to/contest.tar.gz
77
+ clics-server -p /path/to/contest.tar.zst
78
+
79
+ # Custom host and port
80
+ clics-server -p /path/to/contest --host 127.0.0.1 --port 9000
81
+ """
82
+ if verbose:
83
+ log_level = "debug"
84
+ setup_logging(log_level.upper())
85
+
86
+ temp_dir: Optional[Path] = None
87
+
88
+ def cleanup_temp_dir():
89
+ if temp_dir and temp_dir.exists():
90
+ click.echo(f"Cleaning up temporary directory: {temp_dir}")
91
+ try:
92
+ shutil.rmtree(temp_dir)
93
+ except Exception as e:
94
+ click.echo(f"Warning: Failed to cleanup temporary directory: {e}", err=True)
95
+
96
+ atexit.register(cleanup_temp_dir)
97
+
98
+ actual_contest_dir: Path = contest_package
99
+ is_archive = str(contest_package).endswith((".zip", ".tar.gz", ".tar.zst"))
100
+
101
+ if is_archive:
102
+ if not contest_package.is_file():
103
+ click.echo(f"Error: Archive file not found: {contest_package}", err=True)
104
+ raise click.Abort()
105
+
106
+ click.echo(f"Extracting archive: {contest_package}")
107
+ temp_dir = Path(tempfile.mkdtemp(prefix="clics_package_"))
108
+
109
+ try:
110
+ extract_archive(contest_package, temp_dir)
111
+ actual_contest_dir = temp_dir
112
+ click.echo(f"Archive extracted to: {temp_dir}")
113
+ except Exception as e:
114
+ click.echo(f"Error extracting archive: {e}", err=True)
115
+ raise click.Abort()
116
+ else:
117
+ if not contest_package.is_dir():
118
+ click.echo(f"Error: Contest directory not found: {contest_package}", err=True)
119
+ raise click.Abort()
120
+
121
+ click.echo("Starting Contest API Server...")
122
+ click.echo(f"Contest directory: {actual_contest_dir}")
123
+ click.echo(f"Host: {host}")
124
+ click.echo(f"Port: {port}")
125
+ click.echo(f"Log level: {log_level}")
126
+
127
+ try:
128
+ server = ContestAPIServer(actual_contest_dir)
129
+ server.run(host=host, port=port, log_level=log_level.lower())
130
+ except KeyboardInterrupt:
131
+ click.echo("\nServer stopped by user")
132
+ except Exception as e:
133
+ click.echo(f"Error starting server: {e}", err=True)
134
+ raise click.Abort()
135
+
136
+
137
+ if __name__ == "__main__":
138
+ main()
xcpcio/types.py CHANGED
@@ -61,9 +61,28 @@ SubmissionStatus = Literal[
61
61
  ImagePreset = Literal["ICPC", "CCPC", "HUNAN_CPC"]
62
62
  MedalPreset = Literal["ccpc", "icpc"]
63
63
  BannerMode = Literal["ONLY_BANNER", "ALL"]
64
+ Lang = Literal["en", "zh-CN"]
64
65
  DateTimeISO8601String = str
65
66
 
66
67
 
68
+ class I18NStringSet(BaseModel):
69
+ fallback: Optional[str] = None
70
+ fallback_lang: Optional[Lang] = None
71
+ texts: Optional[Dict[Lang, str]] = None
72
+
73
+
74
+ Text = Union[str, I18NStringSet]
75
+
76
+
77
+ class Person(BaseModel):
78
+ name: Text
79
+ cf_id: Optional[str] = None
80
+ icpc_id: Optional[str] = None
81
+
82
+
83
+ Persons = List[Person]
84
+
85
+
67
86
  class Image(BaseModel):
68
87
  url: Optional[str] = None
69
88
  base64: Optional[str] = None
@@ -71,20 +90,33 @@ class Image(BaseModel):
71
90
  preset: Optional[ImagePreset] = None
72
91
 
73
92
 
74
- class Color(BaseModel):
93
+ class BalloonColor(BaseModel):
75
94
  color: str
76
95
  background_color: str
77
96
 
78
97
 
79
- class Reaction(BaseModel):
98
+ class Problem(BaseModel):
99
+ id: str
100
+ label: str
101
+ name: Optional[Text] = None
102
+ time_limit: Optional[str] = None
103
+ memory_limit: Optional[str] = None
104
+ balloon_color: Optional[BalloonColor] = None
105
+
106
+
107
+ Problems = List[Problem]
108
+
109
+
110
+ class SubmissionReaction(BaseModel):
80
111
  url: Optional[str] = None
81
112
 
82
113
 
83
114
  class Submission(BaseModel):
84
- id: str = ""
115
+ id: Optional[str] = None
116
+ submission_id: Optional[str] = None
85
117
 
86
118
  team_id: str = ""
87
- problem_id: int = 0
119
+ problem_id: Union[int, str] = 0
88
120
  timestamp: int = 0 # unit: seconds
89
121
  status: SubmissionStatus = constants.SUBMISSION_STATUS_UNKNOWN
90
122
 
@@ -93,7 +125,7 @@ class Submission(BaseModel):
93
125
 
94
126
  is_ignore: Optional[bool] = None
95
127
 
96
- reaction: Optional[Reaction] = None
128
+ reaction: Optional[SubmissionReaction] = None
97
129
 
98
130
 
99
131
  class Submissions(RootModel[List[Submission]]):
@@ -102,14 +134,14 @@ class Submissions(RootModel[List[Submission]]):
102
134
 
103
135
  class Team(BaseModel):
104
136
  id: str = ""
105
- name: str = ""
137
+ name: Text = ""
106
138
 
107
139
  organization: str = ""
108
140
  group: List[str] = Field(default_factory=list)
109
141
  tag: List[str] = Field(default_factory=list)
110
142
 
111
- coach: Optional[str] = None
112
- members: Optional[List[str]] = None
143
+ coaches: Optional[Union[Text, List[Text], Persons]] = None
144
+ members: Optional[Union[Text, List[Text], Persons]] = None
113
145
 
114
146
  badge: Optional[Image] = None
115
147
 
@@ -139,7 +171,7 @@ class ContestOptions(BaseModel):
139
171
 
140
172
 
141
173
  class Contest(BaseModel):
142
- contest_name: str = ""
174
+ contest_name: Text = ""
143
175
 
144
176
  start_time: Union[int, DateTimeISO8601String] = 0
145
177
  end_time: Union[int, DateTimeISO8601String] = 0
@@ -149,15 +181,17 @@ class Contest(BaseModel):
149
181
  frozen_time: int = 60 * 60 # unit: seconds
150
182
  unfrozen_time: int = 0x3F3F3F3F3F3F3F3F
151
183
 
184
+ problems: Optional[Problems] = None
152
185
  problem_quantity: int = 0
153
186
  problem_id: List[str] = Field(default_factory=list)
187
+ balloon_color: Optional[List[BalloonColor]] = None
154
188
 
155
- organization: str = "School"
156
189
  status_time_display: Optional[Dict[str, bool]] = constants.FULL_STATUS_TIME_DISPLAY
157
190
 
158
191
  badge: Optional[str] = None
192
+ organization: str = "School"
193
+
159
194
  medal: Optional[Union[Dict[str, Dict[str, int]], MedalPreset]] = None
160
- balloon_color: Optional[List[Color]] = None
161
195
 
162
196
  group: Optional[Dict[str, str]] = None
163
197
  tag: Optional[Dict[str, str]] = None
@@ -171,7 +205,7 @@ class Contest(BaseModel):
171
205
 
172
206
  options: ContestOptions = Field(default_factory=ContestOptions)
173
207
 
174
- def append_balloon_color(self, color: Color):
208
+ def append_balloon_color(self, color: BalloonColor):
175
209
  if self.balloon_color is None:
176
210
  self.balloon_color = []
177
211
  self.balloon_color.append(color)
@@ -183,20 +217,20 @@ class Contest(BaseModel):
183
217
 
184
218
  def fill_balloon_color(self):
185
219
  default_balloon_color_list = [
186
- Color(background_color="rgba(189, 14, 14, 0.7)", color="#fff"),
187
- Color(background_color="rgba(149, 31, 217, 0.7)", color="#fff"),
188
- Color(background_color="rgba(16, 32, 96, 0.7)", color="#fff"),
189
- Color(background_color="rgba(38, 185, 60, 0.7)", color="#000"),
190
- Color(background_color="rgba(239, 217, 9, 0.7)", color="#000"),
191
- Color(background_color="rgba(243, 88, 20, 0.7)", color="#fff"),
192
- Color(background_color="rgba(12, 76, 138, 0.7)", color="#fff"),
193
- Color(background_color="rgba(156, 155, 155, 0.7)", color="#000"),
194
- Color(background_color="rgba(4, 154, 115, 0.7)", color="#000"),
195
- Color(background_color="rgba(159, 19, 236, 0.7)", color="#fff"),
196
- Color(background_color="rgba(42, 197, 202, 0.7)", color="#000"),
197
- Color(background_color="rgba(142, 56, 54, 0.7)", color="#fff"),
198
- Color(background_color="rgba(144, 238, 144, 0.7)", color="#000"),
199
- Color(background_color="rgba(77, 57, 0, 0.7)", color="#fff"),
220
+ BalloonColor(background_color="rgba(189, 14, 14, 0.7)", color="#fff"),
221
+ BalloonColor(background_color="rgba(149, 31, 217, 0.7)", color="#fff"),
222
+ BalloonColor(background_color="rgba(16, 32, 96, 0.7)", color="#fff"),
223
+ BalloonColor(background_color="rgba(38, 185, 60, 0.7)", color="#000"),
224
+ BalloonColor(background_color="rgba(239, 217, 9, 0.7)", color="#000"),
225
+ BalloonColor(background_color="rgba(243, 88, 20, 0.7)", color="#fff"),
226
+ BalloonColor(background_color="rgba(12, 76, 138, 0.7)", color="#fff"),
227
+ BalloonColor(background_color="rgba(156, 155, 155, 0.7)", color="#000"),
228
+ BalloonColor(background_color="rgba(4, 154, 115, 0.7)", color="#000"),
229
+ BalloonColor(background_color="rgba(159, 19, 236, 0.7)", color="#fff"),
230
+ BalloonColor(background_color="rgba(42, 197, 202, 0.7)", color="#000"),
231
+ BalloonColor(background_color="rgba(142, 56, 54, 0.7)", color="#fff"),
232
+ BalloonColor(background_color="rgba(144, 238, 144, 0.7)", color="#000"),
233
+ BalloonColor(background_color="rgba(77, 57, 0, 0.7)", color="#fff"),
200
234
  ]
201
235
 
202
236
  self.balloon_color = default_balloon_color_list[: self.problem_quantity]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xcpcio
3
- Version: 0.64.4
3
+ Version: 0.65.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
@@ -1,10 +1,12 @@
1
1
  xcpcio/__init__.py,sha256=NB6wpVr5JUrOx2vLIQSVtYuCz0d7kNFE39TSQlvoENk,125
2
- xcpcio/__version__.py,sha256=YJ36rfIhlC5_Ph8FNpZ2YYGKo1_DImruLCg7WIClnWg,172
2
+ xcpcio/__version__.py,sha256=uSD0tE8vcfG2kBfQmbthAcsvXUvzg1w2cXDA2Bunbco,172
3
3
  xcpcio/constants.py,sha256=MjpAgNXiBlUsx1S09m7JNT-nekNDR-aE6ggvGL3fg0I,2297
4
- xcpcio/types.py,sha256=AkYby2haJgxwtozlgaPMG2ryAZdvsSc3sH-p6qXcM4g,6575
4
+ xcpcio/types.py,sha256=CJYZc3RIWKYFbXIpyqt8fZANqJUMR66vLPno6nFyegA,7466
5
5
  xcpcio/api/__init__.py,sha256=B9gLdAlR3FD7070cvAC5wAwMy3iV63I8hh4mUrnrKpk,274
6
6
  xcpcio/api/client.py,sha256=BuzH8DbJYudJ-Kne-2XziLW__B_7iEqElJ4n2SGZCoY,2374
7
7
  xcpcio/api/models.py,sha256=_dChApnIHVNN3hEL7mR5zonq8IUcxW_h7z1kUz6reSs,772
8
+ xcpcio/app/clics_archiver.py,sha256=wd7zkbq2oyWfkS09w0f3KBr32F2yCcTMx36H5HFMgRg,7995
9
+ xcpcio/app/clics_server.py,sha256=-zUixNf08yICT2sry23h72ZrEm6NPb30bH5AHCXZlBM,4623
8
10
  xcpcio/clics/__init__.py,sha256=coTZiqxzXesn2SYmI2ZCsDZW6XaFi_6p-PFozZ4dfl4,150
9
11
  xcpcio/clics/clics_api_client.py,sha256=jQiOYPNZlYs_cOmQIbp-QovWVMYcmT1yo-33SWoyAn0,7966
10
12
  xcpcio/clics/contest_archiver.py,sha256=66a4YTqHqSoHMe7dFTXo4OrdK40AUnavo0ICSd9UNGo,9911
@@ -38,7 +40,7 @@ xcpcio/clics/model/model_2023_06/model.py,sha256=bVMDWpJTwPSpz1fHPxWrWerxCBIboH3
38
40
  xcpcio/clics/reader/__init__.py,sha256=Nfi78X8J1tJPh7WeSRPLMRUprlS2JYelYJHW4DfyJ7U,162
39
41
  xcpcio/clics/reader/contest_package_reader.py,sha256=0wIzQp4zzdaB10zMY4WALLIeoXcMuhMJ6nRrfKxWhgw,14179
40
42
  xcpcio/clics/reader/interface.py,sha256=lK2JXU1n8GJ4PecXnfFBijMaCVLYk404e4QwV_Ti2Hk,3918
41
- xcpcio-0.64.4.dist-info/METADATA,sha256=AFDUMdnTKGrH97RAEfoBBaXg2FYjWe97bFTYU-dSpIc,2233
42
- xcpcio-0.64.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
43
- xcpcio-0.64.4.dist-info/entry_points.txt,sha256=vJ7vcfzL_j7M1jvq--10ENgAJNB15O6WVGjwiRWAgas,96
44
- xcpcio-0.64.4.dist-info/RECORD,,
43
+ xcpcio-0.65.0.dist-info/METADATA,sha256=8VaTk5-ea4wUe-gBv4j2GfrErvY-Yk8l5ey6ouzMHkI,2233
44
+ xcpcio-0.65.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
+ xcpcio-0.65.0.dist-info/entry_points.txt,sha256=JYkvmmxKFWv0EBU6Ys65XsjkOO02KGlzASau0GX9TQ8,110
46
+ xcpcio-0.65.0.dist-info/RECORD,,
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ clics-archiver = xcpcio.app.clics_archiver:main
3
+ clics-server = xcpcio.app.clics_server:main
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- clics-archiver = app.clics_archiver:main
3
- clics-server = app.clics_server:main