Skip to content

Commit

Permalink
Support for POST methods with JSON payloads.
Browse files Browse the repository at this point in the history
  • Loading branch information
kjolley committed Oct 31, 2024
1 parent 5b4ec84 commit a699848
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 2 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ To download you would run something like the following:
./bigsdb_downloader.py --key_name PubMLST --site PubMLST --url "https://rest.pubmlst.org/db/pubmlst_neisseria_seqdef/schemes/1/profiles_csv"
```

It is also possible to use HTTP POST method calls and include a JSON payload.
For example, to perform a BLAST query of a single abcZ sequence against the
MLST scheme in the Neisseria database you can do:

```
./bigsdb_downloader.py --key_name PubMLST --site PubMLST --url "https://rest.pubmlst.org/db/pubmlst_neisseria_seqdef/schemes/1/sequence" --method POST --json_body '{"sequence":"TTTGATACCGTTGCCGAAGGTTTGGGCGAAATTCGTGATTTATTGCGCCGTTATCATCATGTCAGCCATGAGTTGGAAAATGGTTCGAGTGAGGCTTTGTTGAAAGAACTCAACGAATTGCAACTTGAAATCGAAGCGAAGGACGGCTGGAAACTGGATGCGGCAGTCAAGCAGACTTTGGGGGAACTCGGTTTGCCGGAAAATGAAAAAATCGGCAACCTTTCCGGCGGTCAGAAAAAGCGCGTCGCCTTGGCTCAGGCTTGGGTGCAAAAGCCCGACGTATTGCTGCTGGACGAGCCGACCAACCATTTGGATATCGACGCGATTATTTGGCTGGAAAATCTGCTCAAAGCGTTTGAAGGCAGCTTGGTTGTGATTACCCACGACCGCCGTTTTTTGGACAATATCGCCACGCGGATTGTCGAACTCGATC"}'
```
For payloads larger than the command line character limit, e.g. whole genome
assemblies, you can write the JSON payload to a temporary file and pass this
filename as an option. For example to query a FASTA file, contigs.fasta,
against the same scheme you can do:

```
file_contents=$(base64 -w 0 contigs.fasta)
json_body=$(echo -n '{"base64":true,"details":false,"sequence": "'; echo -n "$file_contents"; echo '"}')
echo "$json_body" > temp.json
```
# Options

```
Expand Down
74 changes: 72 additions & 2 deletions bigsdb_downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# Version 20241023
# Version 20241030
import argparse
import os
import stat
import re
import configparser
import sys
import json
from urllib.parse import parse_qs
from pathlib import Path
from rauth import OAuth1Service, OAuth1Session

Expand All @@ -35,6 +37,7 @@


parser = argparse.ArgumentParser()

parser.add_argument(
"--cron",
action="store_true",
Expand All @@ -43,11 +46,30 @@
parser.add_argument(
"--db", required=False, help="Database config - only needed for setup."
)
parser.add_argument(
"--json_body",
required=False,
help="JSON body to be included in a POST call. If this is longer than "
"the command line limit (probably about 128kb) then you will need to "
"save the JSON payload to a file and use --json_body_file",
)
parser.add_argument(
"--json_body_file",
required=False,
help="File containing JSON to use in the body of a POST call.",
)
parser.add_argument(
"--key_name",
required=True,
help="Name of API key - use a different name for each site.",
)
parser.add_argument(
"--method",
required=False,
choices=["GET", "POST"],
default="GET",
help="HTTP method",
)
parser.add_argument(
"--output_file",
required=False,
Expand Down Expand Up @@ -90,14 +112,60 @@ def check_required_args(args):
else:
if not args.url:
parser.error("--url is required")
if args.json_body:
if args.method != "POST":
parser.error("You cannot use --json_body with --method=GET")
if args.json_body_file:
parser.error("You cannot use both --json_body and --json_body_file")

if args.json_body_file:
if args.method != "POST":
parser.error("You cannot use --json_body_file with --method=GET")


def is_valid_json(json_string):
try:
json.loads(json_string)
return True
except ValueError:
return False


def trim_url_args(url):
if not "?" in url:
return url, {}
trimmed_url, param_string = url.split("?")
params = parse_qs(param_string)
params = {k: int(v[0]) for k, v in params.items()}

return trimmed_url, params


def get_route(url, token, secret):
(client_key, client_secret) = get_client_credentials()
session = OAuth1Session(
client_key, client_secret, access_token=token, access_token_secret=secret
)
r = session.get(url)
trimmed_url, request_params = trim_url_args(url)
if args.method == "GET":
r = session.get(trimmed_url, params=request_params)
else:
json_body = "{}"
if args.json_body:
json_body = args.json_body
elif args.json_body_file:
with open(args.json_body_file, "r") as file:
json_body = file.read()
if not is_valid_json(json_body):
parser.error("Body does not contain valid JSON")
r = session.post(
trimmed_url,
params=request_params,
data=json_body,
headers={"Content-Type": "application/json"},
header_auth=True,
)

if r.status_code == 200 or r.status_code == 201:
if args.output_file:
try:
Expand All @@ -122,6 +190,8 @@ def get_route(url, token, secret):
sys.stderr.write("Access denied - client is unauthorized\n")
sys.exit(1)
else:
sys.stderr.write(r.json()["message"] + "\n")
sys.exit(1) # TODO Remove
sys.stderr.write("Invalid session token, requesting new one...\n")
(token, secret) = get_new_session_token()
get_route(url, token, secret)
Expand Down

0 comments on commit a699848

Please sign in to comment.