xcpcio 0.64.2__tar.gz → 0.76.8__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 (59) hide show
  1. {xcpcio-0.64.2 → xcpcio-0.76.8}/PKG-INFO +5 -3
  2. {xcpcio-0.64.2 → xcpcio-0.76.8}/README.md +4 -2
  3. {xcpcio-0.64.2 → xcpcio-0.76.8}/pyproject.toml +3 -1
  4. {xcpcio-0.64.2 → xcpcio-0.76.8}/scripts/generate_ccs_models.sh +1 -1
  5. {xcpcio-0.64.2 → xcpcio-0.76.8}/tests/test_contest.py +53 -65
  6. {xcpcio-0.64.2 → xcpcio-0.76.8}/tests/test_submission.py +6 -6
  7. {xcpcio-0.64.2 → xcpcio-0.76.8}/tests/test_team.py +39 -11
  8. {xcpcio-0.64.2 → xcpcio-0.76.8}/tests/test_types.py +22 -22
  9. xcpcio-0.76.8/xcpcio/__init__.py +4 -0
  10. {xcpcio-0.64.2 → xcpcio-0.76.8}/xcpcio/__version__.py +1 -1
  11. xcpcio-0.76.8/xcpcio/api/__init__.py +17 -0
  12. xcpcio-0.76.8/xcpcio/api/client.py +74 -0
  13. xcpcio-0.76.8/xcpcio/api/models.py +36 -0
  14. xcpcio-0.64.2/cli/ccs_archiver_cli.py → xcpcio-0.76.8/xcpcio/app/clics_archiver.py +12 -10
  15. xcpcio-0.64.2/app/contest_api_server.py → xcpcio-0.76.8/xcpcio/app/clics_server.py +13 -15
  16. xcpcio-0.76.8/xcpcio/app/clics_uploader.py +103 -0
  17. xcpcio-0.76.8/xcpcio/clics/__init__.py +9 -0
  18. xcpcio-0.76.8/xcpcio/clics/api_server/app.py +43 -0
  19. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/dependencies.py +3 -4
  20. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/__init__.py +1 -1
  21. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/server.py +15 -35
  22. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/services/__init__.py +3 -1
  23. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/services/contest_service.py +4 -4
  24. xcpcio-0.76.8/xcpcio/clics/clics_api_client.py +251 -0
  25. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/contest_archiver.py +27 -168
  26. xcpcio-0.76.8/xcpcio/clics/contest_uploader.py +304 -0
  27. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/model/model_2023_06/__init__.py +1 -1
  28. xcpcio-0.76.8/xcpcio/clics/reader/__init__.py +7 -0
  29. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/reader/contest_package_reader.py +42 -30
  30. xcpcio-0.64.2/xcpcio/ccs/reader/base_ccs_reader.py → xcpcio-0.76.8/xcpcio/clics/reader/interface.py +2 -2
  31. {xcpcio-0.64.2 → xcpcio-0.76.8}/xcpcio/types.py +115 -41
  32. xcpcio-0.64.2/xcpcio/__init__.py +0 -4
  33. xcpcio-0.64.2/xcpcio/ccs/__init__.py +0 -3
  34. xcpcio-0.64.2/xcpcio/ccs/reader/__init__.py +0 -0
  35. {xcpcio-0.64.2 → xcpcio-0.76.8}/.gitignore +0 -0
  36. {xcpcio-0.64.2 → xcpcio-0.76.8}/.python-version +0 -0
  37. {xcpcio-0.64.2 → xcpcio-0.76.8}/tests/__init__.py +0 -0
  38. {xcpcio-0.64.2 → xcpcio-0.76.8}/uv.lock +0 -0
  39. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/__init__.py +0 -0
  40. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/access.py +0 -0
  41. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/accounts.py +0 -0
  42. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/awards.py +0 -0
  43. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/clarifications.py +0 -0
  44. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/contests.py +0 -0
  45. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/general.py +0 -0
  46. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/groups.py +0 -0
  47. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/judgement_types.py +0 -0
  48. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/judgements.py +0 -0
  49. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/languages.py +0 -0
  50. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/organizations.py +0 -0
  51. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/problems.py +0 -0
  52. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/runs.py +0 -0
  53. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/submissions.py +0 -0
  54. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/teams.py +0 -0
  55. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/base/__init__.py +0 -0
  56. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/base/types.py +0 -0
  57. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/model/__init__.py +0 -0
  58. {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/model/model_2023_06/model.py +0 -0
  59. {xcpcio-0.64.2 → xcpcio-0.76.8}/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.76.8
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,15 +8,14 @@ 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 == []
18
+ assert contest.problem_id is None
22
19
  assert contest.organization == "School"
23
20
  assert contest.medal is None
24
21
  assert contest.balloon_color is None
@@ -30,18 +27,17 @@ class TestContest:
30
27
  assert contest.tag is None
31
28
  assert contest.board_link is None
32
29
  assert contest.version is None
33
-
30
+
34
31
  # Check default values
35
32
  assert contest.status_time_display == constants.FULL_STATUS_TIME_DISPLAY
36
- assert isinstance(contest.options, ContestOptions)
33
+ assert contest.options is None
37
34
 
38
35
  def test_contest_creation_with_values(self):
39
36
  """Test Contest creation with provided values"""
40
37
  contest_options = ContestOptions(
41
- calculation_of_penalty=constants.CALCULATION_OF_PENALTY_IN_SECONDS,
42
- has_reaction_videos=True
38
+ calculation_of_penalty=constants.CALCULATION_OF_PENALTY_IN_SECONDS, has_reaction_videos=True
43
39
  )
44
-
40
+
45
41
  contest = Contest(
46
42
  contest_name="ICPC World Finals 2024",
47
43
  start_time=1234567890,
@@ -52,13 +48,12 @@ class TestContest:
52
48
  problem_id=["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"],
53
49
  organization="ICPC",
54
50
  medal="icpc", # Use Literal value
55
- options=contest_options
51
+ options=contest_options,
56
52
  )
57
-
53
+
58
54
  assert contest.contest_name == "ICPC World Finals 2024"
59
55
  assert contest.start_time == 1234567890
60
56
  assert contest.end_time == 1234567890 + 5 * 60 * 60
61
- assert contest.problem_quantity == 12
62
57
  assert len(contest.problem_id) == 12
63
58
  assert contest.problem_id[0] == "A"
64
59
  assert contest.problem_id[-1] == "L"
@@ -72,18 +67,16 @@ class TestContest:
72
67
  contest_name="Test Contest",
73
68
  start_time=1000000000,
74
69
  end_time=1000000000 + 60 * 60 * 5,
75
- problem_quantity=5,
76
70
  problem_id=["A", "B", "C", "D", "E"],
77
- organization="Test Org"
71
+ organization="Test Org",
78
72
  )
79
-
73
+
80
74
  # Test model_dump
81
75
  contest_dict = contest.model_dump()
82
76
  assert contest_dict["contest_name"] == "Test Contest"
83
77
  assert contest_dict["start_time"] == 1000000000
84
- assert contest_dict["problem_quantity"] == 5
85
78
  assert contest_dict["organization"] == "Test Org"
86
-
79
+
87
80
  # Test JSON round-trip
88
81
  contest_json = contest.model_dump_json()
89
82
  reconstructed_contest = Contest.model_validate_json(contest_json)
@@ -92,21 +85,20 @@ class TestContest:
92
85
  def test_contest_with_colors_and_images(self):
93
86
  """Test Contest with balloon colors, logo, and banner"""
94
87
  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)")
88
+ BalloonColor(color="#fff", background_color="rgba(255, 0, 0, 0.7)"),
89
+ BalloonColor(color="#000", background_color="rgba(0, 255, 0, 0.7)"),
97
90
  ]
98
91
  logo = Image(url="https://example.com/logo.png", type="png")
99
92
  banner = Image(url="https://example.com/banner.jpg", type="jpg")
100
-
93
+
101
94
  contest = Contest(
102
95
  contest_name="Contest with Media",
103
- problem_quantity=2,
104
96
  balloon_color=colors,
105
97
  logo=logo,
106
98
  banner=banner,
107
- banner_mode="ALL" # Use correct Literal value
99
+ banner_mode="ALL", # Use correct Literal value
108
100
  )
109
-
101
+
110
102
  assert len(contest.balloon_color) == 2
111
103
  assert contest.balloon_color[0].color == "#fff"
112
104
  assert contest.balloon_color[1].background_color == "rgba(0, 255, 0, 0.7)"
@@ -117,59 +109,59 @@ class TestContest:
117
109
  def test_append_balloon_color(self):
118
110
  """Test append_balloon_color method"""
119
111
  contest = Contest()
120
-
112
+
121
113
  # Initially no colors
122
114
  assert contest.balloon_color is None
123
-
115
+
124
116
  # Add first color
125
- red_color = Color(color="#fff", background_color="red")
117
+ red_color = BalloonColor(color="#fff", background_color="red")
126
118
  contest.append_balloon_color(red_color)
127
-
119
+
128
120
  assert contest.balloon_color is not None
129
121
  assert len(contest.balloon_color) == 1
130
122
  assert contest.balloon_color[0] == red_color
131
-
123
+
132
124
  # Add second color
133
- blue_color = Color(color="#fff", background_color="blue")
125
+ blue_color = BalloonColor(color="#fff", background_color="blue")
134
126
  contest.append_balloon_color(blue_color)
135
-
127
+
136
128
  assert len(contest.balloon_color) == 2
137
129
  assert contest.balloon_color[1] == blue_color
138
130
 
139
131
  def test_fill_problem_id(self):
140
132
  """Test fill_problem_id method"""
141
- contest = Contest(problem_quantity=5)
142
-
133
+ contest = Contest()
134
+
143
135
  # Initially empty
144
- assert contest.problem_id == []
145
-
136
+ assert contest.problem_id is None
137
+
146
138
  # Fill with A-E
147
- contest.fill_problem_id()
148
-
139
+ contest.fill_problem_id(5)
140
+
149
141
  assert len(contest.problem_id) == 5
150
142
  assert contest.problem_id == ["A", "B", "C", "D", "E"]
151
-
143
+
152
144
  # Test with larger quantity
153
- contest.problem_quantity = 10
154
- contest.fill_problem_id()
155
-
145
+ contest.fill_problem_id(10)
146
+
156
147
  assert len(contest.problem_id) == 10
157
148
  assert contest.problem_id == ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
158
149
 
159
150
  def test_fill_balloon_color(self):
160
151
  """Test fill_balloon_color method"""
161
- contest = Contest(problem_quantity=3)
162
-
152
+ contest = Contest()
153
+
163
154
  # Initially no colors
164
155
  assert contest.balloon_color is None
165
-
166
- # Fill with default colors
156
+
157
+ # Fill problem IDs first, then balloon colors
158
+ contest.fill_problem_id(3)
167
159
  contest.fill_balloon_color()
168
-
160
+
169
161
  assert contest.balloon_color is not None
170
162
  assert len(contest.balloon_color) == 3
171
- assert all(isinstance(color, Color) for color in contest.balloon_color)
172
-
163
+ assert all(isinstance(color, BalloonColor) for color in contest.balloon_color)
164
+
173
165
  # Check first few default colors
174
166
  assert contest.balloon_color[0].background_color == "rgba(189, 14, 14, 0.7)"
175
167
  assert contest.balloon_color[0].color == "#fff"
@@ -183,22 +175,21 @@ class TestContest:
183
175
  contest_name="Complex Contest",
184
176
  start_time=1234567890,
185
177
  end_time=1234567890 + 5 * 60 * 60,
186
- problem_quantity=3,
187
178
  organization="Complex Org",
188
- medal="ccpc" # Use preset instead of dict
179
+ medal="ccpc", # Use preset instead of dict
189
180
  )
190
-
181
+
191
182
  # Add problem IDs and colors
192
- contest.fill_problem_id()
183
+ contest.fill_problem_id(3)
193
184
  contest.fill_balloon_color()
194
-
185
+
195
186
  # Add logo
196
187
  contest.logo = Image(url="https://example.com/logo.png")
197
-
188
+
198
189
  # Test serialization
199
190
  contest_json = contest.model_dump_json()
200
191
  reconstructed_contest = Contest.model_validate_json(contest_json)
201
-
192
+
202
193
  # Verify all data is preserved
203
194
  assert reconstructed_contest.contest_name == contest.contest_name
204
195
  assert reconstructed_contest.start_time == contest.start_time
@@ -212,18 +203,15 @@ class TestContest:
212
203
  """Test contest with custom group and status display"""
213
204
  custom_group = {"team_a": "Team A", "team_b": "Team B"}
214
205
  custom_status = {"show_penalty": True, "show_time": False}
215
-
216
- contest = Contest(
217
- group=custom_group,
218
- status_time_display=custom_status
219
- )
220
-
206
+
207
+ contest = Contest(group=custom_group, status_time_display=custom_status)
208
+
221
209
  # Custom values should override defaults
222
210
  assert contest.group == custom_group
223
211
  assert contest.status_time_display == custom_status
224
-
212
+
225
213
  # Test serialization preserves custom values
226
214
  contest_json = contest.model_dump_json()
227
215
  reconstructed = Contest.model_validate_json(contest_json)
228
216
  assert reconstructed.group == custom_group
229
- assert reconstructed.status_time_display == custom_status
217
+ 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
 
@@ -15,7 +15,7 @@ class TestTeam:
15
15
  assert team.name == ""
16
16
  assert team.organization == ""
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.76.8'
@@ -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
+ ]