Created
May 8, 2023 22:25
-
-
Save matthew-white/3ece8de162c2ef5d974d5d2f6ed4ff23 to your computer and use it in GitHub Desktop.
[FOR DISCUSSION ONLY] Untested CLI script using pyODK
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import json | |
import re | |
import sys | |
import urllib.parse | |
from mimetypes import guess_type | |
from pathlib import Path | |
from pyodk.client import Client | |
parser = argparse.ArgumentParser() | |
parser.add_argument('url') | |
parser.add_argument('--config') | |
parser.add_argument('--cache') | |
parser.add_argument('-X', '--request', default='get') | |
parser.add_argument('--json', nargs='?', const=True) | |
parser.add_argument('--xml', nargs='?', const=True) | |
parser.add_argument('-f', '--file') | |
parser.add_argument('-H', '--header', nargs=2, action='append') | |
parser.add_argument('-e', '--extended', action='store_true') | |
parser.add_argument('-i', '--include', action='store_true') | |
parser.add_argument('-o', '--output', nargs='?', const=True) | |
args = parser.parse_args() | |
# Use --config and --cache to create the client. | |
client = Client(config_path=args.config, cache_path=args.cache) | |
# --request | |
method = args.request.lower() | |
if method not in ['get', 'post', 'put', 'patch', 'delete']: | |
raise Exception('--request: invalid method') | |
# Request data: --json, --xml, and --file | |
data = None | |
headers = {} | |
if args.json is not None and args.xml is not None: | |
raise Exception('--json and --xml cannot both be specified') | |
if args.json is not None: | |
data = args.json | |
headers['content-type'] = 'application/json' | |
elif args.xml is not None: | |
data = args.xml | |
headers['content-type'] = 'application/xml' | |
headers['x-openrosa-version'] = '1.0' | |
if args.file is not None: | |
if isinstance(args.json, str): | |
raise Exception('cannot pass a string to --json and specify --file') | |
if isinstance(args.xml, str): | |
raise Exception('cannot pass a string to --xml and specify --file') | |
# TODO. Do we need to distinguish between ASCII and binary files? request() | |
# seems to want bytes, never a string. | |
with open(args.file, 'rb') as f: data = f.read() | |
if data is not None and method in ['get', 'delete']: | |
raise Exception(f'data not allowed for {method.upper()} request') | |
if data is True: data = sys.stdin.read() | |
# XLSForms | |
if args.file is not None: | |
file_path = Path(args.file) | |
if file_path.suffix.lower() in ['.xlsx', '.xls']: | |
headers['content-type'] = guess_type(args.file) | |
headers['x-xlsform-formid-fallback'] = file_path.stem | |
# --header and --extended | |
if args.extended: headers['x-extended-metadata'] = 'true' | |
# Headers specified using --header take precedence over other headers. | |
if args.header is not None: | |
# We need to call lower() to ensure that an existing header is overwritten. | |
for name, value in args.header: headers[name.lower()] = value | |
# Encode Central custom headers. | |
for name in ['x-xlsform-formid-fallback', 'x-action-notes']: | |
if name in headers: headers[name] = urllib.parse.quote(headers[name]) | |
# Send the request. | |
response = client.session.request(method, args.url, data=data, headers=headers) | |
# --include | |
# If --output was also specified, the status and headers are still printed, not | |
# written to file. | |
if args.include: | |
print(f'{response.status_code} {response.reason}') | |
for name, value in response.headers.items(): print(f'{name}: {value}') | |
if args.output is None: print('') | |
# Print the response data or, if --output was specified, save it to disk. | |
if args.output is None: | |
if re.match(r'^attachment(;|$)', response.headers.get('Content-Disposition', '')): | |
print('The response is an attachment. Use --output to save it to disk.') | |
elif re.match(r'^application/json(;|$)', response.headers['Content-Type']): | |
print(json.dumps(response.json(), indent=4)) | |
# Probably XML? | |
else: | |
print(response.content.decode('utf-8')) | |
else: | |
# Determine the file path at which to write the response data (output_path). | |
if args.output is not True: | |
output_path = args.output | |
else: | |
if 'Content-Disposition' not in response.headers: | |
print('The response did not return a Content-Disposition header. You must specify a filename to --output.') | |
sys.exit(0) | |
content_disposition = response.headers['Content-Disposition'] | |
if re.match(r'^attachment(;|$)', content_disposition) is None: | |
print('The response did not return an attachment Content-Disposition header. You must specify a filename to --output.') | |
sys.exit(0) | |
match = re.match(r"; filename\*=UTF-8''([^; ]+)", content_disposition) | |
if match is None: | |
raise Exception('unexpected Content-Disposition header') | |
output_path = urllib.parse.unquote(match.group(1)) | |
with open(output_path, 'wb') as f: f.write(response.content) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment