def add_chatroom(): response = Response() try: add_data = request.get_json() # check required parament add_data["access_token"] add_data["chatroom_name"] add_data["chatroom_image_base64"] result = model.add_chatroom(add_data["access_token"], add_data["chatroom_name"], add_data["chatroom_image_base64"]) if result == None: raise Exception(result) response.success(result) except KeyError as e: response.error("KeyError, maybe missing parameter: " + e.args[0]) except Exception as e: response.error(str(e)) return response.get_json()
def apply(self, input_message: Message, input_parameters: dict) -> Response: # Define a default response response = Response() self.response = response self.message = input_message # Basic checks on arguments if not isinstance(input_parameters, dict): response.error("Provided parameters is not a dict", error_code="ParametersNotDict") return response # Return if any of the parameters generated an error (showing not just the first one) if response.status != 'OK': return response # Store these final parameters for convenience response.data['parameters'] = input_parameters self.parameters = input_parameters response.debug( f"Applying Resultifier to Message with parameters {input_parameters}" ) # call _resultify self._resultify(describe=False) # Clean up the KG (should only contain nodes used in the results) self._clean_up_kg() # Return the response and done return response
def get_preferred_curies(curie: Union[str, List[str]], log: Response) -> Dict[str, Dict[str, str]]: curies = convert_string_or_list_to_list(curie) try: synonymizer = NodeSynonymizer() log.debug( f"Sending NodeSynonymizer.get_canonical_curies() a list of {len(curies)} curies" ) canonical_curies_dict = synonymizer.get_canonical_curies(curies) log.debug(f"Got response back from NodeSynonymizer") except Exception: tb = traceback.format_exc() error_type, error, _ = sys.exc_info() log.error(f"Encountered a problem using NodeSynonymizer: {tb}", error_code=error_type.__name__) return {} else: if canonical_curies_dict is not None: unrecognized_curies = { input_curie for input_curie in canonical_curies_dict if not canonical_curies_dict.get(input_curie) } if unrecognized_curies: log.warning( f"NodeSynonymizer did not return canonical info for: {unrecognized_curies}" ) return canonical_curies_dict else: log.error(f"NodeSynonymizer returned None", error_code="NodeNormalizationIssue") return {}
def apply(self, input_message, input_parameters): #### Define a default response response = Response() self.response = response self.message = input_message #### Basic checks on arguments if not isinstance(input_parameters, dict): response.error("Provided parameters is not a dict", error_code="ParametersNotDict") return response #### Define a complete set of allowed parameters and their defaults parameters = { 'maximum_results': None, 'minimum_confidence': None, 'start_node': 1 } #### Loop through the input_parameters and override the defaults and make sure they are allowed for key,value in input_parameters.items(): if key not in parameters: response.error(f"Supplied parameter {key} is not permitted", error_code="UnknownParameter") else: parameters[key] = value #### Return if any of the parameters generated an error (showing not just the first one) if response.status != 'OK': return response #### Store these final parameters for convenience response.data['parameters'] = parameters self.parameters = parameters #### Now apply the filters. Order of operations is probably quite important #### Scalar value filters probably come first like minimum_confidence, then complex logic filters #### based on edge or node properties, and then finally maximum_results response.debug(f"Applying filter to Message with parameters {parameters}") #### First, as a test, blow away the results and see if we can recompute them #message.n_results = 0 #message.results = [] #self.__recompute_results() #### Apply scalar value filters first to do easy things and reduce the problem # TODO #### Complex logic filters probably come next. These may be hard # TODO #### Finally, if the maximum_results parameter is set, then limit the number of results to that last if parameters['maximum_results'] is not None: self.__apply_maximum_results_filter(parameters['maximum_results']) #### Return the response return response
def get_message(): response = Response() chatroom_secret = request.args.get('chatroom_secret') messages = model.get_message_by_secret(chatroom_secret) if messages != None: response.success(messages) return response.get_json() else: response.error("invalid chatroom_secret") return response.get_json()
def get_chatroom_info(): response = Response() chatroom_secret = request.args.get('chatroom_secret') chatroom = model.get_chatroominfo_by_secret(chatroom_secret) if chatroom != None: response.success(chatroom) return response.get_json() else: response.error("invalid chatroom_secret") return response.get_json()
def add_account(): response = Response() access_token = request.args.get('facebook_token') result = model.add_account(access_token) if result != None: response.success() return response.get_json() else: response.error("invalid token") return response.get_json()
def check_token(): response = Response() access_token = request.args.get('facebook_token') user = model.get_facebook_user_info(access_token) if user != None: response.success() return response.get_json() else: response.error("invalid token") return response.get_json()
def main(): # Note that most of this is just manually doing what ARAXQuery() would normally do for you response = Response() from actions_parser import ActionsParser actions_parser = ActionsParser() actions_list = [ "create_message", "add_qnode(id=n00, curie=CHEMBL.COMPOUND:CHEMBL112)", # acetaminophen "add_qnode(id=n01, type=protein, is_set=true)", "add_qedge(id=e00, source_id=n00, target_id=n01)", "expand(edge_id=e00, kp=BTE)", "return(message=true, store=false)", ] # Parse the raw action_list into commands and parameters result = actions_parser.parse(actions_list) response.merge(result) if result.status != 'OK': print(response.show(level=Response.DEBUG)) return response actions = result.data['actions'] from ARAX_messenger import ARAXMessenger messenger = ARAXMessenger() expander = ARAXExpander() for action in actions: if action['command'] == 'create_message': result = messenger.create_message() message = result.data['message'] response.data = result.data elif action['command'] == 'add_qnode': result = messenger.add_qnode(message, action['parameters']) elif action['command'] == 'add_qedge': result = messenger.add_qedge(message, action['parameters']) elif action['command'] == 'expand': result = expander.apply(message, action['parameters']) elif action['command'] == 'return': break else: response.error(f"Unrecognized command {action['command']}", error_code="UnrecognizedCommand") print(response.show(level=Response.DEBUG)) return response # Merge down this result and end if we're in an error state response.merge(result) if result.status != 'OK': print(response.show(level=Response.DEBUG)) return response # Show the final response # print(json.dumps(ast.literal_eval(repr(message.knowledge_graph)),sort_keys=True,indent=2)) print(response.show(level=Response.DEBUG))
def _convert_one_hop_query_graph_to_cypher_query(self, query_graph: QueryGraph, enforce_directionality: bool, kp: str, log: Response) -> str: log.debug(f"Generating cypher for edge {query_graph.edges[0].id} query graph") try: # Build the match clause qedge = query_graph.edges[0] source_qnode = eu.get_query_node(query_graph, qedge.source_id) target_qnode = eu.get_query_node(query_graph, qedge.target_id) qedge_cypher = self._get_cypher_for_query_edge(qedge, enforce_directionality) source_qnode_cypher = self._get_cypher_for_query_node(source_qnode) target_qnode_cypher = self._get_cypher_for_query_node(target_qnode) match_clause = f"MATCH {source_qnode_cypher}{qedge_cypher}{target_qnode_cypher}" # Build the where clause where_fragments = [] for qnode in [source_qnode, target_qnode]: if qnode.curie: if type(qnode.curie) is str: node_id_where_fragment = f"{qnode.id}.id='{qnode.curie}'" else: node_id_where_fragment = f"{qnode.id}.id in {qnode.curie}" where_fragments.append(node_id_where_fragment) if qnode.type and isinstance(qnode.type, list): if "KG2" in kp: node_type_property = "category_label" else: node_type_property = "category" where_fragments.append(f"{qnode.id}.{node_type_property} in {qnode.type}") if where_fragments: where_clause = f"WHERE {' AND '.join(where_fragments)}" else: where_clause = "" # Build the with clause source_qnode_col_name = f"nodes_{source_qnode.id}" target_qnode_col_name = f"nodes_{target_qnode.id}" qedge_col_name = f"edges_{qedge.id}" # This line grabs the edge's ID and a record of which of its nodes correspond to which qnode ID extra_edge_properties = "{.*, " + f"id:ID({qedge.id}), {source_qnode.id}:{source_qnode.id}.id, {target_qnode.id}:{target_qnode.id}.id" + "}" with_clause = f"WITH collect(distinct {source_qnode.id}) as {source_qnode_col_name}, " \ f"collect(distinct {target_qnode.id}) as {target_qnode_col_name}, " \ f"collect(distinct {qedge.id}{extra_edge_properties}) as {qedge_col_name}" # Build the return clause return_clause = f"RETURN {source_qnode_col_name}, {target_qnode_col_name}, {qedge_col_name}" cypher_query = f"{match_clause} {where_clause} {with_clause} {return_clause}" return cypher_query except Exception: tb = traceback.format_exc() error_type, error, _ = sys.exc_info() log.error(f"Problem generating cypher for query. {tb}", error_code=error_type.__name__) return ""
def _answer_one_hop_query_using_neo4j(self, cypher_query: str, qedge_id: str, kp: str, continue_if_no_results: bool, log: Response) -> List[Dict[str, List[Dict[str, any]]]]: log.info(f"Sending cypher query for edge {qedge_id} to {kp} neo4j") results_from_neo4j = self._run_cypher_query(cypher_query, kp, log) if log.status == 'OK': columns_with_lengths = dict() for column in results_from_neo4j[0]: columns_with_lengths[column] = len(results_from_neo4j[0].get(column)) if any(length == 0 for length in columns_with_lengths.values()): if continue_if_no_results: log.warning(f"No paths were found in {kp} satisfying this query graph") else: log.error(f"No paths were found in {kp} satisfying this query graph", error_code="NoResults") return results_from_neo4j
def _run_arax_query(actions_list: List[str], log: Response) -> DictKnowledgeGraph: araxq = ARAXQuery() sub_query_response = araxq.query({ "previous_message_processing_plan": { "processing_actions": actions_list } }) if sub_query_response.status != 'OK': log.error( f"Encountered an error running ARAXQuery within Expand: {sub_query_response.show(level=sub_query_response.DEBUG)}" ) return dict() sub_query_message = araxq.message return sub_query_message.knowledge_graph
def _run_cypher_query(cypher_query: str, kp: str, log: Response) -> List[Dict[str, any]]: rtxc = RTXConfiguration() if kp == "KG2": # Flip into KG2 mode if that's our KP (rtx config is set to KG1 info by default) rtxc.live = "KG2" try: driver = GraphDatabase.driver(rtxc.neo4j_bolt, auth=(rtxc.neo4j_username, rtxc.neo4j_password)) with driver.session() as session: query_results = session.run(cypher_query).data() driver.close() except Exception: tb = traceback.format_exc() error_type, error, _ = sys.exc_info() log.error(f"Encountered an error interacting with {kp} neo4j. {tb}", error_code=error_type.__name__) return [] else: return query_results
def request(self, action, xml): try: return Response(self.client.send(action, xml), action, True) except SocketError as e: if e.errno == errno.ECONNRESET: response_error = Response("ECONNRESET", action) response_error.error, response_error.error_msg = "ECONNRESET", "[Errno 104] Connection reset by peer" return response_error else: raise
def _log_proper_no_results_message(accepted_curies: Set[str], continue_if_no_results: bool, valid_prefixes: Set[str], log: Response): if continue_if_no_results: if not accepted_curies: log.warning( f"BTE could not accept any of the input curies. Valid curie prefixes for BTE are: " f"{valid_prefixes}") log.warning( f"No paths were found in BTE satisfying this query graph") else: if not accepted_curies: log.error( f"BTE could not accept any of the input curies. Valid curie prefixes for BTE are: " f"{valid_prefixes}", error_code="InvalidPrefix") log.error( f"No paths were found in BTE satisfying this query graph", error_code="NoResults")
def _answer_query_using_bte( self, input_qnode: QNode, output_qnode: QNode, qedge: QEdge, answer_kg: DictKnowledgeGraph, valid_bte_inputs_dict: Dict[str, Set[str]], log: Response) -> Tuple[DictKnowledgeGraph, Set[str]]: accepted_curies = set() # Send this single-edge query to BTE, input curie by input curie (adding findings to our answer KG as we go) for curie in input_qnode.curie: # Consider all different combinations of qnode types (can be multiple if gene/protein) for input_qnode_type, output_qnode_type in itertools.product( input_qnode.type, output_qnode.type): if eu.get_curie_prefix( curie) in valid_bte_inputs_dict['curie_prefixes']: accepted_curies.add(curie) try: loop = asyncio.new_event_loop() seqd = SingleEdgeQueryDispatcher( input_cls=input_qnode_type, output_cls=output_qnode_type, pred=qedge.type, input_id=eu.get_curie_prefix(curie), values=eu.get_curie_local_id(curie), loop=loop) log.debug( f"Sending query to BTE: {curie}-{qedge.type if qedge.type else ''}->{output_qnode_type}" ) seqd.query() reasoner_std_response = seqd.to_reasoner_std() except Exception: trace_back = traceback.format_exc() error_type, error, _ = sys.exc_info() log.error( f"Encountered a problem while using BioThings Explorer. {trace_back}", error_code=error_type.__name__) return answer_kg, accepted_curies else: answer_kg = self._add_answers_to_kg( answer_kg, reasoner_std_response, input_qnode.id, output_qnode.id, qedge.id, log) return answer_kg, accepted_curies
def get_curie_synonyms(curie: Union[str, List[str]], log: Response) -> List[str]: curies = convert_string_or_list_to_list(curie) try: synonymizer = NodeSynonymizer() log.debug( f"Sending NodeSynonymizer.get_equivalent_nodes() a list of {len(curies)} curies" ) equivalent_curies_dict = synonymizer.get_equivalent_nodes( curies, kg_name="KG2") log.debug(f"Got response back from NodeSynonymizer") except Exception: tb = traceback.format_exc() error_type, error, _ = sys.exc_info() log.error(f"Encountered a problem using NodeSynonymizer: {tb}", error_code=error_type.__name__) return [] else: if equivalent_curies_dict is not None: curies_missing_info = { curie for curie in equivalent_curies_dict if not equivalent_curies_dict.get(curie) } if curies_missing_info: log.warning( f"NodeSynonymizer did not find any equivalent curies for: {curies_missing_info}" ) equivalent_curies = { curie for curie_dict in equivalent_curies_dict.values() if curie_dict for curie in curie_dict } all_curies = equivalent_curies.union(set( curies)) # Make sure even curies without synonyms are included return sorted(list(all_curies)) else: log.error(f"NodeSynonymizer returned None", error_code="NodeNormalizationIssue") return []
def apply(self, input_message, input_parameters, response=None): #### Define a default response if response is None: response = Response() self.response = response self.message = input_message #### Basic checks on arguments if not isinstance(input_parameters, dict): response.error("Provided parameters is not a dict", error_code="ParametersNotDict") return response # list of actions that have so far been created for ARAX_overlay allowable_actions = self.allowable_actions # check to see if an action is actually provided if 'action' not in input_parameters: response.error( f"Must supply an action. Allowable actions are: action={allowable_actions}", error_code="MissingAction") elif input_parameters['action'] not in allowable_actions: response.error( f"Supplied action {input_parameters['action']} is not permitted. Allowable actions are: {allowable_actions}", error_code="UnknownAction") #### Return if any of the parameters generated an error (showing not just the first one) if response.status != 'OK': return response # populate the parameters dict parameters = dict() for key, value in input_parameters.items(): parameters[key] = value #### Store these final parameters for convenience response.data['parameters'] = parameters self.parameters = parameters # convert the action string to a function call (so I don't need a ton of if statements getattr( self, '_' + self.__class__.__name__ + '__' + parameters['action'] )( ) # thank you https://stackoverflow.com/questions/11649848/call-methods-by-string response.debug( f"Applying Overlay to Message with parameters {parameters}" ) # TODO: re-write this to be more specific about the actual action # TODO: add_pubmed_ids # TODO: compute_confidence_scores # TODO: finish COHD # TODO: Jaccard #### Return the response and done if self.report_stats: # helper to report information in debug if class self.report_stats = True response = self.report_response_stats(response) return response
def _expand_node(self, qnode_id: str, kp_to_use: str, continue_if_no_results: bool, query_graph: QueryGraph, use_synonyms: bool, synonym_handling: str, log: Response) -> DictKnowledgeGraph: # This function expands a single node using the specified knowledge provider log.debug(f"Expanding node {qnode_id} using {kp_to_use}") query_node = eu.get_query_node(query_graph, qnode_id) answer_kg = DictKnowledgeGraph() if log.status != 'OK': return answer_kg if not query_node.curie: log.error( f"Cannot expand a single query node if it doesn't have a curie", error_code="InvalidQuery") return answer_kg copy_of_qnode = eu.copy_qnode(query_node) if use_synonyms: self._add_curie_synonyms_to_query_nodes(qnodes=[copy_of_qnode], log=log, kp=kp_to_use) if copy_of_qnode.type in ["protein", "gene"]: copy_of_qnode.type = ["protein", "gene"] log.debug(f"Modified query node is: {copy_of_qnode.to_dict()}") # Answer the query using the proper KP valid_kps_for_single_node_queries = ["ARAX/KG1", "ARAX/KG2"] if kp_to_use in valid_kps_for_single_node_queries: from Expand.kg_querier import KGQuerier kg_querier = KGQuerier(log, kp_to_use) answer_kg = kg_querier.answer_single_node_query(copy_of_qnode) log.info( f"Query for node {copy_of_qnode.id} returned results ({eu.get_printable_counts_by_qg_id(answer_kg)})" ) # Make sure all qnodes have been fulfilled (unless we're continuing if no results) if log.status == 'OK' and not continue_if_no_results: if copy_of_qnode.id not in answer_kg.nodes_by_qg_id or not answer_kg.nodes_by_qg_id[ copy_of_qnode.id]: log.error( f"Returned answer KG does not contain any results for QNode {copy_of_qnode.id}", error_code="UnfulfilledQGID") return answer_kg if synonym_handling != 'add_all': answer_kg, edge_node_usage_map = self._deduplicate_nodes( dict_kg=answer_kg, edge_to_nodes_map={}, log=log) return answer_kg else: log.error( f"Invalid knowledge provider: {kp_to_use}. Valid options for single-node queries are " f"{', '.join(valid_kps_for_single_node_queries)}", error_code="InvalidKP") return answer_kg
def _verify_one_hop_query_graph_is_valid(query_graph: QueryGraph, log: Response): if len(query_graph.edges) != 1: log.error( f"answer_one_hop_query() was passed a query graph that is not one-hop: " f"{query_graph.to_dict()}", error_code="InvalidQuery") elif len(query_graph.nodes) > 2: log.error( f"answer_one_hop_query() was passed a query graph with more than two nodes: " f"{query_graph.to_dict()}", error_code="InvalidQuery") elif len(query_graph.nodes) < 2: log.error( f"answer_one_hop_query() was passed a query graph with less than two nodes: " f"{query_graph.to_dict()}", error_code="InvalidQuery")
def _extract_query_subgraph(qedge_ids_to_expand: List[str], query_graph: QueryGraph, log: Response) -> QueryGraph: # This function extracts a sub-query graph containing the provided qedge IDs from a larger query graph sub_query_graph = QueryGraph(nodes=[], edges=[]) for qedge_id in qedge_ids_to_expand: # Make sure this query edge actually exists in the query graph if not any(qedge.id == qedge_id for qedge in query_graph.edges): log.error( f"An edge with ID '{qedge_id}' does not exist in Message.QueryGraph", error_code="UnknownValue") return None qedge = next(qedge for qedge in query_graph.edges if qedge.id == qedge_id) # Make sure this qedge's qnodes actually exist in the query graph if not eu.get_query_node(query_graph, qedge.source_id): log.error( f"Qedge {qedge.id}'s source_id refers to a qnode that does not exist in the query graph: " f"{qedge.source_id}", error_code="InvalidQEdge") return None if not eu.get_query_node(query_graph, qedge.target_id): log.error( f"Qedge {qedge.id}'s target_id refers to a qnode that does not exist in the query graph: " f"{qedge.target_id}", error_code="InvalidQEdge") return None qnodes = [ eu.get_query_node(query_graph, qedge.source_id), eu.get_query_node(query_graph, qedge.target_id) ] # Add (copies of) this qedge and its two qnodes to our new query sub graph qedge_copy = eu.copy_qedge(qedge) if not any(qedge.id == qedge_copy.id for qedge in sub_query_graph.edges): sub_query_graph.edges.append(qedge_copy) for qnode in qnodes: qnode_copy = eu.copy_qnode(qnode) if not any(qnode.id == qnode_copy.id for qnode in sub_query_graph.nodes): sub_query_graph.nodes.append(qnode_copy) return sub_query_graph
def apply(self, input_message, input_parameters, response=None): if response is None: response = Response() self.response = response self.message = input_message # Basic checks on arguments if not isinstance(input_parameters, dict): response.error("Provided parameters is not a dict", error_code="ParametersNotDict") return response # Define a complete set of allowed parameters and their defaults parameters = self.parameters parameters['kp'] = "ARAX/KG1" parameters['enforce_directionality'] = False parameters['use_synonyms'] = True parameters['synonym_handling'] = 'map_back' parameters['continue_if_no_results'] = False for key, value in input_parameters.items(): if key and key not in parameters: response.error(f"Supplied parameter {key} is not permitted", error_code="UnknownParameter") else: if type(value) is str and value.lower() == "true": value = True elif type(value) is str and value.lower() == "false": value = False parameters[key] = value # Default to expanding the entire query graph if the user didn't specify what to expand if not parameters['edge_id'] and not parameters['node_id']: parameters['edge_id'] = [ edge.id for edge in self.message.query_graph.edges ] parameters['node_id'] = self._get_orphan_query_node_ids( self.message.query_graph) if response.status != 'OK': return response response.data['parameters'] = parameters self.parameters = parameters # Do the actual expansion response.debug( f"Applying Expand to Message with parameters {parameters}") input_edge_ids = eu.convert_string_or_list_to_list( parameters['edge_id']) input_node_ids = eu.convert_string_or_list_to_list( parameters['node_id']) kp_to_use = self.parameters['kp'] continue_if_no_results = self.parameters['continue_if_no_results'] # Convert message knowledge graph to dictionary format, for faster processing dict_kg = eu.convert_standard_kg_to_dict_kg( self.message.knowledge_graph) # Expand any specified edges if input_edge_ids: query_sub_graph = self._extract_query_subgraph( input_edge_ids, self.message.query_graph) if response.status != 'OK': return response self.response.debug( f"Query graph for this Expand() call is: {query_sub_graph.to_dict()}" ) # Expand the query graph edge by edge (much faster for neo4j queries, and allows easy integration with BTE) ordered_qedges_to_expand = self._get_order_to_expand_edges_in( query_sub_graph) node_usages_by_edges_map = dict() for qedge in ordered_qedges_to_expand: answer_kg, edge_node_usage_map = self._expand_edge( qedge, kp_to_use, dict_kg, continue_if_no_results, self.message.query_graph) if response.status != 'OK': return response node_usages_by_edges_map[qedge.id] = edge_node_usage_map self._process_and_merge_answer(answer_kg, dict_kg) if response.status != 'OK': return response self._prune_dead_end_paths(dict_kg, query_sub_graph, node_usages_by_edges_map) if response.status != 'OK': return response # Expand any specified nodes if input_node_ids: for qnode_id in input_node_ids: answer_kg = self._expand_node(qnode_id, kp_to_use, continue_if_no_results, self.message.query_graph) if response.status != 'OK': return response self._process_and_merge_answer(answer_kg, dict_kg) if response.status != 'OK': return response # Convert message knowledge graph back to API standard format self.message.knowledge_graph = eu.convert_dict_kg_to_standard_kg( dict_kg) # Return the response and done kg = self.message.knowledge_graph response.info( f"After Expand, Message.KnowledgeGraph has {len(kg.nodes)} nodes and {len(kg.edges)} edges" ) return response
class ARAXQueryGraphInterpreter: #### Constructor def __init__(self): self.response = Response() self.message = None self.parameters = None self.query_graph_templates = None self.query_graph_tree = None self.read_query_graph_templates() # #### Create a fresh Message object and fill with defaults def translate_to_araxi(self, message, describe=False): """ Translate an input query_graph into ARAXi :return: Response object with execution information and the DSL command set :rtype: Response """ #### Get a default response response = self.response debug = False #### Ensure that query_graph_templates is ready if self.query_graph_templates is None: response.error( "QueryGraph templates cannot be read from reference file", error_code="QueryGraphInterpreterMissingTemplates") return response query_graph_info = QueryGraphInfo() result = query_graph_info.assess(message) response.merge(result) if result.status != 'OK': print(response.show(level=Response.DEBUG)) return response query_graph_template = query_graph_info.query_graph_templates[ 'detailed'] # Check the number of nodes since the tree is based on the number of nodes n_nodes = query_graph_template['n_nodes'] if n_nodes not in self.query_graph_tree['n_nodes']: response.error( "QueryGraphInterpreter finds more nodes than supported in this QueryGraph", error_code="QueryGraphInterpreterUnsupportedGraph") return response # Set up a list of tree pointers with both a pointer to the next dict as well as the running score total # There are potentially multiple matches to track especially since we permit less specific matches tree_pointers = [{ 'pointer': self.query_graph_tree['n_nodes'][n_nodes], 'score': 0 }] # Now look over each component looking for matches possible_next_steps = [] for component in query_graph_template['components']: if debug: print(f"- Component is {component}") possible_next_steps = [] #### If the component is a node, then score it if component['component_type'] == 'node': # Go through the list of possible things it could be and those or lesser possible next steps if component['has_curie'] and component[ 'has_type'] and component['type_value']: possible_next_steps.append({ 'content': f"curie,type={component['type_value']}", 'score': 10000 }) possible_next_steps.append({ 'content': 'curie', 'score': 1000 }) possible_next_steps.append({'content': '', 'score': 0}) elif component['has_curie']: possible_next_steps.append({ 'content': 'curie', 'score': 1000 }) possible_next_steps.append({'content': '', 'score': 0}) elif component['has_type'] and component['type_value']: possible_next_steps.append({ 'content': f"type={component['type_value']}", 'score': 100 }) possible_next_steps.append({ 'content': 'type', 'score': 10 }) possible_next_steps.append({'content': '', 'score': 0}) elif component['has_type']: possible_next_steps.append({ 'content': 'type', 'score': 10 }) possible_next_steps.append({'content': '', 'score': 0}) else: possible_next_steps.append({'content': '', 'score': 0}) # Else it's an edge. Don't do anything with those currently else: possible_next_steps.append({'content': '', 'score': 0}) # For each of the current tree pointers new_tree_pointers = [] for tree_pointer in tree_pointers: # Consider each of the new possibilities for possible_next_step in possible_next_steps: component_string = f"{component['component_id']}({possible_next_step['content']})" if debug: print(f" - component_string={component_string}") # If this component is a possible next step in the tree, then add the next step to new_tree_pointers if component_string in tree_pointer['pointer']: if debug: print( f" - Found this component with score {possible_next_step['score']}" ) new_tree_pointers.append({ 'pointer': tree_pointer['pointer'][component_string], 'score': tree_pointer['score'] + possible_next_step['score'] }) #tree_pointer = tree_pointer[component_string] # When we're done, reset the surviving tree pointers tree_pointers = new_tree_pointers # Now determine the best scoring match and assign that one query_graph_template_name = '??' best_score = -1 for tree_pointer in tree_pointers: if 'name' in tree_pointer['pointer']: if debug: print( f"==> Found template is {tree_pointer['pointer']['name']} with score {tree_pointer['score']}" ) if tree_pointer['score'] > best_score: query_graph_template_name = tree_pointer['pointer']['name'] best_score = tree_pointer['score'] # If the final best template name is a real one in templates, then get the ARAXI for it if query_graph_template_name in self.query_graph_templates[ 'templates']: araxi_commands = self.query_graph_templates['templates'][ query_graph_template_name]['DSL'] # Need to remap the theoretical node and edge ids into the actual ones new_araxi_commands = [] for command in araxi_commands: node_index = 0 new_command = command for node in query_graph_info.node_order: template_id = f"n{node_index:02}" new_command = re.sub(template_id, node['id'], new_command) node_index += 1 edge_index = 0 for edge in query_graph_info.edge_order: template_id = f"e{edge_index:02}" new_command = re.sub(template_id, edge['id'], new_command) edge_index += 1 new_araxi_commands.append(new_command) # TODO: Create the restated_question from the template response.data['araxi_commands'] = new_araxi_commands return response response.error( "QueryGraphInterpreter cannot interpret this QueryGraph", error_code="QueryGraphInterpreterUnsupportedGraph") return response # #### Read the YAML file containing the current QueryGraph templates def read_query_graph_templates(self): """ Read the YAML file containing the current QueryGraph templates :rtype: None """ # The template file is stored right next to this code template_file = os.path.dirname(os.path.abspath( __file__)) + "/ARAX_query_graph_interpreter_templates.yaml" # If the template file is not found, record an error and return if not os.path.exists(template_file): self.response.error( "QueryGraphInterpreter templates file is missing", error_code="QueryGraphInterpreterTemplateMissing") return self.response # Open the file and try to load it with open(template_file, 'r') as stream: try: self.query_graph_templates = yaml.safe_load(stream) except yaml.YAMLError as exc: self.response.error( f"Error parsing YAML file template_file: {exc}", error_code="CannotParseQueryGraphInterpreterTemplate") return self.response # Make sure the version is as expected if 'ARAX_QG_DSL_mapping' not in self.query_graph_templates: self.response.error( f"Missing version number in QueryGraphInterpreter templates file {template_file}", error_code="MissingQueryGraphInterpreterTemplateFileVersion") self.query_graph_templates = None return self.response if self.query_graph_templates['ARAX_QG_DSL_mapping'] != 0.1: self.response.error( f"Incorrect version number in QueryGraphInterpreter templates file {template_file}", error_code="BadQueryGraphInterpreterTemplateFileVersion") self.query_graph_templates = None return self.response # We will create dict lookup table of all the template string [e.g. 'n00(curie)-e00()-n01(type)' -> template_name] self.query_graph_templates['template_strings'] = {} # We will also create dict tree of all templates organized by the number of nodes and then by each component self.query_graph_tree = {'n_nodes': {}} # Loop over all the templates in the YAML file for template_name, template in self.query_graph_templates[ 'templates'].items(): # Initialize an empty string to build the template in template_string = '' i = 0 # Determine the number of components and nodes in this template and start building a tree n_components = len(template['template']) n_nodes = int((n_components + 1) / 2) if n_nodes not in self.query_graph_tree['n_nodes']: self.query_graph_tree['n_nodes'][n_nodes] = {} # previous_dict will contain the last place we left off in the tree previous_dict = self.query_graph_tree['n_nodes'][n_nodes] # Loop over each component building the tree for component in template['template']: # If this branch of the tree doesn't exist yet, add it if component not in previous_dict: previous_dict[component] = {} # This location will be the next starting point previous_dict = previous_dict[component] # Append to the end of the template string if i > 0: template_string += '-' template_string += component # If this is the last component, then attach the name of this template at the end i += 1 if i == n_components: previous_dict['name'] = template_name # Store the created template string and name self.query_graph_templates['template_strings'][ template_string] = template_name
def main(): #### Some qnode examples test_query_graphs = [ [{ 'id': 'n10', 'curie': 'DOID:9281' }, { 'id': 'n11', 'type': 'protein' }, { 'id': 'e10', 'source_id': 'n10', 'target_id': 'n11' }], [{ 'id': 'n10', 'curie': 'DOID:9281' }, { 'id': 'n11', 'type': 'protein' }, { 'id': 'n12', 'type': 'chemical_substance' }, { 'id': 'e10', 'source_id': 'n10', 'target_id': 'n11' }, { 'id': 'e11', 'source_id': 'n11', 'target_id': 'n12' }], [{ 'id': 'n10', 'curie': 'DOID:9281' }, { 'id': 'n11', 'type': 'chemical_substance' }, { 'id': 'e10', 'source_id': 'n10', 'target_id': 'n11' }], [{ 'id': 'n10', 'curie': 'DOID:9281', 'type': 'disease' }, { 'id': 'n11', 'type': 'chemical_substance' }, { 'id': 'e10', 'source_id': 'n10', 'target_id': 'n11' }], ] #interpreter = ARAXQueryGraphInterpreter() #print(json.dumps(interpreter.query_graph_tree,sort_keys=True,indent=2)) #return for test_query_graph in test_query_graphs: #### Create a response object for each test response = Response() #### Create a template Message messenger = ARAXMessenger() result = messenger.create_message() response.merge(result) message = messenger.message for parameters in test_query_graph: if 'n' in parameters['id']: result = messenger.add_qnode(message, parameters) response.merge(result) if result.status != 'OK': print(response.show(level=Response.DEBUG)) return response elif 'e' in parameters['id']: result = messenger.add_qedge(message, parameters) response.merge(result) if result.status != 'OK': print(response.show(level=Response.DEBUG)) return response else: response.error(f"Unrecognized type {parameters['id']}") return response interpreter = ARAXQueryGraphInterpreter() result = interpreter.translate_to_araxi(message) response.merge(result) if result.status != 'OK': print(response.show(level=Response.DEBUG)) return response araxi_commands = result.data['araxi_commands'] print(araxi_commands)
def _deduplicate_nodes( dict_kg: DictKnowledgeGraph, edge_to_nodes_map: Dict[str, Dict[str, str]], log: Response ) -> Tuple[DictKnowledgeGraph, Dict[str, Dict[str, str]]]: log.debug(f"Deduplicating nodes") deduplicated_kg = DictKnowledgeGraph( nodes={qnode_id: dict() for qnode_id in dict_kg.nodes_by_qg_id}, edges={qedge_id: dict() for qedge_id in dict_kg.edges_by_qg_id}) updated_edge_to_nodes_map = { edge_id: dict() for edge_id in edge_to_nodes_map } curie_mappings = dict() # First deduplicate the nodes for qnode_id, nodes in dict_kg.nodes_by_qg_id.items(): # Load preferred curie info from NodeSynonymizer for nodes we haven't seen before unmapped_node_ids = set(nodes).difference(set(curie_mappings)) log.debug( f"Getting preferred curies for {qnode_id} nodes returned in this step" ) canonicalized_nodes = eu.get_preferred_curies( list(unmapped_node_ids), log) if unmapped_node_ids else dict() if log.status != 'OK': return deduplicated_kg, updated_edge_to_nodes_map for node_id in unmapped_node_ids: # Figure out the preferred curie/name for this node node = nodes.get(node_id) canonicalized_node = canonicalized_nodes.get(node_id) if canonicalized_node: preferred_curie = canonicalized_node.get( 'preferred_curie', node_id) preferred_name = canonicalized_node.get( 'preferred_name', node.name) preferred_type = eu.convert_string_or_list_to_list( canonicalized_node.get('preferred_type', node.type)) curie_mappings[node_id] = preferred_curie else: # Means the NodeSynonymizer didn't recognize this curie preferred_curie = node_id preferred_name = node.name preferred_type = node.type curie_mappings[node_id] = preferred_curie # Add this node into our deduplicated KG as necessary # TODO: merge certain fields, like uri? if preferred_curie not in deduplicated_kg.nodes_by_qg_id[ qnode_id]: node.id = preferred_curie node.name = preferred_name node.type = preferred_type deduplicated_kg.add_node(node, qnode_id) # Then update the edges to reflect changes made to the nodes for qedge_id, edges in dict_kg.edges_by_qg_id.items(): for edge_id, edge in edges.items(): edge.source_id = curie_mappings.get(edge.source_id) edge.target_id = curie_mappings.get(edge.target_id) if not edge.source_id or not edge.target_id: log.error( f"Could not find preferred curie mappings for edge {edge_id}'s node(s)" ) return deduplicated_kg, updated_edge_to_nodes_map deduplicated_kg.add_edge(edge, qedge_id) # Update the edge-to-node map for this edge (used down the line for pruning) for qnode_id, corresponding_node_id in edge_to_nodes_map[ edge_id].items(): updated_edge_to_nodes_map[edge_id][ qnode_id] = curie_mappings.get(corresponding_node_id) log.debug( f"After deduplication, answer KG counts are: {eu.get_printable_counts_by_qg_id(deduplicated_kg)}" ) return deduplicated_kg, updated_edge_to_nodes_map
def _expand_edge( self, qedge: QEdge, kp_to_use: str, dict_kg: DictKnowledgeGraph, continue_if_no_results: bool, query_graph: QueryGraph, use_synonyms: bool, synonym_handling: str, log: Response ) -> Tuple[DictKnowledgeGraph, Dict[str, Dict[str, str]]]: # This function answers a single-edge (one-hop) query using the specified knowledge provider log.info(f"Expanding edge {qedge.id} using {kp_to_use}") answer_kg = DictKnowledgeGraph() edge_to_nodes_map = dict() # Create a query graph for this edge (that uses synonyms as well as curies found in prior steps) edge_query_graph = self._get_query_graph_for_edge( qedge, query_graph, dict_kg, use_synonyms, kp_to_use, log) if log.status != 'OK': return answer_kg, edge_to_nodes_map if not any(qnode for qnode in edge_query_graph.nodes if qnode.curie): log.error( f"Cannot expand an edge for which neither end has any curies. (Could not find curies to use from " f"a prior expand step, and neither qnode has a curie specified.)", error_code="InvalidQuery") return answer_kg, edge_to_nodes_map valid_kps = ["ARAX/KG1", "ARAX/KG2", "BTE", "COHD", "NGD"] if kp_to_use not in valid_kps: log.error( f"Invalid knowledge provider: {kp_to_use}. Valid options are {', '.join(valid_kps)}", error_code="InvalidKP") return answer_kg, edge_to_nodes_map else: if kp_to_use == 'BTE': from Expand.bte_querier import BTEQuerier kp_querier = BTEQuerier(log) elif kp_to_use == 'COHD': from Expand.COHD_querier import COHDQuerier kp_querier = COHDQuerier(log) elif kp_to_use == 'NGD': from Expand.ngd_querier import NGDQuerier kp_querier = NGDQuerier(log) else: from Expand.kg_querier import KGQuerier kp_querier = KGQuerier(log, kp_to_use) answer_kg, edge_to_nodes_map = kp_querier.answer_one_hop_query( edge_query_graph) if log.status != 'OK': return answer_kg, edge_to_nodes_map log.debug( f"Query for edge {qedge.id} returned results ({eu.get_printable_counts_by_qg_id(answer_kg)})" ) # Do some post-processing (deduplicate nodes, remove self-edges..) if synonym_handling != 'add_all': answer_kg, edge_to_nodes_map = self._deduplicate_nodes( answer_kg, edge_to_nodes_map, log) if eu.qg_is_fulfilled(edge_query_graph, answer_kg): answer_kg = self._remove_self_edges(answer_kg, edge_to_nodes_map, qedge.id, edge_query_graph.nodes, log) # Make sure our query has been fulfilled (unless we're continuing if no results) if not eu.qg_is_fulfilled(edge_query_graph, answer_kg): if continue_if_no_results: log.warning( f"No paths were found in {kp_to_use} satisfying this query graph" ) else: log.error( f"No paths were found in {kp_to_use} satisfying this query graph", error_code="NoResults") return answer_kg, edge_to_nodes_map
def main(): #### Create a response object response = Response() #### Some qnode examples test_query_graphs = [ [{ 'id': 'n10', 'curie': 'DOID:9281' }, { 'id': 'n11', 'type': 'protein' }, { 'id': 'e10', 'source_id': 'n10', 'target_id': 'n11' }], [{ 'id': 'n10', 'curie': 'DOID:9281' }, { 'id': 'n11', 'type': 'protein' }, { 'id': 'n12', 'type': 'drug' }, { 'id': 'e10', 'source_id': 'n10', 'target_id': 'n11' }, { 'id': 'e11', 'source_id': 'n11', 'target_id': 'n12' }], ] for test_query_graph in test_query_graphs: #### Create a template Message messenger = ARAXMessenger() result = messenger.create_message() response.merge(result) message = messenger.message for parameters in test_query_graph: if 'n' in parameters['id']: result = messenger.add_qnode(message, parameters) response.merge(result) if result.status != 'OK': print(response.show(level=Response.DEBUG)) return response elif 'e' in parameters['id']: result = messenger.add_qedge(message, parameters) response.merge(result) if result.status != 'OK': print(response.show(level=Response.DEBUG)) return response else: response.error(f"Unrecognized type {parameters['id']}") return response interpreter = ARAXQueryGraphInterpreter() result = interpreter.translate_to_araxi(message) response.merge(result) if result.status != 'OK': print(response.show(level=Response.DEBUG)) return response araxi_commands = result.data['araxi_commands'] print(araxi_commands) #### Show the final result print('-------------------------') print(response.show(level=Response.DEBUG))
def parse(self, input_actions): #### Define a default response response = Response() response.info(f"Parsing input actions list") #### Basic error checking of the input_actions if not isinstance(input_actions, list): response.error("Provided input actions is not a list", error_code="ActionsNotList") return response if len(input_actions) == 0: response.error("Provided input actions is an empty list", error_code="ActionsListEmpty") return response #### Iterate through the list, checking the items actions = [] n_lines = 1 for action in input_actions: response.debug(f"Parsing action: {action}") # If this line is empty, then skip match = re.match(r"\s*$", action) if match: continue # If this line begins with a #, it is a comment, then skip match = re.match(r"#", action) if match: continue #### First look for a naked command without parentheses match = re.match(r"\s*([A-Za-z_]+)\s*$", action) if match is not None: action = { "line": n_lines, "command": match.group(1), "parameters": None } actions.append(action) #### Then look for and parse a command with parentheses and a comma-separated parameter list if match is None: match = re.match(r"\s*([A-Za-z_]+)\((.*)\)\s*$", action) if match is not None: command = match.group(1) param_string = match.group(2) #### Split the parameters on comma and process those param_string_list = re.split(",", param_string) parameters = {} #### If a value is of the form key=[value1,value2] special code is needed to recompose that mode = 'normal' list_buffer = [] key = '' for param_item in param_string_list: param_item = param_item.strip() if mode == 'normal': #### Split on the first = only (might be = in the value) values = re.split("=", param_item, 1) key = values[0] #### If there isn't a value after an =, then just set to string true value = 'true' if len(values) > 1: value = values[1] key = key.strip() value = value.strip() #### If the value begins with a "[", then this is a list match = re.match(r"\[(.+)$", value) if match: #### If it also ends with a "]", then this is a list of one element match2 = re.match(r"\[(.*)\]$", value) if match2: if match2.group(1) == '': parameters[key] = [] else: parameters[key] = [match2.group(1)] else: mode = 'in_list' list_buffer = [match.group(1)] else: parameters[key] = value #### Special processing if we're in the middle of a list elif mode == 'in_list': match = re.match(r"(.*)\]$", param_item) if match: mode = 'normal' list_buffer.append(match.group(1)) parameters[key] = list_buffer else: list_buffer.append(param_item) else: eprint("Inconceivable!") if mode == 'in_list': parameters[key] = list_buffer #### Store the parsed result in a dict and add to the list action = { "line": n_lines, "command": command, "parameters": parameters } actions.append(action) else: response.error(f"Unable to parse action {action}", error_code="ActionsListEmpty") n_lines += 1 #### Put the actions in the response data envelope and return response.data["actions"] = actions return response
def reassign_curies(self, message, input_parameters, describe=False): """ Reassigns CURIEs to the target Knowledge Provider :param message: Translator standard Message object :type message: Message :param input_parameters: Dict of input parameters to control the method :type input_parameters: Message :return: Response object with execution information :rtype: Response """ # #### Internal documentation setup allowable_parameters = { 'knowledge_provider': { 'Name of the Knowledge Provider CURIE space to map to. Default=KG1. Also currently supported KG2' }, 'mismap_result': { 'Desired action when mapping fails: ERROR or WARNING. Default is ERROR' }, } if describe: allowable_parameters[ 'dsl_command'] = '`reassign_curies()`' # can't get this name at run-time, need to manually put it in per https://www.python.org/dev/peps/pep-3130/ allowable_parameters[ 'brief_description'] = """The `reassign_curies` method reassigns all the CURIEs in the Message QueryGraph to the specified knowledge provider. Allowed values are KG1 or KG2. Default is KG1 if not specified.""" return allowable_parameters #### Define a default response response = Response() self.response = response self.message = message #### Basic checks on arguments if not isinstance(input_parameters, dict): response.error("Provided parameters is not a dict", error_code="ParametersNotDict") return response #### Define a complete set of allowed parameters and their defaults parameters = { 'knowledge_provider': 'KG1', 'mismap_result': 'ERROR', } #### Loop through the input_parameters and override the defaults and make sure they are allowed for key, value in input_parameters.items(): if key not in parameters: response.error(f"Supplied parameter {key} is not permitted", error_code="UnknownParameter") else: parameters[key] = value #### Return if any of the parameters generated an error (showing not just the first one) if response.status != 'OK': return response #### Store these final parameters for convenience response.data['parameters'] = parameters self.parameters = parameters # Check that the knowledge_provider is valid: if parameters['knowledge_provider'] != 'KG1' and parameters[ 'knowledge_provider'] != 'KG2': response.error( f"Specified knowledge provider must be 'KG1' or 'KG2', not '{parameters['knowledge_provider']}'", error_code="UnknownKP") return response #### Now try to assign the CURIEs response.info( f"Reassigning the CURIEs in QueryGraph to {parameters['knowledge_provider']} space" ) #### Make sure there's a query_graph already here if message.query_graph is None: message.query_graph = QueryGraph() message.query_graph.nodes = [] message.query_graph.edges = [] if message.query_graph.nodes is None: message.query_graph.nodes = [] #### Set up the KGNodeIndex kgNodeIndex = KGNodeIndex() # Loops through the QueryGraph nodes and adjust them for qnode in message.query_graph.nodes: # If the CURIE is None, then there's nothing to do curie = qnode.curie if curie is None: continue # Map the CURIE to the desired Knowledge Provider if parameters['knowledge_provider'] == 'KG1': if kgNodeIndex.is_curie_present(curie) is True: mapped_curies = [curie] else: mapped_curies = kgNodeIndex.get_KG1_curies(curie) elif parameters['knowledge_provider'] == 'KG2': if kgNodeIndex.is_curie_present(curie, kg_name='KG2'): mapped_curies = [curie] else: mapped_curies = kgNodeIndex.get_curies_and_types( curie, kg_name='KG2') else: response.error( f"Specified knowledge provider must be 'KG1' or 'KG2', not '{parameters['knowledge_provider']}'", error_code="UnknownKP") return response # Try to find a new CURIE new_curie = None if len(mapped_curies) == 0: if parameters['mismap_result'] == 'WARNING': response.warning( f"Did not find a mapping for {curie} to KP '{parameters['knowledge_provider']}'. Leaving as is" ) else: response.error( f"Did not find a mapping for {curie} to KP '{parameters['knowledge_provider']}'. This is an error" ) elif len(mapped_curies) == 1: new_curie = mapped_curies[0] else: original_curie_is_fine = False for potential_curie in mapped_curies: if potential_curie == curie: original_curie_is_fine = True if original_curie_is_fine: new_curie = curie else: new_curie = mapped_curies[0] response.warning( f"There are multiple possible CURIEs in KP '{parameters['knowledge_provider']}'. Selecting the first one {new_curie}" ) # If there's no CURIE, then nothing to do if new_curie is None: pass # If it's the same elif new_curie == curie: response.debug( f"CURIE {curie} is fine for KP '{parameters['knowledge_provider']}'" ) else: response.info( f"Remapping CURIE {curie} to {new_curie} for KP '{parameters['knowledge_provider']}'" ) #### Return the response return response
def add_qedge(self, message, input_parameters, describe=False): """ Adds a new QEdge object to the QueryGraph inside the Message object :return: Response object with execution information :rtype: Response """ # #### Internal documentation setup allowable_parameters = { 'id': { 'Any string that is unique among all QEdge id fields, with recommended format e00, e01, e02, etc.' }, 'source_id': { 'id of the source QNode already present in the QueryGraph (e.g. n01, n02)' }, 'target_id': { 'id of the target QNode already present in the QueryGraph (e.g. n01, n02)' }, 'type': { 'Any valid Translator/BioLink relationship type (e.g. physically_interacts_with, participates_in)' }, } if describe: #allowable_parameters['action'] = { 'None' } #allowable_parameters = dict() allowable_parameters[ 'dsl_command'] = '`add_qedge()`' # can't get this name at run-time, need to manually put it in per https://www.python.org/dev/peps/pep-3130/ allowable_parameters[ 'brief_description'] = """The `add_qedge` method adds an additional QEdge to the QueryGraph in the Message object. Currently source_id and target_id QNodes must already be present in the QueryGraph. The specified type is not currently checked that it is a valid Translator/BioLink relationship type, but it should be.""" return allowable_parameters #### Define a default response response = Response() self.response = response self.message = message #### Basic checks on arguments if not isinstance(input_parameters, dict): response.error("Provided parameters is not a dict", error_code="ParametersNotDict") return response #### Define a complete set of allowed parameters and their defaults parameters = { 'id': None, 'source_id': None, 'target_id': None, 'type': None, } #### Loop through the input_parameters and override the defaults and make sure they are allowed for key, value in input_parameters.items(): if key not in parameters: response.error(f"Supplied parameter {key} is not permitted", error_code="UnknownParameter") else: parameters[key] = value #### Return if any of the parameters generated an error (showing not just the first one) if response.status != 'OK': return response #### Store these final parameters for convenience response.data['parameters'] = parameters self.parameters = parameters #### Now apply the filters. Order of operations is probably quite important #### Scalar value filters probably come first like minimum_confidence, then complex logic filters #### based on edge or node properties, and then finally maximum_results response.info( f"Adding a QueryEdge to Message with parameters {parameters}") #### Make sure there's a query_graph already here if message.query_graph is None: message.query_graph = QueryGraph() message.query_graph.nodes = [] message.query_graph.edges = [] if message.query_graph.edges is None: message.query_graph.edges = [] #### Create a QEdge qedge = QEdge() if parameters['id'] is not None: id = parameters['id'] else: id = self.__get_next_free_edge_id() qedge.id = id #### Get the list of available node_ids qnodes = message.query_graph.nodes ids = {} for qnode in qnodes: id = qnode.id ids[id] = 1 #### Add the source_id if parameters['source_id'] is not None: if parameters['source_id'] not in ids: response.error( f"While trying to add QEdge, there is no QNode with id {parameters['source_id']}", error_code="UnknownSourceId") return response qedge.source_id = parameters['source_id'] else: response.error( f"While trying to add QEdge, source_id is a required parameter", error_code="MissingSourceId") return response #### Add the target_id if parameters['target_id'] is not None: if parameters['target_id'] not in ids: response.error( f"While trying to add QEdge, there is no QNode with id {parameters['target_id']}", error_code="UnknownTargetId") return response qedge.target_id = parameters['target_id'] else: response.error( f"While trying to add QEdge, target_id is a required parameter", error_code="MissingTargetId") return response #### Add the type if any. Need to verify it's an allowed type. FIXME if parameters['type'] is not None: qedge.type = parameters['type'] #### Add it to the query_graph edge list message.query_graph.edges.append(qedge) #### Return the response return response