Skip to content

Commit

Permalink
Updated and tested as per issue 116.
Browse files Browse the repository at this point in the history
  • Loading branch information
ChuckKollar committed Aug 26, 2024
1 parent 4f9606c commit d6c9f8d
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 14 deletions.
111 changes: 97 additions & 14 deletions dev/app.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,117 @@
import os
import json
from flask import Flask, Response
from flask import Flask, request, Response, abort
from urllib.request import urlopen
from urllib.error import HTTPError
import logging

#
# This is a RESTful service that exposes two endpoints:
# /assayclasses?application_context=HUBMAP
# /assayclasses/<assay-code>?application_context=HUBMAP
#
# Both endpoints require the application_context=HUBMAP parameter to be included, otherwise they return a 400.
#
# This service is intended to be for development purposes only, so a developer can easily control the output by
# changing the [assayclasses.json file in GitHub], specified in the ASSAYCLASSES_JSON_URL app.conf parameter.
#
# It is intended to mimic the services:
# https://ontology-api.dev.hubmapconsortium.org/assayclasses?application_context=HUBMAP
# https://ontology-api.dev.hubmapconsortium.org/assayclasses/C200150?application_context=HUBMAP
#
# Please see Issue: https://github.com/orgs/hubmapconsortium/projects/40/views/1?filterQuery=kollar&visibleFields=%5B%22Title%22%2C%22Assignees%22%2C%22Status%22%2C%22Labels%22%2C117184707%5D&pane=issue&itemId=74945308


logging.basicConfig(format='[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
level=logging.DEBUG,
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger()


# Specify the absolute path of the instance folder and use the config file relative to the instance path
app = Flask(__name__,
instance_path=os.path.join(os.path.abspath(os.path.dirname(__file__)), 'instance'),
instance_relative_config=True)
app.config.from_pyfile('app.cfg')


def get_assayclasses() -> dict:
"""
Download the assayclasses information and return it as a dict.
Since this is done at startup time, on error it will log an error and exit as the app cannot recover from this.
"""
if "ASSAYCLASSES_JSON_URL" not in app.config:
logger.error("app.cfg must contain ASSAYCLASSES_JSON_URL")
exit(1)
assayclasses_json_url: str = app.config["ASSAYCLASSES_JSON_URL"]
try:
response = urlopen(assayclasses_json_url)
data: str = response.read().decode("utf-8")
except HTTPError as he:
logger.error(f"Could not read ASSAYCLASSES_JSON_URL: {assayclasses_json_url}, error: {he}")
exit(1)
try:
json_dict: dict = json.loads(data)
except json.JSONDecodeError as jde:
logger.error(f"Invalid JSON syntax: {jde}")
exit(1)
logger.info(f"successfully downloaded app.cfg:ASSAYCLASSES_JSON_URL={assayclasses_json_url} json as dict")
return json_dict


assayclasses_list: dict = get_assayclasses()


def find_assayclass_with_rule_description_code(code: str):
"""
Find the assayclass corresponding the rule_description code given or return None.
"""
for ad in assayclasses_list:
if "rule_description" in ad and "code" in ad["rule_description"] and ad["rule_description"]["code"] == code:
return ad
return None


def check_for_valid_application_context() -> None:
"""
Check for a valid auery parameter application_context, and if not found abort with a 400.
"""
application_context = request.args.get('application_context')
if application_context != "HUBMAP":
abort(400, "A query parameter of application_context=HUBMAP must be specified")


@app.route('/', methods=['GET'])
def index():
return "Hello! This is the DEV AssayClass service :)"


@app.route('/assayclasses/<code>', methods=['GET'])
def assayclasses(code):
response_code = 200
response_data = {
'message': f'This endpoint not yet implemented code: {code}'
}
def assayclasses_by_code(code):
"""
This endpoint searches the same assayclasses.json file for an assayclass item matching
rule_description.code and returns the full matching assayclass item as a json response.
If the code is not found a 404 is returned.
"""
check_for_valid_application_context()

assayclass_dict = find_assayclass_with_rule_description_code(code)

if assayclass_dict is not None:
return Response(json.dumps(assayclass_dict), 200, mimetype='application/json')

return Response(json.dumps({"message": f"No assayclass corresponding the rule_description code:{code} was found"}),
404, mimetype='application/json')

return Response(json.dumps(response_data), response_code, mimetype='application/json')

@app.route('/assayclasses', methods=['GET'])
def assayclass_by_code():
response_code = 200
response_data = {
'message': 'This endpoint not yet implemented'
}
def assayclass():
"""
This endpoint returns the contents of the ASSAYCLASSES_JSON_URL as a json response.
"""
check_for_valid_application_context()

return Response(json.dumps(response_data), response_code, mimetype='application/json')
return Response(json.dumps(assayclasses_list), 200, mimetype='application/json')


# For development/testing only
Expand Down
39 changes: 39 additions & 0 deletions dev/test_api.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash
set -e
set -u

ASSAYCLASS_URL_LOCAL=http://localhost:8181
ASSAYCLASS_URL=$ASSAYCLASS_URL_LOCAL


curl --request GET \
--url "${ASSAYCLASS_URL}"
echo


# Both endpoints require the application_context=HUBMAP parameter to be included, otherwise return a 400.
curl --request GET \
--url "${ASSAYCLASS_URL}/assayclasses" \
--header "Accept: application/json"
echo

curl --request GET \
--url "${ASSAYCLASS_URL}/assayclasses?application_context=HUBMAP" \
--header "Accept: application/json"
echo


curl --request GET \
--url "${ASSAYCLASS_URL}/assayclasses/C200010" \
--header "Accept: application/json"
echo

curl --request GET \
--url "${ASSAYCLASS_URL}/assayclasses/xyzzy?application_context=HUBMAP" \
--header "Accept: application/json"
echo

curl --request GET \
--url "${ASSAYCLASS_URL}/assayclasses/C200010?application_context=HUBMAP" \
--header "Accept: application/json"
echo

0 comments on commit d6c9f8d

Please sign in to comment.