Skip to content

Commit 1a607d1

Browse files
committed
Replace OntologyKP with Automat-Ubergraph
1 parent c06c49d commit 1a607d1

3 files changed

Lines changed: 243 additions & 27 deletions

File tree

cohd/cohd_trapi_14.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from .biolink_mapper import *
1414
from .trapi.reasoner_validator_ext import validate_trapi_14x as validate_trapi
1515
from .translator import bm_toolkit, bm_version
16-
from .translator.ontology_kp import OntologyKP
16+
from .translator.ubergraph import Ubergraph
1717

1818

1919
class CohdTrapi140(CohdTrapi):
@@ -596,11 +596,11 @@ def _interpret_query(self):
596596
found = False
597597
ids = list(set(concept_1_qnode['ids'])) # remove duplicate CURIEs
598598

599-
# Get subclasses for all CURIEs using ontology KP
599+
# Get subclasses for all CURIEs using Automat-Ubergraph
600600
descendant_ids = list()
601601
ancestor_dict = dict()
602602

603-
descendant_results = OntologyKP.get_descendants(ids, self._concept_1_qnode_categories)
603+
descendant_results = Ubergraph.get_descendants(ids, self._concept_1_qnode_categories)
604604
if descendant_results is not None:
605605
# Add new descendant CURIEs to the end of IDs list
606606
descendants, ancestor_dict = descendant_results
@@ -611,7 +611,7 @@ def _interpret_query(self):
611611
n_to_add = CohdTrapi.batch_size_limit - len(ids)
612612
descendant_ids_ignored = descendant_ids[n_to_add:]
613613
descendant_ids = descendant_ids[:n_to_add]
614-
description = f"More descendants from Ontology KP for QNode '{self._concept_1_qnode_key}'"\
614+
description = f"More descendants from Automat-Ubergraph KP for QNode '{self._concept_1_qnode_key}'"\
615615
f"than batch_size_limit allows. Ignored: {descendant_ids_ignored}."
616616
self.log(description, level=logging.WARNING)
617617

@@ -621,14 +621,14 @@ def _interpret_query(self):
621621
ids = ids_deduped
622622
else:
623623
self.log(f'Issue encountered with SRI Node Norm when removing equivalents', level=logging.WARNING)
624-
self.log(f"Adding descendants from Ontology KP to QNode '{self._concept_1_qnode_key}': {descendant_ids}.",
624+
self.log(f"Adding descendants from Automat-Ubergraph to QNode '{self._concept_1_qnode_key}': {descendant_ids}.",
625625
level=logging.INFO)
626626
else:
627-
self.log(f"No descendants found from Ontology KP for QNode '{self._concept_1_qnode_key}'.",
627+
self.log(f"No descendants found from Automat-Ubergraph for QNode '{self._concept_1_qnode_key}'.",
628628
level=logging.INFO)
629629
else:
630-
# Add a warning that we didn't get descendants from Ontology KP
631-
self.log(f"Issue with retrieving descendants from Ontology KP for QNode '{self._concept_1_qnode_key}'",
630+
# Add a warning that we didn't get descendants from Automat-Ubergraph
631+
self.log(f"Issue with retrieving descendants from Automat-Ubergraph for QNode '{self._concept_1_qnode_key}'",
632632
level=logging.WARNING)
633633

634634
# Update the ancestor dictionary for concept 1
@@ -716,10 +716,10 @@ def _interpret_query(self):
716716
# If CURIE of the 2nd node is specified, then query the association between concept_1 and concept_2
717717
self._domain_class_pairs = None
718718

719-
# Get subclasses for all CURIEs using ontology KP
719+
# Get subclasses for all CURIEs using Automat-Ubergraph
720720
descendant_ids = list()
721721
ancestor_dict = dict()
722-
descendant_results = OntologyKP.get_descendants(ids, self._concept_2_qnode_categories)
722+
descendant_results = Ubergraph.get_descendants(ids, self._concept_2_qnode_categories)
723723
if descendant_results is not None:
724724
# Add new descendant CURIEs to the end of IDs list
725725
descendants, ancestor_dict = descendant_results
@@ -730,7 +730,7 @@ def _interpret_query(self):
730730
n_to_add = CohdTrapi.batch_size_limit - len(ids)
731731
descendant_ids_ignored = descendant_ids[n_to_add:]
732732
descendant_ids = descendant_ids[:n_to_add]
733-
description = f"More descendants from Ontology KP for QNode '{self._concept_2_qnode_key}'" \
733+
description = f"More descendants from Automat-Ubergraph for QNode '{self._concept_2_qnode_key}'" \
734734
f"than batch_size_limit allows. Ignored: {descendant_ids_ignored}."
735735
self.log(description, level=logging.WARNING)
736736

@@ -741,14 +741,14 @@ def _interpret_query(self):
741741
else:
742742
self.log(f'Issue encountered with SRI Node Norm when removing equivalents',
743743
level=logging.WARNING)
744-
self.log(f"Adding descendants from Ontology KP to QNode '{self._concept_2_qnode_key}': {descendant_ids}.",
744+
self.log(f"Adding descendants from Automat-Ubergraph to QNode '{self._concept_2_qnode_key}': {descendant_ids}.",
745745
level=logging.INFO)
746746
else:
747-
self.log(f"No descendants found from Ontology KP for QNode '{self._concept_2_qnode_key}'.",
747+
self.log(f"No descendants found from Automat-Ubergraph for QNode '{self._concept_2_qnode_key}'.",
748748
level=logging.INFO)
749749
else:
750-
# Add a warning that we didn't get descendants from Ontology KP
751-
self.log(f"Issue with retrieving descendants from Ontology KP for QNode '{self._concept_2_qnode_key}'",
750+
# Add a warning that we didn't get descendants from Automat-Ubergraph
751+
self.log(f"Issue with retrieving descendants from Automat-Ubergraph for QNode '{self._concept_2_qnode_key}'",
752752
level=logging.WARNING)
753753

754754
# Update the ancestor dictionary for concept 2
@@ -1635,13 +1635,13 @@ def _add_kg_edge_subclass_of(self, descendant_node_id, ancestor_node_id):
16351635
'object': ancestor_node_id,
16361636
'sources': [
16371637
{
1638-
'resource_id': OntologyKP.INFORES_ID,
1638+
'resource_id': Ubergraph.INFORES_ID,
16391639
'resource_role': 'primary_knowledge_source',
16401640
},
16411641
{
16421642
'resource_id': CohdTrapi._INFORES_ID,
16431643
'resource_role': 'aggregator_knowledge_source',
1644-
'upstream_resource_ids': [OntologyKP.INFORES_ID]
1644+
'upstream_resource_ids': [Ubergraph.INFORES_ID]
16451645
},
16461646
]
16471647
}

cohd/translator/ubergraph.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import logging
2+
import requests
3+
from requests.compat import urljoin
4+
from typing import Any, Optional, Dict, List, Set, Tuple
5+
6+
from ..app import app, cache
7+
from .sri_node_normalizer import SriNodeNormalizer
8+
9+
10+
def _bypass_cache(f, *args, **kwargs):
11+
return kwargs.get('bypass', False)
12+
13+
14+
class Ubergraph:
15+
base_url_default = 'https://automat.transltr.io/ubergraph/1.4/'
16+
base_urls = {
17+
'dev': 'https://automat.renci.org/ubergraph/1.4/',
18+
'ITRB-CI': 'https://automat.ci.transltr.io/ubergraph/1.4/',
19+
'ITRB-TEST': 'https://automat.test.transltr.io/ubergraph/1.4/',
20+
'ITRB-PROD': 'https://automat.transltr.io/ubergraph/1.4/'
21+
}
22+
endpoint_query = 'query'
23+
endpoint_meta_kg = 'meta_knowledge_graph'
24+
INFORES_ID = 'infores:automat-ubergraph'
25+
_TIMEOUT = 10 # Query timeout (seconds)
26+
27+
deployment_env = app.config.get('DEPLOYMENT_ENV', 'dev')
28+
base_url = base_urls.get(deployment_env, base_url_default)
29+
logging.info(f'Deployment environment "{deployment_env}" --> using Node Norm @ {base_url}')
30+
31+
32+
@staticmethod
33+
@cache.memoize(timeout=86400, cache_none=False)
34+
def get_meta_kg():
35+
""" Get Ontology KP meta_knowledge_graph """
36+
try:
37+
url = urljoin(Ubergraph.base_url, Ubergraph.endpoint_meta_kg)
38+
resp = requests.get(url, timeout=Ubergraph._TIMEOUT)
39+
if resp.status_code == 200:
40+
return resp.json()
41+
else:
42+
# Return None, indicating an error occurred
43+
logging.warning(f'Received a non-200 status response code from Ontology KP meta_kg ({url}): '
44+
f'{(resp.status_code, resp.text)}')
45+
return None
46+
except requests.RequestException:
47+
# Return None, indicating an error occurred
48+
logging.warning(f'Encountered an RequestException when querying Ontology KP meta_kg: {url}')
49+
return None
50+
51+
@staticmethod
52+
def get_allowed_prefixes(categories: List[str]) -> Optional[Set[str]]:
53+
""" Get the set of id_prefixes for categories from meta_knowledge_graph
54+
55+
Parameters
56+
----------
57+
categories
58+
59+
Returns
60+
-------
61+
Set[str] or None if error
62+
"""
63+
meta_kg = Ubergraph.get_meta_kg()
64+
if meta_kg is None:
65+
return None
66+
nodes = meta_kg.get('nodes')
67+
if nodes is None:
68+
logging.warning('Ontology KP meta_kg has missing "nodes"')
69+
return None
70+
71+
allowed_prefixes = set()
72+
for cat in categories:
73+
if cat in nodes and 'id_prefixes' in nodes[cat]:
74+
allowed_prefixes = allowed_prefixes.union(nodes[cat]['id_prefixes'])
75+
76+
return allowed_prefixes
77+
78+
@staticmethod
79+
def convert_to_preferred(curies: List[str], categories: List[str]) -> Dict[str, str]:
80+
""" Converts the input CURIEs into the prefixes prefered by Ontology KP
81+
82+
Parameters
83+
----------
84+
curies - List[str]
85+
categories - List[str]
86+
87+
Returns
88+
-------
89+
Dict of CURIEs converted to preferred prefixes, if successful. Otherwise, the CURIEs are returned unaltered.
90+
"""
91+
allowed_prefixes = Ubergraph.get_allowed_prefixes(categories)
92+
if allowed_prefixes is not None:
93+
# Get normalized nodes for any of the CURIEs with prefixes that are not in the allowed list
94+
curies_to_convert = [c for c in curies if c.split(':')[0] not in allowed_prefixes]
95+
norm_nodes = SriNodeNormalizer.get_normalized_nodes(curies_to_convert)
96+
if norm_nodes is None:
97+
# Failed node normalizer. Return the original curies
98+
return {c:c for c in curies}
99+
100+
preferred_curies = dict()
101+
for curie in curies:
102+
if curie not in curies_to_convert:
103+
# This CURIE already allowed
104+
preferred_curies[curie] = curie
105+
else:
106+
if norm_nodes.get(curie) is None:
107+
# No node normalizer info for this CURIE, try to use the CURIE as is
108+
preferred_curies[curie] = curie
109+
continue
110+
111+
# Get the ID with the prefix that appears earliest in the allowed
112+
new_ids = [v.id for v in norm_nodes[curie].equivalent_identifiers]
113+
preferred_curie = None
114+
for prefix in allowed_prefixes:
115+
for nid in new_ids:
116+
if nid.split(':')[0] == prefix:
117+
preferred_curie = nid
118+
break
119+
if preferred_curie is not None:
120+
break
121+
if preferred_curie is None:
122+
# No CURIE with allowed prefix found. Just try with the original CURIE
123+
preferred_curie = curie
124+
preferred_curies[curie] = preferred_curie
125+
return preferred_curies
126+
else:
127+
# Didn't get a valid response from meta_knowledge_graph. Don't alter the input CURIEs
128+
return {curie:curie for curie in curies}
129+
130+
131+
@staticmethod
132+
@cache.memoize(timeout=3600, cache_none=False, unless=_bypass_cache)
133+
def get_descendants(curies: List[str], categories: Optional[List[str]] = None, timeout: int = _TIMEOUT, bypass: bool = False) -> \
134+
Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]]]:
135+
""" Get descendant CURIEs from Ontology KP
136+
137+
Parameters
138+
----------
139+
curies - list of curies
140+
categories - list of biolink categories, or None
141+
142+
Returns
143+
-------
144+
All knowledge graph nodes returned by the Ontology KP. If any errors, an emtpy dict is returned.
145+
"""
146+
# Ontology KP doesn't seem to like it when categories is null. Replace it with NamedThing for functionally
147+
# equivalent TRAPI
148+
if categories is None:
149+
categories = ['biolink:NamedThing']
150+
151+
preferred_curies = Ubergraph.convert_to_preferred(curies, categories)
152+
# Reverse mapping to get original CURIE from preferred CURIE
153+
original_curies = {v:k for (k,v) in preferred_curies.items()}
154+
155+
try:
156+
# Query Ontology KP for descendants
157+
m = {
158+
"message": {
159+
"query_graph": {
160+
"nodes": {
161+
"a": {
162+
"ids": list(preferred_curies.values())
163+
},
164+
"b": {
165+
"categories": categories
166+
}
167+
},
168+
"edges": {
169+
"ab": {
170+
"subject": "b",
171+
"object": "a",
172+
"predicates": ["biolink:subclass_of"]
173+
}
174+
}
175+
}
176+
}
177+
}
178+
179+
logging.debug(m)
180+
181+
url = urljoin(Ubergraph.base_url, Ubergraph.endpoint_query)
182+
response = requests.post(url=url, json=m, timeout=timeout)
183+
if response.status_code == 200:
184+
j = response.json()
185+
if 'message' in j and 'knowledge_graph' in j['message']:
186+
kg = j['message']['knowledge_graph']
187+
nodes = kg.get('nodes')
188+
edges = kg.get('edges')
189+
if nodes is not None and edges is not None:
190+
# Replace preferred CURIEs with the original queried CURIE
191+
for curie, pc in preferred_curies.items():
192+
if pc in nodes and curie != pc:
193+
nodes[curie] = nodes[pc]
194+
del nodes[pc]
195+
196+
# Also return a dictionary indicating the QNode IDs that are ancestors of each descendant
197+
ancestor_dict = {original_curies[e['subject']] if e['subject'] in original_curies
198+
else e['subject']:original_curies[e['object']]
199+
for e in edges.values() if e['predicate'] == 'biolink:subclass_of'}
200+
201+
return nodes, ancestor_dict
202+
else:
203+
# Return an empty dict, indicating no descendants found
204+
return dict(), dict()
205+
else:
206+
logging.warning(f'Automat-Ubergraph returned status code {response.status_code}: {response.content}')
207+
except requests.Timeout:
208+
logging.warning(f'Automat-Ubergraph timed out when querying for descendants ({Ubergraph._TIMEOUT} sec)')
209+
return None
210+
except requests.RequestException:
211+
# Return None, indicating an error occurred
212+
logging.warning('Encountered an RequestException when querying descendants from Automat-Ubergraph')
213+
return None
214+
215+
# Return None, indicating an error occurred
216+
return None

test_cohd_trapi.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616

1717
from notebooks.cohd_helpers import cohd_requests as cr
1818
from cohd.trapi.reasoner_validator_ext import validate_trapi_14x as validate_trapi, validate_trapi_response
19-
from cohd.translator.ontology_kp import OntologyKP
19+
from cohd.translator.ubergraph import Ubergraph
2020

2121
# Choose which server to test
2222
# cr.server = 'https://dev.cohd.io/api'
23-
# cr.server = 'https://cohd-api.ci.transltr.io/api'
23+
cr.server = 'https://cohd-api.ci.transltr.io/api'
2424
# cr.server = 'https://cohd-api.test.transltr.io/api' # Temporarily default to Test as Translator consrotia has only deployed TRAPI 1.4 to Test
25-
cr.server = 'https://cohd-api.transltr.io/api' # Default to ITRB-Production instance
25+
# cr.server = 'https://cohd-api.transltr.io/api' # Default to ITRB-Production instance
2626

2727
# Specify what Biolink and TRAPI versions are expected by the server
2828
BIOLINK_VERSION = '3.5.0'
@@ -152,18 +152,18 @@ def _test_ontology_kp():
152152
""" Check if Ontology KP is responding within a desired time """
153153
issue = False
154154
t1 = datetime.now()
155-
r = OntologyKP.get_descendants(curies=['MONDO:0005148'], timeout=None, bypass=True)
155+
r = Ubergraph.get_descendants(curies=['MONDO:0005148'], timeout=None, bypass=True)
156156
s = (datetime.now() - t1).seconds
157157

158-
if s > OntologyKP._TIMEOUT:
159-
warnings.warn(f'OntologyKP::getDescendants took {s} seconds to respond, which is longer than the default timeout ({OntologyKP._TIMEOUT} seconds).')
158+
if s > Ubergraph._TIMEOUT:
159+
warnings.warn(f'Ubergraph::getDescendants took {s} seconds to respond, which is longer than the default timeout ({Ubergraph._TIMEOUT} seconds).')
160160
issue = True
161161

162162
if r is None:
163-
warnings.warn(f'OntologyKP::getDescendants had an error.')
163+
warnings.warn(f'Ubergraph::getDescendants had an error.')
164164
issue = True
165165
elif len(r[0]) < 2:
166-
warnings.warn(f'OntologyKP::getDescendants returned {len(r[0])} descendant nodes for T2DM.')
166+
warnings.warn(f'Ubergraph::getDescendants returned {len(r[0])} descendant nodes for T2DM.')
167167
issue = True
168168

169169
return issue
@@ -836,10 +836,10 @@ def test_translator_query_qnode_subclasses():
836836
# There should be more than 1 result
837837
results = json['message']['results']
838838
if _ontology_kp_issue and (not results or len(results) < 2):
839-
# There was previously an issue observed with the OntologyKP, which may degrade results here.
839+
# There was previously an issue observed with the Ubergraph, which may degrade results here.
840840
# Issue warning, but don't fail the test
841841
warnings.warn('test_translator_query_qnode_subclasses: Expected more than 1 result but only found '
842-
f'{len(results)} results. However, OntologyKP may be having issues right now.')
842+
f'{len(results)} results. However, Automat-Ubergraph may be having issues right now.')
843843
return
844844

845845
assert results and len(results) > 1, _print_trapi_log(json)

0 commit comments

Comments
 (0)