Skip to content

Commit b4bfb7e

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 5f14740 commit b4bfb7e

File tree

1 file changed

+37
-21
lines changed

1 file changed

+37
-21
lines changed

misc-tools/dj_utils.py

Lines changed: 37 additions & 21 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+
result = api_via_cli(name, 'POST', data, {apifilename: file})
125129
else:
126130
global ca_check
127131
files = [(apifilename, open(file, 'rb'))]
@@ -141,7 +145,15 @@ def upload_file(name: str, apifilename: str, file: str, data: dict = {}):
141145
response = requests.post(
142146
url, files=files, headers=headers, data=data, verify=ca_check)
143147

144-
return parse_api_response(name, response)
148+
result = parse_api_response(name, response)
149+
150+
try:
151+
result = json.loads(result)
152+
except json.decoder.JSONDecodeError as e:
153+
print(result)
154+
raise RuntimeError(f'Failed to JSON decode the response for API file upload request {name}')
155+
156+
return result
145157

146158

147159
def api_via_cli(name: str, method: str = 'GET', data: dict = {}, files: dict = {}, jsonData: dict = {}):
@@ -155,7 +167,7 @@ def api_via_cli(name: str, method: str = 'GET', data: dict = {}, files: dict = {
155167
jsonData (dict): the JSON data to use. Only used when method is POST or PUT
156168
157169
Returns:
158-
The parsed endpoint contents.
170+
The raw endpoint contents.
159171
160172
Raises:
161173
RuntimeError when the command exit code is not 0.
@@ -180,10 +192,14 @@ def api_via_cli(name: str, method: str = 'GET', data: dict = {}, files: dict = {
180192
command.append(name)
181193

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

185196
if result.returncode != 0:
186-
print(response)
197+
print(
198+
f"Command: {command}\nOutput:\n" +
199+
result.stdout.decode('utf-8') +
200+
result.stderr.decode('utf-8'),
201+
file=sys.stderr
202+
)
187203
raise RuntimeError(f'API request {name} failed')
188204

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

0 commit comments

Comments
 (0)