Skip to content
Closed
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
56 changes: 54 additions & 2 deletions src/oaklib/implementations/funowl/funowl_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
)

from oaklib.datamodels import obograph
from oaklib.datamodels.search import SearchConfiguration, SearchProperty, SearchTermSyntax
from oaklib.datamodels.vocabulary import (
DEPRECATED_PREDICATE,
EQUIVALENT_CLASS,
Expand Down Expand Up @@ -413,6 +414,57 @@ def entity_alias_map(self, curie: CURIE) -> Dict[PRED_CURIE, List[str]]:
alias_map[predicate].extend(metadata.get(predicate, []))
return dict(alias_map)

def basic_search(
self, search_term: str, config: Optional[SearchConfiguration] = None
) -> Iterable[CURIE]:
if config is None:
config = SearchConfiguration()
properties = list(config.properties or [])
if config.syntax == SearchTermSyntax(SearchTermSyntax.STARTS_WITH):
matcher = lambda text: str(text).startswith(search_term)
elif config.syntax == SearchTermSyntax(SearchTermSyntax.REGULAR_EXPRESSION):
prog = re.compile(search_term)
matcher = lambda text: prog.search(str(text))
elif config.is_partial:
matcher = lambda text: search_term in str(text)
else:
matcher = lambda text: str(text) == search_term
search_all = SearchProperty(SearchProperty.ANYTHING) in properties
search_label = search_all or not properties or SearchProperty(SearchProperty.LABEL) in properties
search_identifier = search_all or SearchProperty(SearchProperty.IDENTIFIER) in properties
search_alias = search_all or SearchProperty(SearchProperty.ALIAS) in properties
search_mapped = search_all or SearchProperty(SearchProperty.MAPPED_IDENTIFIER) in properties
seen: Set[CURIE] = set()
for curie in self.entities(filter_obsoletes=False):
if search_label:
label = self.label(curie)
if label and matcher(label):
if curie not in seen:
seen.add(curie)
yield curie
continue
if search_identifier and matcher(curie):
if curie not in seen:
seen.add(curie)
yield curie
continue
if search_alias:
for alias in self.entity_aliases(curie):
if alias and matcher(alias):
if curie not in seen:
seen.add(curie)
yield curie
break
if curie in seen:
continue
if search_mapped:
for xref in self.entity_metadata_map(curie).get(HAS_DBXREF, []):
if matcher(xref):
if curie not in seen:
seen.add(curie)
yield curie
break

def terms_subsets(self, curies: Iterable[CURIE]) -> Iterable[tuple[CURIE, CURIE]]:
for curie in curies:
for subset in self.entity_metadata_map(curie).get(IN_SUBSET, []):
Expand All @@ -434,10 +486,11 @@ def node(
) -> Optional[obograph.Node]:
entity_types = set(self.owl_type(curie))
label = self.label(curie)
node_id = cast(CURIE, self.curie_to_uri(curie)) if expand_curies else curie
if not entity_types and label is None:
if strict:
raise ValueError(f"Unknown entity: {curie}")
return None
return obograph.Node(id=node_id, type="CLASS")
if any(
owl_type in entity_types
for owl_type in [OWL_OBJECT_PROPERTY, OWL_ANNOTATION_PROPERTY, OWL_DATATYPE_PROPERTY]
Expand All @@ -447,7 +500,6 @@ def node(
node_type = "INDIVIDUAL"
else:
node_type = "CLASS"
node_id = cast(CURIE, self.curie_to_uri(curie)) if expand_curies else curie
meta = None
if include_metadata:
meta = obograph.Meta()
Expand Down
41 changes: 41 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@
TEST_SYNONYMIZER_OBO = "simpleobo:" + str(INPUT_DIR / "synonym-test.obo")
RULES_FILE = INPUT_DIR / "matcher_rules.yaml"
SYNONYMIZER_RULES_FILE = INPUT_DIR / "cli-synonymizer-rules.yaml"
EXTERNAL_REFERENCE_OFN = """\
Prefix(rdfs:=<http://www.w3.org/2000/01/rdf-schema#>)
Prefix(CL:=<http://purl.obolibrary.org/obo/CL_>)
Prefix(BFO:=<http://purl.obolibrary.org/obo/BFO_>)
Prefix(GO:=<http://purl.obolibrary.org/obo/GO_>)
Ontology(
Declaration(Class(CL:0000540))
AnnotationAssertion(rdfs:label CL:0000540 "neuron")
SubClassOf(CL:0000540 GO:0008150)
SubClassOf(CL:0000540 ObjectSomeValuesFrom(BFO:0000050 GO:0008150))
)
"""


def _outpath(test: str, fmt: str = "tmp") -> str:
Expand Down Expand Up @@ -115,6 +127,35 @@ def test_input_type_and_sniff_for_functional_owl_suffix(self):
self.assertEqual(0, result.exit_code, result.output)
self.assertIn("nucleus", result.stdout)

def test_funowl_info_search_and_graph_outputs(self):
with tempfile.TemporaryDirectory() as tmpdir:
ontology_path = Path(tmpdir) / "external-ref.ofn"
ontology_path.write_text(EXTERNAL_REFERENCE_OFN)
viz_out = Path(tmpdir) / "viz.json"
for args, expected in (
(["-I", "ofn", "-i", str(ontology_path), "info", "neuron"], "CL:0000540 ! neuron"),
(
["-I", "ofn", "-i", str(ontology_path), "info", "l~neur"],
"CL:0000540 ! neuron",
),
(
["-I", "ofn", "-i", str(ontology_path), "info", "CL:0000540", "-O", "obo"],
"id: CL:0000540",
),
):
result = self.runner.invoke(main, args)
self.assertEqual(0, result.exit_code, result.output)
self.assertIn(expected, result.stdout)
result = self.runner.invoke(
main,
["-I", "ofn", "-i", str(ontology_path), "viz", "CL:0000540", "-O", "json", "-o", str(viz_out)],
)
self.assertEqual(0, result.exit_code, result.output)
obj = json.load(open(viz_out))
node_ids = {node["id"] for node in obj["nodes"]}
self.assertIn("CL:0000540", node_ids)
self.assertIn("GO:0008150", node_ids)

def test_multilingual(self):
for input_arg in [INPUT_DIR / "hp-international-test.db"]:
results = self.runner.invoke(main, ["-i", str(input_arg), "languages"])
Expand Down
30 changes: 30 additions & 0 deletions tests/test_implementations/test_funowl.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import logging
import tempfile
import unittest
from pathlib import Path

from kgcl_schema.datamodel import kgcl
from pyhornedowl.model import EquivalentClasses, SubClassOf

from oaklib.datamodels.search import SearchConfiguration
from oaklib.implementations.funowl.funowl_implementation import FunOwlImplementation
from oaklib.interfaces.obograph_interface import OboGraphInterface
from oaklib.interfaces.owl_interface import AxiomFilter
Expand All @@ -16,6 +19,18 @@
TEST_GRAPH_PROJECTION_ONT = INPUT_DIR / "graph_projection.owl"
TEST_INST_ONT = INPUT_DIR / "inst.ofn"
NEW_NAME = "new name"
EXTERNAL_REFERENCE_OFN = """\
Prefix(rdfs:=<http://www.w3.org/2000/01/rdf-schema#>)
Prefix(CL:=<http://purl.obolibrary.org/obo/CL_>)
Prefix(BFO:=<http://purl.obolibrary.org/obo/BFO_>)
Prefix(GO:=<http://purl.obolibrary.org/obo/GO_>)
Ontology(
Declaration(Class(CL:0000540))
AnnotationAssertion(rdfs:label CL:0000540 "neuron")
SubClassOf(CL:0000540 GO:0008150)
SubClassOf(CL:0000540 ObjectSomeValuesFrom(BFO:0000050 GO:0008150))
)
"""


class TestFunOwlImplementation(unittest.TestCase):
Expand Down Expand Up @@ -92,6 +107,21 @@ def test_logical_definitions(self):
def test_ancestors_descendants(self):
self.compliance_tester.test_ancestors_descendants(self.oi)

def test_basic_search(self):
self.assertIn(NUCLEUS, list(self.oi.basic_search("nucleus")))
cfg = SearchConfiguration(is_partial=True)
self.assertIn(NUCLEUS, list(self.oi.basic_search("nucl", config=cfg)))

def test_stub_nodes_for_unresolved_external_references(self):
with tempfile.TemporaryDirectory() as tmpdir:
path = Path(tmpdir) / "external-ref.ofn"
path.write_text(EXTERNAL_REFERENCE_OFN)
oi = FunOwlImplementation(OntologyResource(str(path)))
graph = oi.direct_graph("CL:0000540")
node_ids = {node.id for node in graph.nodes}
self.assertIn("CL:0000540", node_ids)
self.assertIn("GO:0008150", node_ids)

def test_patcher(self):
oi = self.oi
anns = list(oi.annotation_assertion_axioms(NUCLEUS))
Expand Down