Exemple #1
0
    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
Exemple #2
0
    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
Exemple #3
0
    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