def validateRequestBody(self): """ Verify its a valid TRAPI request :return: None :raises: Will raise if not valid """ reasoner_validator.validate(self.requestBody, "Query", trapi_version)
def validate_trapi(request_json: dict): """Return request verbatim if it is a valid TRAPI query.""" try: validate(request_json, "Query", "1.1.0") except ValidationError as err: raise HTTPException(422, str(err)) return request_json
def test_edgebinding(): """Test validate_EdgeBinding().""" validate({ "id": "hello", }, "EdgeBinding", "1.0.3") with pytest.raises(ValidationError): validate({ "foo": {}, }, "EdgeBinding", "1.0.3")
def test_query(): """Test validate().""" validate({ "message": {}, }, "Query", "1.0.3") with pytest.raises(ValidationError): validate({ "foo": {}, "bar": {}, }, "Query", "1.0.3")
def test_version_completion(): """Test validate() with version completion.""" validate({ "message": {}, }, "Query", "1.0") with pytest.raises(ValidationError): validate({ "foo": {}, "bar": {}, }, "Query", "1")
def test_nullable(): """Test nullable property.""" qnode = {"category": None} validate(qnode, "QNode", "1.0.3") message = { "knowledge_graph": None, "query_graph": None, "results": None, } validate(message, "Message", "1.0.3")
def userRequestBodyValidation(self): """ A function to evaluate whether the JSON body received from the client conforms to the proper input standard. :return: Boolean: True meaning the body is valid, False meaning the body is not valid """ try: # reasoner_validator.validate_Query(self.userRequestBody) reasoner_validator.validate(self.userRequestBody, "Query", trapi_version) return {"isValid": True, "error": None} except ValidationError as e: return {"isValid": False, "error": e}
def test_trapi_empty_response(): reasoner_query = { "message": { "query_graph": { "edges": { "e00": { "subject": "n00", "object": "n01", "predicates": ["biolink:physically_interacts_with"] } }, "nodes": { "n00": { "ids": ["CHEMBL.COMPOUND:CHEMBL112"] }, "n01": { "categories": ["biolink:Protein"] } } } } } response = client.post( '/query', data=json.dumps(reasoner_query), headers={"Content-Type": "application/json"}, # content_type='application/json' ) print(response.json) assert validate(response.json()['message'], "Message", settings.TRAPI_VERSION_TEST) == None assert len(response.json()['message']['results']) == 0
def test_post_trapi(): """Test Translator ReasonerAPI query POST operation to get predictions""" url = '/query' for trapi_filename in os.listdir( pkg_resources.resource_filename('tests', 'queries')): with open( pkg_resources.resource_filename('tests', 'queries/' + trapi_filename), 'r') as f: reasoner_query = f.read() response = client.post( url, data=reasoner_query, headers={"Content-Type": "application/json"}, # content_type='application/json' ) # print(response.json) edges = response.json( )['message']['knowledge_graph']['edges'].items() # print(response) print(trapi_filename) assert validate(response.json()['message'], "Message", settings.TRAPI_VERSION_TEST) == None if trapi_filename.endswith('limit3.json'): assert len(edges) == 3 elif trapi_filename.endswith('limit1.json'): assert len(edges) == 1 else: assert len(edges) >= 5
def validateResponseBody(self): """ Verify its a valid TRAPI response :return: None :raises: Will raise InvalidSchema if not valid """ try: # reasoner_validator.validate_Response(self.responseBody) reasoner_validator.validate(self.responseBody, "Response", trapi_version) except ValidationError as e: self.logs.append(clsLogEvent( identifier=self.name, level="ERROR", code="KPMalformedResponse", message=f"Knowledge Provider {self.url} did not return a valid TRAPI v1.2 response" )) raise requests.exceptions.InvalidSchema # force raise a request.exception to know its the KP's fault
def userResponseBodyValidation(self): """ A function to evaluate whether the JSON body sent to the client conforms to the proper input standard. :return: Boolean: True meaning the body is valid, False meaning the body is not valid """ validity_status = {"isValid": True, "error": None} # ensure all edge attribute lists end with the Explanatory Agent provenance attribute try: if self.userResponseBody['message'][ 'knowledge_graph'] and "edges" in self.userResponseBody[ 'message']['knowledge_graph']: for edge_id, edge in self.userResponseBody['message'][ 'knowledge_graph']['edges'].items(): last_attribute = edge['attributes'][-1] assert last_attribute[ "attribute_type_id"] == "biolink:aggregator_knowledge_source", "Edge missing xARA provenance data" assert last_attribute[ "attribute_source"] == "infores:explanatory-agent", "Edge missing xARA provenance data" except AssertionError as e: self.logs.append( clsLogEvent( identifier="", level="DEBUG", code="", message=f"Provenance assertion failure: {e}").dict()) # validity_status = {"isValid": False, "error": e} except Exception as e: self.logs.append( clsLogEvent( identifier="", level="DEBUG", code="", message=f"Provenance assertion failure: {e}").dict()) try: reasoner_validator.validate(self.userResponseBody, "Response", trapi_version) except ValidationError as e: validity_status = {"isValid": False, "error": e} return validity_status
def test_post_trapi(): """Test Translator ReasonerAPI query POST operation to get predictions""" headers = {'Content-type': 'application/json'} for trapi_filename in os.listdir( pkg_resources.resource_filename('tests', 'queries')): with open( pkg_resources.resource_filename('tests', 'queries/' + trapi_filename), 'r') as f: trapi_query = f.read() trapi_results = requests.post(PROD_API_URL + '/query', data=trapi_query, headers=headers).json() edges = trapi_results['message']['knowledge_graph']['edges'].items( ) print(trapi_filename) assert validate(trapi_results['message'], "Message", VALIDATE_TRAPI_VERSION) == None if trapi_filename.endswith('limit3.json'): assert len(edges) == 3 elif trapi_filename.endswith('limit1.json'): assert len(edges) == 1 else: assert len(edges) >= 5 # TODO: Check for this edge structure: # "knowledge_graph": { # "edges": { # "e0": { # "attributes": [ # { # "name": "model_id", # "source": "OpenPredict", # "type": "EDAM:data_1048", # "value": "openpredict-baseline-omim-drugbank" # }, # { # "name": "score", # "source": "OpenPredict", # "type": "EDAM:data_1772", # "value": "0.8267106697312154" # } # ], # "object": "DRUGBANK:DB00394", # "predicate": "biolink:treated_by", # "relation": "RO:0002434", # "subject": "OMIM:246300" # },
def main(): #### Parse command line options import argparse argparser = argparse.ArgumentParser( description='CLI testing of the ResponseCache class') argparser.add_argument( '--verbose', action='count', help='If set, print more information about ongoing processing') #argparser.add_argument('list', action='store', help='List all local response ids') argparser.add_argument('response_id', type=str, nargs='*', help='Id of a response to fetch and display') params = argparser.parse_args() #### Create a new ResponseStore object response_cache = ResponseCache() #### Get the session handle session = response_cache.session #### Query and print some rows from the reference tables #if params.list is True: if False: print("Listing of all responses") for response in session.query(Response).all(): print( f"response_id={response.response_id} response_datetime={response.response_datetime}" ) return if len(params.response_id) > 0: print(f"Content of response_id {params.response_id[0]}:") envelope = response_cache.get_response(params.response_id[0]) #print(json.dumps(ast.literal_eval(repr(envelope)), sort_keys=True, indent=2)) #print(json.dumps(envelope, sort_keys=True, indent=2)) print(json.dumps(envelope['logs'], sort_keys=True, indent=2)) #return try: validate(envelope['message'], 'Message', trapi_version) print('- Message is valid') except ValidationError as error: print(f"- Message INVALID: {error}") #return for component, klass in { 'query_graph': 'QueryGraph', 'knowledge_graph': 'KnowledgeGraph' }.items(): if component in envelope['message']: try: validate(envelope['message'][component], klass, trapi_version) print(f" - {component} is valid") except ValidationError: print(f" - {component} INVALID") else: print(f" - {component} is not present") for node_key, node in envelope['message']['knowledge_graph'][ 'nodes'].items(): print(f"{node_key}") for attribute in node['attributes']: attribute['value_type_id'] = None try: validate(attribute, 'Attribute', trapi_version) print( f" - attribute with {attribute['attribute_type_id']} is valid" ) except ValidationError: print( f" - attribute with {attribute['attribute_type_id']} is INVALID" ) for result in envelope['message']['results']: try: validate(result, 'Result', trapi_version) print(f" - result is valid") except ValidationError: print(f" - result INVALID") for key, node_binding_list in result['node_bindings'].items(): for node_binding in node_binding_list: try: validate(node_binding, 'NodeBinding', trapi_version) print(f" - node_binding {key} is valid") except ValidationError: print(f" - node_binding {key} INVALID") for key, edge_binding_list in result['edge_bindings'].items(): for edge_binding in edge_binding_list: #print(json.dumps(edge_binding, sort_keys=True, indent=2)) try: validate(edge_binding, 'EdgeBinding', trapi_version) print(f" - edge_binding {key} is valid") except ValidationError: print(f" - edge_binding {key} INVALID")
def get_response(self, response_id): session = self.session if response_id is None: return ({ "status": 400, "title": "response_id missing", "detail": "Required attribute response_id is missing from URL", "type": "about:blank" }, 400) response_id = str(response_id) #### Check to see if this is an integer. If so, it is a local response id match = re.match(r'\d+\s*$', response_id) if match: #### Find the response stored_response = session.query(Response).filter( Response.response_id == int(response_id)).first() if stored_response is not None: found_response_locally = False response_dir = os.path.dirname( os.path.abspath(__file__)) + '/../../../data/responses_1_0' response_filename = f"{stored_response.response_id}.json" response_path = f"{response_dir}/{response_filename}" try: with open(response_path) as infile: envelope = json.load(infile) found_response_locally = True except: eprint( f"ERROR: Unable to read response from file '{response_path}'. Will now try S3" ) #### If the file wasn't local, try it in S3 if not found_response_locally: rtx_config = RTXConfiguration() KEY_ID = rtx_config.config["Global"]['s3']['access'] ACCESS_KEY = rtx_config.config["Global"]['s3']['secret'] try: s3 = boto3.resource('s3', region_name='us-west-2', aws_access_key_id=KEY_ID, aws_secret_access_key=ACCESS_KEY) response_filename = f"/responses/{response_id}.json" eprint( f"INFO: Attempting to read {response_filename} from S3" ) t0 = timeit.default_timer() content = s3.Object( 'arax-response-storage', response_filename).get()["Body"].read() envelope = json.loads(content) t1 = timeit.default_timer() print("Elapsed time: " + str(t1 - t0)) eprint( f"INFO: Successfully read {response_filename} from S3 in {t1-t0} seconds" ) except: eprint( f"ERROR: Unable to read {response_filename} from S3" ) return ({ "status": 404, "title": "Response not found", "detail": "There is no response corresponding to response_id=" + str(response_id), "type": "about:blank" }, 404) #### Perform a validation on it try: validate(envelope, 'Response', trapi_version) if 'description' not in envelope or envelope[ 'description'] is None: envelope['description'] = 'reasoner-validator: PASS' except ValidationError as error: timestamp = str(datetime.now().isoformat()) if 'logs' not in envelope or envelope['logs'] is None: envelope['logs'] = [] envelope['logs'].append({ "code": 'InvalidTRAPI', "level": "ERROR", "message": "TRAPI validator reported an error: " + str(error), "timestamp": timestamp }) if 'description' not in envelope or envelope[ 'description'] is None: envelope['description'] = '' envelope[ 'description'] = 'ERROR: TRAPI validator reported an error: ' + str( error) + ' --- ' + envelope['description'] return envelope else: return ({ "status": 404, "title": "Response not found", "detail": "There is no response corresponding to response_id=" + str(response_id), "type": "about:blank" }, 404) #### Otherwise, see if it is an ARS style response_id if len(response_id) > 30: ars_hosts = [ 'ars.transltr.io', 'ars-dev.transltr.io', 'ars.ci.transltr.io' ] for ars_host in ars_hosts: with requests_cache.disabled(): try: response_content = requests.get( f"https://{ars_host}/ars/api/messages/" + response_id, headers={'accept': 'application/json'}) except Exception as e: return ({ "status": 404, "title": f"Remote host {ars_host} unavailable", "detail": f"Connection attempts to {ars_host} triggered an exception: {e}", "type": "about:blank" }, 404) status_code = response_content.status_code #eprint(f"--- Fetch of {response_id} from {ars_host} yielded {status_code}") if status_code == 200: break if status_code != 200: return ({ "status": 404, "title": "Response not found", "detail": "Cannot fetch from ARS a response corresponding to response_id=" + str(response_id), "type": "about:blank" }, 404) content_size = len(response_content.content) if content_size < 1000: content_size = '{:.2f} kB'.format(content_size / 1000) elif content_size < 1000000: content_size = '{:.0f} kB'.format(content_size / 1000) elif content_size < 10000000000: content_size = '{:.1f} MB'.format(content_size / 1000000) else: content_size = '{:.0f} MB'.format(content_size / 1000000) #### Unpack the response content into a dict try: response_dict = response_content.json() except: return ({ "status": 404, "title": "Error decoding Response", "detail": "Cannot decode ARS response_id=" + str(response_id) + " to a Translator Response", "type": "about:blank" }, 404) if 'fields' in response_dict and 'actor' in response_dict[ 'fields'] and str(response_dict['fields']['actor']) == '9': with requests_cache.disabled(): response_content = requests.get( f"https://{ars_host}/ars/api/messages/" + response_id + '?trace=y', headers={'accept': 'application/json'}) status_code = response_content.status_code if status_code != 200: return ({ "status": 404, "title": "Response not found", "detail": "Failed attempting to fetch trace=y from ARS with response_id=" + str(response_id), "type": "about:blank" }, 404) #### Unpack the response content into a dict and dump try: response_dict = response_content.json() except: return ({ "status": 404, "title": "Error decoding Response", "detail": "Cannot decode ARS response_id=" + str(response_id) + " to a Translator Response", "type": "about:blank" }, 404) return response_dict if 'fields' in response_dict and 'data' in response_dict['fields']: envelope = response_dict['fields']['data'] if envelope is None: envelope = {} return envelope actual_response = str(envelope) if not isinstance(envelope, dict): envelope = {'detail': envelope} #### Actor lookup actor_lookup = { '1': 'Aragorn', '2': 'ARAX', '3': 'BTE', '4': 'NCATS', '5': 'Robokop', '6': 'Unsecret', '7': 'Genetics', '8': 'MolePro', '10': 'Explanatory', '11': 'ImProving', '12': 'Cam', '13': 'TextMining' } #Remove warning code hack #if 'logs' in envelope and envelope['logs'] is not None: # for log in envelope['logs']: # if isinstance(log,dict): # if 'code' in log and log['code'] is None: # log['code'] = '-' is_trapi = True if 'message' in envelope: #eprint("INFO: envelope has a message") #eprint(json.dumps(envelope,indent=2,sort_keys=True)) if 'logs' in envelope and isinstance( envelope['logs'], list) and len(envelope['logs']) > 0: #eprint("INFO: envelope has logs") if isinstance(envelope['logs'][0], str): #eprint("INFO: logs[0] is str") is_trapi = False actual_response = envelope['logs'][0] for i in range(len(envelope['logs'])): if isinstance(envelope['logs'][i], str): envelope['logs'][i] = { 'level': 'INFO', 'message': 'ARS info: ' + envelope['logs'][i] } try: #eprint(f"INFO: Actual response: {actual_response}") import html actual_response = html.unescape( actual_response) #eprint(f"INFO: Actual decoded response: {actual_response}") actual_response_dict = json.loads( actual_response) if 'message' in actual_response_dict: is_trapi = True envelope = actual_response_dict except: eprint( "WARNING: tried to convert the response to JSON and it did not work" ) eprint(f"It was: {envelope['logs'][0]}") else: #eprint("INFO: envelope has no message") is_trapi = False if not is_trapi: envelope['validation_result'] = { 'status': 'NA', 'version': trapi_version, 'size': content_size, 'message': 'Returned response is not TRAPI: ' + actual_response } return envelope #### Perform a validation on it try: validate(envelope, 'Response', trapi_version) envelope['validation_result'] = { 'status': 'PASS', 'version': trapi_version, 'size': content_size, 'message': '' } except ValidationError as error: timestamp = str(datetime.now().isoformat()) if 'logs' not in envelope or envelope['logs'] is None: envelope['logs'] = [] envelope['logs'].append({ "code": 'InvalidTRAPI', "level": "ERROR", "message": "TRAPI validator reported an error: " + str(error), "timestamp": timestamp }) if 'description' not in envelope or envelope[ 'description'] is None: envelope['description'] = '' envelope['validation_result'] = { 'status': 'FAIL', 'version': trapi_version, 'size': content_size, 'message': 'TRAPI validator reported an error: ' + str(error) + ' --- ' + envelope['description'] } #### Try to add the reasoner_id if 'actor' in response_dict['fields'] and response_dict[ 'fields']['actor'] is not None: actor = str(response_dict['fields']['actor']) if actor in actor_lookup: if 'message' in envelope and 'results' in envelope[ 'message'] and envelope['message'][ 'results'] is not None: for result in envelope['message']['results']: if 'reasoner_id' in result and result[ 'reasoner_id'] is not None: pass else: result['reasoner_id'] = actor_lookup[actor] if 'message' in envelope and 'knowledge_graph' in envelope[ 'message'] and envelope['message'][ 'knowledge_graph'] is not None: n_nodes = None if 'nodes' in envelope['message'][ 'knowledge_graph'] and envelope['message'][ 'knowledge_graph']['nodes'] is not None: n_nodes = len( envelope['message']['knowledge_graph']['nodes']) n_edges = None if 'edges' in envelope['message'][ 'knowledge_graph'] and envelope['message'][ 'knowledge_graph']['edges'] is not None: n_edges = len( envelope['message']['knowledge_graph']['edges']) envelope['validation_result']['n_nodes'] = n_nodes envelope['validation_result']['n_edges'] = n_edges #### Count provenance information attribute_parser = ARAXAttributeParser( envelope, envelope['message']) envelope['validation_result'][ 'provenance_summary'] = attribute_parser.summarize_provenance_info( ) return envelope return ({ "status": 404, "title": "Cannot find Response (in 'fields' and 'data') in ARS response packet", "detail": "Cannot decode ARS response_id=" + str(response_id) + " to a Translator Response", "type": "about:blank" }, 404) return ({ "status": 404, "title": "UnrecognizedResponse_idFormat", "detail": "Unrecognized response_id format", "type": "about:blank" }, 404)
iterateDictionary(value) iterateDictionary(fileDataClean) x = 5 nominal = { "query_graph": { "edges": { "e00": { "subject": "n00", "object": "n01", "type": "biolink:associated" } }, "nodes": { "n00": { "curie": "EFO:0004465", "type": "biolink:Disease" }, "n01": { "type": "biolink:Gene" } } } } reasoner_validator.validate(nominal) x = 5