def answer_one_hop_query( self, query_graph: QueryGraph ) -> Tuple[QGOrganizedKnowledgeGraph, Dict[str, Dict[str, str]]]: """ This function answers a one-hop (single-edge) query using the Molecular Provider. :param query_graph: A Reasoner API standard query graph. :return: A tuple containing: 1. an (almost) Reasoner API standard knowledge graph containing all of the nodes and edges returned as results for the query. (Dictionary version, organized by QG IDs.) 2. a map of which nodes fulfilled which qnode_keys for each edge. Example: {'KG1:111221': {'n00': 'DOID:111', 'n01': 'HP:124'}, 'KG1:111223': {'n00': 'DOID:111', 'n01': 'HP:126'}} """ log = self.response final_kg = QGOrganizedKnowledgeGraph() edge_to_nodes_map = dict() # Verify this is a valid one-hop query graph and tweak its contents as needed for this KP self._verify_one_hop_query_graph_is_valid(query_graph, log) if log.status != 'OK': return final_kg, edge_to_nodes_map modified_query_graph = self._pre_process_query_graph(query_graph, log) if log.status != 'OK': return final_kg, edge_to_nodes_map qedge = next(qedge for qedge in modified_query_graph.edges.values()) source_qnode_key = qedge.subject target_qnode_key = qedge.object # Answer the query using the KP and load its answers into our Swagger model json_response = self._send_query_to_kp(modified_query_graph, log) returned_kg = json_response.get('knowledge_graph') if not returned_kg: log.warning( f"No KG is present in the response from {self.kp_name}") else: # Build a map of node/edge IDs to qnode/qedge IDs qg_id_mappings = self._get_qg_id_mappings_from_results( json_response['results']) # Populate our final KG with nodes and edges for returned_edge_key, returned_edge in returned_kg['edges'].items( ): kp_edge_key, swagger_edge = self._create_swagger_edge_from_kp_edge( returned_edge_key, returned_edge) swagger_edge_key = self._create_unique_edge_key( swagger_edge) # Convert to an ID that's unique for us for qedge_key in qg_id_mappings['edges'][kp_edge_key]: final_kg.add_edge(swagger_edge_key, swagger_edge, qedge_key) edge_to_nodes_map[swagger_edge_key] = { source_qnode_key: swagger_edge.subject, target_qnode_key: swagger_edge.object } for returned_node_key, returned_node in returned_kg['nodes'].items( ): swagger_node_key, swagger_node = self._create_swagger_node_from_kp_node( returned_node_key, returned_node) for qnode_key in qg_id_mappings['nodes'][swagger_node_key]: final_kg.add_node(swagger_node_key, swagger_node, qnode_key) return final_kg, edge_to_nodes_map
def _answer_query_using_kp(self, query_graph: QueryGraph) -> Tuple[QGOrganizedKnowledgeGraph, Dict[str, Dict[str, str]]]: answer_kg = QGOrganizedKnowledgeGraph() edge_to_nodes_map = dict() # Strip non-essential and 'empty' properties off of our qnodes and qedges stripped_qnodes = {qnode_key: self._strip_empty_properties(qnode) for qnode_key, qnode in query_graph.nodes.items()} stripped_qedges = {qedge_key: self._strip_empty_properties(qedge) for qedge_key, qedge in query_graph.edges.items()} # Send the query to the KP's API body = {'message': {'query_graph': {'nodes': stripped_qnodes, 'edges': stripped_qedges}}} self.log.debug(f"Sending query to {self.kp_name} API") kp_response = requests.post(f"{self.kp_endpoint}/query", json=body, headers={'accept': 'application/json'}) json_response = kp_response.json() if kp_response.status_code == 200: if not json_response.get("message"): self.log.warning( f"No 'message' was included in the response from {self.kp_name}. Response from KP was: " f"{json.dumps(json_response, indent=4)}") elif not json_response["message"].get("results"): self.log.warning(f"No 'results' were returned from {self.kp_name}. Response from KP was: " f"{json.dumps(json_response, indent=4)}") json_response["message"]["results"] = [] # Setting this to empty list helps downstream processing else: kp_message = ARAXMessenger().from_dict(json_response["message"]) # Build a map that indicates which qnodes/qedges a given node/edge fulfills kg_to_qg_mappings = self._get_kg_to_qg_mappings_from_results(kp_message.results) # Populate our final KG with the returned nodes and edges for returned_edge_key, returned_edge in kp_message.knowledge_graph.edges.items(): arax_edge_key = self._get_arax_edge_key(returned_edge) # Convert to an ID that's unique for us for qedge_key in kg_to_qg_mappings['edges'][returned_edge_key]: answer_kg.add_edge(arax_edge_key, returned_edge, qedge_key) for returned_node_key, returned_node in kp_message.knowledge_graph.nodes.items(): for qnode_key in kg_to_qg_mappings['nodes'][returned_node_key]: answer_kg.add_node(returned_node_key, returned_node, qnode_key) # Build a map that indicates which of an edge's nodes fulfill which qnode if query_graph.edges: qedge = next(qedge for qedge in query_graph.edges.values()) edge_to_nodes_map = self._create_edge_to_nodes_map(kg_to_qg_mappings, kp_message.knowledge_graph, qedge) else: self.log.warning(f"{self.kp_name} API returned response of {kp_response.status_code}. Response from KP was:" f" {json.dumps(json_response, indent=4)}") return answer_kg, edge_to_nodes_map
def _load_kp_json_response( self, json_response: dict) -> QGOrganizedKnowledgeGraph: # Load the results into the object model answer_kg = QGOrganizedKnowledgeGraph() if not json_response.get("message"): self.log.warning( f"{self.kp_name}: No 'message' was included in the response from {self.kp_name}. " f"Response was: {json.dumps(json_response, indent=4)}") return answer_kg elif not json_response["message"].get("results"): self.log.debug(f"{self.kp_name}: No 'results' were returned.") json_response["message"]["results"] = [ ] # Setting this to empty list helps downstream processing return answer_kg else: self.log.debug(f"{self.kp_name}: Got results from {self.kp_name}.") kp_message = ARAXMessenger().from_dict(json_response["message"]) # Build a map that indicates which qnodes/qedges a given node/edge fulfills kg_to_qg_mappings = self._get_kg_to_qg_mappings_from_results( kp_message.results) # Populate our final KG with the returned nodes and edges returned_edge_keys_missing_qg_bindings = set() for returned_edge_key, returned_edge in kp_message.knowledge_graph.edges.items( ): arax_edge_key = self._get_arax_edge_key( returned_edge) # Convert to an ID that's unique for us if not returned_edge.attributes: returned_edge.attributes = [] # Put in a placeholder for missing required attribute fields to try to keep our answer TRAPI-compliant for attribute in returned_edge.attributes: if not attribute.attribute_type_id: attribute.attribute_type_id = f"not provided (this attribute came from {self.kp_name})" # Check if KPs are properly indicating that these edges came from them (indicate it ourselves if not) attribute_has_kp_name = lambda value, kp_name: (type( value) is list and kp_name in value) or (value == kp_name) if not any( attribute_has_kp_name(attribute.value, self.kp_name) for attribute in returned_edge.attributes): returned_edge.attributes.append( eu.get_kp_source_attribute(self.kp_name)) # Add an attribute to indicate that this edge passed through ARAX returned_edge.attributes.append(eu.get_arax_source_attribute()) if returned_edge_key in kg_to_qg_mappings['edges']: for qedge_key in kg_to_qg_mappings['edges'][returned_edge_key]: answer_kg.add_edge(arax_edge_key, returned_edge, qedge_key) else: returned_edge_keys_missing_qg_bindings.add(returned_edge_key) if returned_edge_keys_missing_qg_bindings: self.log.warning( f"{self.kp_name}: {len(returned_edge_keys_missing_qg_bindings)} edges in the KP's answer " f"KG have no bindings to the QG: {returned_edge_keys_missing_qg_bindings}" ) returned_node_keys_missing_qg_bindings = set() for returned_node_key, returned_node in kp_message.knowledge_graph.nodes.items( ): if returned_node_key not in kg_to_qg_mappings['nodes']: returned_node_keys_missing_qg_bindings.add(returned_node_key) else: for qnode_key in kg_to_qg_mappings['nodes'][returned_node_key]: answer_kg.add_node(returned_node_key, returned_node, qnode_key) if returned_node.attributes: for attribute in returned_node.attributes: if not attribute.attribute_type_id: attribute.attribute_type_id = f"not provided (this attribute came from {self.kp_name})" if returned_node_keys_missing_qg_bindings: self.log.warning( f"{self.kp_name}: {len(returned_node_keys_missing_qg_bindings)} nodes in the KP's answer " f"KG have no bindings to the QG: {returned_node_keys_missing_qg_bindings}" ) return answer_kg