xcpcio 0.63.5__py3-none-any.whl → 0.63.7__py3-none-any.whl
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.
Potentially problematic release.
This version of xcpcio might be problematic. Click here for more details.
- xcpcio/__version__.py +4 -1
- xcpcio/ccs/__init__.py +2 -2
- xcpcio/ccs/api_server/__init__.py +9 -0
- xcpcio/ccs/api_server/dependencies.py +48 -0
- xcpcio/ccs/api_server/routes/__init__.py +52 -0
- xcpcio/ccs/api_server/routes/access.py +18 -0
- xcpcio/ccs/api_server/routes/accounts.py +35 -0
- xcpcio/ccs/api_server/routes/awards.py +33 -0
- xcpcio/ccs/api_server/routes/clarifications.py +34 -0
- xcpcio/ccs/api_server/routes/contests.py +119 -0
- xcpcio/ccs/api_server/routes/general.py +33 -0
- xcpcio/ccs/api_server/routes/groups.py +34 -0
- xcpcio/ccs/api_server/routes/judgement_types.py +34 -0
- xcpcio/ccs/api_server/routes/judgements.py +35 -0
- xcpcio/ccs/api_server/routes/languages.py +34 -0
- xcpcio/ccs/api_server/routes/organizations.py +71 -0
- xcpcio/ccs/api_server/routes/problems.py +69 -0
- xcpcio/ccs/api_server/routes/runs.py +35 -0
- xcpcio/ccs/api_server/routes/submissions.py +71 -0
- xcpcio/ccs/api_server/routes/teams.py +69 -0
- xcpcio/ccs/api_server/server.py +83 -0
- xcpcio/ccs/api_server/services/__init__.py +9 -0
- xcpcio/ccs/api_server/services/contest_service.py +325 -0
- xcpcio/ccs/contest_archiver.py +2 -3
- {xcpcio-0.63.5.dist-info → xcpcio-0.63.7.dist-info}/METADATA +4 -1
- xcpcio-0.63.7.dist-info/RECORD +34 -0
- xcpcio-0.63.5.dist-info/RECORD +0 -13
- {xcpcio-0.63.5.dist-info → xcpcio-0.63.7.dist-info}/WHEEL +0 -0
- {xcpcio-0.63.5.dist-info → xcpcio-0.63.7.dist-info}/entry_points.txt +0 -0
xcpcio/__version__.py
CHANGED
xcpcio/ccs/__init__.py
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from . import contest_archiver, model
|
|
1
|
+
from . import api_server, contest_archiver, model
|
|
2
2
|
|
|
3
|
-
__all__ = [model, contest_archiver]
|
|
3
|
+
__all__ = [model, contest_archiver, api_server]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI Dependencies
|
|
3
|
+
|
|
4
|
+
Dependency injection system for Contest API Server.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Annotated
|
|
9
|
+
|
|
10
|
+
from fastapi import Depends
|
|
11
|
+
|
|
12
|
+
from .services.contest_service import ContestService
|
|
13
|
+
|
|
14
|
+
# Global contest service instance cache
|
|
15
|
+
_contest_service_instance = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_contest_service() -> ContestService:
|
|
19
|
+
"""
|
|
20
|
+
Dependency that provides ContestService instance.
|
|
21
|
+
|
|
22
|
+
This is configured by the main server class and cached globally.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
ContestService instance
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
RuntimeError: If service not configured
|
|
29
|
+
"""
|
|
30
|
+
global _contest_service_instance
|
|
31
|
+
if _contest_service_instance is None:
|
|
32
|
+
raise RuntimeError("ContestService not configured. Call configure_dependencies() first.")
|
|
33
|
+
return _contest_service_instance
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def configure_dependencies(contest_package_dir: Path) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Configure the dependency injection system.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
contest_package_dir: Path to contest package directory
|
|
42
|
+
"""
|
|
43
|
+
global _contest_service_instance
|
|
44
|
+
_contest_service_instance = ContestService(contest_package_dir)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Type alias for dependency injection
|
|
48
|
+
ContestServiceDep = Annotated[ContestService, Depends(get_contest_service)]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Routes package for Contest API Server
|
|
3
|
+
|
|
4
|
+
Contains all API route definitions organized by functionality.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter
|
|
8
|
+
|
|
9
|
+
from . import (
|
|
10
|
+
access,
|
|
11
|
+
accounts,
|
|
12
|
+
awards,
|
|
13
|
+
clarifications,
|
|
14
|
+
contests,
|
|
15
|
+
general,
|
|
16
|
+
groups,
|
|
17
|
+
judgement_types,
|
|
18
|
+
judgements,
|
|
19
|
+
languages,
|
|
20
|
+
organizations,
|
|
21
|
+
problems,
|
|
22
|
+
runs,
|
|
23
|
+
submissions,
|
|
24
|
+
teams,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_router() -> APIRouter:
|
|
29
|
+
"""Create and configure the main API router"""
|
|
30
|
+
router = APIRouter(prefix="/api")
|
|
31
|
+
|
|
32
|
+
# Include all route modules
|
|
33
|
+
router.include_router(access.router, tags=["Access"])
|
|
34
|
+
router.include_router(accounts.router, tags=["Accounts"])
|
|
35
|
+
router.include_router(awards.router, tags=["Awards"])
|
|
36
|
+
router.include_router(clarifications.router, tags=["Clarifications"])
|
|
37
|
+
router.include_router(contests.router, tags=["Contests"])
|
|
38
|
+
router.include_router(general.router, tags=["General"])
|
|
39
|
+
router.include_router(groups.router, tags=["Groups"])
|
|
40
|
+
router.include_router(judgements.router, tags=["Judgements"])
|
|
41
|
+
router.include_router(judgement_types.router, tags=["Judgement types"])
|
|
42
|
+
router.include_router(languages.router, tags=["Languages"])
|
|
43
|
+
router.include_router(organizations.router, tags=["Organizations"])
|
|
44
|
+
router.include_router(problems.router, tags=["Problems"])
|
|
45
|
+
router.include_router(runs.router, tags=["Runs"])
|
|
46
|
+
router.include_router(submissions.router, tags=["Submissions"])
|
|
47
|
+
router.include_router(teams.router, tags=["Teams"])
|
|
48
|
+
|
|
49
|
+
return router
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
__all__ = ["create_router"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter
|
|
5
|
+
|
|
6
|
+
from ..dependencies import ContestServiceDep
|
|
7
|
+
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@router.get(
|
|
13
|
+
"/contests/{contest_id}/access",
|
|
14
|
+
summary="Get access information",
|
|
15
|
+
response_model=Dict[str, Any],
|
|
16
|
+
)
|
|
17
|
+
async def get_access(contest_id: str, service: ContestServiceDep) -> Dict[str, Any]:
|
|
18
|
+
return service.get_access(contest_id)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter
|
|
5
|
+
from fastapi import Path as FastAPIPath
|
|
6
|
+
|
|
7
|
+
from ..dependencies import ContestServiceDep
|
|
8
|
+
|
|
9
|
+
router = APIRouter()
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@router.get(
|
|
14
|
+
"/contests/{contest_id}/accounts",
|
|
15
|
+
summary="Get all the accounts",
|
|
16
|
+
response_model=List[Dict[str, Any]],
|
|
17
|
+
)
|
|
18
|
+
async def get_accounts(
|
|
19
|
+
contest_id: str = FastAPIPath(..., description="Contest identifier"),
|
|
20
|
+
service: ContestServiceDep = None,
|
|
21
|
+
) -> List[Dict[str, Any]]:
|
|
22
|
+
return service.get_accounts(contest_id)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@router.get(
|
|
26
|
+
"/contests/{contest_id}/accounts/{account_id}",
|
|
27
|
+
summary="Get the given account",
|
|
28
|
+
response_model=Dict[str, Any],
|
|
29
|
+
)
|
|
30
|
+
async def get_account(
|
|
31
|
+
contest_id: str = FastAPIPath(..., description="Contest identifier"),
|
|
32
|
+
account_id: str = FastAPIPath(..., description="Account identifier"),
|
|
33
|
+
service: ContestServiceDep = None,
|
|
34
|
+
) -> Dict[str, Any]:
|
|
35
|
+
return service.get_account(contest_id, account_id)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Path
|
|
5
|
+
|
|
6
|
+
from ..dependencies import ContestServiceDep
|
|
7
|
+
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@router.get(
|
|
13
|
+
"/contests/{contest_id}/awards",
|
|
14
|
+
summary="Get all the awards standings for this contest",
|
|
15
|
+
response_model=List[Dict[str, Any]],
|
|
16
|
+
)
|
|
17
|
+
async def get_awards(
|
|
18
|
+
contest_id: str = Path(..., description="Contest identifier"), service: ContestServiceDep = None
|
|
19
|
+
) -> List[Dict[str, Any]]:
|
|
20
|
+
return service.get_awards(contest_id)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@router.get(
|
|
24
|
+
"/contests/{contest_id}/awards/{award_id}",
|
|
25
|
+
summary="Get the specific award for this contest",
|
|
26
|
+
response_model=Dict[str, Any],
|
|
27
|
+
)
|
|
28
|
+
async def get_award(
|
|
29
|
+
contest_id: str = Path(..., description="Contest identifier"),
|
|
30
|
+
award_id: str = Path(..., description="Award identifier"),
|
|
31
|
+
service: ContestServiceDep = None,
|
|
32
|
+
) -> Dict[str, Any]:
|
|
33
|
+
return service.get_award(contest_id, award_id)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Path
|
|
5
|
+
|
|
6
|
+
from ..dependencies import ContestServiceDep
|
|
7
|
+
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@router.get(
|
|
13
|
+
"/contests/{contest_id}/clarifications",
|
|
14
|
+
summary="Get all the clarifications for this contest",
|
|
15
|
+
response_model=List[Dict[str, Any]],
|
|
16
|
+
)
|
|
17
|
+
async def get_clarifications(
|
|
18
|
+
contest_id: str = Path(..., description="Contest identifier"),
|
|
19
|
+
service: ContestServiceDep = None,
|
|
20
|
+
) -> List[Dict[str, Any]]:
|
|
21
|
+
return service.get_clarifications(contest_id)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@router.get(
|
|
25
|
+
"/contests/{contest_id}/clarifications/{clarification_id}",
|
|
26
|
+
summary="Get the given clarifications for this contest",
|
|
27
|
+
response_model=Dict[str, Any],
|
|
28
|
+
)
|
|
29
|
+
async def get_clarification(
|
|
30
|
+
contest_id: str = Path(..., description="Contest identifier"),
|
|
31
|
+
clarification_id: str = Path(..., description="Clarification identifier"),
|
|
32
|
+
service: ContestServiceDep = None,
|
|
33
|
+
) -> Dict[str, Any]:
|
|
34
|
+
return service.get_clarification(contest_id, clarification_id)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
7
|
+
from fastapi import Path as FastAPIPath
|
|
8
|
+
from fastapi.responses import FileResponse, StreamingResponse
|
|
9
|
+
|
|
10
|
+
from ..dependencies import ContestServiceDep
|
|
11
|
+
|
|
12
|
+
router = APIRouter()
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@router.get(
|
|
17
|
+
"/contests",
|
|
18
|
+
summary="Get all the contests",
|
|
19
|
+
response_model=List[Dict[str, Any]],
|
|
20
|
+
)
|
|
21
|
+
async def get_contests(service: ContestServiceDep) -> List[Dict[str, Any]]:
|
|
22
|
+
return service.get_contests()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@router.get(
|
|
26
|
+
"/contests/{contest_id}",
|
|
27
|
+
summary="Get the given contest",
|
|
28
|
+
response_model=Dict[str, Any],
|
|
29
|
+
)
|
|
30
|
+
async def get_contest(
|
|
31
|
+
contest_id: str = FastAPIPath(..., description="Contest identifier"),
|
|
32
|
+
service: ContestServiceDep = None,
|
|
33
|
+
) -> Dict[str, Any]:
|
|
34
|
+
return service.get_contest(contest_id)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@router.get(
|
|
38
|
+
"/contests/{contest_id}/state",
|
|
39
|
+
summary="Get the current contest state",
|
|
40
|
+
response_model=Dict[str, Any],
|
|
41
|
+
)
|
|
42
|
+
async def get_state(
|
|
43
|
+
contest_id: str = FastAPIPath(..., description="Contest identifier"),
|
|
44
|
+
service: ContestServiceDep = None,
|
|
45
|
+
) -> Dict[str, Any]:
|
|
46
|
+
return service.get_contest_state(contest_id)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@router.get(
|
|
50
|
+
"/contests/{contest_id}/banner",
|
|
51
|
+
summary="Get the banner for the given contest",
|
|
52
|
+
response_class=FileResponse,
|
|
53
|
+
)
|
|
54
|
+
async def get_contest_banner(
|
|
55
|
+
contest_id: str = FastAPIPath(..., description="Contest identifier"),
|
|
56
|
+
service: ContestServiceDep = None,
|
|
57
|
+
) -> FileResponse:
|
|
58
|
+
service.validate_contest_id(contest_id)
|
|
59
|
+
|
|
60
|
+
expected_href = f"contests/{contest_id}/banner"
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
banners = service.contest.get("banner", [])
|
|
64
|
+
for banner in banners:
|
|
65
|
+
href = banner["href"]
|
|
66
|
+
if href == expected_href:
|
|
67
|
+
filename = banner["filename"]
|
|
68
|
+
banner_file: Path = service.contest_package_dir / "contest" / filename
|
|
69
|
+
if banner_file.exists():
|
|
70
|
+
mime_type = banner["mime"]
|
|
71
|
+
return FileResponse(path=banner_file, media_type=mime_type, filename=filename)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
raise HTTPException(status_code=404, detail=f"Banner not found. [contest_id={contest_id}] [err={e}]")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@router.get(
|
|
77
|
+
"/contests/{contest_id}/problemset",
|
|
78
|
+
summary="Get the problemset document for the given contest",
|
|
79
|
+
response_class=FileResponse,
|
|
80
|
+
)
|
|
81
|
+
async def get_contest_problem_set(
|
|
82
|
+
contest_id: str = FastAPIPath(..., description="Contest identifier"),
|
|
83
|
+
service: ContestServiceDep = None,
|
|
84
|
+
) -> FileResponse:
|
|
85
|
+
service.validate_contest_id(contest_id)
|
|
86
|
+
|
|
87
|
+
expected_href = f"contests/{contest_id}/problemset"
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
problem_set_list = service.contest.get("problemset", [])
|
|
91
|
+
for problem_set in problem_set_list:
|
|
92
|
+
href = problem_set["href"]
|
|
93
|
+
if href == expected_href:
|
|
94
|
+
filename = problem_set["filename"]
|
|
95
|
+
problem_set_file: Path = service.contest_package_dir / "contest" / filename
|
|
96
|
+
if problem_set_file.exists():
|
|
97
|
+
mime_type = problem_set["mime"]
|
|
98
|
+
return FileResponse(path=problem_set_file, media_type=mime_type, filename=filename)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
raise HTTPException(status_code=404, detail=f"Problem set not found. [contest_id={contest_id}] [err={e}]")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@router.get(
|
|
104
|
+
"/contests/{contest_id}/event-feed",
|
|
105
|
+
summary="Get event feed for contest",
|
|
106
|
+
description="Get events for the contest in NDJSON format. Each line contains a single event object.",
|
|
107
|
+
)
|
|
108
|
+
async def get_event_feed(
|
|
109
|
+
contest_id: str = FastAPIPath(..., description="Contest identifier"),
|
|
110
|
+
stream: bool = Query(False, description="Whether to stream the output or stop immediately"),
|
|
111
|
+
since_token: Optional[str] = Query(None, description="Return events after this token"),
|
|
112
|
+
service: ContestServiceDep = None,
|
|
113
|
+
) -> StreamingResponse:
|
|
114
|
+
async def generate():
|
|
115
|
+
events = service.get_event_feed(contest_id, since_token)
|
|
116
|
+
for event in events:
|
|
117
|
+
yield json.dumps(event, ensure_ascii=False, separators=(",", ":")) + "\n"
|
|
118
|
+
|
|
119
|
+
return StreamingResponse(generate(), media_type="application/x-ndjson")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter
|
|
6
|
+
|
|
7
|
+
from ..dependencies import ContestServiceDep
|
|
8
|
+
|
|
9
|
+
router = APIRouter()
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@router.get(
|
|
14
|
+
"/",
|
|
15
|
+
summary="API Information",
|
|
16
|
+
response_model=Dict[str, Any],
|
|
17
|
+
)
|
|
18
|
+
async def get_api_info(service: ContestServiceDep) -> Dict[str, Any]:
|
|
19
|
+
return service.get_api_info()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@router.get(
|
|
23
|
+
"/health",
|
|
24
|
+
summary="Health Check",
|
|
25
|
+
response_model=Dict[str, Any],
|
|
26
|
+
)
|
|
27
|
+
async def health_check(service: ContestServiceDep) -> Dict[str, Any]:
|
|
28
|
+
return {
|
|
29
|
+
"status": "healthy",
|
|
30
|
+
"timestamp": datetime.now().isoformat(),
|
|
31
|
+
"contest_package": str(service.contest_package_dir),
|
|
32
|
+
"api_version": "2023-06",
|
|
33
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Path
|
|
5
|
+
|
|
6
|
+
from ..dependencies import ContestServiceDep
|
|
7
|
+
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@router.get(
|
|
13
|
+
"/contests/{contest_id}/groups",
|
|
14
|
+
summary="Get all the groups for this contest",
|
|
15
|
+
response_model=List[Dict[str, Any]],
|
|
16
|
+
)
|
|
17
|
+
async def get_groups(
|
|
18
|
+
contest_id: str = Path(..., description="Contest identifier"),
|
|
19
|
+
service: ContestServiceDep = None,
|
|
20
|
+
) -> List[Dict[str, Any]]:
|
|
21
|
+
return service.get_groups(contest_id)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@router.get(
|
|
25
|
+
"/contests/{contest_id}/groups/{group_id}",
|
|
26
|
+
summary="Get the given group for this contest",
|
|
27
|
+
response_model=Dict[str, Any],
|
|
28
|
+
)
|
|
29
|
+
async def get_group(
|
|
30
|
+
contest_id: str = Path(..., description="Contest identifier"),
|
|
31
|
+
group_id: str = Path(..., description="Group identifier"),
|
|
32
|
+
service: ContestServiceDep = None,
|
|
33
|
+
) -> Dict[str, Any]:
|
|
34
|
+
return service.get_group(contest_id, group_id)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Path
|
|
5
|
+
|
|
6
|
+
from ..dependencies import ContestServiceDep
|
|
7
|
+
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@router.get(
|
|
13
|
+
"/contests/{contest_id}/judgement-types",
|
|
14
|
+
summary="Get all the judgement types for this contest",
|
|
15
|
+
response_model=List[Dict[str, Any]],
|
|
16
|
+
)
|
|
17
|
+
async def get_judgement_types(
|
|
18
|
+
contest_id: str = Path(..., description="Contest identifier"),
|
|
19
|
+
service: ContestServiceDep = None,
|
|
20
|
+
) -> List[Dict[str, Any]]:
|
|
21
|
+
return service.get_judgement_types(contest_id)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@router.get(
|
|
25
|
+
"/contests/{contest_id}/judgement-types/{judgement_type_id}",
|
|
26
|
+
summary="Get the given judgement type for this contest",
|
|
27
|
+
response_model=Dict[str, Any],
|
|
28
|
+
)
|
|
29
|
+
async def get_judgement_type(
|
|
30
|
+
contest_id: str = Path(..., description="Contest identifier"),
|
|
31
|
+
judgement_type_id: str = Path(..., description="Judgement type identifier"),
|
|
32
|
+
service: ContestServiceDep = None,
|
|
33
|
+
) -> Dict[str, Any]:
|
|
34
|
+
return service.get_judgement_type(contest_id, judgement_type_id)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Path, Query
|
|
5
|
+
|
|
6
|
+
from ..dependencies import ContestServiceDep
|
|
7
|
+
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@router.get(
|
|
13
|
+
"/contests/{contest_id}/judgements",
|
|
14
|
+
summary="Get all the judgements for this contest",
|
|
15
|
+
response_model=List[Dict[str, Any]],
|
|
16
|
+
)
|
|
17
|
+
async def get_judgements(
|
|
18
|
+
contest_id: str = Path(..., description="Contest identifier"),
|
|
19
|
+
submission_id: Optional[str] = Query(None, description="Filter judgements by submission ID"),
|
|
20
|
+
service: ContestServiceDep = None,
|
|
21
|
+
) -> List[Dict[str, Any]]:
|
|
22
|
+
return service.get_judgements(contest_id, submission_id)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@router.get(
|
|
26
|
+
"/contests/{contest_id}/judgements/{judgement_id}",
|
|
27
|
+
summary="Get the given judgement for this contest",
|
|
28
|
+
response_model=Dict[str, Any],
|
|
29
|
+
)
|
|
30
|
+
async def get_judgement(
|
|
31
|
+
contest_id: str = Path(..., description="Contest identifier"),
|
|
32
|
+
judgement_id: str = Path(..., description="Judgement identifier"),
|
|
33
|
+
service: ContestServiceDep = None,
|
|
34
|
+
) -> Dict[str, Any]:
|
|
35
|
+
return service.get_judgement(contest_id, judgement_id)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Path
|
|
5
|
+
|
|
6
|
+
from ..dependencies import ContestServiceDep
|
|
7
|
+
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@router.get(
|
|
13
|
+
"/contests/{contest_id}/languages",
|
|
14
|
+
summary="Get all the languages for this contest",
|
|
15
|
+
response_model=List[Dict[str, Any]],
|
|
16
|
+
)
|
|
17
|
+
async def get_languages(
|
|
18
|
+
contest_id: str = Path(..., description="Contest identifier"),
|
|
19
|
+
service: ContestServiceDep = None,
|
|
20
|
+
) -> List[Dict[str, Any]]:
|
|
21
|
+
return service.get_languages(contest_id)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@router.get(
|
|
25
|
+
"/contests/{contest_id}/languages/{language_id}",
|
|
26
|
+
summary="Get the given language for this contest",
|
|
27
|
+
response_model=Dict[str, Any],
|
|
28
|
+
)
|
|
29
|
+
async def get_language(
|
|
30
|
+
contest_id: str = Path(..., description="Contest identifier"),
|
|
31
|
+
language_id: str = Path(..., description="Language identifier"),
|
|
32
|
+
service: ContestServiceDep = None,
|
|
33
|
+
) -> Dict[str, Any]:
|
|
34
|
+
return service.get_language(contest_id, language_id)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any, Dict, List
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, HTTPException
|
|
6
|
+
from fastapi import Path as FastAPIPath
|
|
7
|
+
from fastapi.responses import FileResponse
|
|
8
|
+
|
|
9
|
+
from ..dependencies import ContestServiceDep
|
|
10
|
+
|
|
11
|
+
router = APIRouter()
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@router.get(
|
|
16
|
+
"/contests/{contest_id}/organizations",
|
|
17
|
+
summary="Get all the organizations for this contest",
|
|
18
|
+
response_model=List[Dict[str, Any]],
|
|
19
|
+
)
|
|
20
|
+
async def get_organizations(
|
|
21
|
+
contest_id: str = FastAPIPath(..., description="Contest identifier"),
|
|
22
|
+
service: ContestServiceDep = None,
|
|
23
|
+
) -> List[Dict[str, Any]]:
|
|
24
|
+
return service.get_organizations(contest_id)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@router.get(
|
|
28
|
+
"/contests/{contest_id}/organizations/{organization_id}",
|
|
29
|
+
summary="Get the given organization for this contest",
|
|
30
|
+
response_model=Dict[str, Any],
|
|
31
|
+
)
|
|
32
|
+
async def get_organization(
|
|
33
|
+
contest_id: str = FastAPIPath(..., description="Contest identifier"),
|
|
34
|
+
organization_id: str = FastAPIPath(..., description="Organization identifier"),
|
|
35
|
+
service: ContestServiceDep = None,
|
|
36
|
+
) -> Dict[str, Any]:
|
|
37
|
+
return service.get_organization(contest_id, organization_id)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@router.get(
|
|
41
|
+
"/contests/{contest_id}/organizations/{organization_id}/logo",
|
|
42
|
+
summary="Get Organization Logo",
|
|
43
|
+
response_class=FileResponse,
|
|
44
|
+
)
|
|
45
|
+
async def get_organization_logo(
|
|
46
|
+
contest_id: str = FastAPIPath(..., description="Contest identifier"),
|
|
47
|
+
organization_id: str = FastAPIPath(..., description="Organization identifier"),
|
|
48
|
+
service: ContestServiceDep = None,
|
|
49
|
+
) -> FileResponse:
|
|
50
|
+
service.validate_contest_id(contest_id)
|
|
51
|
+
|
|
52
|
+
org = service.organizations_by_id.get(organization_id)
|
|
53
|
+
if not org:
|
|
54
|
+
raise HTTPException(status_code=404, detail=f"Organization {organization_id} not found")
|
|
55
|
+
|
|
56
|
+
expected_href = f"contests/{contest_id}/organizations/{organization_id}/logo"
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
logos = org["logo"]
|
|
60
|
+
for logo in logos:
|
|
61
|
+
href = logo["href"]
|
|
62
|
+
if href == expected_href:
|
|
63
|
+
filename = logo["filename"]
|
|
64
|
+
logo_file: Path = service.contest_package_dir / "organizations" / organization_id / filename
|
|
65
|
+
if logo_file.exists():
|
|
66
|
+
mime_type = logo["mime"]
|
|
67
|
+
return FileResponse(path=logo_file, media_type=mime_type, filename=filename)
|
|
68
|
+
except Exception as e:
|
|
69
|
+
raise HTTPException(
|
|
70
|
+
status_code=404, detail=f"Logo file not found. [organization_id={organization_id}] [err={e}]"
|
|
71
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any, Dict, List
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, HTTPException
|
|
6
|
+
from fastapi import Path as FastAPIPath
|
|
7
|
+
from fastapi.responses import FileResponse
|
|
8
|
+
|
|
9
|
+
from ..dependencies import ContestServiceDep
|
|
10
|
+
|
|
11
|
+
router = APIRouter()
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@router.get(
|
|
16
|
+
"/contests/{contest_id}/problems",
|
|
17
|
+
summary="Get all the problems for this contest",
|
|
18
|
+
response_model=List[Dict[str, Any]],
|
|
19
|
+
)
|
|
20
|
+
async def get_problems(
|
|
21
|
+
contest_id: str = FastAPIPath(..., description="Contest identifier"),
|
|
22
|
+
service: ContestServiceDep = None,
|
|
23
|
+
) -> List[Dict[str, Any]]:
|
|
24
|
+
return service.get_problems(contest_id)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@router.get(
|
|
28
|
+
"/contests/{contest_id}/problems/{problem_id}",
|
|
29
|
+
summary="Get the given problem for this contest",
|
|
30
|
+
response_model=Dict[str, Any],
|
|
31
|
+
)
|
|
32
|
+
async def get_problem(
|
|
33
|
+
contest_id: str = FastAPIPath(..., description="Contest identifier"),
|
|
34
|
+
problem_id: str = FastAPIPath(..., description="Problem identifier"),
|
|
35
|
+
service: ContestServiceDep = None,
|
|
36
|
+
) -> Dict[str, Any]:
|
|
37
|
+
return service.get_problem(contest_id, problem_id)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@router.get(
|
|
41
|
+
"/contests/{contest_id}/problems/{problem_id}/statement",
|
|
42
|
+
summary="Get Problem Statement",
|
|
43
|
+
response_class=FileResponse,
|
|
44
|
+
)
|
|
45
|
+
async def get_problem_statement(
|
|
46
|
+
contest_id: str = FastAPIPath(..., description="Contest identifier"),
|
|
47
|
+
problem_id: str = FastAPIPath(..., description="Problem identifier"),
|
|
48
|
+
service: ContestServiceDep = None,
|
|
49
|
+
) -> FileResponse:
|
|
50
|
+
service.validate_contest_id(contest_id)
|
|
51
|
+
|
|
52
|
+
problem: Dict = service.problems_by_id.get(problem_id)
|
|
53
|
+
if not problem:
|
|
54
|
+
raise HTTPException(status_code=404, detail=f"Problem {problem_id} not found")
|
|
55
|
+
|
|
56
|
+
expected_href = f"contests/{contest_id}/problems/{problem_id}/statement"
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
statements = problem.get("statement", [])
|
|
60
|
+
for statement in statements:
|
|
61
|
+
href = statement["href"]
|
|
62
|
+
filename = statement["filename"]
|
|
63
|
+
if href == expected_href and filename:
|
|
64
|
+
statement_file: Path = service.contest_package_dir / "problems" / problem_id / filename
|
|
65
|
+
if statement_file.exists():
|
|
66
|
+
mime_type = statement["mime"]
|
|
67
|
+
return FileResponse(path=statement_file, media_type=mime_type, filename=filename)
|
|
68
|
+
except Exception as e:
|
|
69
|
+
raise HTTPException(status_code=404, detail=f"Statement not found. [problem_id={problem_id}] [err={e}]")
|