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.
- {xcpcio-0.64.2 → xcpcio-0.76.8}/PKG-INFO +5 -3
- {xcpcio-0.64.2 → xcpcio-0.76.8}/README.md +4 -2
- {xcpcio-0.64.2 → xcpcio-0.76.8}/pyproject.toml +3 -1
- {xcpcio-0.64.2 → xcpcio-0.76.8}/scripts/generate_ccs_models.sh +1 -1
- {xcpcio-0.64.2 → xcpcio-0.76.8}/tests/test_contest.py +53 -65
- {xcpcio-0.64.2 → xcpcio-0.76.8}/tests/test_submission.py +6 -6
- {xcpcio-0.64.2 → xcpcio-0.76.8}/tests/test_team.py +39 -11
- {xcpcio-0.64.2 → xcpcio-0.76.8}/tests/test_types.py +22 -22
- xcpcio-0.76.8/xcpcio/__init__.py +4 -0
- {xcpcio-0.64.2 → xcpcio-0.76.8}/xcpcio/__version__.py +1 -1
- xcpcio-0.76.8/xcpcio/api/__init__.py +17 -0
- xcpcio-0.76.8/xcpcio/api/client.py +74 -0
- xcpcio-0.76.8/xcpcio/api/models.py +36 -0
- xcpcio-0.64.2/cli/ccs_archiver_cli.py → xcpcio-0.76.8/xcpcio/app/clics_archiver.py +12 -10
- xcpcio-0.64.2/app/contest_api_server.py → xcpcio-0.76.8/xcpcio/app/clics_server.py +13 -15
- xcpcio-0.76.8/xcpcio/app/clics_uploader.py +103 -0
- xcpcio-0.76.8/xcpcio/clics/__init__.py +9 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/app.py +43 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/dependencies.py +3 -4
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/__init__.py +1 -1
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/server.py +15 -35
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/services/__init__.py +3 -1
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/services/contest_service.py +4 -4
- xcpcio-0.76.8/xcpcio/clics/clics_api_client.py +251 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/contest_archiver.py +27 -168
- xcpcio-0.76.8/xcpcio/clics/contest_uploader.py +304 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/model/model_2023_06/__init__.py +1 -1
- xcpcio-0.76.8/xcpcio/clics/reader/__init__.py +7 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/reader/contest_package_reader.py +42 -30
- xcpcio-0.64.2/xcpcio/ccs/reader/base_ccs_reader.py → xcpcio-0.76.8/xcpcio/clics/reader/interface.py +2 -2
- {xcpcio-0.64.2 → xcpcio-0.76.8}/xcpcio/types.py +115 -41
- xcpcio-0.64.2/xcpcio/__init__.py +0 -4
- xcpcio-0.64.2/xcpcio/ccs/__init__.py +0 -3
- xcpcio-0.64.2/xcpcio/ccs/reader/__init__.py +0 -0
- {xcpcio-0.64.2 → xcpcio-0.76.8}/.gitignore +0 -0
- {xcpcio-0.64.2 → xcpcio-0.76.8}/.python-version +0 -0
- {xcpcio-0.64.2 → xcpcio-0.76.8}/tests/__init__.py +0 -0
- {xcpcio-0.64.2 → xcpcio-0.76.8}/uv.lock +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/__init__.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/access.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/accounts.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/awards.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/clarifications.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/contests.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/general.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/groups.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/judgement_types.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/judgements.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/languages.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/organizations.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/problems.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/runs.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/submissions.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/api_server/routes/teams.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/base/__init__.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/base/types.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/model/__init__.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/model/model_2023_06/model.py +0 -0
- {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.
|
|
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
|
-
- [
|
|
82
|
+
- [Clics Utility Guide](https://xcpcio.com/guide/clics-utility)
|
|
83
83
|
|
|
84
84
|
## License
|
|
85
85
|
|
|
86
|
-
MIT License © 2020-PRESENT [
|
|
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
|
-
- [
|
|
52
|
+
- [Clics Utility Guide](https://xcpcio.com/guide/clics-utility)
|
|
53
53
|
|
|
54
54
|
## License
|
|
55
55
|
|
|
56
|
-
MIT License © 2020-PRESENT [
|
|
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
|
-
|
|
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/
|
|
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
|
|
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.
|
|
16
|
+
assert contest.thaw_time == 0x3F3F3F3F3F3F3F3F
|
|
19
17
|
assert contest.penalty == 20 * 60 # 20 minutes
|
|
20
|
-
assert contest.
|
|
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
|
|
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
|
-
|
|
96
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
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.
|
|
154
|
-
|
|
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(
|
|
162
|
-
|
|
152
|
+
contest = Contest()
|
|
153
|
+
|
|
163
154
|
# Initially no colors
|
|
164
155
|
assert contest.balloon_color is None
|
|
165
|
-
|
|
166
|
-
# Fill
|
|
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,
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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=
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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["
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
39
|
-
"""Test cases for
|
|
38
|
+
class TestBalloonColor:
|
|
39
|
+
"""Test cases for BalloonColor Pydantic model"""
|
|
40
40
|
|
|
41
41
|
def test_color_creation(self):
|
|
42
|
-
"""Test
|
|
43
|
-
color =
|
|
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
|
|
49
|
-
color =
|
|
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 =
|
|
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
|
|
100
|
-
"""Test cases for
|
|
99
|
+
class TestSubmissionReaction:
|
|
100
|
+
"""Test cases for SubmissionReaction Pydantic model"""
|
|
101
101
|
|
|
102
102
|
def test_reaction_empty(self):
|
|
103
|
-
"""Test
|
|
104
|
-
reaction =
|
|
105
|
-
assert reaction.url
|
|
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
|
|
109
|
-
reaction =
|
|
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
|
|
114
|
-
reaction =
|
|
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 =
|
|
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
|
|
127
|
-
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"]
|
|
130
|
+
assert reaction_dict["url"] == ""
|
|
131
131
|
|
|
132
132
|
# Test JSON round-trip
|
|
133
133
|
reaction_json = reaction.model_dump_json()
|
|
134
|
-
reconstructed_reaction =
|
|
134
|
+
reconstructed_reaction = SubmissionReaction.model_validate_json(reaction_json)
|
|
135
135
|
assert reconstructed_reaction == reaction
|
|
@@ -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
|
+
]
|