33the module for CodeChef (https://www.codechef.com/)
44"""
55
6+ import json
67import re
78import urllib .parse
9+ from logging import getLogger
810from typing import *
911
1012import requests
1113
1214import onlinejudge ._implementation .testcase_zipper
15+ import onlinejudge ._implementation .utils as utils
1316import onlinejudge .dispatch
1417import onlinejudge .type
1518from onlinejudge .type import SampleParseError
1619
20+ logger = getLogger (__name__ )
21+
1722
1823class CodeChefService (onlinejudge .type .Service ):
1924 def get_url (self ) -> str :
@@ -31,21 +36,141 @@ def from_url(cls, url: str) -> Optional['CodeChefService']:
3136 return cls ()
3237 return None
3338
39+ def get_url_of_login_page (self ) -> str :
40+ return 'https://www.codechef.com/'
41+
42+ def is_logged_in (self , * , session : Optional [requests .Session ] = None ) -> bool :
43+ session = session or utils .get_default_session ()
44+ url = 'https://www.codechef.com/certificates/'
45+ resp = utils .request ('GET' , url , session = session , raise_for_status = False )
46+ return resp .status_code == 200
47+
48+
49+ class CodeChefProblemData (onlinejudge .type .ProblemData ):
50+ def __init__ (self , * , contest_id : str , data : Dict [str , Any ]):
51+ self .contest_id = contest_id
52+ self .data = data
53+
54+ @property
55+ def json (self ) -> bytes :
56+ return json .dumps (self .data ).encode ()
57+
58+ @property
59+ def problem (self ) -> 'CodeChefProblem' :
60+ return CodeChefProblem (contest_id = self .contest_id , problem_id = self .data ['code' ])
61+
62+ @property
63+ def name (self ) -> str :
64+ return self .data ['name' ]
65+
66+ # TODO: Support problems with old formats. Our old parser may help it: https://github.com/online-judge-tools/api-client/pull/50/commits/a6c2c0808bc2b5ef5c81985877522b8e8ea92bd1
67+ @property
68+ def sample_cases (self ) -> Optional [List [onlinejudge .type .TestCase ]]:
69+ if 'problemComponents' not in self .data :
70+ return None
71+ testcases : List [onlinejudge .type .TestCase ] = []
72+ for testcase in self .data ['problemComponents' ]['sampleTestCases' ]:
73+ testcases .append (onlinejudge .type .TestCase (
74+ name = 'sample-{}' .format (testcase ['id' ]),
75+ input_name = 'input' ,
76+ input_data = utils .textfile (testcase ['input' ]).encode (),
77+ output_name = 'output' ,
78+ output_data = utils .textfile (testcase ['output' ]).encode (),
79+ ))
80+ return testcases
81+
82+
83+ class CodeChefContestData (onlinejudge .type .ContestData ):
84+ def __init__ (self , * , data : Dict [str , Any ]):
85+ self .data = data
86+
87+ @property
88+ def json (self ) -> bytes :
89+ return json .dumps (self .data ).encode ()
90+
91+ @property
92+ def contest (self ) -> 'CodeChefContest' :
93+ return CodeChefContest (contest_id = self .data ['code' ])
94+
95+ @property
96+ def name (self ) -> str :
97+ return self .data ['name' ]
98+
99+ def get_problem_data (self ) -> List ['CodeChefProblemData' ]:
100+ return [CodeChefProblemData (contest_id = self .data ['code' ], data = data ) for data in self .data ['problems' ].values ()]
101+
102+
103+ class CodeChefContest (onlinejudge .type .Contest ):
104+ def __init__ (self , * , contest_id : str ):
105+ self .contest_id = contest_id
106+
107+ def get_url (self ) -> str :
108+ return 'https://www.codechef.com/{}' .format (self .contest_id )
109+
110+ def get_service (self ) -> CodeChefService :
111+ return CodeChefService ()
112+
113+ @classmethod
114+ def from_url (cls , url : str ) -> Optional ['CodeChefContest' ]:
115+ # example: https://www.codechef.com/JAN20A
116+ result = urllib .parse .urlparse (url )
117+ if result .scheme in ('' , 'http' , 'https' ) \
118+ and result .netloc == 'www.codechef.com' :
119+ m = re .match (r'/([0-9A-Z_a-z-]+)' , result .path )
120+ if m :
121+ contest_id = m .group (1 )
122+ return cls (contest_id = contest_id )
123+ return None
124+
125+ def list_problems (self , * , session : Optional [requests .Session ] = None ) -> Sequence ['CodeChefProblem' ]:
126+ return [problem_data .problem for problem_data in self .download_data (session = session ).get_problem_data ()]
127+
128+ def download_data (self , * , session : Optional [requests .Session ] = None ) -> CodeChefContestData :
129+ session = session or utils .get_default_session ()
130+
131+ # get
132+ url = 'https://www.codechef.com/api/contests/{}' .format (self .contest_id )
133+ resp = utils .request ('GET' , url , session = session )
134+ data = json .loads (resp .content )
135+ if data ['status' ] != 'success' :
136+ logger .debug ('json: %s' , resp .content .decode ())
137+ raise SampleParseError ('CodeChef API failed with: {}' .format (data .get ('message' )))
138+
139+ return CodeChefContestData (data = data )
140+
34141
35142class CodeChefProblem (onlinejudge .type .Problem ):
36143 def __init__ (self , * , contest_id : str , problem_id : str ):
37144 self .contest_id = contest_id
38145 self .problem_id = problem_id
39146
147+ def download_data (self , * , session : Optional [requests .Session ] = None ) -> CodeChefProblemData :
148+ session = session or utils .get_default_session ()
149+
150+ # get
151+ url = 'https://www.codechef.com/api/contests/{}/problems/{}' .format (self .contest_id , self .problem_id )
152+ resp = utils .request ('GET' , url , session = session )
153+ data = json .loads (resp .content )
154+ if data ['status' ] != 'success' :
155+ logger .debug ('json: %s' , resp .content .decode ())
156+ raise SampleParseError ('CodeChef API failed with: {}' .format (data .get ('message' )))
157+
158+ return CodeChefProblemData (contest_id = self .contest_id , data = data )
159+
40160 def download_sample_cases (self , * , session : Optional [requests .Session ] = None ) -> List [onlinejudge .type .TestCase ]:
41- raise SampleParseError ("removed. see https://github.com/online-judge-tools/api-client/issues/49" )
161+ sample_cases = self .download_data (session = session ).sample_cases
162+ assert sample_cases is not None
163+ return sample_cases
42164
43165 def get_url (self , * , contests : bool = True ) -> str :
44166 return 'https://www.codechef.com/{}/problems/{}' .format (self .contest_id , self .problem_id )
45167
46168 def get_service (self ) -> CodeChefService :
47169 return CodeChefService ()
48170
171+ def get_contest (self ) -> CodeChefContest :
172+ return CodeChefContest (contest_id = self .contest_id )
173+
49174 @classmethod
50175 def from_url (cls , url : str ) -> Optional ['CodeChefProblem' ]:
51176 # example: https://www.codechef.com/JAN20A/problems/DYNAMO
@@ -63,4 +188,5 @@ def from_url(cls, url: str) -> Optional['CodeChefProblem']:
63188
64189
65190onlinejudge .dispatch .services += [CodeChefService ]
191+ onlinejudge .dispatch .contests += [CodeChefContest ]
66192onlinejudge .dispatch .problems += [CodeChefProblem ]
0 commit comments