def add_qnode(self, response, input_parameters, describe=False): """ Adds a new QNode object to the QueryGraph inside the Message object :return: ARAXResponse object with execution information :rtype: ARAXResponse """ # #### Command definition for autogenerated documentation command_definition = { 'dsl_command': 'add_qnode()', 'description': """The `add_qnode` method adds an additional QNode to the QueryGraph in the Message object.""", 'parameters': { 'key': { 'is_required': False, 'examples': ['n00', 'n01'], 'default': '', 'type': 'string', 'description': """Any string that is unique among all QNode key fields, with recommended format n00, n01, n02, etc. If no value is provided, autoincrementing values beginning for n00 are used.""", }, 'id': { 'is_required': False, 'examples': ['DOID:9281', '[UniProtKB:P12345,UniProtKB:Q54321]'], 'type': 'string', 'description': 'Any compact URI (CURIE) (e.g. DOID:9281) (May also be a list like [UniProtKB:P12345,UniProtKB:Q54321])', }, 'name': { 'is_required': False, 'examples': ['hypertension', 'insulin'], 'type': 'string', 'description': 'Any name of a bioentity that will be resolved into a CURIE if possible or result in an error if not (e.g. hypertension, insulin)', }, 'category': { 'is_required': False, 'examples': ['protein', 'chemical_substance', 'disease'], 'type': 'ARAXnode', 'description': 'Any valid Translator bioentity category (e.g. protein, chemical_substance, disease)', }, 'is_set': { 'is_required': False, 'enum': ["true", "false", "True", "False", "t", "f", "T", "F"], 'examples': ['true', 'false'], 'type': 'boolean', 'description': 'If set to true, this QNode represents a set of nodes that are all in common between the two other linked QNodes (assumed to be false if not specified or value is not recognized as true/t case insensitive)' }, 'option_group_id': { 'is_required': False, 'examples': ['1', 'a', 'b2', 'option'], 'type': 'string', 'description': 'A group identifier indicating a group of nodes and edges should either all be included or all excluded. An optional match for all elements in this group. If not included Node will be treated as required.' }, } } if describe: return command_definition #### Extract the message to work on message = response.envelope.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 = { 'key': None, 'id': None, 'name': None, 'category': None, 'is_set': None, 'option_group_id': 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 #### Check for option_group_id and is_set: if parameters['option_group_id'] is not None and parameters[ 'id'] is None and parameters['name'] is None: if parameters['is_set'] is None: parameters['is_set'] = 'true' response.warning( f"An 'option_group_id' was set to {parameters['option_group_id']}, but 'is_set' was not an included parameter. It must be true when an 'option_group_id' is given, so automatically setting to true. Avoid this warning by explictly setting to true." ) elif not (parameters['is_set'].lower() == 'true' or parameters['is_set'].lower() == 't'): response.error( f"When an 'option_group_id' is given 'is_set' must be set to true. However, supplied input for parameter 'is_set' was {parameters['is_set']}.", error_code="InputMismatch") #### Return if any of the parameters generated an error (showing not just the first one) if response.status != 'OK': return response #### 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 QueryNode to Message with input 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.nodes is None: message.query_graph.nodes = {} #### Set up the NodeSynonymizer to find curies and names synonymizer = NodeSynonymizer() # Create the QNode and set the key qnode = QNode() if parameters['key'] is not None: key = parameters['key'] else: key = self.__get_next_free_node_key() if parameters['option_group_id'] is not None: qnode.option_group_id = parameters['option_group_id'] # Set the is_set parameter to what the user selected if parameters['is_set'] is not None: qnode.is_set = (parameters['is_set'].lower() == 'true' or parameters['is_set'].lower() == 't') #### If the id is specified, try to find that if parameters['id'] is not None: # If the id is a scalar then treat it here as a list of one if isinstance(parameters['id'], str): id_list = [parameters['id']] is_id_a_list = False if parameters['is_set'] is not None and qnode.is_set is True: response.error( f"Specified id '{parameters['id']}' is a scalar, but is_set=true, which doesn't make sense", error_code="IdScalarButIsSetTrue") return response # Or else set it up as a list elif isinstance(parameters['id'], list): id_list = parameters['id'] is_id_a_list = True qnode.id = [] if parameters['is_set'] is None: response.warning( f"Specified id '{parameters['id']}' is a list, but is_set was not set to true. It must be true in this context, so automatically setting to true. Avoid this warning by explictly setting to true." ) qnode.is_set = True else: if qnode.is_set == False: response.warning( f"Specified id '{parameters['id']}' is a list, but is_set=false, which doesn't make sense, so automatically setting to true. Avoid this warning by explictly setting to true." ) qnode.is_set = True # Or if it's neither a list or a string, then error out. This cannot be handled at present else: response.error( f"Specified id '{parameters['id']}' is neither a string nor a list. This cannot to handled", error_code="IdNotListOrScalar") return response # Loop over the available ids and create the list for id in id_list: response.debug(f"Looking up id {id} in NodeSynonymizer") synonymizer_results = synonymizer.get_canonical_curies( curies=[id]) # If nothing was found, we won't bail out, but rather just issue a warning that this id is suspect if synonymizer_results[id] is None: response.warning( f"A node with id {id} is not in our knowledge graph KG2, but will continue with it" ) if is_id_a_list: qnode.id.append(id) else: qnode.id = id # And if it is found, keep the same id but report the preferred id else: response.info(f"id {id} is found. Adding it to the qnode") if is_id_a_list: qnode.id.append(id) else: qnode.id = id if 'category' in parameters and parameters[ 'category'] is not None: if isinstance(parameters['category'], str): qnode.category = parameters['category'] else: qnode.category = parameters['category'][0] message.query_graph.nodes[key] = qnode return response #### If the name is specified, try to find that if parameters['name'] is not None: name = parameters['name'] response.debug( f"Looking up id for name '{name}' in NodeSynonymizer") synonymizer_results = synonymizer.get_canonical_curies( curies=[name], names=[name]) if synonymizer_results[name] is None: response.error( f"A node with name '{name}' is not in our knowledge graph", error_code="UnresolvableNodeName") return response qnode.id = synonymizer_results[name]['preferred_curie'] response.info( f"Creating QueryNode with id '{qnode.id}' for name '{name}'") if parameters['category'] is not None: qnode.category = parameters['category'] message.query_graph.nodes[key] = qnode return response #### If the category is specified, just add that category. There should be checking that it is legal. FIXME if parameters['category'] is not None: qnode.category = parameters['category'] if parameters['is_set'] is not None: qnode.is_set = (parameters['is_set'].lower() == 'true') message.query_graph.nodes[key] = qnode return response #### If we get here, it means that all three main parameters are null. Just a generic node with no category or anything. This is okay. message.query_graph.nodes[key] = qnode return response
def copy_qnode(old_qnode: QNode) -> QNode: new_qnode = QNode() for node_property in new_qnode.to_dict(): value = getattr(old_qnode, node_property) setattr(new_qnode, node_property, value) return new_qnode