Skip to content

Commit ee81fdf

Browse files
committed
Add option to not JSON decode API responses
This will be used by the contest export script for downloading the event feed (NDJSON) and other files. Also improve error reporting when an internal API call fails.
1 parent 8350a2f commit ee81fdf

File tree

1 file changed

+28
-20
lines changed

1 file changed

+28
-20
lines changed

misc-tools/dj_utils.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def confirm(message: str, default: bool) -> bool:
3030
return answer == 'y'
3131

3232

33-
def parse_api_response(name: str, response: requests.Response):
33+
def parse_api_response(name: str, response: requests.Response) -> bytes:
3434
# The connection worked, but we may have received an HTTP error
3535
if response.status_code >= 300:
3636
print(response.text)
@@ -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:
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.content
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:
69-
The endpoint contents.
63+
The endpoint contents, either as raw bytes or JSON decoded.
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}'
@@ -86,7 +81,7 @@ def do_api_request(name: str, method: str = 'GET', jsonData: dict = {}):
8681
if method == 'GET':
8782
response = requests.get(url, headers=headers, verify=ca_check, auth=auth)
8883
elif method == 'PUT':
89-
response = requests.put(url, headers=headers, verify=ca_check, json=jsonData, auth=auth)
84+
response = requests.put(url, headers=headers, verify=ca_check, auth=auth, json=jsonData)
9085
else:
9186
raise RuntimeError("Method not supported")
9287
except requests.exceptions.SSLError:
@@ -99,7 +94,16 @@ def do_api_request(name: str, method: str = 'GET', jsonData: dict = {}):
9994
return do_api_request(name)
10095
except requests.exceptions.RequestException as e:
10196
raise RuntimeError(e)
102-
return parse_api_response(name, response)
97+
result = parse_api_response(name, response)
98+
99+
if decode:
100+
try:
101+
result = json.loads(result)
102+
except json.decoder.JSONDecodeError as e:
103+
print(result)
104+
raise RuntimeError(f'Failed to JSON decode the response for API request {name}')
105+
106+
return result
103107

104108

105109
def upload_file(name: str, apifilename: str, file: str, data: dict = {}):
@@ -121,7 +125,7 @@ def upload_file(name: str, apifilename: str, file: str, data: dict = {}):
121125
'''
122126

123127
if os.path.isdir(domjudge_webapp_folder_or_api_url):
124-
return api_via_cli(name, 'POST', data, {apifilename: file})
128+
response = api_via_cli(name, 'POST', data, {apifilename: file})
125129
else:
126130
global ca_check
127131
files = [(apifilename, open(file, 'rb'))]
@@ -155,7 +159,7 @@ def api_via_cli(name: str, method: str = 'GET', data: dict = {}, files: dict = {
155159
jsonData (dict): the JSON data to use. Only used when method is POST or PUT
156160
157161
Returns:
158-
The parsed endpoint contents.
162+
The raw endpoint contents.
159163
160164
Raises:
161165
RuntimeError when the command exit code is not 0.
@@ -180,10 +184,14 @@ def api_via_cli(name: str, method: str = 'GET', data: dict = {}, files: dict = {
180184
command.append(name)
181185

182186
result = subprocess.run(command, capture_output=True)
183-
response = result.stdout.decode('ascii')
184187

185188
if result.returncode != 0:
186-
print(response)
189+
print(
190+
f"Command: {command}\nOutput:\n" +
191+
result.stdout.decode('utf-8') +
192+
result.stderr.decode('utf-8'),
193+
file=sys.stderr
194+
)
187195
raise RuntimeError(f'API request {name} failed')
188196

189-
return json.loads(response)
197+
return result.stdout

0 commit comments

Comments
 (0)