xcpcio 0.58.2__tar.gz

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-0.58.2/PKG-INFO ADDED
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: xcpcio
3
+ Version: 0.58.2
4
+ Summary: xcpcio python lib
5
+ Author-email: Dup4 <hi@dup4.com>
6
+ License-Expression: MIT
7
+ Project-URL: homepage, https://github.com/xcpcio/xcpcio
8
+ Project-URL: documentation, https://github.com/xcpcio/xcpcio
9
+ Project-URL: repository, https://github.com/xcpcio/xcpcio
10
+ Keywords: xcpcio
11
+ Classifier: Topic :: Software Development :: Build Tools
12
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: pydantic>=2.11.7
20
+
21
+ # xcpcio-python
@@ -0,0 +1 @@
1
+ # xcpcio-python
@@ -0,0 +1,29 @@
1
+ [project]
2
+ name = "xcpcio"
3
+ version = "0.58.2"
4
+ description = "xcpcio python lib"
5
+ readme = "README.md"
6
+ authors = [ { name = "Dup4", email = "hi@dup4.com" } ]
7
+ license = "MIT"
8
+ keywords = [ "xcpcio" ]
9
+ classifiers = [
10
+ "Topic :: Software Development :: Build Tools",
11
+ "Topic :: Software Development :: Libraries :: Python Modules",
12
+ "Programming Language :: Python :: 3",
13
+ "Programming Language :: Python :: 3.11",
14
+ "Programming Language :: Python :: 3.12",
15
+ "Programming Language :: Python :: 3.13",
16
+ ]
17
+ requires-python = ">=3.11"
18
+ dependencies = [ "pydantic>=2.11.7", ]
19
+
20
+ [project.urls]
21
+ homepage = "https://github.com/xcpcio/xcpcio"
22
+ documentation = "https://github.com/xcpcio/xcpcio"
23
+ repository = "https://github.com/xcpcio/xcpcio"
24
+
25
+ [tool.ruff]
26
+ line-length = 120
27
+
28
+ [dependency-groups]
29
+ dev = [ "pytest>=8.4.2", "ruff>=0.4.0", ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,229 @@
1
+
2
+
3
+ from xcpcio import constants
4
+ from xcpcio.types import Color, Contest, ContestOptions, Image
5
+
6
+
7
+ class TestContest:
8
+ """Test cases for Contest Pydantic model"""
9
+
10
+ def test_contest_creation_defaults(self):
11
+ """Test Contest creation with default values"""
12
+ contest = Contest()
13
+
14
+ assert contest.contest_name == ""
15
+ assert contest.start_time == 0
16
+ assert contest.end_time == 0
17
+ assert contest.frozen_time == 60 * 60 # 1 hour
18
+ assert contest.unfrozen_time == 0x3F3F3F3F3F3F3F3F
19
+ assert contest.penalty == 20 * 60 # 20 minutes
20
+ assert contest.problem_quantity == 0
21
+ assert contest.problem_id == []
22
+ assert contest.organization == "School"
23
+ assert contest.medal is None
24
+ assert contest.balloon_color is None
25
+ assert contest.logo is None
26
+ assert contest.banner is None
27
+ assert contest.banner_mode is None
28
+ assert contest.badge is None
29
+ assert contest.group is None
30
+ assert contest.tag is None
31
+ assert contest.board_link is None
32
+ assert contest.version is None
33
+
34
+ # Check default values
35
+ assert contest.status_time_display == constants.FULL_STATUS_TIME_DISPLAY
36
+ assert isinstance(contest.options, ContestOptions)
37
+
38
+ def test_contest_creation_with_values(self):
39
+ """Test Contest creation with provided values"""
40
+ contest_options = ContestOptions(
41
+ calculation_of_penalty=constants.CALCULATION_OF_PENALTY_IN_SECONDS,
42
+ has_reaction_videos=True
43
+ )
44
+
45
+ contest = Contest(
46
+ contest_name="ICPC World Finals 2024",
47
+ start_time=1234567890,
48
+ end_time=1234567890 + 5 * 60 * 60, # 5 hours later
49
+ frozen_time=60 * 60, # 1 hour before end
50
+ penalty=20 * 60, # 20 minutes
51
+ problem_quantity=12,
52
+ problem_id=["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"],
53
+ organization="ICPC",
54
+ medal="icpc", # Use Literal value
55
+ options=contest_options
56
+ )
57
+
58
+ assert contest.contest_name == "ICPC World Finals 2024"
59
+ assert contest.start_time == 1234567890
60
+ assert contest.end_time == 1234567890 + 5 * 60 * 60
61
+ assert contest.problem_quantity == 12
62
+ assert len(contest.problem_id) == 12
63
+ assert contest.problem_id[0] == "A"
64
+ assert contest.problem_id[-1] == "L"
65
+ assert contest.organization == "ICPC"
66
+ assert contest.medal == "icpc"
67
+ assert contest.options == contest_options
68
+
69
+ def test_contest_serialization(self):
70
+ """Test Contest serialization and deserialization"""
71
+ contest = Contest(
72
+ contest_name="Test Contest",
73
+ start_time=1000000000,
74
+ end_time=1000000000 + 60 * 60 * 5,
75
+ problem_quantity=5,
76
+ problem_id=["A", "B", "C", "D", "E"],
77
+ organization="Test Org"
78
+ )
79
+
80
+ # Test model_dump
81
+ contest_dict = contest.model_dump()
82
+ assert contest_dict["contest_name"] == "Test Contest"
83
+ assert contest_dict["start_time"] == 1000000000
84
+ assert contest_dict["problem_quantity"] == 5
85
+ assert contest_dict["organization"] == "Test Org"
86
+
87
+ # Test JSON round-trip
88
+ contest_json = contest.model_dump_json()
89
+ reconstructed_contest = Contest.model_validate_json(contest_json)
90
+ assert reconstructed_contest == contest
91
+
92
+ def test_contest_with_colors_and_images(self):
93
+ """Test Contest with balloon colors, logo, and banner"""
94
+ colors = [
95
+ Color(color="#fff", background_color="rgba(255, 0, 0, 0.7)"),
96
+ Color(color="#000", background_color="rgba(0, 255, 0, 0.7)")
97
+ ]
98
+ logo = Image(url="https://example.com/logo.png", type="png")
99
+ banner = Image(url="https://example.com/banner.jpg", type="jpg")
100
+
101
+ contest = Contest(
102
+ contest_name="Contest with Media",
103
+ problem_quantity=2,
104
+ balloon_color=colors,
105
+ logo=logo,
106
+ banner=banner,
107
+ banner_mode="ALL" # Use correct Literal value
108
+ )
109
+
110
+ assert len(contest.balloon_color) == 2
111
+ assert contest.balloon_color[0].color == "#fff"
112
+ assert contest.balloon_color[1].background_color == "rgba(0, 255, 0, 0.7)"
113
+ assert contest.logo.url == "https://example.com/logo.png"
114
+ assert contest.banner.url == "https://example.com/banner.jpg"
115
+ assert contest.banner_mode == "ALL"
116
+
117
+ def test_append_balloon_color(self):
118
+ """Test append_balloon_color method"""
119
+ contest = Contest()
120
+
121
+ # Initially no colors
122
+ assert contest.balloon_color is None
123
+
124
+ # Add first color
125
+ red_color = Color(color="#fff", background_color="red")
126
+ contest.append_balloon_color(red_color)
127
+
128
+ assert contest.balloon_color is not None
129
+ assert len(contest.balloon_color) == 1
130
+ assert contest.balloon_color[0] == red_color
131
+
132
+ # Add second color
133
+ blue_color = Color(color="#fff", background_color="blue")
134
+ contest.append_balloon_color(blue_color)
135
+
136
+ assert len(contest.balloon_color) == 2
137
+ assert contest.balloon_color[1] == blue_color
138
+
139
+ def test_fill_problem_id(self):
140
+ """Test fill_problem_id method"""
141
+ contest = Contest(problem_quantity=5)
142
+
143
+ # Initially empty
144
+ assert contest.problem_id == []
145
+
146
+ # Fill with A-E
147
+ contest.fill_problem_id()
148
+
149
+ assert len(contest.problem_id) == 5
150
+ assert contest.problem_id == ["A", "B", "C", "D", "E"]
151
+
152
+ # Test with larger quantity
153
+ contest.problem_quantity = 10
154
+ contest.fill_problem_id()
155
+
156
+ assert len(contest.problem_id) == 10
157
+ assert contest.problem_id == ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
158
+
159
+ def test_fill_balloon_color(self):
160
+ """Test fill_balloon_color method"""
161
+ contest = Contest(problem_quantity=3)
162
+
163
+ # Initially no colors
164
+ assert contest.balloon_color is None
165
+
166
+ # Fill with default colors
167
+ contest.fill_balloon_color()
168
+
169
+ assert contest.balloon_color is not None
170
+ assert len(contest.balloon_color) == 3
171
+ assert all(isinstance(color, Color) for color in contest.balloon_color)
172
+
173
+ # Check first few default colors
174
+ assert contest.balloon_color[0].background_color == "rgba(189, 14, 14, 0.7)"
175
+ assert contest.balloon_color[0].color == "#fff"
176
+ assert contest.balloon_color[1].background_color == "rgba(149, 31, 217, 0.7)"
177
+ assert contest.balloon_color[1].color == "#fff"
178
+
179
+ def test_contest_round_trip_serialization(self):
180
+ """Test complete round-trip serialization with complex data"""
181
+ # Create a contest with all features
182
+ contest = Contest(
183
+ contest_name="Complex Contest",
184
+ start_time=1234567890,
185
+ end_time=1234567890 + 5 * 60 * 60,
186
+ problem_quantity=3,
187
+ organization="Complex Org",
188
+ medal="ccpc" # Use preset instead of dict
189
+ )
190
+
191
+ # Add problem IDs and colors
192
+ contest.fill_problem_id()
193
+ contest.fill_balloon_color()
194
+
195
+ # Add logo
196
+ contest.logo = Image(url="https://example.com/logo.png")
197
+
198
+ # Test serialization
199
+ contest_json = contest.model_dump_json()
200
+ reconstructed_contest = Contest.model_validate_json(contest_json)
201
+
202
+ # Verify all data is preserved
203
+ assert reconstructed_contest.contest_name == contest.contest_name
204
+ assert reconstructed_contest.start_time == contest.start_time
205
+ assert reconstructed_contest.end_time == contest.end_time
206
+ assert reconstructed_contest.problem_id == contest.problem_id
207
+ assert reconstructed_contest.medal == contest.medal
208
+ assert len(reconstructed_contest.balloon_color) == len(contest.balloon_color)
209
+ assert reconstructed_contest.logo.url == contest.logo.url
210
+
211
+ def test_custom_group_and_status_display(self):
212
+ """Test contest with custom group and status display"""
213
+ custom_group = {"team_a": "Team A", "team_b": "Team B"}
214
+ custom_status = {"show_penalty": True, "show_time": False}
215
+
216
+ contest = Contest(
217
+ group=custom_group,
218
+ status_time_display=custom_status
219
+ )
220
+
221
+ # Custom values should override defaults
222
+ assert contest.group == custom_group
223
+ assert contest.status_time_display == custom_status
224
+
225
+ # Test serialization preserves custom values
226
+ contest_json = contest.model_dump_json()
227
+ reconstructed = Contest.model_validate_json(contest_json)
228
+ assert reconstructed.group == custom_group
229
+ assert reconstructed.status_time_display == custom_status
@@ -0,0 +1,227 @@
1
+ import json
2
+
3
+ import pytest
4
+
5
+ from xcpcio import constants
6
+ from xcpcio.types import Reaction, Submission, Submissions
7
+
8
+
9
+ class TestSubmission:
10
+ """Test cases for Submission Pydantic model"""
11
+
12
+ def test_submission_creation_defaults(self):
13
+ """Test Submission creation with default values"""
14
+ submission = Submission()
15
+ assert submission.id == ""
16
+ assert submission.team_id == ""
17
+ assert submission.problem_id == 0
18
+ assert submission.timestamp == 0
19
+ assert submission.status == constants.SUBMISSION_STATUS_UNKNOWN
20
+ assert submission.time is None
21
+ assert submission.language is None
22
+ assert submission.is_ignore is None
23
+ assert submission.reaction is None
24
+
25
+ def test_submission_creation_with_values(self):
26
+ """Test Submission creation with provided values"""
27
+ reaction = Reaction(url="https://reaction.com/video.mp4")
28
+ submission = Submission(
29
+ id="sub_001",
30
+ status=constants.SUBMISSION_STATUS_ACCEPTED,
31
+ team_id="team001",
32
+ problem_id=1,
33
+ timestamp=1234567890,
34
+ time=120,
35
+ language="Python",
36
+ is_ignore=False,
37
+ reaction=reaction,
38
+ )
39
+
40
+ assert submission.id == "sub_001"
41
+ assert submission.status == constants.SUBMISSION_STATUS_ACCEPTED
42
+ assert submission.team_id == "team001"
43
+ assert submission.problem_id == 1
44
+ assert submission.timestamp == 1234567890
45
+ assert submission.time == 120
46
+ assert submission.language == "Python"
47
+ assert not submission.is_ignore
48
+ assert submission.reaction == reaction
49
+ assert submission.reaction.url == "https://reaction.com/video.mp4"
50
+
51
+ def test_submission_serialization(self):
52
+ """Test Submission serialization and deserialization"""
53
+ submission = Submission(
54
+ id="sub_002",
55
+ status=constants.SUBMISSION_STATUS_WRONG_ANSWER,
56
+ team_id="team002",
57
+ problem_id=3,
58
+ timestamp=1234567891,
59
+ time=300,
60
+ language="C++",
61
+ )
62
+
63
+ # Test model_dump
64
+ submission_dict = submission.model_dump()
65
+ assert submission_dict["id"] == "sub_002"
66
+ assert submission_dict["status"] == constants.SUBMISSION_STATUS_WRONG_ANSWER
67
+ assert submission_dict["team_id"] == "team002"
68
+ assert submission_dict["problem_id"] == 3
69
+ assert submission_dict["timestamp"] == 1234567891
70
+ assert submission_dict["time"] == 300
71
+ assert submission_dict["language"] == "C++"
72
+ assert submission_dict["is_ignore"] is None
73
+ assert submission_dict["reaction"] is None
74
+
75
+ # Test JSON round-trip
76
+ submission_json = submission.model_dump_json()
77
+ reconstructed_submission = Submission.model_validate_json(submission_json)
78
+ assert reconstructed_submission == submission
79
+
80
+ def test_submission_with_reaction_serialization(self):
81
+ """Test Submission with Reaction serialization"""
82
+ reaction = Reaction(url="https://example.com/reaction.mp4")
83
+ submission = Submission(
84
+ id="sub_003",
85
+ status=constants.SUBMISSION_STATUS_ACCEPTED,
86
+ team_id="team003",
87
+ problem_id=2,
88
+ timestamp=1234567892,
89
+ reaction=reaction,
90
+ )
91
+
92
+ # Test model_dump
93
+ submission_dict = submission.model_dump()
94
+ assert submission_dict["reaction"]["url"] == "https://example.com/reaction.mp4"
95
+
96
+ # Test JSON round-trip
97
+ submission_json = submission.model_dump_json()
98
+ reconstructed_submission = Submission.model_validate_json(submission_json)
99
+ assert reconstructed_submission == submission
100
+ assert reconstructed_submission.reaction.url == "https://example.com/reaction.mp4"
101
+
102
+
103
+ class TestSubmissions:
104
+ """Test cases for Submissions RootModel"""
105
+
106
+ @pytest.fixture
107
+ def sample_submissions(self) -> Submissions:
108
+ """Create sample submissions for testing"""
109
+ return Submissions(
110
+ [
111
+ Submission(
112
+ id="sub_001",
113
+ status=constants.SUBMISSION_STATUS_ACCEPTED,
114
+ team_id="team001",
115
+ problem_id=1,
116
+ timestamp=1234567890,
117
+ time=120,
118
+ language="Python",
119
+ ),
120
+ Submission(
121
+ id="sub_002",
122
+ status=constants.SUBMISSION_STATUS_WRONG_ANSWER,
123
+ team_id="team002",
124
+ problem_id=2,
125
+ timestamp=1234567891,
126
+ time=300,
127
+ language="C++",
128
+ reaction=Reaction(url="https://reaction.com/video.mp4"),
129
+ ),
130
+ Submission(
131
+ id="sub_003",
132
+ status=constants.SUBMISSION_STATUS_TIME_LIMIT_EXCEEDED,
133
+ team_id="team003",
134
+ problem_id=1,
135
+ timestamp=1234567892,
136
+ time=600,
137
+ language="Java",
138
+ ),
139
+ ]
140
+ )
141
+
142
+ def test_submissions_basic_operations(self, sample_submissions: Submissions):
143
+ """Test basic Submissions operations"""
144
+ assert len(sample_submissions.root) == 3
145
+
146
+ # Test iteration
147
+ statuses = [sub.status for sub in sample_submissions.root]
148
+ assert constants.SUBMISSION_STATUS_ACCEPTED in statuses
149
+ assert constants.SUBMISSION_STATUS_WRONG_ANSWER in statuses
150
+ assert constants.SUBMISSION_STATUS_TIME_LIMIT_EXCEEDED in statuses
151
+
152
+ def test_submissions_serialization(self, sample_submissions: Submissions):
153
+ """Test Submissions serialization and deserialization"""
154
+ # Test JSON serialization
155
+ submissions_json = sample_submissions.model_dump_json()
156
+ assert isinstance(submissions_json, str)
157
+
158
+ # Verify it's a valid JSON array
159
+ parsed = json.loads(submissions_json)
160
+ assert isinstance(parsed, list)
161
+ assert len(parsed) == 3
162
+
163
+ # Check first submission data
164
+ assert parsed[0]["status"] == constants.SUBMISSION_STATUS_ACCEPTED
165
+ assert parsed[0]["team_id"] == "team001"
166
+ assert parsed[0]["problem_id"] == 1
167
+
168
+ # Test JSON deserialization
169
+ reconstructed_submissions = Submissions.model_validate_json(submissions_json)
170
+ assert len(reconstructed_submissions.root) == 3
171
+
172
+ # Verify the data is correct
173
+ assert reconstructed_submissions.root[0].status == constants.SUBMISSION_STATUS_ACCEPTED
174
+ assert reconstructed_submissions.root[1].status == constants.SUBMISSION_STATUS_WRONG_ANSWER
175
+ assert reconstructed_submissions.root[2].status == constants.SUBMISSION_STATUS_TIME_LIMIT_EXCEEDED
176
+
177
+ # Check reaction is preserved
178
+ assert reconstructed_submissions.root[1].reaction.url == "https://reaction.com/video.mp4"
179
+
180
+ def test_submissions_round_trip(self, sample_submissions: Submissions):
181
+ """Test complete round-trip serialization"""
182
+ # Dict round-trip
183
+ submissions_dict = sample_submissions.model_dump()
184
+ reconstructed_from_dict = Submissions.model_validate(submissions_dict)
185
+ assert len(reconstructed_from_dict.root) == len(sample_submissions.root)
186
+
187
+ # JSON round-trip
188
+ submissions_json = sample_submissions.model_dump_json()
189
+ reconstructed_from_json = Submissions.model_validate_json(submissions_json)
190
+ assert len(reconstructed_from_json.root) == len(sample_submissions.root)
191
+
192
+ def test_empty_submissions(self):
193
+ """Test operations with empty Submissions"""
194
+ empty_submissions = Submissions([])
195
+
196
+ assert len(empty_submissions.root) == 0
197
+
198
+ # Test serialization
199
+ submissions_json = empty_submissions.model_dump_json()
200
+ assert submissions_json == "[]"
201
+
202
+ # Test deserialization
203
+ reconstructed = Submissions.model_validate_json("[]")
204
+ assert len(reconstructed.root) == 0
205
+
206
+ def test_submissions_with_minimal_data(self):
207
+ """Test Submissions with minimal submission data"""
208
+ minimal_submissions = Submissions(
209
+ [
210
+ Submission(
211
+ id="sub_min",
212
+ status=constants.SUBMISSION_STATUS_ACCEPTED,
213
+ team_id="team001",
214
+ problem_id=1,
215
+ timestamp=123,
216
+ ),
217
+ Submission(), # All defaults
218
+ ]
219
+ )
220
+
221
+ # Test serialization
222
+ submissions_json = minimal_submissions.model_dump_json()
223
+ reconstructed = Submissions.model_validate_json(submissions_json)
224
+
225
+ assert len(reconstructed.root) == 2
226
+ assert reconstructed.root[0].status == constants.SUBMISSION_STATUS_ACCEPTED
227
+ assert reconstructed.root[1].status == constants.SUBMISSION_STATUS_UNKNOWN # Default value
@@ -0,0 +1,238 @@
1
+ import json
2
+
3
+ import pytest
4
+
5
+ from xcpcio.types import Team, Teams
6
+
7
+
8
+ class TestTeam:
9
+ """Test cases for the Team dataclass"""
10
+
11
+ def test_team_creation(self):
12
+ """Test basic team creation with default values"""
13
+ team = Team()
14
+ assert team.id == ""
15
+ assert team.name == ""
16
+ assert team.organization == ""
17
+ assert team.members is None
18
+ assert team.coach is None
19
+ assert team.location is None
20
+ assert team.group == []
21
+ assert team.extra == {}
22
+
23
+ def test_team_creation_with_values(self):
24
+ """Test team creation with provided values"""
25
+ team = Team(
26
+ id="team001",
27
+ name="Test Team",
28
+ organization="Test University",
29
+ members=["Alice", "Bob", "Charlie"],
30
+ coach="Dr. Smith",
31
+ location="Building A",
32
+ )
33
+
34
+ assert team.id == "team001"
35
+ assert team.name == "Test Team"
36
+ assert team.organization == "Test University"
37
+ assert team.members == ["Alice", "Bob", "Charlie"]
38
+ assert team.coach == "Dr. Smith"
39
+ assert team.location == "Building A"
40
+
41
+ def test_group_management(self):
42
+ """Test add_group and remove_group methods"""
43
+ team = Team(id="test", name="Test Team", organization="Test Org")
44
+
45
+ # Test adding groups
46
+ team.add_group("undergraduate")
47
+ assert "undergraduate" in team.group
48
+
49
+ team.add_group("local")
50
+ assert "local" in team.group
51
+ assert len(team.group) == 2
52
+
53
+ # Test that duplicate groups are not added
54
+ team.add_group("undergraduate")
55
+ assert len(team.group) == 2
56
+
57
+ # Test removing groups
58
+ team.remove_group("local")
59
+ assert "local" not in team.group
60
+ assert len(team.group) == 1
61
+
62
+ # Test removing non-existent group (should not raise error)
63
+ team.remove_group("nonexistent")
64
+ assert len(team.group) == 1
65
+
66
+
67
+ class TestTeamSerialization:
68
+ """Test cases for Team serialization and deserialization"""
69
+
70
+ @pytest.fixture
71
+ def sample_team(self) -> Team:
72
+ """Create a sample team for testing"""
73
+ return Team(
74
+ id="team001",
75
+ name="Alpha Team",
76
+ organization="University A",
77
+ members=["Alice", "Bob", "Charlie"],
78
+ coach="Dr. Smith",
79
+ location="Building A",
80
+ group=["undergraduate", "local"],
81
+ extra={"room": "101", "contact": "alice@test.edu"},
82
+ )
83
+
84
+ def test_model_dump(self, sample_team: Team):
85
+ """Test Team model_dump method"""
86
+ team_dict = sample_team.model_dump()
87
+
88
+ assert isinstance(team_dict, dict)
89
+ assert team_dict["id"] == "team001"
90
+ assert team_dict["name"] == "Alpha Team"
91
+ assert team_dict["organization"] == "University A"
92
+ assert team_dict["members"] == ["Alice", "Bob", "Charlie"]
93
+ assert team_dict["coach"] == "Dr. Smith"
94
+ assert team_dict["location"] == "Building A"
95
+ assert team_dict["group"] == ["undergraduate", "local"]
96
+ assert team_dict["extra"] == {"room": "101", "contact": "alice@test.edu"}
97
+
98
+ def test_model_validate(self, sample_team: Team):
99
+ """Test Team model_validate method"""
100
+ team_dict = sample_team.model_dump()
101
+ reconstructed_team = Team.model_validate(team_dict)
102
+
103
+ assert reconstructed_team == sample_team
104
+
105
+ def test_model_dump_json(self, sample_team: Team):
106
+ """Test Team model_dump_json method"""
107
+ team_json = sample_team.model_dump_json()
108
+
109
+ assert isinstance(team_json, str)
110
+ # Verify it's valid JSON
111
+ parsed = json.loads(team_json)
112
+ assert parsed["id"] == "team001"
113
+ assert parsed["name"] == "Alpha Team"
114
+
115
+ def test_model_validate_json(self, sample_team: Team):
116
+ """Test Team model_validate_json method"""
117
+ team_json = sample_team.model_dump_json()
118
+ reconstructed_team = Team.model_validate_json(team_json)
119
+
120
+ assert reconstructed_team == sample_team
121
+
122
+ def test_round_trip_dict(self, sample_team: Team):
123
+ """Test complete round-trip through dict serialization"""
124
+ team_dict = sample_team.model_dump()
125
+ reconstructed_team = Team.model_validate(team_dict)
126
+
127
+ assert reconstructed_team == sample_team
128
+
129
+ def test_round_trip_json(self, sample_team: Team):
130
+ """Test complete round-trip through JSON serialization"""
131
+ team_json = sample_team.model_dump_json()
132
+ reconstructed_team = Team.model_validate_json(team_json)
133
+
134
+ assert reconstructed_team == sample_team
135
+
136
+ def test_minimal_team_serialization(self):
137
+ """Test serialization of team with default/minimal values"""
138
+ minimal_team = Team()
139
+
140
+ # Test dict round-trip
141
+ team_dict = minimal_team.model_dump()
142
+ reconstructed_from_dict = Team.model_validate(team_dict)
143
+ assert reconstructed_from_dict == minimal_team
144
+
145
+ # Test JSON round-trip
146
+ team_json = minimal_team.model_dump_json()
147
+ reconstructed_from_json = Team.model_validate_json(team_json)
148
+ assert reconstructed_from_json == minimal_team
149
+
150
+
151
+ class TestTeamList:
152
+ """Test cases for Teams (RootModel) serialization and deserialization"""
153
+
154
+ @pytest.fixture
155
+ def sample_teams(self) -> Teams:
156
+ """Create a Teams instance for testing"""
157
+ return Teams(
158
+ [
159
+ Team(
160
+ id="team001",
161
+ name="Alpha Team",
162
+ organization="University A",
163
+ members=["Alice", "Bob"],
164
+ coach="Coach A",
165
+ ),
166
+ Team(
167
+ id="team002",
168
+ name="Beta Team",
169
+ organization="University B",
170
+ members=["Charlie", "David", "Eve"],
171
+ location="Remote",
172
+ ),
173
+ Team(
174
+ id="team003",
175
+ name="Gamma Team",
176
+ organization="University C",
177
+ group=["graduate", "international"],
178
+ extra={"sponsor": "Tech Corp"},
179
+ ),
180
+ ]
181
+ )
182
+
183
+ def test_teams_basic_operations(self, sample_teams: Teams):
184
+ """Test basic Teams operations"""
185
+ assert len(sample_teams.root) == 3
186
+
187
+ # Test iteration
188
+ team_ids = [team.id for team in sample_teams.root]
189
+ assert "team001" in team_ids
190
+ assert "team002" in team_ids
191
+ assert "team003" in team_ids
192
+
193
+ def test_teams_serialization(self, sample_teams: Teams):
194
+ """Test Teams serialization and deserialization"""
195
+ # Test JSON serialization
196
+ teams_json = sample_teams.model_dump_json()
197
+ assert isinstance(teams_json, str)
198
+
199
+ # Verify it's a valid JSON array
200
+ parsed = json.loads(teams_json)
201
+ assert isinstance(parsed, list)
202
+ assert len(parsed) == 3
203
+
204
+ # Test JSON deserialization
205
+ reconstructed_teams = Teams.model_validate_json(teams_json)
206
+ assert len(reconstructed_teams.root) == 3
207
+
208
+ # Verify the data is correct
209
+ reconstructed_teams.root.sort(key=lambda x: x.id)
210
+ assert reconstructed_teams.root[0].id == "team001"
211
+ assert reconstructed_teams.root[1].id == "team002"
212
+ assert reconstructed_teams.root[2].id == "team003"
213
+
214
+ def test_teams_round_trip(self, sample_teams: Teams):
215
+ """Test complete round-trip serialization"""
216
+ # Dict round-trip
217
+ teams_dict = sample_teams.model_dump()
218
+ reconstructed_from_dict = Teams.model_validate(teams_dict)
219
+ assert len(reconstructed_from_dict.root) == len(sample_teams.root)
220
+
221
+ # JSON round-trip
222
+ teams_json = sample_teams.model_dump_json()
223
+ reconstructed_from_json = Teams.model_validate_json(teams_json)
224
+ assert len(reconstructed_from_json.root) == len(sample_teams.root)
225
+
226
+ def test_empty_teams(self):
227
+ """Test operations with empty Teams"""
228
+ empty_teams = Teams([])
229
+
230
+ assert len(empty_teams.root) == 0
231
+
232
+ # Test serialization
233
+ teams_json = empty_teams.model_dump_json()
234
+ assert teams_json == "[]"
235
+
236
+ # Test deserialization
237
+ reconstructed = Teams.model_validate_json("[]")
238
+ assert len(reconstructed.root) == 0
@@ -0,0 +1,135 @@
1
+ from xcpcio import constants
2
+ from xcpcio.types import Color, ContestOptions, Image, Reaction
3
+
4
+
5
+ class TestImage:
6
+ """Test cases for Image Pydantic model"""
7
+
8
+ def test_image_creation_empty(self):
9
+ """Test Image creation with default values"""
10
+ image = Image()
11
+ assert image.url is None
12
+ assert image.base64 is None
13
+ assert image.preset is None
14
+
15
+ def test_image_creation_with_values(self):
16
+ """Test Image creation with provided values"""
17
+ image = Image(url="https://example.com/image.png", base64="base64data", preset="ICPC")
18
+ assert image.url == "https://example.com/image.png"
19
+ assert image.base64 == "base64data"
20
+ assert image.preset == "ICPC"
21
+
22
+ def test_image_serialization(self):
23
+ """Test Image serialization and deserialization"""
24
+ image = Image(url="https://test.com/img.jpg", preset="CCPC")
25
+
26
+ # Test model_dump
27
+ image_dict = image.model_dump()
28
+ assert image_dict["url"] == "https://test.com/img.jpg"
29
+ assert image_dict["preset"] == "CCPC"
30
+ assert image_dict["base64"] is None
31
+
32
+ # Test JSON serialization
33
+ image_json = image.model_dump_json()
34
+ reconstructed_image = Image.model_validate_json(image_json)
35
+ assert reconstructed_image == image
36
+
37
+
38
+ class TestColor:
39
+ """Test cases for Color Pydantic model"""
40
+
41
+ def test_color_creation(self):
42
+ """Test Color creation"""
43
+ color = Color(color="#ffffff", background_color="#000000")
44
+ assert color.color == "#ffffff"
45
+ assert color.background_color == "#000000"
46
+
47
+ def test_color_serialization(self):
48
+ """Test Color serialization and deserialization"""
49
+ color = Color(color="red", background_color="blue")
50
+
51
+ # Test model_dump
52
+ color_dict = color.model_dump()
53
+ assert color_dict["color"] == "red"
54
+ assert color_dict["background_color"] == "blue"
55
+
56
+ # Test JSON round-trip
57
+ color_json = color.model_dump_json()
58
+ reconstructed_color = Color.model_validate_json(color_json)
59
+ assert reconstructed_color == color
60
+
61
+
62
+ class TestContestOptions:
63
+ """Test cases for ContestOptions Pydantic model"""
64
+
65
+ def test_contest_options_empty(self):
66
+ """Test ContestOptions with default values"""
67
+ options = ContestOptions()
68
+ assert options.calculation_of_penalty is None
69
+ assert options.submission_timestamp_unit is None
70
+ assert options.has_reaction_videos is None
71
+ assert options.reaction_video_url_template is None
72
+
73
+ def test_contest_options_with_values(self):
74
+ """Test ContestOptions with provided values"""
75
+ options = ContestOptions(
76
+ calculation_of_penalty=constants.CALCULATION_OF_PENALTY_IN_SECONDS,
77
+ submission_timestamp_unit=constants.TIME_UNIT_MILLISECOND,
78
+ has_reaction_videos=True,
79
+ reaction_video_url_template="https://videos.com/{team_id}/{problem_id}",
80
+ )
81
+
82
+ assert options.calculation_of_penalty == constants.CALCULATION_OF_PENALTY_IN_SECONDS
83
+ assert options.submission_timestamp_unit == constants.TIME_UNIT_MILLISECOND
84
+ assert options.has_reaction_videos is True
85
+ assert options.reaction_video_url_template == "https://videos.com/{team_id}/{problem_id}"
86
+
87
+ def test_contest_options_serialization(self):
88
+ """Test ContestOptions serialization"""
89
+ options = ContestOptions(
90
+ calculation_of_penalty=constants.CALCULATION_OF_PENALTY_IN_MINUTES, has_reaction_videos=False
91
+ )
92
+
93
+ # Test JSON round-trip
94
+ options_json = options.model_dump_json()
95
+ reconstructed_options = ContestOptions.model_validate_json(options_json)
96
+ assert reconstructed_options == options
97
+
98
+
99
+ class TestReaction:
100
+ """Test cases for Reaction Pydantic model"""
101
+
102
+ def test_reaction_empty(self):
103
+ """Test Reaction with default values"""
104
+ reaction = Reaction()
105
+ assert reaction.url is None
106
+
107
+ def test_reaction_with_url(self):
108
+ """Test Reaction with URL"""
109
+ reaction = Reaction(url="https://reaction.com/video.mp4")
110
+ assert reaction.url == "https://reaction.com/video.mp4"
111
+
112
+ def test_reaction_serialization(self):
113
+ """Test Reaction serialization"""
114
+ reaction = Reaction(url="https://test.com/reaction.mp4")
115
+
116
+ # Test model_dump
117
+ reaction_dict = reaction.model_dump()
118
+ assert reaction_dict["url"] == "https://test.com/reaction.mp4"
119
+
120
+ # Test JSON round-trip
121
+ reaction_json = reaction.model_dump_json()
122
+ reconstructed_reaction = Reaction.model_validate_json(reaction_json)
123
+ assert reconstructed_reaction == reaction
124
+
125
+ def test_reaction_no_url_serialization(self):
126
+ """Test Reaction serialization with no URL"""
127
+ reaction = Reaction()
128
+
129
+ reaction_dict = reaction.model_dump()
130
+ assert reaction_dict["url"] is None
131
+
132
+ # Test JSON round-trip
133
+ reaction_json = reaction.model_dump_json()
134
+ reconstructed_reaction = Reaction.model_validate_json(reaction_json)
135
+ assert reconstructed_reaction == reaction
@@ -0,0 +1,5 @@
1
+ from . import constants, types
2
+
3
+ __version__ = "0.58.2"
4
+
5
+ __all__ = [constants, types]
@@ -0,0 +1,66 @@
1
+ SUBMISSION_STATUS_PENDING = "PENDING"
2
+ SUBMISSION_STATUS_WAITING = "WAITING"
3
+ SUBMISSION_STATUS_PREPARING = "PREPARING"
4
+ SUBMISSION_STATUS_COMPILING = "COMPILING"
5
+ SUBMISSION_STATUS_RUNNING = "RUNNING"
6
+ SUBMISSION_STATUS_JUDGING = "JUDGING"
7
+ SUBMISSION_STATUS_FROZEN = "FROZEN"
8
+
9
+ SUBMISSION_STATUS_ACCEPTED = "ACCEPTED"
10
+ SUBMISSION_STATUS_CORRECT = "CORRECT"
11
+ SUBMISSION_STATUS_PARTIALLY_CORRECT = "PARTIALLY_CORRECT"
12
+
13
+ SUBMISSION_STATUS_REJECTED = "REJECTED"
14
+ SUBMISSION_STATUS_WRONG_ANSWER = "WRONG_ANSWER"
15
+ SUBMISSION_STATUS_INCORRECT = "INCORRECT"
16
+
17
+ SUBMISSION_STATUS_NO_OUTPUT = "NO_OUTPUT"
18
+
19
+ SUBMISSION_STATUS_COMPILATION_ERROR = "COMPILATION_ERROR"
20
+ SUBMISSION_STATUS_PRESENTATION_ERROR = "PRESENTATION_ERROR"
21
+
22
+ SUBMISSION_STATUS_RUNTIME_ERROR = "RUNTIME_ERROR"
23
+ SUBMISSION_STATUS_TIME_LIMIT_EXCEEDED = "TIME_LIMIT_EXCEEDED"
24
+ SUBMISSION_STATUS_MEMORY_LIMIT_EXCEEDED = "MEMORY_LIMIT_EXCEEDED"
25
+ SUBMISSION_STATUS_OUTPUT_LIMIT_EXCEEDED = "OUTPUT_LIMIT_EXCEEDED"
26
+ SUBMISSION_STATUS_IDLENESS_LIMIT_EXCEEDED = "IDLENESS_LIMIT_EXCEEDED"
27
+
28
+ SUBMISSION_STATUS_HACKED = "HACKED"
29
+
30
+ SUBMISSION_STATUS_JUDGEMENT_FAILED = "JUDGEMENT_FAILED"
31
+ SUBMISSION_STATUS_CONFIGURATION_ERROR = "CONFIGURATION_ERROR"
32
+ SUBMISSION_STATUS_FILE_ERROR = "FILE_ERROR"
33
+ SUBMISSION_STATUS_SYSTEM_ERROR = "SYSTEM_ERROR"
34
+ SUBMISSION_STATUS_CANCELED = "CANCELED"
35
+ SUBMISSION_STATUS_SKIPPED = "SKIPPED"
36
+
37
+ SUBMISSION_STATUS_SECURITY_VIOLATED = "SECURITY_VIOLATED"
38
+ SUBMISSION_STATUS_DENIAL_OF_JUDGEMENT = "DENIAL_OF_JUDGEMENT"
39
+
40
+ SUBMISSION_STATUS_UNKNOWN = "UNKNOWN"
41
+ SUBMISSION_STATUS_UNDEFINED = "UNDEFINED"
42
+
43
+ CALCULATION_OF_PENALTY_IN_MINUTES = "in_minutes"
44
+ CALCULATION_OF_PENALTY_IN_SECONDS = "in_seconds"
45
+ CALCULATION_OF_PENALTY_ACCUMULATE_IN_SECONDS_AND_FINALLY_TO_THE_MINUTE = (
46
+ "accumulate_in_seconds_and_finally_to_the_minute"
47
+ )
48
+
49
+ TIME_UNIT_SECOND = "second"
50
+ TIME_UNIT_MILLISECOND = "millisecond"
51
+ TIME_UNIT_MICROSECOND = "microsecond"
52
+ TIME_UNIT_NANOSECOND = "nanosecond"
53
+
54
+ FULL_STATUS_TIME_DISPLAY = {
55
+ SUBMISSION_STATUS_CORRECT.lower(): True,
56
+ SUBMISSION_STATUS_INCORRECT.lower(): True,
57
+ SUBMISSION_STATUS_PENDING.lower(): True,
58
+ }
59
+
60
+ TEAM_TYPE_OFFICIAL = "official"
61
+ TEAM_TYPE_UNOFFICIAL = "unofficial"
62
+ TEAM_TYPE_GIRL = "girl"
63
+
64
+ TEAM_TYPE_ZH_CN_OFFICIAL = "正式队伍"
65
+ TEAM_TYPE_ZH_CN_UNOFFICIAL = "打星队伍"
66
+ TEAM_TYPE_ZH_CN_GIRL = "女队"
@@ -0,0 +1,204 @@
1
+ from typing import Dict, List, Literal, Optional, Union
2
+
3
+ from pydantic import BaseModel, Field, RootModel
4
+
5
+ from . import constants
6
+
7
+ CalculationOfPenalty = Literal[
8
+ constants.CALCULATION_OF_PENALTY_IN_SECONDS,
9
+ constants.CALCULATION_OF_PENALTY_IN_MINUTES,
10
+ constants.CALCULATION_OF_PENALTY_ACCUMULATE_IN_SECONDS_AND_FINALLY_TO_THE_MINUTE,
11
+ ]
12
+ TimeUnit = Literal[
13
+ constants.TIME_UNIT_SECOND,
14
+ constants.TIME_UNIT_MILLISECOND,
15
+ constants.TIME_UNIT_MICROSECOND,
16
+ constants.TIME_UNIT_NANOSECOND,
17
+ ]
18
+ SubmissionStatus = Literal[
19
+ constants.SUBMISSION_STATUS_PENDING,
20
+ constants.SUBMISSION_STATUS_WAITING,
21
+ constants.SUBMISSION_STATUS_PREPARING,
22
+ constants.SUBMISSION_STATUS_COMPILING,
23
+ constants.SUBMISSION_STATUS_RUNNING,
24
+ constants.SUBMISSION_STATUS_JUDGING,
25
+ constants.SUBMISSION_STATUS_FROZEN,
26
+ #
27
+ constants.SUBMISSION_STATUS_ACCEPTED,
28
+ constants.SUBMISSION_STATUS_CORRECT,
29
+ constants.SUBMISSION_STATUS_PARTIALLY_CORRECT,
30
+ #
31
+ constants.SUBMISSION_STATUS_REJECTED,
32
+ constants.SUBMISSION_STATUS_WRONG_ANSWER,
33
+ constants.SUBMISSION_STATUS_INCORRECT,
34
+ #
35
+ constants.SUBMISSION_STATUS_NO_OUTPUT,
36
+ #
37
+ constants.SUBMISSION_STATUS_COMPILATION_ERROR,
38
+ constants.SUBMISSION_STATUS_PRESENTATION_ERROR,
39
+ #
40
+ constants.SUBMISSION_STATUS_RUNTIME_ERROR,
41
+ constants.SUBMISSION_STATUS_TIME_LIMIT_EXCEEDED,
42
+ constants.SUBMISSION_STATUS_MEMORY_LIMIT_EXCEEDED,
43
+ constants.SUBMISSION_STATUS_OUTPUT_LIMIT_EXCEEDED,
44
+ constants.SUBMISSION_STATUS_IDLENESS_LIMIT_EXCEEDED,
45
+ #
46
+ constants.SUBMISSION_STATUS_HACKED,
47
+ #
48
+ constants.SUBMISSION_STATUS_JUDGEMENT_FAILED,
49
+ constants.SUBMISSION_STATUS_CONFIGURATION_ERROR,
50
+ constants.SUBMISSION_STATUS_FILE_ERROR,
51
+ constants.SUBMISSION_STATUS_SYSTEM_ERROR,
52
+ constants.SUBMISSION_STATUS_CANCELED,
53
+ constants.SUBMISSION_STATUS_SKIPPED,
54
+ #
55
+ constants.SUBMISSION_STATUS_SECURITY_VIOLATED,
56
+ constants.SUBMISSION_STATUS_DENIAL_OF_JUDGEMENT,
57
+ #
58
+ constants.SUBMISSION_STATUS_UNKNOWN,
59
+ constants.SUBMISSION_STATUS_UNDEFINED,
60
+ ]
61
+ ImagePreset = Literal["ICPC", "CCPC", "HUNAN_CPC"]
62
+ MedalPreset = Literal["ccpc", "icpc"]
63
+ BannerMode = Literal["ONLY_BANNER", "ALL"]
64
+ DateTimeISO8601String = str
65
+
66
+
67
+ class Image(BaseModel):
68
+ url: Optional[str] = None
69
+ base64: Optional[str] = None
70
+ type: Optional[Literal["png", "svg", "jpg", "jpeg"]] = None
71
+ preset: Optional[ImagePreset] = None
72
+
73
+
74
+ class Color(BaseModel):
75
+ color: str
76
+ background_color: str
77
+
78
+
79
+ class Reaction(BaseModel):
80
+ url: Optional[str] = None
81
+
82
+
83
+ class Submission(BaseModel):
84
+ id: str = ""
85
+
86
+ team_id: str = ""
87
+ problem_id: int = 0
88
+ timestamp: int = 0 # unit: seconds
89
+ status: SubmissionStatus = constants.SUBMISSION_STATUS_UNKNOWN
90
+
91
+ time: Optional[int] = None
92
+ language: Optional[str] = None
93
+
94
+ is_ignore: Optional[bool] = None
95
+
96
+ reaction: Optional[Reaction] = None
97
+
98
+
99
+ class Submissions(RootModel[List[Submission]]):
100
+ root: List[Submission]
101
+
102
+
103
+ class Team(BaseModel):
104
+ id: str = ""
105
+ name: str = ""
106
+
107
+ organization: str = ""
108
+ group: List[str] = Field(default_factory=list)
109
+ tag: List[str] = Field(default_factory=list)
110
+
111
+ coach: Optional[str] = None
112
+ members: Optional[List[str]] = None
113
+
114
+ badge: Optional[Image] = None
115
+
116
+ location: Optional[str] = None
117
+ icpc_id: Optional[str] = None
118
+
119
+ extra: Dict[str, str] = Field(default_factory=dict)
120
+
121
+ def add_group(self, group: str):
122
+ if group not in self.group:
123
+ self.group.append(group)
124
+
125
+ def remove_group(self, group: str):
126
+ if group in self.group:
127
+ self.group.remove(group)
128
+
129
+
130
+ class Teams(RootModel[List[Team]]):
131
+ pass
132
+
133
+
134
+ class ContestOptions(BaseModel):
135
+ calculation_of_penalty: Optional[CalculationOfPenalty] = None
136
+ submission_timestamp_unit: Optional[TimeUnit] = None
137
+ has_reaction_videos: Optional[bool] = None
138
+ reaction_video_url_template: Optional[str] = None
139
+
140
+
141
+ class Contest(BaseModel):
142
+ contest_name: str = ""
143
+
144
+ start_time: Union[int, DateTimeISO8601String] = 0
145
+ end_time: Union[int, DateTimeISO8601String] = 0
146
+ penalty: int = 20 * 60 # unit: seconds
147
+
148
+ freeze_time: Optional[Union[int, DateTimeISO8601String]] = None
149
+ frozen_time: int = 60 * 60 # unit: seconds
150
+ unfrozen_time: int = 0x3F3F3F3F3F3F3F3F
151
+
152
+ problem_quantity: int = 0
153
+ problem_id: List[str] = Field(default_factory=list)
154
+
155
+ organization: str = "School"
156
+ status_time_display: Optional[Dict[str, bool]] = constants.FULL_STATUS_TIME_DISPLAY
157
+
158
+ badge: Optional[str] = None
159
+ medal: Optional[Union[Dict[str, Dict[str, int]], MedalPreset]] = None
160
+ balloon_color: Optional[List[Color]] = None
161
+
162
+ group: Optional[Dict[str, str]] = None
163
+ tag: Optional[Dict[str, str]] = None
164
+
165
+ logo: Optional[Image] = None
166
+ banner: Optional[Image] = None
167
+ banner_mode: Optional[BannerMode] = None
168
+ board_link: Optional[str] = None
169
+
170
+ version: Optional[str] = None
171
+
172
+ options: ContestOptions = Field(default_factory=ContestOptions)
173
+
174
+ def append_balloon_color(self, color: Color):
175
+ if self.balloon_color is None:
176
+ self.balloon_color = []
177
+ self.balloon_color.append(color)
178
+ return self
179
+
180
+ def fill_problem_id(self):
181
+ self.problem_id = [chr(ord("A") + i) for i in range(self.problem_quantity)]
182
+ return self
183
+
184
+ def fill_balloon_color(self):
185
+ 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"),
200
+ ]
201
+
202
+ self.balloon_color = default_balloon_color_list[: self.problem_quantity]
203
+
204
+ return self
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: xcpcio
3
+ Version: 0.58.2
4
+ Summary: xcpcio python lib
5
+ Author-email: Dup4 <hi@dup4.com>
6
+ License-Expression: MIT
7
+ Project-URL: homepage, https://github.com/xcpcio/xcpcio
8
+ Project-URL: documentation, https://github.com/xcpcio/xcpcio
9
+ Project-URL: repository, https://github.com/xcpcio/xcpcio
10
+ Keywords: xcpcio
11
+ Classifier: Topic :: Software Development :: Build Tools
12
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: pydantic>=2.11.7
20
+
21
+ # xcpcio-python
@@ -0,0 +1,14 @@
1
+ README.md
2
+ pyproject.toml
3
+ tests/test_contest.py
4
+ tests/test_submission.py
5
+ tests/test_team.py
6
+ tests/test_types.py
7
+ xcpcio/__init__.py
8
+ xcpcio/constants.py
9
+ xcpcio/types.py
10
+ xcpcio.egg-info/PKG-INFO
11
+ xcpcio.egg-info/SOURCES.txt
12
+ xcpcio.egg-info/dependency_links.txt
13
+ xcpcio.egg-info/requires.txt
14
+ xcpcio.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ pydantic>=2.11.7
@@ -0,0 +1 @@
1
+ xcpcio