xcpcio 0.64.2__tar.gz → 0.82.0__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.
Files changed (60) hide show
  1. {xcpcio-0.64.2 → xcpcio-0.82.0}/PKG-INFO +5 -3
  2. {xcpcio-0.64.2 → xcpcio-0.82.0}/README.md +4 -2
  3. {xcpcio-0.64.2 → xcpcio-0.82.0}/pyproject.toml +3 -1
  4. {xcpcio-0.64.2 → xcpcio-0.82.0}/scripts/generate_ccs_models.sh +1 -1
  5. {xcpcio-0.64.2 → xcpcio-0.82.0}/tests/test_contest.py +52 -71
  6. {xcpcio-0.64.2 → xcpcio-0.82.0}/tests/test_submission.py +6 -6
  7. {xcpcio-0.64.2 → xcpcio-0.82.0}/tests/test_team.py +40 -12
  8. {xcpcio-0.64.2 → xcpcio-0.82.0}/tests/test_types.py +22 -22
  9. xcpcio-0.82.0/xcpcio/__init__.py +4 -0
  10. {xcpcio-0.64.2 → xcpcio-0.82.0}/xcpcio/__version__.py +1 -1
  11. xcpcio-0.82.0/xcpcio/api/__init__.py +17 -0
  12. xcpcio-0.82.0/xcpcio/api/client.py +74 -0
  13. xcpcio-0.82.0/xcpcio/api/models.py +36 -0
  14. xcpcio-0.64.2/cli/ccs_archiver_cli.py → xcpcio-0.82.0/xcpcio/app/clics_archiver.py +12 -10
  15. xcpcio-0.64.2/app/contest_api_server.py → xcpcio-0.82.0/xcpcio/app/clics_server.py +13 -15
  16. xcpcio-0.82.0/xcpcio/app/clics_uploader.py +103 -0
  17. xcpcio-0.82.0/xcpcio/clics/__init__.py +9 -0
  18. xcpcio-0.82.0/xcpcio/clics/api_server/app.py +43 -0
  19. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/dependencies.py +3 -4
  20. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/__init__.py +1 -1
  21. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/server.py +15 -35
  22. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/services/__init__.py +3 -1
  23. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/services/contest_service.py +4 -4
  24. xcpcio-0.82.0/xcpcio/clics/base/types.py +19 -0
  25. xcpcio-0.82.0/xcpcio/clics/clics_api_client.py +251 -0
  26. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/contest_archiver.py +27 -168
  27. xcpcio-0.82.0/xcpcio/clics/contest_uploader.py +304 -0
  28. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/model/model_2023_06/__init__.py +1 -1
  29. xcpcio-0.82.0/xcpcio/clics/reader/__init__.py +7 -0
  30. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/reader/contest_package_reader.py +64 -30
  31. xcpcio-0.64.2/xcpcio/ccs/reader/base_ccs_reader.py → xcpcio-0.82.0/xcpcio/clics/reader/interface.py +6 -2
  32. {xcpcio-0.64.2 → xcpcio-0.82.0}/xcpcio/types.py +125 -43
  33. xcpcio-0.64.2/xcpcio/__init__.py +0 -4
  34. xcpcio-0.64.2/xcpcio/ccs/__init__.py +0 -3
  35. xcpcio-0.64.2/xcpcio/ccs/base/types.py +0 -9
  36. xcpcio-0.64.2/xcpcio/ccs/reader/__init__.py +0 -0
  37. {xcpcio-0.64.2 → xcpcio-0.82.0}/.gitignore +0 -0
  38. {xcpcio-0.64.2 → xcpcio-0.82.0}/.python-version +0 -0
  39. {xcpcio-0.64.2 → xcpcio-0.82.0}/tests/__init__.py +0 -0
  40. {xcpcio-0.64.2 → xcpcio-0.82.0}/uv.lock +0 -0
  41. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/__init__.py +0 -0
  42. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/access.py +0 -0
  43. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/accounts.py +0 -0
  44. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/awards.py +0 -0
  45. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/clarifications.py +0 -0
  46. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/contests.py +0 -0
  47. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/general.py +0 -0
  48. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/groups.py +0 -0
  49. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/judgement_types.py +0 -0
  50. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/judgements.py +0 -0
  51. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/languages.py +0 -0
  52. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/organizations.py +0 -0
  53. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/problems.py +0 -0
  54. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/runs.py +0 -0
  55. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/submissions.py +0 -0
  56. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/teams.py +0 -0
  57. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/base/__init__.py +0 -0
  58. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/model/__init__.py +0 -0
  59. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/model/model_2023_06/model.py +0 -0
  60. {xcpcio-0.64.2 → xcpcio-0.82.0}/xcpcio/constants.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xcpcio
3
- Version: 0.64.2
3
+ Version: 0.82.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
@@ -79,8 +79,10 @@ uv run pytest tests/test_types.py
79
79
 
80
80
  For detailed documentation, visit:
81
81
 
82
- - [CCS Utility Guide](https://xcpcio.com/guide/ccs-utility)
82
+ - [Clics Utility Guide](https://xcpcio.com/guide/clics-utility)
83
83
 
84
84
  ## License
85
85
 
86
- MIT License © 2020-PRESENT [Dup4](https://github.com/Dup4)
86
+ [MIT](../LICENSE) License © 2020 - PRESENT [XCPCIO][xcpcio]
87
+
88
+ [xcpcio]: https://xcpcio.com
@@ -49,8 +49,10 @@ uv run pytest tests/test_types.py
49
49
 
50
50
  For detailed documentation, visit:
51
51
 
52
- - [CCS Utility Guide](https://xcpcio.com/guide/ccs-utility)
52
+ - [Clics Utility Guide](https://xcpcio.com/guide/clics-utility)
53
53
 
54
54
  ## License
55
55
 
56
- MIT License © 2020-PRESENT [Dup4](https://github.com/Dup4)
56
+ [MIT](../LICENSE) License © 2020 - PRESENT [XCPCIO][xcpcio]
57
+
58
+ [xcpcio]: https://xcpcio.com
@@ -38,7 +38,9 @@ documentation = "https://github.com/xcpcio/xcpcio"
38
38
  repository = "https://github.com/xcpcio/xcpcio"
39
39
 
40
40
  [project.scripts]
41
- ccs-archiver = "cli.ccs_archiver_cli:main"
41
+ clics-archiver = "xcpcio.app.clics_archiver:main"
42
+ clics-server = "xcpcio.app.clics_server:main"
43
+ clics-uploader = "xcpcio.app.clics_uploader:main"
42
44
 
43
45
  [tool.ruff]
44
46
  line-length = 120
@@ -5,7 +5,7 @@ set -euo pipefail
5
5
  CUR_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
6
6
  PYTHON_DIR="$(dirname "${CUR_DIR}")"
7
7
  CCS_SPECS_DIR="${PYTHON_DIR}/ccs-specs"
8
- CCS_MODELS_DIR="${PYTHON_DIR}/xcpcio/ccs/model"
8
+ CCS_MODELS_DIR="${PYTHON_DIR}/xcpcio/clics/model"
9
9
 
10
10
  BRANCH="${1:-2023-06}"
11
11
  CCS_REPO_URL="https://github.com/icpc/ccs-specs.git"
@@ -1,7 +1,5 @@
1
-
2
-
3
1
  from xcpcio import constants
4
- from xcpcio.types import Color, Contest, ContestOptions, Image
2
+ from xcpcio.types import BalloonColor, Contest, ContestOptions, Image
5
3
 
6
4
 
7
5
  class TestContest:
@@ -10,38 +8,34 @@ class TestContest:
10
8
  def test_contest_creation_defaults(self):
11
9
  """Test Contest creation with default values"""
12
10
  contest = Contest()
13
-
11
+
14
12
  assert contest.contest_name == ""
15
13
  assert contest.start_time == 0
16
14
  assert contest.end_time == 0
17
15
  assert contest.frozen_time == 60 * 60 # 1 hour
18
- assert contest.unfrozen_time == 0x3F3F3F3F3F3F3F3F
16
+ assert contest.thaw_time == 0x3F3F3F3F3F3F3F3F
19
17
  assert contest.penalty == 20 * 60 # 20 minutes
20
- assert contest.problem_quantity == 0
21
- assert contest.problem_id == []
22
- assert contest.organization == "School"
18
+ assert contest.problem_id is None
23
19
  assert contest.medal is None
24
20
  assert contest.balloon_color is None
25
21
  assert contest.logo is None
26
22
  assert contest.banner is None
27
23
  assert contest.banner_mode is None
28
- assert contest.badge is None
29
24
  assert contest.group is None
30
25
  assert contest.tag is None
31
26
  assert contest.board_link is None
32
27
  assert contest.version is None
33
-
28
+
34
29
  # Check default values
35
30
  assert contest.status_time_display == constants.FULL_STATUS_TIME_DISPLAY
36
- assert isinstance(contest.options, ContestOptions)
31
+ assert contest.options is None
37
32
 
38
33
  def test_contest_creation_with_values(self):
39
34
  """Test Contest creation with provided values"""
40
35
  contest_options = ContestOptions(
41
- calculation_of_penalty=constants.CALCULATION_OF_PENALTY_IN_SECONDS,
42
- has_reaction_videos=True
36
+ calculation_of_penalty=constants.CALCULATION_OF_PENALTY_IN_SECONDS, has_reaction_videos=True
43
37
  )
44
-
38
+
45
39
  contest = Contest(
46
40
  contest_name="ICPC World Finals 2024",
47
41
  start_time=1234567890,
@@ -50,19 +44,16 @@ class TestContest:
50
44
  penalty=20 * 60, # 20 minutes
51
45
  problem_quantity=12,
52
46
  problem_id=["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"],
53
- organization="ICPC",
54
47
  medal="icpc", # Use Literal value
55
- options=contest_options
48
+ options=contest_options,
56
49
  )
57
-
50
+
58
51
  assert contest.contest_name == "ICPC World Finals 2024"
59
52
  assert contest.start_time == 1234567890
60
53
  assert contest.end_time == 1234567890 + 5 * 60 * 60
61
- assert contest.problem_quantity == 12
62
54
  assert len(contest.problem_id) == 12
63
55
  assert contest.problem_id[0] == "A"
64
56
  assert contest.problem_id[-1] == "L"
65
- assert contest.organization == "ICPC"
66
57
  assert contest.medal == "icpc"
67
58
  assert contest.options == contest_options
68
59
 
@@ -72,18 +63,14 @@ class TestContest:
72
63
  contest_name="Test Contest",
73
64
  start_time=1000000000,
74
65
  end_time=1000000000 + 60 * 60 * 5,
75
- problem_quantity=5,
76
66
  problem_id=["A", "B", "C", "D", "E"],
77
- organization="Test Org"
78
67
  )
79
-
68
+
80
69
  # Test model_dump
81
70
  contest_dict = contest.model_dump()
82
71
  assert contest_dict["contest_name"] == "Test Contest"
83
72
  assert contest_dict["start_time"] == 1000000000
84
- assert contest_dict["problem_quantity"] == 5
85
- assert contest_dict["organization"] == "Test Org"
86
-
73
+
87
74
  # Test JSON round-trip
88
75
  contest_json = contest.model_dump_json()
89
76
  reconstructed_contest = Contest.model_validate_json(contest_json)
@@ -92,21 +79,20 @@ class TestContest:
92
79
  def test_contest_with_colors_and_images(self):
93
80
  """Test Contest with balloon colors, logo, and banner"""
94
81
  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)")
82
+ BalloonColor(color="#fff", background_color="rgba(255, 0, 0, 0.7)"),
83
+ BalloonColor(color="#000", background_color="rgba(0, 255, 0, 0.7)"),
97
84
  ]
98
85
  logo = Image(url="https://example.com/logo.png", type="png")
99
86
  banner = Image(url="https://example.com/banner.jpg", type="jpg")
100
-
87
+
101
88
  contest = Contest(
102
89
  contest_name="Contest with Media",
103
- problem_quantity=2,
104
90
  balloon_color=colors,
105
91
  logo=logo,
106
92
  banner=banner,
107
- banner_mode="ALL" # Use correct Literal value
93
+ banner_mode="ALL", # Use correct Literal value
108
94
  )
109
-
95
+
110
96
  assert len(contest.balloon_color) == 2
111
97
  assert contest.balloon_color[0].color == "#fff"
112
98
  assert contest.balloon_color[1].background_color == "rgba(0, 255, 0, 0.7)"
@@ -117,59 +103,59 @@ class TestContest:
117
103
  def test_append_balloon_color(self):
118
104
  """Test append_balloon_color method"""
119
105
  contest = Contest()
120
-
106
+
121
107
  # Initially no colors
122
108
  assert contest.balloon_color is None
123
-
109
+
124
110
  # Add first color
125
- red_color = Color(color="#fff", background_color="red")
111
+ red_color = BalloonColor(color="#fff", background_color="red")
126
112
  contest.append_balloon_color(red_color)
127
-
113
+
128
114
  assert contest.balloon_color is not None
129
115
  assert len(contest.balloon_color) == 1
130
116
  assert contest.balloon_color[0] == red_color
131
-
117
+
132
118
  # Add second color
133
- blue_color = Color(color="#fff", background_color="blue")
119
+ blue_color = BalloonColor(color="#fff", background_color="blue")
134
120
  contest.append_balloon_color(blue_color)
135
-
121
+
136
122
  assert len(contest.balloon_color) == 2
137
123
  assert contest.balloon_color[1] == blue_color
138
124
 
139
125
  def test_fill_problem_id(self):
140
126
  """Test fill_problem_id method"""
141
- contest = Contest(problem_quantity=5)
142
-
127
+ contest = Contest()
128
+
143
129
  # Initially empty
144
- assert contest.problem_id == []
145
-
130
+ assert contest.problem_id is None
131
+
146
132
  # Fill with A-E
147
- contest.fill_problem_id()
148
-
133
+ contest.fill_problem_id(5)
134
+
149
135
  assert len(contest.problem_id) == 5
150
136
  assert contest.problem_id == ["A", "B", "C", "D", "E"]
151
-
137
+
152
138
  # Test with larger quantity
153
- contest.problem_quantity = 10
154
- contest.fill_problem_id()
155
-
139
+ contest.fill_problem_id(10)
140
+
156
141
  assert len(contest.problem_id) == 10
157
142
  assert contest.problem_id == ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
158
143
 
159
144
  def test_fill_balloon_color(self):
160
145
  """Test fill_balloon_color method"""
161
- contest = Contest(problem_quantity=3)
162
-
146
+ contest = Contest()
147
+
163
148
  # Initially no colors
164
149
  assert contest.balloon_color is None
165
-
166
- # Fill with default colors
150
+
151
+ # Fill problem IDs first, then balloon colors
152
+ contest.fill_problem_id(3)
167
153
  contest.fill_balloon_color()
168
-
154
+
169
155
  assert contest.balloon_color is not None
170
156
  assert len(contest.balloon_color) == 3
171
- assert all(isinstance(color, Color) for color in contest.balloon_color)
172
-
157
+ assert all(isinstance(color, BalloonColor) for color in contest.balloon_color)
158
+
173
159
  # Check first few default colors
174
160
  assert contest.balloon_color[0].background_color == "rgba(189, 14, 14, 0.7)"
175
161
  assert contest.balloon_color[0].color == "#fff"
@@ -183,22 +169,20 @@ class TestContest:
183
169
  contest_name="Complex Contest",
184
170
  start_time=1234567890,
185
171
  end_time=1234567890 + 5 * 60 * 60,
186
- problem_quantity=3,
187
- organization="Complex Org",
188
- medal="ccpc" # Use preset instead of dict
172
+ medal="ccpc", # Use preset instead of dict
189
173
  )
190
-
174
+
191
175
  # Add problem IDs and colors
192
- contest.fill_problem_id()
176
+ contest.fill_problem_id(3)
193
177
  contest.fill_balloon_color()
194
-
178
+
195
179
  # Add logo
196
180
  contest.logo = Image(url="https://example.com/logo.png")
197
-
181
+
198
182
  # Test serialization
199
183
  contest_json = contest.model_dump_json()
200
184
  reconstructed_contest = Contest.model_validate_json(contest_json)
201
-
185
+
202
186
  # Verify all data is preserved
203
187
  assert reconstructed_contest.contest_name == contest.contest_name
204
188
  assert reconstructed_contest.start_time == contest.start_time
@@ -212,18 +196,15 @@ class TestContest:
212
196
  """Test contest with custom group and status display"""
213
197
  custom_group = {"team_a": "Team A", "team_b": "Team B"}
214
198
  custom_status = {"show_penalty": True, "show_time": False}
215
-
216
- contest = Contest(
217
- group=custom_group,
218
- status_time_display=custom_status
219
- )
220
-
199
+
200
+ contest = Contest(group=custom_group, status_time_display=custom_status)
201
+
221
202
  # Custom values should override defaults
222
203
  assert contest.group == custom_group
223
204
  assert contest.status_time_display == custom_status
224
-
205
+
225
206
  # Test serialization preserves custom values
226
207
  contest_json = contest.model_dump_json()
227
208
  reconstructed = Contest.model_validate_json(contest_json)
228
209
  assert reconstructed.group == custom_group
229
- assert reconstructed.status_time_display == custom_status
210
+ assert reconstructed.status_time_display == custom_status
@@ -3,7 +3,7 @@ import json
3
3
  import pytest
4
4
 
5
5
  from xcpcio import constants
6
- from xcpcio.types import Reaction, Submission, Submissions
6
+ from xcpcio.types import SubmissionReaction, Submission, Submissions
7
7
 
8
8
 
9
9
  class TestSubmission:
@@ -12,7 +12,7 @@ class TestSubmission:
12
12
  def test_submission_creation_defaults(self):
13
13
  """Test Submission creation with default values"""
14
14
  submission = Submission()
15
- assert submission.id == ""
15
+ assert submission.id is None
16
16
  assert submission.team_id == ""
17
17
  assert submission.problem_id == 0
18
18
  assert submission.timestamp == 0
@@ -24,7 +24,7 @@ class TestSubmission:
24
24
 
25
25
  def test_submission_creation_with_values(self):
26
26
  """Test Submission creation with provided values"""
27
- reaction = Reaction(url="https://reaction.com/video.mp4")
27
+ reaction = SubmissionReaction(url="https://reaction.com/video.mp4")
28
28
  submission = Submission(
29
29
  id="sub_001",
30
30
  status=constants.SUBMISSION_STATUS_ACCEPTED,
@@ -79,7 +79,7 @@ class TestSubmission:
79
79
 
80
80
  def test_submission_with_reaction_serialization(self):
81
81
  """Test Submission with Reaction serialization"""
82
- reaction = Reaction(url="https://example.com/reaction.mp4")
82
+ reaction = SubmissionReaction(url="https://example.com/reaction.mp4")
83
83
  submission = Submission(
84
84
  id="sub_003",
85
85
  status=constants.SUBMISSION_STATUS_ACCEPTED,
@@ -125,7 +125,7 @@ class TestSubmissions:
125
125
  timestamp=1234567891,
126
126
  time=300,
127
127
  language="C++",
128
- reaction=Reaction(url="https://reaction.com/video.mp4"),
128
+ reaction=SubmissionReaction(url="https://reaction.com/video.mp4"),
129
129
  ),
130
130
  Submission(
131
131
  id="sub_003",
@@ -214,7 +214,7 @@ class TestSubmissions:
214
214
  problem_id=1,
215
215
  timestamp=123,
216
216
  ),
217
- Submission(), # All defaults
217
+ Submission(id="sub_default"), # All other defaults
218
218
  ]
219
219
  )
220
220
 
@@ -13,9 +13,9 @@ class TestTeam:
13
13
  team = Team()
14
14
  assert team.id == ""
15
15
  assert team.name == ""
16
- assert team.organization == ""
16
+ assert team.organization is None
17
17
  assert team.members is None
18
- assert team.coach is None
18
+ assert team.coaches is None
19
19
  assert team.location is None
20
20
  assert team.group == []
21
21
  assert team.extra == {}
@@ -27,7 +27,7 @@ class TestTeam:
27
27
  name="Test Team",
28
28
  organization="Test University",
29
29
  members=["Alice", "Bob", "Charlie"],
30
- coach="Dr. Smith",
30
+ coaches="Dr. Smith",
31
31
  location="Building A",
32
32
  )
33
33
 
@@ -35,7 +35,7 @@ class TestTeam:
35
35
  assert team.name == "Test Team"
36
36
  assert team.organization == "Test University"
37
37
  assert team.members == ["Alice", "Bob", "Charlie"]
38
- assert team.coach == "Dr. Smith"
38
+ assert team.coaches == "Dr. Smith"
39
39
  assert team.location == "Building A"
40
40
 
41
41
  def test_group_management(self):
@@ -75,7 +75,7 @@ class TestTeamSerialization:
75
75
  name="Alpha Team",
76
76
  organization="University A",
77
77
  members=["Alice", "Bob", "Charlie"],
78
- coach="Dr. Smith",
78
+ coaches="Dr. Smith",
79
79
  location="Building A",
80
80
  group=["undergraduate", "local"],
81
81
  extra={"room": "101", "contact": "alice@test.edu"},
@@ -90,17 +90,24 @@ class TestTeamSerialization:
90
90
  assert team_dict["name"] == "Alpha Team"
91
91
  assert team_dict["organization"] == "University A"
92
92
  assert team_dict["members"] == ["Alice", "Bob", "Charlie"]
93
- assert team_dict["coach"] == "Dr. Smith"
93
+ assert team_dict["coaches"] == "Dr. Smith"
94
94
  assert team_dict["location"] == "Building A"
95
95
  assert team_dict["group"] == ["undergraduate", "local"]
96
- assert team_dict["extra"] == {"room": "101", "contact": "alice@test.edu"}
96
+ assert "extra" not in team_dict # extra field is excluded from serialization
97
97
 
98
98
  def test_model_validate(self, sample_team: Team):
99
99
  """Test Team model_validate method"""
100
100
  team_dict = sample_team.model_dump()
101
101
  reconstructed_team = Team.model_validate(team_dict)
102
102
 
103
- assert reconstructed_team == sample_team
103
+ assert reconstructed_team.id == sample_team.id
104
+ assert reconstructed_team.name == sample_team.name
105
+ assert reconstructed_team.organization == sample_team.organization
106
+ assert reconstructed_team.members == sample_team.members
107
+ assert reconstructed_team.coaches == sample_team.coaches
108
+ assert reconstructed_team.location == sample_team.location
109
+ assert reconstructed_team.group == sample_team.group
110
+ assert reconstructed_team.extra == {} # extra is not serialized
104
111
 
105
112
  def test_model_dump_json(self, sample_team: Team):
106
113
  """Test Team model_dump_json method"""
@@ -117,21 +124,42 @@ class TestTeamSerialization:
117
124
  team_json = sample_team.model_dump_json()
118
125
  reconstructed_team = Team.model_validate_json(team_json)
119
126
 
120
- assert reconstructed_team == sample_team
127
+ assert reconstructed_team.id == sample_team.id
128
+ assert reconstructed_team.name == sample_team.name
129
+ assert reconstructed_team.organization == sample_team.organization
130
+ assert reconstructed_team.members == sample_team.members
131
+ assert reconstructed_team.coaches == sample_team.coaches
132
+ assert reconstructed_team.location == sample_team.location
133
+ assert reconstructed_team.group == sample_team.group
134
+ assert reconstructed_team.extra == {} # extra is not serialized
121
135
 
122
136
  def test_round_trip_dict(self, sample_team: Team):
123
137
  """Test complete round-trip through dict serialization"""
124
138
  team_dict = sample_team.model_dump()
125
139
  reconstructed_team = Team.model_validate(team_dict)
126
140
 
127
- assert reconstructed_team == sample_team
141
+ assert reconstructed_team.id == sample_team.id
142
+ assert reconstructed_team.name == sample_team.name
143
+ assert reconstructed_team.organization == sample_team.organization
144
+ assert reconstructed_team.members == sample_team.members
145
+ assert reconstructed_team.coaches == sample_team.coaches
146
+ assert reconstructed_team.location == sample_team.location
147
+ assert reconstructed_team.group == sample_team.group
148
+ assert reconstructed_team.extra == {} # extra is excluded and won't round-trip
128
149
 
129
150
  def test_round_trip_json(self, sample_team: Team):
130
151
  """Test complete round-trip through JSON serialization"""
131
152
  team_json = sample_team.model_dump_json()
132
153
  reconstructed_team = Team.model_validate_json(team_json)
133
154
 
134
- assert reconstructed_team == sample_team
155
+ assert reconstructed_team.id == sample_team.id
156
+ assert reconstructed_team.name == sample_team.name
157
+ assert reconstructed_team.organization == sample_team.organization
158
+ assert reconstructed_team.members == sample_team.members
159
+ assert reconstructed_team.coaches == sample_team.coaches
160
+ assert reconstructed_team.location == sample_team.location
161
+ assert reconstructed_team.group == sample_team.group
162
+ assert reconstructed_team.extra == {} # extra is excluded and won't round-trip
135
163
 
136
164
  def test_minimal_team_serialization(self):
137
165
  """Test serialization of team with default/minimal values"""
@@ -161,7 +189,7 @@ class TestTeamList:
161
189
  name="Alpha Team",
162
190
  organization="University A",
163
191
  members=["Alice", "Bob"],
164
- coach="Coach A",
192
+ coaches="Coach A",
165
193
  ),
166
194
  Team(
167
195
  id="team002",
@@ -1,5 +1,5 @@
1
1
  from xcpcio import constants
2
- from xcpcio.types import Color, ContestOptions, Image, Reaction
2
+ from xcpcio.types import BalloonColor, ContestOptions, Image, SubmissionReaction
3
3
 
4
4
 
5
5
  class TestImage:
@@ -35,18 +35,18 @@ class TestImage:
35
35
  assert reconstructed_image == image
36
36
 
37
37
 
38
- class TestColor:
39
- """Test cases for Color Pydantic model"""
38
+ class TestBalloonColor:
39
+ """Test cases for BalloonColor Pydantic model"""
40
40
 
41
41
  def test_color_creation(self):
42
- """Test Color creation"""
43
- color = Color(color="#ffffff", background_color="#000000")
42
+ """Test BalloonColor creation"""
43
+ color = BalloonColor(color="#ffffff", background_color="#000000")
44
44
  assert color.color == "#ffffff"
45
45
  assert color.background_color == "#000000"
46
46
 
47
47
  def test_color_serialization(self):
48
- """Test Color serialization and deserialization"""
49
- color = Color(color="red", background_color="blue")
48
+ """Test BalloonColor serialization and deserialization"""
49
+ color = BalloonColor(color="red", background_color="blue")
50
50
 
51
51
  # Test model_dump
52
52
  color_dict = color.model_dump()
@@ -55,7 +55,7 @@ class TestColor:
55
55
 
56
56
  # Test JSON round-trip
57
57
  color_json = color.model_dump_json()
58
- reconstructed_color = Color.model_validate_json(color_json)
58
+ reconstructed_color = BalloonColor.model_validate_json(color_json)
59
59
  assert reconstructed_color == color
60
60
 
61
61
 
@@ -96,22 +96,22 @@ class TestContestOptions:
96
96
  assert reconstructed_options == options
97
97
 
98
98
 
99
- class TestReaction:
100
- """Test cases for Reaction Pydantic model"""
99
+ class TestSubmissionReaction:
100
+ """Test cases for SubmissionReaction Pydantic model"""
101
101
 
102
102
  def test_reaction_empty(self):
103
- """Test Reaction with default values"""
104
- reaction = Reaction()
105
- assert reaction.url is None
103
+ """Test SubmissionReaction with default values"""
104
+ reaction = SubmissionReaction(url="")
105
+ assert reaction.url == ""
106
106
 
107
107
  def test_reaction_with_url(self):
108
- """Test Reaction with URL"""
109
- reaction = Reaction(url="https://reaction.com/video.mp4")
108
+ """Test SubmissionReaction with URL"""
109
+ reaction = SubmissionReaction(url="https://reaction.com/video.mp4")
110
110
  assert reaction.url == "https://reaction.com/video.mp4"
111
111
 
112
112
  def test_reaction_serialization(self):
113
- """Test Reaction serialization"""
114
- reaction = Reaction(url="https://test.com/reaction.mp4")
113
+ """Test SubmissionReaction serialization"""
114
+ reaction = SubmissionReaction(url="https://test.com/reaction.mp4")
115
115
 
116
116
  # Test model_dump
117
117
  reaction_dict = reaction.model_dump()
@@ -119,17 +119,17 @@ class TestReaction:
119
119
 
120
120
  # Test JSON round-trip
121
121
  reaction_json = reaction.model_dump_json()
122
- reconstructed_reaction = Reaction.model_validate_json(reaction_json)
122
+ reconstructed_reaction = SubmissionReaction.model_validate_json(reaction_json)
123
123
  assert reconstructed_reaction == reaction
124
124
 
125
125
  def test_reaction_no_url_serialization(self):
126
- """Test Reaction serialization with no URL"""
127
- reaction = Reaction()
126
+ """Test SubmissionReaction serialization with no URL"""
127
+ reaction = SubmissionReaction(url="")
128
128
 
129
129
  reaction_dict = reaction.model_dump()
130
- assert reaction_dict["url"] is None
130
+ assert reaction_dict["url"] == ""
131
131
 
132
132
  # Test JSON round-trip
133
133
  reaction_json = reaction.model_dump_json()
134
- reconstructed_reaction = Reaction.model_validate_json(reaction_json)
134
+ reconstructed_reaction = SubmissionReaction.model_validate_json(reaction_json)
135
135
  assert reconstructed_reaction == reaction
@@ -0,0 +1,4 @@
1
+ from . import clics, constants, types
2
+ from .__version__ import __version__
3
+
4
+ __all__ = [constants, types, clics, __version__]
@@ -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.2'
4
+ __version__ = VERSION = '0.82.0'
@@ -0,0 +1,17 @@
1
+ from .client import ApiClient
2
+ from .models import (
3
+ FileData,
4
+ HTTPValidationError,
5
+ UploadBoardDataReq,
6
+ UploadBoardDataResp,
7
+ ValidationError,
8
+ )
9
+
10
+ __all__ = [
11
+ ApiClient,
12
+ FileData,
13
+ HTTPValidationError,
14
+ UploadBoardDataReq,
15
+ UploadBoardDataResp,
16
+ ValidationError,
17
+ ]