Skip to content

Commit 7eb68e4

Browse files
author
DOMjudge team
committed
WIP add export-contest script - Jaap
1 parent d57f387 commit 7eb68e4

File tree

3 files changed

+124
-16
lines changed

3 files changed

+124
-16
lines changed

misc-tools/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ include $(TOPDIR)/Makefile.global
99
TARGETS =
1010
OBJECTS =
1111

12-
SUBST_DOMSERVER = fix_permissions configure-domjudge import-contest
12+
SUBST_DOMSERVER = fix_permissions configure-domjudge import-contest \
13+
export-contest
1314

1415
SUBST_JUDGEHOST = dj_make_chroot dj_run_chroot dj_make_chroot_docker \
1516
dj_judgehost_cleanup

misc-tools/dj_utils.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,10 @@ def parse_api_response(name: str, response: requests.Response):
4444
if response.status_code == 204:
4545
return None
4646

47-
# We got a successful HTTP response. It worked. Return the full response
48-
try:
49-
result = json.loads(response.text)
50-
except json.decoder.JSONDecodeError as e:
51-
print(response.text)
52-
raise RuntimeError(f'Failed to JSON decode the response for API request {name}')
53-
54-
return result
47+
return response.text
5548

5649

57-
def do_api_request(name: str, method: str = 'GET', jsonData: dict = {}):
50+
def do_api_request(name: str, method: str = 'GET', jsonData: dict = {}, decode: bool = True):
5851
'''Perform an API call to the given endpoint and return its data.
5952
6053
Based on whether `domjudge_webapp_folder_or_api_url` is a folder or URL this
@@ -64,16 +57,18 @@ def do_api_request(name: str, method: str = 'GET', jsonData: dict = {}):
6457
name (str): the endpoint to call
6558
method (str): the method to use, GET or PUT are supported
6659
jsonData (dict): the JSON data to PUT. Only used when method is PUT
60+
decode (bool): whether to decode the returned JSON data, default true
6761
6862
Returns:
6963
The endpoint contents.
7064
7165
Raises:
72-
RuntimeError when the response is not JSON or the HTTP status code is non 2xx.
66+
RuntimeError when the HTTP status code is non-2xx or the response
67+
cannot be JSON decoded.
7368
'''
7469

7570
if os.path.isdir(domjudge_webapp_folder_or_api_url):
76-
return api_via_cli(name, method, {}, {}, jsonData)
71+
result = api_via_cli(name, method, {}, {}, jsonData)
7772
else:
7873
global ca_check
7974
url = f'{domjudge_webapp_folder_or_api_url}/{name}'
@@ -97,7 +92,17 @@ def do_api_request(name: str, method: str = 'GET', jsonData: dict = {}):
9792
return do_api_request(name)
9893
except requests.exceptions.RequestException as e:
9994
raise RuntimeError(e)
100-
return parse_api_response(name, response)
95+
result = parse_api_response(name, response)
96+
97+
if decode:
98+
try:
99+
result = json.loads(result)
100+
except json.decoder.JSONDecodeError as e:
101+
print(result)
102+
raise RuntimeError(f'Failed to JSON decode the response for API request {name}')
103+
104+
return result
105+
101106

102107
def upload_file(name: str, apifilename: str, file: str, data: dict = {}):
103108
'''Upload the given file to the API at the given path with the given name.
@@ -118,7 +123,7 @@ def upload_file(name: str, apifilename: str, file: str, data: dict = {}):
118123
'''
119124

120125
if os.path.isdir(domjudge_webapp_folder_or_api_url):
121-
return api_via_cli(name, 'POST', data, {apifilename: file})
126+
response = api_via_cli(name, 'POST', data, {apifilename: file})
122127
else:
123128
global ca_check
124129
files = [(apifilename, open(file, 'rb'))]
@@ -152,7 +157,7 @@ def api_via_cli(name: str, method: str = 'GET', data: dict = {}, files: dict = {
152157
jsonData (dict): the JSON data to use. Only used when method is POST or PUT
153158
154159
Returns:
155-
The parsed endpoint contents.
160+
The endpoint contents.
156161
157162
Raises:
158163
RuntimeError when the command exit code is not 0.
@@ -183,4 +188,4 @@ def api_via_cli(name: str, method: str = 'GET', data: dict = {}, files: dict = {
183188
print(response)
184189
raise RuntimeError(f'API request {name} failed')
185190

186-
return json.loads(response)
191+
return response

misc-tools/export-contest.in

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env python3
2+
3+
'''
4+
export-contest -- Convenience script to export a contest (including metadata,
5+
teams and problems) from the command line. Defaults to using the CLI interface;
6+
Specify a DOMjudge API URL as to use that.
7+
8+
Reads credentials from ~/.netrc when using the API.
9+
10+
Part of the DOMjudge Programming Contest Jury System and licensed
11+
under the GNU GPL. See README and COPYING for details.
12+
'''
13+
14+
import json
15+
import sys
16+
# from concurrent.futures import ThreadPoolExecutor
17+
# from multiprocessing import Pool
18+
from pathlib import Path
19+
20+
sys.path.append('@domserver_libdir@')
21+
import dj_utils
22+
23+
cid = None
24+
webappdir = '@domserver_webappdir@'
25+
26+
27+
def usage():
28+
print(f'Usage: {sys.argv[0]} [<domjudge-api-url>]')
29+
exit(1)
30+
31+
32+
def api_to_file(endpoint: str, filename: str):
33+
print(f"Fetching '{endpoint}' to '{filename}'")
34+
data = dj_utils.do_api_request(endpoint, decode=False)
35+
with open(filename, 'w') as f:
36+
f.write(data)
37+
38+
return data
39+
40+
41+
def download_submission(submission):
42+
d = f'submissions/{submission["id"]}'
43+
Path(d).mkdir(parents=True, exist_ok=True)
44+
for f in submission['files']:
45+
if f['mime'] == 'application/zip':
46+
print(f"Downloading '{f['href']}'")
47+
data = dj_utils.do_api_request(f['href'], decode=False)
48+
with open(f'{d}/files.zip', 'w') as f:
49+
f.write(data)
50+
break
51+
52+
53+
if len(sys.argv) == 1:
54+
dj_utils.domjudge_webapp_folder_or_api_url = webappdir
55+
elif len(sys.argv) == 2:
56+
dj_utils.domjudge_webapp_folder_or_api_url = sys.argv[1]
57+
else:
58+
usage()
59+
60+
61+
user_data = dj_utils.do_api_request('user')
62+
if 'admin' not in user_data['roles']:
63+
print('Your user does not have the \'admin\' role, can not export.')
64+
exit(1)
65+
66+
67+
contest_id = 'wf48_systest2'
68+
69+
for endpoint in [
70+
'accounts',
71+
'awards',
72+
'balloons',
73+
'clarifications',
74+
'groups',
75+
'judgements',
76+
'languages',
77+
'organizations',
78+
'problems',
79+
'runs',
80+
'scoreboard',
81+
'submissions',
82+
'teams',
83+
]:
84+
data = api_to_file(f'contests/{contest_id}/{endpoint}', f'{endpoint}.json')
85+
if endpoint == 'submissions':
86+
submissions = json.loads(data)
87+
88+
api_to_file(f'contests/{contest_id}/event-feed?stream=false', 'event-feed.ndjson')
89+
90+
91+
for submission in submissions:
92+
download_submission(submission)
93+
94+
# with Pool(processes=10) as pool:
95+
# result = pool.map_async(download_submission, submissions)
96+
#
97+
# print(result)
98+
# result.wait()
99+
100+
# with ThreadPoolExecutor(20) as executor:
101+
# for submission in submissions:
102+
# executor.submit(download_submission, submission)

0 commit comments

Comments
 (0)