Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/nodenorm/handlers/semantic_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,20 @@ async def get(self) -> dict:
)
raise network_error from gen_exc

# The aggregation's bucket keys are the indexed keyword values of `type`, which the
# index mapping lowercases ("biolink:smallmolecule"), unlike the `_source` that
# /get_normalized_nodes reads. Don't add the keys directly: look each one up in the
# Biolink model (bmt matches case-insensitively) and report its `class_uri`
# ("biolink:SmallMolecule"). A key the model doesn't know is reported as is.
#
# These lookups don't need memoizing here: bmt's Toolkit wraps get_element() and
# get_ancestors() in functools.lru_cache (1024 entries, more than the model's
# classes), so after the first request the loop costs about a millisecond.
semantic_types = set()
for bucket in type_aggregation_result.body["aggregations"]["unique_types"]["buckets"]:
biolink_type = bucket["key"]
semantic_types.add(biolink_type)
element = toolkit.get_element(biolink_type)
semantic_types.add(element["class_uri"] if element else biolink_type)
for ancestor in toolkit.get_ancestors(biolink_type):
semantic_types.add(toolkit.get_element(ancestor)["class_uri"])

Expand Down
7 changes: 5 additions & 2 deletions tests/test_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,8 @@ def __init__(self):

async def search(self, **kwargs):
self.calls.append(kwargs)
return SimpleNamespace(body={"aggregations": {"unique_types": {"buckets": [{"key": "biolink:Disease"}]}}})
# Real indices lowercase the `type` keyword, so aggregation keys come back lowercased.
return SimpleNamespace(body={"aggregations": {"unique_types": {"buckets": [{"key": "biolink:disease"}]}}})

async def msearch(self, **kwargs):
self.calls.append(kwargs)
Expand Down Expand Up @@ -356,7 +357,8 @@ def test_status_matches_current_metadata_schema(self):


class TestSemanticTypes(AsyncHTTPTestCase):
"""Regression: the handler lower-cased every class ("biolink:namedthing")."""
"""Regression: the handler lower-cased every class ("biolink:namedthing"), and then
reported the lowercased aggregation key next to its capitalised class_uri."""

def get_app(self) -> tornado.web.Application:
app = tornado.web.Application([(r"/get_semantic_types", SemanticTypeHandler)])
Expand All @@ -374,6 +376,7 @@ def test_types_are_biolink_class_curies(self):
types = body["semantic_types"]["types"]
assert "biolink:Disease" in types and "biolink:NamedThing" in types
assert all(re.fullmatch(r"biolink:[A-Z][A-Za-z]*", t) for t in types), types
assert len({t.lower() for t in types}) == len(types), types


class UnreachableElasticsearch:
Expand Down