def get_qg_without_kryptonite_portion(qg: QueryGraph) -> QueryGraph: kryptonite_qedge_keys = [ qedge_key for qedge_key, qedge in qg.edges.items() if qedge.exclude ] normal_qedge_keys = set(qg.edges).difference(kryptonite_qedge_keys) qnode_keys_used_by_kryptonite_qedges = { qnode_key for qedge_key in kryptonite_qedge_keys for qnode_key in {qg.edges[qedge_key].subject, qg.edges[qedge_key].object} } qnode_keys_used_by_normal_qedges = { qnode_key for qedge_key in normal_qedge_keys for qnode_key in {qg.edges[qedge_key].subject, qg.edges[qedge_key].object} } qnode_keys_used_only_by_kryptonite_qedges = qnode_keys_used_by_kryptonite_qedges.difference( qnode_keys_used_by_normal_qedges) normal_qnode_keys = set( qg.nodes).difference(qnode_keys_used_only_by_kryptonite_qedges) return QueryGraph(nodes={ qnode_key: qnode for qnode_key, qnode in qg.nodes.items() if qnode_key in normal_qnode_keys }, edges={ qedge_key: qedge for qedge_key, qedge in qg.edges.items() if qedge_key in normal_qedge_keys })
def test_example1(): query_graph = { "edges": { "e00": { "subject": "n00", "object": "n01" }, "e01": { "subject": "n00", "object": "n01", "predicate": "biolink:contraindicated_for", "exclude": True } }, "nodes": { "n00": { "id": "MONDO:0001627", "category": "biolink:Disease" }, "n01": { "category": "biolink:ChemicalSubstance" } } } from ARAX_messenger import ARAXMessenger response = ARAXResponse() messenger = ARAXMessenger() messenger.create_envelope(response) response.envelope.message.query_graph = QueryGraph().from_dict(query_graph) query_graph_info = QueryGraphInfo() result = query_graph_info.assess(response.envelope.message) response.merge(result) if result.status != 'OK': print(response.show(level=ARAXResponse.DEBUG)) return response query_graph_info_dict = { 'n_nodes': query_graph_info.n_nodes, 'n_edges': query_graph_info.n_edges, 'is_bifurcated_graph': query_graph_info.is_bifurcated_graph, 'start_node': query_graph_info.start_node, 'node_info': query_graph_info.node_info, 'edge_info': query_graph_info.edge_info, 'node_order': query_graph_info.node_order, 'edge_order': query_graph_info.edge_order, 'node_category_map': query_graph_info.node_category_map, 'edge_predicate_map': query_graph_info.edge_predicate_map, } print( json.dumps(ast.literal_eval(repr(query_graph_info_dict)), sort_keys=True, indent=2))
def get_required_portion_of_qg(query_graph: QueryGraph) -> QueryGraph: return QueryGraph(nodes={ qnode_key: qnode for qnode_key, qnode in query_graph.nodes.items() if not qnode.option_group_id }, edges={ qedge_key: qedge for qedge_key, qedge in query_graph.edges.items() if not qedge.option_group_id })
def make_qg_use_old_snake_case_types(qg: QueryGraph) -> QueryGraph: # This is a temporary patch needed for KPs not yet TRAPI 1.0 compliant qg_copy = QueryGraph(nodes={qnode_key: copy_qnode(qnode) for qnode_key, qnode in qg.nodes.items()}, edges={qedge_key: copy_qedge(qedge) for qedge_key, qedge in qg.edges.items()}) for qnode in qg_copy.nodes.values(): if qnode.category: categories = convert_to_list(qnode.category) prefixless_categories = [category.split(":")[-1] for category in categories] qnode.category = [convert_string_to_snake_case(category) for category in prefixless_categories] for qedge in qg_copy.edges.values(): if qedge.predicate: predicates = convert_to_list(qedge.predicate) qedge.predicate = [predicate.split(":")[-1] for predicate in predicates] return qg_copy
def _answer_query_using_plover( qg: QueryGraph, log: ARAXResponse ) -> Tuple[Dict[str, Dict[str, Set[Union[str, int]]]], int]: rtxc = RTXConfiguration() rtxc.live = "Production" log.debug(f"Sending query to Plover") response = requests.post(f"{rtxc.plover_url}/query", json=qg.to_dict(), headers={'accept': 'application/json'}) if response.status_code == 200: log.debug(f"Got response back from Plover") return response.json(), response.status_code else: log.warning( f"Plover returned a status code of {response.status_code}. Response was: {response.text}" ) return dict(), response.status_code
def get_node_pairs_to_overlay(subject_qnode_key: str, object_qnode_key: str, query_graph: QueryGraph, knowledge_graph: KnowledgeGraph, log: ARAXResponse) -> Set[Tuple[str, str]]: """ This function determines which combinations of subject/object nodes in the KG need to be overlayed (e.g., have a virtual edge added between). It makes use of Resultify to determine what combinations of subject and object nodes may actually appear together in the same Results. (See issue #1069.) If it fails to narrow the node pairs for whatever reason, it defaults to returning all possible combinations of subject/object nodes. """ log.debug(f"Narrowing down {subject_qnode_key}--{object_qnode_key} node pairs to overlay") kg_nodes_by_qg_id = get_node_ids_by_qg_id(knowledge_graph) kg_edges_by_qg_id = get_edge_ids_by_qg_id(knowledge_graph) # Grab the portion of the QG already 'expanded' (aka, present in the KG) sub_query_graph = QueryGraph(nodes={key:qnode for key, qnode in query_graph.nodes.items() if key in set(kg_nodes_by_qg_id)}, edges={key:qedge for key, qedge in query_graph.edges.items() if key in set(kg_edges_by_qg_id)}) # Compute results using Resultify so we can see which nodes appear in the same results resultifier = ARAXResultify() sub_response = ARAXResponse() sub_response.envelope = Response() sub_response.envelope.message = Message() sub_message = sub_response.envelope.message sub_message.query_graph = sub_query_graph sub_message.knowledge_graph = KnowledgeGraph(nodes=knowledge_graph.nodes.copy(), edges=knowledge_graph.edges.copy()) #sub_response.envelope.message = sub_message resultify_response = resultifier.apply(sub_response, {}) # Figure out which node pairs appear together in one or more results if resultify_response.status == 'OK': node_pairs = set() for result in sub_message.results: subject_curies_in_this_result = {node_binding.id for key, node_binding_list in result.node_bindings.items() for node_binding in node_binding_list if key == subject_qnode_key} object_curies_in_this_result = {node_binding.id for key, node_binding_list in result.node_bindings.items() for node_binding in node_binding_list if key == object_qnode_key} pairs_in_this_result = set(itertools.product(subject_curies_in_this_result, object_curies_in_this_result)) node_pairs = node_pairs.union(pairs_in_this_result) log.debug(f"Identified {len(node_pairs)} node pairs to overlay (with help of resultify)") if node_pairs: return node_pairs # Back up to using the old (O(n^2)) method of all combinations of subject/object nodes in the KG log.warning(f"Failed to narrow down node pairs to overlay; defaulting to all possible combinations") return set(itertools.product(kg_nodes_by_qg_id[subject_qnode_key], kg_nodes_by_qg_id[object_qnode_key]))
def make_qg_use_old_types(qg: QueryGraph) -> QueryGraph: # This is a temporary patch until we switch to KG2.5+ predicates_with_commas = {"positively_regulates_entity_to_entity": "positively_regulates,_entity_to_entity", "negatively_regulates_entity_to_entity": "negatively_regulates,_entity_to_entity", "positively_regulates_process_to_process": "positively_regulates,_process_to_process", "regulates_process_to_process": "regulates,_process_to_process", "negatively_regulates_process_to_process": "negatively_regulates,_process_to_process"} qg_copy = QueryGraph(nodes={qnode_key: copy_qnode(qnode) for qnode_key, qnode in qg.nodes.items()}, edges={qedge_key: copy_qedge(qedge) for qedge_key, qedge in qg.edges.items()}) for qnode in qg_copy.nodes.values(): if qnode.category: categories = convert_to_list(qnode.category) prefixless_categories = [category.split(":")[-1] for category in categories] qnode.category = [convert_string_to_snake_case(category) for category in prefixless_categories] for qedge in qg_copy.edges.values(): if qedge.predicate: predicates = convert_to_list(qedge.predicate) prefixless_predicates = [predicate.split(":")[-1] for predicate in predicates] qedge.predicate = [predicates_with_commas.get(predicate, predicate) for predicate in prefixless_predicates] return qg_copy
def get_qg_expanded_thus_far(qg: QueryGraph, kg: QGOrganizedKnowledgeGraph) -> QueryGraph: expanded_qnodes = { qnode_key for qnode_key in qg.nodes if kg.nodes_by_qg_id.get(qnode_key) } expanded_qedges = { qedge_key for qedge_key in qg.edges if kg.edges_by_qg_id.get(qedge_key) } qg_expanded_thus_far = QueryGraph(nodes={ qnode_key: copy.deepcopy(qg.nodes[qnode_key]) for qnode_key in expanded_qnodes }, edges={ qedge_key: copy.deepcopy(qg.edges[qedge_key]) for qedge_key in expanded_qedges }) return qg_expanded_thus_far
def _answer_query_using_plover(qg: QueryGraph, log: ARAXResponse) -> Tuple[Dict[str, Dict[str, Union[set, dict]]], int]: rtxc = RTXConfiguration() rtxc.live = "Production" # First prep the query graph (requires some minor additions for Plover) dict_qg = qg.to_dict() dict_qg["include_metadata"] = True # Ask plover to return node/edge objects (not just IDs) dict_qg["respect_predicate_symmetry"] = True # Ignore direction for symmetric predicate, enforce for asymmetric # Allow subclass_of reasoning for qnodes with a small number of curies for qnode in dict_qg["nodes"].values(): if qnode.get("ids") and len(qnode["ids"]) < 5: if "allow_subclasses" not in qnode or qnode["allow_subclasses"] is None: qnode["allow_subclasses"] = True # Then send the actual query response = requests.post(f"{rtxc.plover_url}/query", json=dict_qg, timeout=60, headers={'accept': 'application/json'}) if response.status_code == 200: log.debug(f"Got response back from Plover") return response.json(), response.status_code else: log.warning(f"Plover returned a status code of {response.status_code}. Response was: {response.text}") return dict(), response.status_code
def __get_next_free_edge_key(self): #### Set up local references to the message and verify the query_graph nodes message = self.envelope.message 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 = {} qedges = message.query_graph.edges #### Find the first unused key index = 0 while 1: pad = '0' if index > 9: pad = '' potential_edge_key = f"e{pad}{str(index)}" if potential_edge_key not in qedges: return potential_edge_key index += 1
def create_envelope(self, response, describe=False): """ Creates a basic empty ARAXResponse object with basic boilerplate metadata :return: ARAXResponse object with execution information and the new message object inside the data envelope :rtype: ARAXResponse """ # #### Command definition for autogenerated documentation command_definition = { 'dsl_command': 'create_envelope()', 'description': """The `create_envelope` command creates a basic empty Response object with basic boilerplate metadata such as reasoner_id, schema_version, etc. filled in. This DSL command takes no arguments. This command is not explicitly necessary, as it is called implicitly when needed. e.g. If a DSL program begins with add_qnode(), the create_envelope() will be executed automatically if there is not yet a ARAXResponse. If there is already ARAXResponse in memory, then this command will destroy the previous one (in memory) and begin a new envelope.""", 'parameters': {} } if describe: return command_definition #### Store the passed response object self.response = response #### Create the top-level Response object called an envelope response.info("Creating an empty template TRAPI Response") envelope = Response() response.envelope = envelope self.envelope = envelope # Create a Message object and place it in the envelope message = Message() response.envelope.message = message self.message = message #### Fill it with default information envelope.id = None envelope.type = "translator_reasoner_response" envelope.reasoner_id = "ARAX" envelope.tool_version = RTXConfiguration().version envelope.schema_version = "1.0.0" envelope.status = "OK" envelope.description = "Created empty template response" envelope.context = "https://raw.githubusercontent.com/biolink/biolink-model/master/context.jsonld" envelope.logs = response.messages envelope.datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S") #### Create an empty master knowledge graph message.knowledge_graph = KnowledgeGraph() message.knowledge_graph.nodes = {} message.knowledge_graph.edges = {} #### Create an empty query graph message.query_graph = QueryGraph() message.query_graph.nodes = {} message.query_graph.edges = {} #### Create empty results message.results = [] #### Return the response response.data['envelope'] = envelope return response
def add_qedge(self, response, input_parameters, describe=False): """ Adds a new QEdge 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_qedge()', 'description': """The `add_qedge` command adds an additional QEdge to the QueryGraph in the Message object. Currently subject and object 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.""", 'parameters': { 'key': { 'is_required': False, 'examples': ['e00', 'e01'], 'default': '', 'type': 'string', 'description': """Any string that is unique among all QEdge key fields, with recommended format e00, e01, e02, etc. If no value is provided, autoincrementing values beginning for e00 are used.""", }, 'subject': { 'is_required': True, 'examples': ['n00', 'n01'], 'type': 'string', 'description': 'key of the source QNode already present in the QueryGraph (e.g. n00, n01)', }, 'object': { 'is_required': True, 'examples': ['n01', 'n02'], 'type': 'string', 'description': 'key of the target QNode already present in the QueryGraph (e.g. n01, n02)', }, 'predicate': { 'is_required': False, 'examples': [ 'protein', 'physically_interacts_with', 'participates_in' ], 'type': 'ARAXedge', 'description': 'Any valid Translator/BioLink relationship predicate (e.g. physically_interacts_with, participates_in)', }, '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.' }, 'exclude': { 'is_required': False, 'enum': ['true', 'false'], 'examples': ['true', 'false'], 'type': 'boolean', 'description': 'If set to true, results with this node will be excluded. If set to false or not included nodes will be treated as part of a normal query.' }, } } 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, 'subject': None, 'object': None, 'predicate': None, 'option_group_id': None, 'exclude': 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['key'] is not None: key = parameters['key'] else: key = self.__get_next_free_edge_key() #### Get the list of available node_keys qnodes = message.query_graph.nodes #### Add the subject if parameters['subject'] is not None: if parameters['subject'] not in qnodes: response.error( f"While trying to add QEdge, there is no QNode with key {parameters['subject']}", error_code="UnknownSourceKey") return response qedge.subject = parameters['subject'] else: response.error( f"While trying to add QEdge, subject is a required parameter", error_code="MissingSourceKey") return response #### Add the object if parameters['object'] is not None: if parameters['object'] not in qnodes: response.error( f"While trying to add QEdge, there is no QNode with key {parameters['object']}", error_code="UnknownTargetKey") return response qedge.object = parameters['object'] else: response.error( f"While trying to add QEdge, object is a required parameter", error_code="MissingTargetKey") return response #### Add the predicate if any. Need to verify it's an allowed predicate. FIXME if parameters['predicate'] is not None: qedge.predicate = parameters['predicate'] if parameters['exclude'] is not None: if parameters['exclude'] in {'t', 'T', 'true', 'True'}: qedge.exclude = True elif parameters['exclude'] in {'f', 'F', 'false', 'False'}: qedge.exclude = False elif parameters['exclude'] not in {True, False}: response.error( f"Supplied input, {parameters['exclude']}, for the 'exclude' parameter is not valid. Acceptable inputs are t, T, f, F, true, True, false, and False.", error_code="UnknownInput") else: qedge.exclude = False if parameters['option_group_id'] is not None: qedge.option_group_id = parameters['option_group_id'] #### Add it to the query_graph edge list message.query_graph.edges[key] = qedge #### Return the response return response
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 test_example1(): test_query_graphs = [ { "description": "Two nodes, one edge linking them, 1 CURIE", "nodes": { "n00": { "ids": ["MONDO:0001627"] }, "n01": { "categories": ["biolink:ChemicalEntity"] } }, "edges": { "e00": { "subject": "n00", "object": "n01", "predicates": ["biolink:physically_interacts_with"] } } }, { "description": "Two nodes, two edges linking them, 1 CURIE, one of which is excluded", "nodes": { "n00": { "ids": ["MONDO:0001627"] }, "n01": { "categories": ["biolink:ChemicalEntity"] } }, "edges": { "e00": { "subject": "n00", "object": "n01" }, "e01": { "subject": "n00", "object": "n01", "predicates": ["biolink:contraindicated_for"], "exclude": True } } }, { "description": "Two nodes, one edge linking them, both nodes are CURIEs", "nodes": { "n00": { "ids": ["MONDO:0001627"] }, "n01": { "ids": ["CHEMBL.COMPOUND:CHEMBL112"] } }, "edges": { "e00": { "subject": "n00", "object": "n01" } } }, { "description": "Three nodes, 2 edges, 1 CURIE, simple linear chain", "nodes": { "n00": { "ids": ["MONDO:0001627"] }, "n01": { "categories": ["biolink:ChemicalEntity"] }, "n02": { "categories": ["biolink:Protein"] } }, "edges": { "e00": { "subject": "n00", "object": "n01", "predicates": ["biolink:physically_interacts_with"] }, "e01": { "subject": "n01", "object": "n02" } } }, { "description": "Three nodes, 2 edges, but the CURIE is in the middle. What does that even mean?", "nodes": { "n00": { "categories": ["biolink:ChemicalEntity"] }, "n01": { "ids": ["MONDO:0001627"] }, "n02": { "categories": ["biolink:Protein"] } }, "edges": { "e00": { "subject": "n00", "object": "n01", "predicates": ["biolink:physically_interacts_with"] }, "e01": { "subject": "n01", "object": "n02" } } }, { "description": "Four nodes, 3 edges, 1 CURIE, simple linear chain", "nodes": { "n00": { "ids": ["MONDO:0001627"] }, "n01": { "categories": ["biolink:ChemicalEntity"] }, "n02": { "categories": ["biolink:Protein"] }, "n03": { "categories": ["biolink:Disease"] } }, "edges": { "e00": { "subject": "n00", "object": "n01", "predicates": ["biolink:physically_interacts_with"] }, "e01": { "subject": "n01", "object": "n02" }, "e02": { "subject": "n02", "object": "n03" } } }, { "description": "Two nodes, one edge linking them, 0 CURIEs", "nodes": { "n00": { "categories": ["biolink:Drug"] }, "n01": { "categories": ["biolink:ChemicalEntity"] } }, "edges": { "e00": { "subject": "n00", "object": "n01", "predicates": ["biolink:physically_interacts_with"] } } }, { "description": "One node only", "nodes": { "n00": { "ids": ["MONDO:0001627"] } }, "edges": {} }, ] from ARAX_messenger import ARAXMessenger for test_query_graph in test_query_graphs: response = ARAXResponse() messenger = ARAXMessenger() messenger.create_envelope(response) print( '==================================================================' ) description = test_query_graph['description'] del test_query_graph['description'] print(f"Query Graph '{description}'") response.envelope.message.query_graph = QueryGraph().from_dict( test_query_graph) query_graph_info = QueryGraphInfo() result = query_graph_info.assess(response.envelope.message) response.merge(result) if result.status != 'OK': print(response.show(level=ARAXResponse.DEBUG)) return response query_graph_info_dict = { 'n_nodes': query_graph_info.n_nodes, 'n_edges': query_graph_info.n_edges, 'is_bifurcated_graph': query_graph_info.is_bifurcated_graph, 'start_node': query_graph_info.start_node['key'], 'simple_query_graph_template': query_graph_info.query_graph_templates['simple'], #'start_node': query_graph_info.start_node, #'node_info': query_graph_info.node_info, #'edge_info': query_graph_info.edge_info, #'node_order': query_graph_info.node_order, #'edge_order': query_graph_info.edge_order, #'node_category_map': query_graph_info.node_category_map, #'edge_predicate_map': query_graph_info.edge_predicate_map, } print( json.dumps(ast.literal_eval(repr(query_graph_info_dict)), sort_keys=True, indent=2))
def copy_qg(qg: QueryGraph) -> QueryGraph: return QueryGraph(nodes={qnode_key: copy_qnode(qnode) for qnode_key, qnode in qg.nodes.items()}, edges={qedge_key: copy_qedge(qedge) for qedge_key, qedge in qg.edges.items()})