xcpcio 0.63.4__py3-none-any.whl → 0.63.6__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.

@@ -0,0 +1,40 @@
1
+ import logging
2
+ from typing import Any, Dict, List, Optional
3
+
4
+ from fastapi import APIRouter, Path, Query
5
+
6
+ from ...model import Run, Runs
7
+ from ..dependencies import ContestServiceDep
8
+
9
+ router = APIRouter()
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @router.get(
14
+ "/contests/{contest_id}/runs",
15
+ summary="Get Runs",
16
+ description="Get all test case runs, optionally filtered by judgement",
17
+ response_model=Runs,
18
+ )
19
+ async def get_runs(
20
+ contest_id: str = Path(..., description="Contest identifier"),
21
+ judgement_id: Optional[str] = Query(None, description="Filter runs by judgement ID"),
22
+ service: ContestServiceDep = None,
23
+ ) -> List[Dict[str, Any]]:
24
+ """Get all test case runs, optionally filtered by judgement"""
25
+ return service.get_runs(contest_id, judgement_id)
26
+
27
+
28
+ @router.get(
29
+ "/contests/{contest_id}/runs/{run_id}",
30
+ summary="Get Run",
31
+ description="Get specific test case run information",
32
+ response_model=Run,
33
+ )
34
+ async def get_run(
35
+ contest_id: str = Path(..., description="Contest identifier"),
36
+ run_id: str = Path(..., description="Run identifier"),
37
+ service: ContestServiceDep = None,
38
+ ) -> Dict[str, Any]:
39
+ """Get specific test case run information"""
40
+ return service.get_run(contest_id, run_id)
@@ -0,0 +1,82 @@
1
+ import logging
2
+ from pathlib import Path
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ from fastapi import APIRouter, HTTPException, Query
6
+ from fastapi import Path as FastAPIPath
7
+ from fastapi.responses import FileResponse
8
+
9
+ from ...model import Submission, Submissions
10
+ from ..dependencies import ContestServiceDep
11
+
12
+ router = APIRouter()
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @router.get(
17
+ "/contests/{contest_id}/submissions",
18
+ summary="Get Submissions",
19
+ description="Get all submissions, optionally filtered by team or problem",
20
+ response_model=Submissions,
21
+ )
22
+ async def get_submissions(
23
+ contest_id: str = FastAPIPath(..., description="Contest identifier"),
24
+ team_id: Optional[str] = Query(None, description="Filter submissions by team ID"),
25
+ problem_id: Optional[str] = Query(None, description="Filter submissions by problem ID"),
26
+ service: ContestServiceDep = None,
27
+ ) -> List[Dict[str, Any]]:
28
+ """Get all submissions, optionally filtered by team or problem"""
29
+ return service.get_submissions(contest_id, team_id, problem_id)
30
+
31
+
32
+ @router.get(
33
+ "/contests/{contest_id}/submissions/{submission_id}",
34
+ summary="Get Submission",
35
+ description="Get specific submission information",
36
+ response_model=Submission,
37
+ )
38
+ async def get_submission(
39
+ contest_id: str = FastAPIPath(..., description="Contest identifier"),
40
+ submission_id: str = FastAPIPath(..., description="Submission identifier"),
41
+ service: ContestServiceDep = None,
42
+ ) -> Dict[str, Any]:
43
+ """Get specific submission information"""
44
+ return service.get_submission(contest_id, submission_id)
45
+
46
+
47
+ @router.get(
48
+ "/contests/{contest_id}/submissions/{submission_id}/files",
49
+ summary="Get Submission Files",
50
+ description="Get files for a specific submission",
51
+ response_class=FileResponse,
52
+ )
53
+ async def get_submission_files(
54
+ contest_id: str = FastAPIPath(..., description="Contest identifier"),
55
+ submission_id: str = FastAPIPath(..., description="Submission identifier"),
56
+ service: ContestServiceDep = None,
57
+ ) -> FileResponse:
58
+ """Get submission files"""
59
+ service.validate_contest_id(contest_id)
60
+
61
+ # Get submission from indexed data
62
+ submission = service.submissions_by_id.get(submission_id)
63
+ if not submission:
64
+ raise HTTPException(status_code=404, detail=f"Submission {submission_id} not found")
65
+
66
+ # Expected href pattern for this endpoint
67
+ expected_href = f"contests/{contest_id}/submissions/{submission_id}/files"
68
+
69
+ try:
70
+ files: List[Dict] = submission["files"]
71
+ for file_info in files:
72
+ href = file_info["href"]
73
+ if href == expected_href:
74
+ filename = file_info["filename"]
75
+ submission_file: Path = service.contest_package_dir / "submissions" / submission_id / filename
76
+ if submission_file.exists():
77
+ mime_type = file_info["mime"]
78
+ return FileResponse(path=submission_file, media_type=mime_type, filename=filename)
79
+ except Exception as e:
80
+ raise HTTPException(
81
+ status_code=404, detail=f"Submission files not found. [submission_id={submission_id}] [err={e}]"
82
+ )
@@ -0,0 +1,82 @@
1
+ import logging
2
+ from pathlib import Path
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ from fastapi import APIRouter, HTTPException, Query
6
+ from fastapi import Path as FastAPIPath
7
+ from fastapi.responses import FileResponse
8
+
9
+ from ...model import (
10
+ Team,
11
+ Teams,
12
+ )
13
+ from ..dependencies import ContestServiceDep
14
+
15
+ router = APIRouter()
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @router.get(
20
+ "/contests/{contest_id}/teams",
21
+ summary="Get Teams",
22
+ description="Get all teams, optionally filtered by group",
23
+ response_model=Teams,
24
+ )
25
+ async def get_teams(
26
+ contest_id: str = FastAPIPath(..., description="Contest identifier"),
27
+ group_id: Optional[str] = Query(None, description="Filter teams by group ID"),
28
+ service: ContestServiceDep = None,
29
+ ) -> List[Dict[str, Any]]:
30
+ """Get all teams, optionally filtered by group"""
31
+ return service.get_teams(contest_id, group_id)
32
+
33
+
34
+ @router.get(
35
+ "/contests/{contest_id}/teams/{team_id}",
36
+ summary="Get Team",
37
+ description="Get specific team information",
38
+ response_model=Team,
39
+ )
40
+ async def get_team(
41
+ contest_id: str = FastAPIPath(..., description="Contest identifier"),
42
+ team_id: str = FastAPIPath(..., description="Team identifier"),
43
+ service: ContestServiceDep = None,
44
+ ) -> Dict[str, Any]:
45
+ """Get specific team information"""
46
+ return service.get_team(contest_id, team_id)
47
+
48
+
49
+ @router.get(
50
+ "/contests/{contest_id}/teams/{team_id}/photo",
51
+ summary="Get Team Photo",
52
+ description="Get photo file for a specific team",
53
+ response_class=FileResponse,
54
+ )
55
+ async def get_team_photo(
56
+ contest_id: str = FastAPIPath(..., description="Contest identifier"),
57
+ team_id: str = FastAPIPath(..., description="Team identifier"),
58
+ service: ContestServiceDep = None,
59
+ ) -> FileResponse:
60
+ """Get team photo file"""
61
+ service.validate_contest_id(contest_id)
62
+
63
+ # Get team from indexed data
64
+ team = service.teams_by_id.get(team_id)
65
+ if not team:
66
+ raise HTTPException(status_code=404, detail=f"Team {team_id} not found")
67
+
68
+ # Expected href pattern for this endpoint
69
+ expected_href = f"contests/{contest_id}/teams/{team_id}/photo"
70
+
71
+ try:
72
+ photos = team["photo"]
73
+ for photo in photos:
74
+ href = photo["href"]
75
+ if href == expected_href:
76
+ filename = ["filename"]
77
+ photo_file: Path = service.contest_package_dir / "teams" / team_id / filename
78
+ if photo_file.exists():
79
+ mime_type = photo["mime"]
80
+ return FileResponse(path=photo_file, media_type=mime_type, filename=filename)
81
+ except Exception as e:
82
+ raise HTTPException(status_code=404, detail=f"Photo not found. [team_id={team_id}] [err={e}]")
@@ -0,0 +1,83 @@
1
+ """
2
+ Contest API Server
3
+
4
+ Main server class implementing the Contest API specification using modern FastAPI architecture.
5
+ """
6
+
7
+ import logging
8
+ from pathlib import Path
9
+
10
+ import uvicorn
11
+ from fastapi import FastAPI
12
+ from fastapi.middleware.cors import CORSMiddleware
13
+
14
+ from xcpcio.__version__ import __version__
15
+
16
+ from .dependencies import configure_dependencies
17
+ from .routes import create_router
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class ContestAPIServer:
23
+ """
24
+ Contest API Server implementing the Contest API specification.
25
+
26
+ This server provides REST API endpoints for contest data based on
27
+ the Contest Package format and Contest API specification.
28
+ """
29
+
30
+ def __init__(self, contest_package_dir: Path):
31
+ """
32
+ Initialize the Contest API Server.
33
+
34
+ Args:
35
+ contest_packages: Dictionary mapping contest_id to contest package directory
36
+ """
37
+ self.contest_package_dir = contest_package_dir
38
+
39
+ # Configure dependency injection for multi-contest mode
40
+ # This might need adjustment based on how dependencies work
41
+ configure_dependencies(contest_package_dir)
42
+
43
+ # Create FastAPI application
44
+ self.app = FastAPI(
45
+ title="Contest API Server",
46
+ description="REST API for Contest Control System specifications",
47
+ version=__version__,
48
+ docs_url="/docs",
49
+ redoc_url="/redoc",
50
+ openapi_url="/openapi.json",
51
+ )
52
+
53
+ # Add CORS middleware
54
+ self.app.add_middleware(
55
+ CORSMiddleware,
56
+ allow_origins=["*"],
57
+ allow_credentials=True,
58
+ allow_methods=["*"],
59
+ allow_headers=["*"],
60
+ )
61
+
62
+ # Include all routes
63
+ router = create_router()
64
+ self.app.include_router(router)
65
+
66
+ def run(self, host: str = "0.0.0.0", port: int = 8000, reload: bool = True, log_level: str = "info"):
67
+ """
68
+ Run the contest API server.
69
+
70
+ Args:
71
+ host: Host to bind to
72
+ port: Port to bind to
73
+ reload: Enable auto-reload for development
74
+ log_level: Log level (debug, info, warning, error, critical)
75
+ """
76
+
77
+ logger.info("Starting Contest API Server...")
78
+ logger.info(f"Contest package dir: {self.contest_package_dir}")
79
+ logger.info(f"API will be available at: http://{host}:{port}")
80
+ logger.info(f"Interactive docs at: http://{host}:{port}/docs")
81
+ logger.info(f"ReDoc at: http://{host}:{port}/redoc")
82
+
83
+ uvicorn.run(self.app, host=host, port=port, reload=reload, log_level=log_level)
@@ -0,0 +1,9 @@
1
+ """
2
+ Services package for Contest API Server
3
+
4
+ Contains business logic and data access layers.
5
+ """
6
+
7
+ from .contest_service import ContestService
8
+
9
+ __all__ = ["ContestService"]