xcpcio 0.63.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.76.8/.gitignore +10 -0
- xcpcio-0.76.8/.python-version +1 -0
- xcpcio-0.76.8/PKG-INFO +88 -0
- xcpcio-0.76.8/README.md +58 -0
- {xcpcio-0.63.2 → xcpcio-0.76.8}/pyproject.toml +34 -2
- xcpcio-0.76.8/scripts/generate_ccs_models.sh +69 -0
- xcpcio-0.76.8/tests/__init__.py +0 -0
- {xcpcio-0.63.2 → xcpcio-0.76.8}/tests/test_contest.py +53 -65
- {xcpcio-0.63.2 → xcpcio-0.76.8}/tests/test_submission.py +6 -6
- {xcpcio-0.63.2 → xcpcio-0.76.8}/tests/test_team.py +39 -11
- {xcpcio-0.63.2 → xcpcio-0.76.8}/tests/test_types.py +22 -22
- xcpcio-0.76.8/uv.lock +1607 -0
- xcpcio-0.76.8/xcpcio/__init__.py +4 -0
- xcpcio-0.76.8/xcpcio/__version__.py +4 -0
- 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.76.8/xcpcio/app/clics_archiver.py +223 -0
- xcpcio-0.76.8/xcpcio/app/clics_server.py +138 -0
- 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/__init__.py +9 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/app.py +43 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/dependencies.py +52 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/__init__.py +52 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/access.py +18 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/accounts.py +35 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/awards.py +33 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/clarifications.py +34 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/contests.py +90 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/general.py +33 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/groups.py +34 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/judgement_types.py +34 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/judgements.py +35 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/languages.py +34 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/organizations.py +50 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/problems.py +50 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/runs.py +35 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/submissions.py +50 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/routes/teams.py +50 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/server.py +63 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/services/__init__.py +11 -0
- xcpcio-0.76.8/xcpcio/clics/api_server/services/contest_service.py +194 -0
- xcpcio-0.76.8/xcpcio/clics/base/__init__.py +3 -0
- xcpcio-0.76.8/xcpcio/clics/base/types.py +9 -0
- xcpcio-0.76.8/xcpcio/clics/clics_api_client.py +251 -0
- xcpcio-0.76.8/xcpcio/clics/contest_archiver.py +297 -0
- xcpcio-0.76.8/xcpcio/clics/contest_uploader.py +304 -0
- {xcpcio-0.63.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.76.8/xcpcio/clics/reader/contest_package_reader.py +343 -0
- xcpcio-0.76.8/xcpcio/clics/reader/interface.py +165 -0
- {xcpcio-0.63.2 → xcpcio-0.76.8}/xcpcio/types.py +115 -41
- xcpcio-0.63.2/PKG-INFO +0 -21
- xcpcio-0.63.2/README.md +0 -1
- xcpcio-0.63.2/setup.cfg +0 -4
- xcpcio-0.63.2/xcpcio/__init__.py +0 -5
- xcpcio-0.63.2/xcpcio/ccs/__init__.py +0 -3
- xcpcio-0.63.2/xcpcio.egg-info/PKG-INFO +0 -21
- xcpcio-0.63.2/xcpcio.egg-info/SOURCES.txt +0 -18
- xcpcio-0.63.2/xcpcio.egg-info/dependency_links.txt +0 -1
- xcpcio-0.63.2/xcpcio.egg-info/requires.txt +0 -1
- xcpcio-0.63.2/xcpcio.egg-info/top_level.txt +0 -1
- {xcpcio-0.63.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/model/__init__.py +0 -0
- {xcpcio-0.63.2/xcpcio/ccs → xcpcio-0.76.8/xcpcio/clics}/model/model_2023_06/model.py +0 -0
- {xcpcio-0.63.2 → xcpcio-0.76.8}/xcpcio/constants.py +0 -0
xcpcio-0.76.8/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12.11
|
xcpcio-0.76.8/PKG-INFO
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xcpcio
|
|
3
|
+
Version: 0.76.8
|
|
4
|
+
Summary: xcpcio python lib
|
|
5
|
+
Project-URL: homepage, https://github.com/xcpcio/xcpcio
|
|
6
|
+
Project-URL: documentation, https://github.com/xcpcio/xcpcio
|
|
7
|
+
Project-URL: repository, https://github.com/xcpcio/xcpcio
|
|
8
|
+
Author-email: Dup4 <hi@dup4.com>
|
|
9
|
+
Maintainer-email: Dup4 <hi@dup4.com>, cubercsl <hi@cubercsl.site>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
Keywords: xcpcio
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: aiofiles>=23.0.0
|
|
20
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
21
|
+
Requires-Dist: click>=8.0.0
|
|
22
|
+
Requires-Dist: fastapi>=0.117.1
|
|
23
|
+
Requires-Dist: pydantic>=2.11.7
|
|
24
|
+
Requires-Dist: pyyaml>=6.0.0
|
|
25
|
+
Requires-Dist: semver>=3.0.0
|
|
26
|
+
Requires-Dist: tenacity>=8.0.0
|
|
27
|
+
Requires-Dist: uvicorn>=0.36.0
|
|
28
|
+
Requires-Dist: zstandard>=0.25.0
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# xcpcio-python
|
|
32
|
+
|
|
33
|
+
Python library and CLI tools for XCPCIO.
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- **Type Definitions**: Pydantic models for contest data structures (teams, submissions, problems, etc.)
|
|
38
|
+
- **Constants**: Shared constants for submission statuses, time units, and penalty calculations
|
|
39
|
+
- **CCS Archiver**: CLI tool to archive CCS API data to contest package format
|
|
40
|
+
- **Contest API Server**: CLI tool to serve contest packages via CCS API
|
|
41
|
+
- **Cross-Language Compatibility**: Mirrors TypeScript types for data consistency
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install xcpcio
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or install with [uv](https://github.com/astral-sh/uv):
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv add xcpcio
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Development
|
|
56
|
+
|
|
57
|
+
### Setup
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Clone repository
|
|
61
|
+
git clone https://github.com/xcpcio/xcpcio.git
|
|
62
|
+
cd xcpcio/python
|
|
63
|
+
|
|
64
|
+
# Install dependencies with uv
|
|
65
|
+
uv sync
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Testing
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Run tests
|
|
72
|
+
uv run pytest
|
|
73
|
+
|
|
74
|
+
# Run specific test file
|
|
75
|
+
uv run pytest tests/test_types.py
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Documentation
|
|
79
|
+
|
|
80
|
+
For detailed documentation, visit:
|
|
81
|
+
|
|
82
|
+
- [Clics Utility Guide](https://xcpcio.com/guide/clics-utility)
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
[MIT](../LICENSE) License © 2020 - PRESENT [XCPCIO][xcpcio]
|
|
87
|
+
|
|
88
|
+
[xcpcio]: https://xcpcio.com
|
xcpcio-0.76.8/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# xcpcio-python
|
|
2
|
+
|
|
3
|
+
Python library and CLI tools for XCPCIO.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Type Definitions**: Pydantic models for contest data structures (teams, submissions, problems, etc.)
|
|
8
|
+
- **Constants**: Shared constants for submission statuses, time units, and penalty calculations
|
|
9
|
+
- **CCS Archiver**: CLI tool to archive CCS API data to contest package format
|
|
10
|
+
- **Contest API Server**: CLI tool to serve contest packages via CCS API
|
|
11
|
+
- **Cross-Language Compatibility**: Mirrors TypeScript types for data consistency
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install xcpcio
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install with [uv](https://github.com/astral-sh/uv):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
uv add xcpcio
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Development
|
|
26
|
+
|
|
27
|
+
### Setup
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Clone repository
|
|
31
|
+
git clone https://github.com/xcpcio/xcpcio.git
|
|
32
|
+
cd xcpcio/python
|
|
33
|
+
|
|
34
|
+
# Install dependencies with uv
|
|
35
|
+
uv sync
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Testing
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Run tests
|
|
42
|
+
uv run pytest
|
|
43
|
+
|
|
44
|
+
# Run specific test file
|
|
45
|
+
uv run pytest tests/test_types.py
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Documentation
|
|
49
|
+
|
|
50
|
+
For detailed documentation, visit:
|
|
51
|
+
|
|
52
|
+
- [Clics Utility Guide](https://xcpcio.com/guide/clics-utility)
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
[MIT](../LICENSE) License © 2020 - PRESENT [XCPCIO][xcpcio]
|
|
57
|
+
|
|
58
|
+
[xcpcio]: https://xcpcio.com
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "xcpcio"
|
|
3
|
-
version = "0.63.2"
|
|
4
3
|
description = "xcpcio python lib"
|
|
5
4
|
readme = "README.md"
|
|
6
5
|
authors = [ { name = "Dup4", email = "hi@dup4.com" } ]
|
|
6
|
+
maintainers = [
|
|
7
|
+
{ name = "Dup4", email = "hi@dup4.com" },
|
|
8
|
+
{ name = "cubercsl", email = "hi@cubercsl.site" },
|
|
9
|
+
]
|
|
7
10
|
license = "MIT"
|
|
8
11
|
keywords = [ "xcpcio" ]
|
|
9
12
|
classifiers = [
|
|
@@ -15,19 +18,48 @@ classifiers = [
|
|
|
15
18
|
"Programming Language :: Python :: 3.13",
|
|
16
19
|
]
|
|
17
20
|
requires-python = ">=3.11"
|
|
18
|
-
dependencies = [
|
|
21
|
+
dependencies = [
|
|
22
|
+
"pydantic>=2.11.7",
|
|
23
|
+
"aiohttp>=3.8.0",
|
|
24
|
+
"aiofiles>=23.0.0",
|
|
25
|
+
"click>=8.0.0",
|
|
26
|
+
"pyyaml>=6.0.0",
|
|
27
|
+
"semver>=3.0.0",
|
|
28
|
+
"tenacity>=8.0.0",
|
|
29
|
+
"fastapi>=0.117.1",
|
|
30
|
+
"uvicorn>=0.36.0",
|
|
31
|
+
"zstandard>=0.25.0",
|
|
32
|
+
]
|
|
33
|
+
dynamic = ["version"]
|
|
19
34
|
|
|
20
35
|
[project.urls]
|
|
21
36
|
homepage = "https://github.com/xcpcio/xcpcio"
|
|
22
37
|
documentation = "https://github.com/xcpcio/xcpcio"
|
|
23
38
|
repository = "https://github.com/xcpcio/xcpcio"
|
|
24
39
|
|
|
40
|
+
[project.scripts]
|
|
41
|
+
clics-archiver = "xcpcio.app.clics_archiver:main"
|
|
42
|
+
clics-server = "xcpcio.app.clics_server:main"
|
|
43
|
+
clics-uploader = "xcpcio.app.clics_uploader:main"
|
|
44
|
+
|
|
25
45
|
[tool.ruff]
|
|
26
46
|
line-length = 120
|
|
27
47
|
|
|
48
|
+
[build-system]
|
|
49
|
+
requires = ["hatchling", "hatch-nodejs-version"]
|
|
50
|
+
build-backend = "hatchling.build"
|
|
51
|
+
|
|
52
|
+
[tool.hatch.version]
|
|
53
|
+
source = "nodejs"
|
|
54
|
+
path = "../package.json"
|
|
55
|
+
|
|
56
|
+
[tool.hatch.build.hooks.version]
|
|
57
|
+
path = "xcpcio/__version__.py"
|
|
58
|
+
|
|
28
59
|
[dependency-groups]
|
|
29
60
|
dev = [
|
|
30
61
|
"datamodel-code-generator[http]>=0.33.0",
|
|
62
|
+
"hatch>=1.14.2",
|
|
31
63
|
"pytest>=8.4.2",
|
|
32
64
|
"ruff>=0.4.0",
|
|
33
65
|
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
CUR_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")"
|
|
6
|
+
PYTHON_DIR="$(dirname "${CUR_DIR}")"
|
|
7
|
+
CCS_SPECS_DIR="${PYTHON_DIR}/ccs-specs"
|
|
8
|
+
CCS_MODELS_DIR="${PYTHON_DIR}/xcpcio/clics/model"
|
|
9
|
+
|
|
10
|
+
BRANCH="${1:-2023-06}"
|
|
11
|
+
CCS_REPO_URL="https://github.com/icpc/ccs-specs.git"
|
|
12
|
+
|
|
13
|
+
MODEL_DIR_NAME="model_${BRANCH//-/_}"
|
|
14
|
+
OUTPUT_DIR="${CCS_MODELS_DIR}/${MODEL_DIR_NAME}"
|
|
15
|
+
|
|
16
|
+
if [[ -d "${CCS_SPECS_DIR}" ]]; then
|
|
17
|
+
echo "Please remove existing ${CCS_SPECS_DIR} directory first"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if [[ -d "${OUTPUT_DIR}" ]]; then
|
|
22
|
+
echo "Please remove existing ${OUTPUT_DIR} directory first"
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
echo "Generating CCS models for branch: ${BRANCH}"
|
|
27
|
+
|
|
28
|
+
pushd "${PYTHON_DIR}"
|
|
29
|
+
|
|
30
|
+
echo "Cloning CCS specs repository (branch: ${BRANCH})..."
|
|
31
|
+
git clone --branch "${BRANCH}" --depth 1 "${CCS_REPO_URL}" ccs-specs
|
|
32
|
+
|
|
33
|
+
echo "Creating output directory: ${OUTPUT_DIR}"
|
|
34
|
+
mkdir -p "${OUTPUT_DIR}"
|
|
35
|
+
|
|
36
|
+
ENTRY_POINT="${CCS_SPECS_DIR}/json-schema/event-feed-array.json"
|
|
37
|
+
|
|
38
|
+
if [[ ! -f "${ENTRY_POINT}" ]]; then
|
|
39
|
+
echo "Error: ${ENTRY_POINT} not found in ccs-specs directory"
|
|
40
|
+
exit 2
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
echo "Generating Python models using datamodel-codegen..."
|
|
44
|
+
uv run datamodel-codegen \
|
|
45
|
+
--input "${ENTRY_POINT}" \
|
|
46
|
+
--input-file-type jsonschema \
|
|
47
|
+
--output-model-type pydantic_v2.BaseModel \
|
|
48
|
+
--target-python-version "3.11" \
|
|
49
|
+
--output "${OUTPUT_DIR}/model.py"
|
|
50
|
+
|
|
51
|
+
echo "Creating __init__.py file..."
|
|
52
|
+
cat >"${OUTPUT_DIR}/__init__.py" <<EOF
|
|
53
|
+
"""
|
|
54
|
+
CCS (Contest Control System) models for branch ${BRANCH}.
|
|
55
|
+
|
|
56
|
+
Auto-generated from https://github.com/icpc/ccs-specs/tree/${BRANCH}
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
from .model import * # noqa: F403
|
|
60
|
+
EOF
|
|
61
|
+
|
|
62
|
+
echo "CCS models generated successfully!"
|
|
63
|
+
echo "Output directory: ${OUTPUT_DIR}"
|
|
64
|
+
echo "Generated files:"
|
|
65
|
+
ls -la "${OUTPUT_DIR}"
|
|
66
|
+
|
|
67
|
+
echo "Done!"
|
|
68
|
+
|
|
69
|
+
popd
|
|
File without changes
|
|
@@ -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
|
|