Skip to content

Commit 5cab8c1

Browse files
authored
Merge pull request #144 from online-judge-tools/fix-codechef
Fix CodeChef
2 parents 78203fe + 375466f commit 5cab8c1

File tree

6 files changed

+314
-15
lines changed

6 files changed

+314
-15
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ $ pip3 install online-judge-api-client
2727
| [Anarchy Golf](http://golf.shinh.org/) | :heavy_check_mark: | :grey_question: (same to samples) | | | | |
2828
| [AtCoder](https://atcoder.jp/) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
2929
| [AtCoder Problems](https://kenkoooo.com/atcoder) (virtual contests) | | | | :heavy_check_mark: | | |
30-
| [CodeChef](https://www.codechef.com/) | :x: [issue](https://github.com/online-judge-tools/api-client/issues/49) | | | | | |
30+
| [CodeChef](https://www.codechef.com/) | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | :grey_question: | |
3131
| [Codeforces](https://codeforces.com/) | :heavy_check_mark: | | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :x: [issue](https://github.com/online-judge-tools/api-client/issues/127) |
3232
| [CS Academy](https://csacademy.com/) | :heavy_check_mark: | | | | | |
3333
| [Facebook Hacker Cup](https://www.facebook.com/hackercup/) | :heavy_check_mark: | | | | | |

onlinejudge/service/codechef.py

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,22 @@
33
the module for CodeChef (https://www.codechef.com/)
44
"""
55

6+
import json
67
import re
78
import urllib.parse
9+
from logging import getLogger
810
from typing import *
911

1012
import requests
1113

1214
import onlinejudge._implementation.testcase_zipper
15+
import onlinejudge._implementation.utils as utils
1316
import onlinejudge.dispatch
1417
import onlinejudge.type
1518
from onlinejudge.type import SampleParseError
1619

20+
logger = getLogger(__name__)
21+
1722

1823
class 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

35142
class 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

65190
onlinejudge.dispatch.services += [CodeChefService]
191+
onlinejudge.dispatch.contests += [CodeChefContest]
66192
onlinejudge.dispatch.problems += [CodeChefProblem]

onlinejudge_api/get_contest.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import json
12
from typing import *
23

34
from onlinejudge.service.atcoder import AtCoderContest
45
from onlinejudge.service.atcoder_problems import AtCoderProblemsContest
6+
from onlinejudge.service.codechef import CodeChefContest
57
from onlinejudge.service.codeforces import CodeforcesContest
68
from onlinejudge.service.yukicoder import YukicoderContest
79
from onlinejudge.type import *
@@ -152,6 +154,25 @@ def main(contest: Contest, *, is_full: bool, session: requests.Session) -> Dict[
152154
assert 'raw' in result
153155
del result["raw"]
154156

157+
elif isinstance(contest, CodeChefContest):
158+
data = contest.download_data(session=session)
159+
result["url"] = data.url
160+
result["name"] = data.name
161+
for problem_data in data.get_problem_data():
162+
data_ = {
163+
"url": problem_data.url,
164+
"name": problem_data.name,
165+
"context": {
166+
"contest": {
167+
"name": data.name,
168+
"url": contest.get_url(),
169+
},
170+
},
171+
}
172+
result["problems"].append(data_)
173+
if is_full:
174+
result["raw"] = json.loads(data.json)
175+
155176
else:
156177
assert False
157178

tests/get_contest_codechef.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import unittest
2+
3+
from onlinejudge_api.main import main
4+
5+
6+
class GetContestCodeChefTest(unittest.TestCase):
7+
def test_cook131b(self):
8+
url = "https://www.codechef.com/COOK131B"
9+
expected = {
10+
"status": "ok",
11+
"messages": [],
12+
"result": {
13+
"url": "https://www.codechef.com/COOK131B",
14+
"problems": [{
15+
"url": "https://www.codechef.com/COOK131B/problems/SHOEFIT",
16+
"name": "Shoe Fit",
17+
"context": {
18+
"contest": {
19+
"name": "July Cook-Off 2021 Division 2",
20+
"url": "https://www.codechef.com/COOK131B"
21+
}
22+
}
23+
}, {
24+
"url": "https://www.codechef.com/COOK131B/problems/CHFGCD",
25+
"name": "Chef and GCD",
26+
"context": {
27+
"contest": {
28+
"name": "July Cook-Off 2021 Division 2",
29+
"url": "https://www.codechef.com/COOK131B"
30+
}
31+
}
32+
}, {
33+
"url": "https://www.codechef.com/COOK131B/problems/XORORED",
34+
"name": "XOR-ORED",
35+
"context": {
36+
"contest": {
37+
"name": "July Cook-Off 2021 Division 2",
38+
"url": "https://www.codechef.com/COOK131B"
39+
}
40+
}
41+
}, {
42+
"url": "https://www.codechef.com/COOK131B/problems/CHFPLN",
43+
"name": "Chef In Infinite Plane",
44+
"context": {
45+
"contest": {
46+
"name": "July Cook-Off 2021 Division 2",
47+
"url": "https://www.codechef.com/COOK131B"
48+
}
49+
}
50+
}, {
51+
"url": "https://www.codechef.com/COOK131B/problems/MODEQUAL",
52+
"name": "Mod Equality",
53+
"context": {
54+
"contest": {
55+
"name": "July Cook-Off 2021 Division 2",
56+
"url": "https://www.codechef.com/COOK131B"
57+
}
58+
}
59+
}, {
60+
"url": "https://www.codechef.com/COOK131B/problems/BEAUSUB",
61+
"name": "Beautiful Subsequence",
62+
"context": {
63+
"contest": {
64+
"name": "July Cook-Off 2021 Division 2",
65+
"url": "https://www.codechef.com/COOK131B"
66+
}
67+
}
68+
}, {
69+
"url": "https://www.codechef.com/COOK131B/problems/COLRGRPH",
70+
"name": "Hidden Colored Graph",
71+
"context": {
72+
"contest": {
73+
"name": "July Cook-Off 2021 Division 2",
74+
"url": "https://www.codechef.com/COOK131B"
75+
}
76+
}
77+
}, {
78+
"url": "https://www.codechef.com/COOK131B/problems/MATBEAUT",
79+
"name": "Make the Matrix Beautiful",
80+
"context": {
81+
"contest": {
82+
"name": "July Cook-Off 2021 Division 2",
83+
"url": "https://www.codechef.com/COOK131B"
84+
}
85+
}
86+
}, {
87+
"url": "https://www.codechef.com/COOK131B/problems/SPTREE2",
88+
"name": "A Special Tree 2",
89+
"context": {
90+
"contest": {
91+
"name": "July Cook-Off 2021 Division 2",
92+
"url": "https://www.codechef.com/COOK131B"
93+
}
94+
}
95+
}, {
96+
"url": "https://www.codechef.com/COOK131B/problems/GCDLEN",
97+
"name": "Maximal GCD",
98+
"context": {
99+
"contest": {
100+
"name": "July Cook-Off 2021 Division 2",
101+
"url": "https://www.codechef.com/COOK131B"
102+
}
103+
}
104+
}],
105+
"name": "July Cook-Off 2021 Division 2"
106+
},
107+
}
108+
actual = main(['get-contest', url], debug=True)
109+
self.assertEqual(expected, actual)

tests/get_problem_codechef.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import unittest
2+
3+
from onlinejudge_api.main import main
4+
5+
6+
class GetProblemCodeChefTest(unittest.TestCase):
7+
def test_xorored(self):
8+
url = 'https://www.codechef.com/COOK131B/problems/XORORED'
9+
expected = {
10+
"status": "ok",
11+
"messages": [],
12+
"result": {
13+
"url": "https://www.codechef.com/COOK131B/problems/XORORED",
14+
"tests": [{
15+
"input": "1\n2\n4 6\n",
16+
"output": "6 2\n"
17+
}],
18+
"context": {}
19+
},
20+
}
21+
actual = main(['get-problem', url], debug=True)
22+
self.assertEqual(expected, actual)

0 commit comments

Comments
 (0)