Skip to content

Commit d8db6cd

Browse files
committed
Add option to save API output to file
This is needed for binary content that otherwise gets mangled when fetched through the CLI. Also force all but the `endpoint` and `method` parameters to `do_api_request` to be named parameters to prevent mistakes.
1 parent 4e3b52a commit d8db6cd

File tree

3 files changed

+33
-7
lines changed

3 files changed

+33
-7
lines changed

misc-tools/configure-domjudge.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ if os.path.exists('config.json'):
8484
print(f' - missing keys from new config = {missing_keys}')
8585
if diffs or new_keys or missing_keys:
8686
if dj_utils.confirm(' - Upload these configuration changes?', True):
87-
actual_config = dj_utils.do_api_request('config', 'PUT', expected_config)
87+
actual_config = dj_utils.do_api_request('config', 'PUT', jsonData=expected_config)
8888
diffs, new_keys, missing_keys = compare_configs(
8989
actual_config=actual_config,
9090
expected_config=expected_config

misc-tools/dj_utils.py.in

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ def is_relative(url: str) -> bool:
5555
(len(parsed.path)==0 or parsed.path[0]!='/'))
5656

5757

58-
def do_api_request(endpoint: str, method: str = 'GET', jsonData: dict = {},
59-
data: dict = {}, files: dict = {}, decode: bool = True):
58+
def do_api_request(endpoint: str, method: str = 'GET', *,
59+
data: dict = {}, files: dict = {}, jsonData: dict = {},
60+
decode: bool = True, output_file: str = None):
6061
'''Perform an API call to the given endpoint and return its data.
6162

6263
Based on whether `domjudge_api_url` is set, this will call the DOMjudge
@@ -69,9 +70,12 @@ def do_api_request(endpoint: str, method: str = 'GET', jsonData: dict = {},
6970
jsonData (dict): the JSON data to PUT. Only used when method is PUT
7071
data (dict): data to pass in a POST request
7172
decode (bool): whether to decode the returned JSON data, default true
73+
output_file (str): write API response to file, e.g. for binary content;
74+
incompatible with decode=True.
7275

7376
Returns:
74-
The endpoint contents, either as raw bytes or JSON decoded.
77+
The endpoint contents, either as raw bytes or JSON decoded, unless
78+
output_file is specified.
7579

7680
Raises:
7781
RuntimeError when the HTTP status code is non-2xx or the response
@@ -82,7 +86,7 @@ def do_api_request(endpoint: str, method: str = 'GET', jsonData: dict = {},
8286
orig_kwargs = locals()
8387

8488
if domjudge_api_url is None and is_relative(endpoint):
85-
result = api_via_cli(endpoint, method, {}, {}, jsonData)
89+
result = api_via_cli(endpoint, method, jsonData=jsonData, output_file=output_file)
8690
else:
8791
if not is_relative(endpoint):
8892
raise RuntimeError(f'Cannot access non-relative URL {endpoint} without API base URL')
@@ -122,6 +126,10 @@ def do_api_request(endpoint: str, method: str = 'GET', jsonData: dict = {},
122126
raise RuntimeError(e)
123127
result = parse_api_response(endpoint, response)
124128

129+
if output_file is not None:
130+
with open(output_file, 'wb') as f:
131+
f.write(result)
132+
125133
if decode:
126134
try:
127135
result = json.loads(result)
@@ -141,7 +149,9 @@ def upload_file(endpoint: str, apifilename: str, file: str, data: dict = {}):
141149
do_api_request(endpoint, 'POST', data=data, files={apifilename: file})
142150

143151

144-
def api_via_cli(endpoint: str, method: str = 'GET', data: dict = {}, files: dict = {}, jsonData: dict = {}):
152+
def api_via_cli(endpoint: str, method: str = 'GET', *,
153+
data: dict = {}, files: dict = {}, jsonData: dict = {},
154+
output_file: str = None):
145155
'''Perform the given API request using the CLI
146156

147157
Parameters:
@@ -150,6 +160,7 @@ def api_via_cli(endpoint: str, method: str = 'GET', data: dict = {}, files: dict
150160
data (dict): the POST data to use. Only used when method is POST or PUT
151161
files (dict): the files to use. Only used when method is POST or PUT
152162
jsonData (dict): the JSON data to use. Only used when method is POST or PUT
163+
output_file (str): write API response to file, e.g. for binary content
153164

154165
Returns:
155166
The raw endpoint contents.
@@ -174,6 +185,9 @@ def api_via_cli(endpoint: str, method: str = 'GET', data: dict = {}, files: dict
174185
if jsonData:
175186
command.extend(['-j', json.dumps(jsonData)])
176187

188+
if output_file:
189+
command.extend(['-o', output_file])
190+
177191
command.append(endpoint)
178192

179193
result = subprocess.run(command, capture_output=True)

webapp/src/Command/CallApiActionCommand.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ protected function configure(): void
6565
'u',
6666
InputOption::VALUE_REQUIRED,
6767
'User to use for API requests. If not given, the first admin user will be used'
68+
)
69+
->addOption(
70+
'output',
71+
'o',
72+
InputOption::VALUE_REQUIRED,
73+
'Filename to write output to. Useful for binary content'
6874
);
6975
}
7076

@@ -154,7 +160,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int
154160
return Command::FAILURE;
155161
}
156162

157-
$output->writeln($response);
163+
if ($filename = $input->getOption('output')) {
164+
$fd = fopen($filename, 'w');
165+
fwrite($fd, $response);
166+
fclose($fd);
167+
} else {
168+
$output->write($response);
169+
}
158170
return Command::SUCCESS;
159171
}
160172
}

0 commit comments

Comments
 (0)