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.
- {xcpcio-0.64.2 → xcpcio-0.82.0}/PKG-INFO +5 -3
- {xcpcio-0.64.2 → xcpcio-0.82.0}/README.md +4 -2
- {xcpcio-0.64.2 → xcpcio-0.82.0}/pyproject.toml +3 -1
- {xcpcio-0.64.2 → xcpcio-0.82.0}/scripts/generate_ccs_models.sh +1 -1
- {xcpcio-0.64.2 → xcpcio-0.82.0}/tests/test_contest.py +52 -71
- {xcpcio-0.64.2 → xcpcio-0.82.0}/tests/test_submission.py +6 -6
- {xcpcio-0.64.2 → xcpcio-0.82.0}/tests/test_team.py +40 -12
- {xcpcio-0.64.2 → xcpcio-0.82.0}/tests/test_types.py +22 -22
- xcpcio-0.82.0/xcpcio/__init__.py +4 -0
- {xcpcio-0.64.2 → xcpcio-0.82.0}/xcpcio/__version__.py +1 -1
- xcpcio-0.82.0/xcpcio/api/__init__.py +17 -0
- xcpcio-0.82.0/xcpcio/api/client.py +74 -0
- xcpcio-0.82.0/xcpcio/api/models.py +36 -0
- xcpcio-0.64.2/cli/ccs_archiver_cli.py → xcpcio-0.82.0/xcpcio/app/clics_archiver.py +12 -10
- xcpcio-0.64.2/app/contest_api_server.py → xcpcio-0.82.0/xcpcio/app/clics_server.py +13 -15
- xcpcio-0.82.0/xcpcio/app/clics_uploader.py +103 -0
- xcpcio-0.82.0/xcpcio/clics/__init__.py +9 -0
- xcpcio-0.82.0/xcpcio/clics/api_server/app.py +43 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/dependencies.py +3 -4
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/__init__.py +1 -1
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/server.py +15 -35
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/services/__init__.py +3 -1
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/services/contest_service.py +4 -4
- xcpcio-0.82.0/xcpcio/clics/base/types.py +19 -0
- xcpcio-0.82.0/xcpcio/clics/clics_api_client.py +251 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/contest_archiver.py +27 -168
- xcpcio-0.82.0/xcpcio/clics/contest_uploader.py +304 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/model/model_2023_06/__init__.py +1 -1
- xcpcio-0.82.0/xcpcio/clics/reader/__init__.py +7 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/reader/contest_package_reader.py +64 -30
- xcpcio-0.64.2/xcpcio/ccs/reader/base_ccs_reader.py → xcpcio-0.82.0/xcpcio/clics/reader/interface.py +6 -2
- {xcpcio-0.64.2 → xcpcio-0.82.0}/xcpcio/types.py +125 -43
- 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/base/types.py +0 -9
- xcpcio-0.64.2/xcpcio/ccs/reader/__init__.py +0 -0
- {xcpcio-0.64.2 → xcpcio-0.82.0}/.gitignore +0 -0
- {xcpcio-0.64.2 → xcpcio-0.82.0}/.python-version +0 -0
- {xcpcio-0.64.2 → xcpcio-0.82.0}/tests/__init__.py +0 -0
- {xcpcio-0.64.2 → xcpcio-0.82.0}/uv.lock +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/__init__.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/access.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/accounts.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/awards.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/clarifications.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/contests.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/general.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/groups.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/judgement_types.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/judgements.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/languages.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/organizations.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/problems.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/runs.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/submissions.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/api_server/routes/teams.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/base/__init__.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/model/__init__.py +0 -0
- {xcpcio-0.64.2/xcpcio/ccs → xcpcio-0.82.0/xcpcio/clics}/model/model_2023_06/model.py +0 -0
- {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.
|
|
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
|
-
- [
|
|
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,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.
|
|
16
|
+
assert contest.thaw_time == 0x3F3F3F3F3F3F3F3F
|
|
19
17
|
assert contest.penalty == 20 * 60 # 20 minutes
|
|
20
|
-
assert contest.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
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.
|
|
154
|
-
|
|
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(
|
|
162
|
-
|
|
146
|
+
contest = Contest()
|
|
147
|
+
|
|
163
148
|
# Initially no colors
|
|
164
149
|
assert contest.balloon_color is None
|
|
165
|
-
|
|
166
|
-
# Fill
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
|
@@ -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.
|
|
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
|
+
]
|