From 8b25588affe25c34f5063ad8016cd2a8911ddb1e Mon Sep 17 00:00:00 2001 From: Chris Mungall Date: Wed, 5 Jun 2024 13:59:31 -0700 Subject: [PATCH] Making prefix mapping less strict, fixes #702 (#761) * Making prefix mapping less strict, fixes #702 Adds test that currently codifies ambiguous behavior. See also #760 for broader issue. This PR also extends the obo test suite to include this, and adds some derived files previously missing. * format * add missing * add missing * fix GCI example * fixed docs * regenrate compliance test outputs * Ensure prefixmap is not mutated * relax test * fmt --- .../interfaces/basic_ontology_interface.py | 9 +- tests/input/metadata-map-prefixes-test.obo | 17 +++ tests/input/obo-compliance.obo | 61 ++++++++ .../expansion-conflict-main-idspace.meta.yaml | 5 + .../expansion-conflict-main-idspace.obo | 10 ++ .../gci-is-a/gci-is-a.expected.json | 31 ++++ .../gci-is-a/gci-is-a.expected.obo | 7 + .../gci-is-a/gci-is-a.expected.ofn | 41 +++++ .../gci-is-a/gci-is-a.expected.owl | 83 ++++++++++ .../gci-is-a/gci-is-a.meta.yaml | 3 + .../obo-compliance/gci-is-a/gci-is-a.obo | 7 + .../gci-relation/gci-relation.expected.json | 31 ++++ .../gci-relation/gci-relation.expected.obo | 7 + .../gci-relation/gci-relation.expected.ofn | 43 ++++++ .../gci-relation/gci-relation.expected.owl | 104 +++++++++++++ .../gci-relation/gci-relation.meta.yaml | 3 + .../gci-relation/gci-relation.obo | 7 + .../prefixes-conflict-main-idspace.meta.yaml | 5 + .../prefixes-conflict-main-idspace.obo | 9 ++ .../prefixes-conflict-oio.expected.json | 33 ++++ .../prefixes-conflict-oio.expected.obo | 9 ++ .../prefixes-conflict-oio.expected.ofn | 47 ++++++ .../prefixes-conflict-oio.expected.owl | 81 ++++++++++ .../prefixes-conflict-oio.meta.yaml | 5 + .../prefixes-conflict-oio.obo | 9 ++ .../prefixes-conflict-skos.expected.json | 38 +++++ .../prefixes-conflict-skos.expected.obo | 12 ++ .../prefixes-conflict-skos.expected.ofn | 47 ++++++ .../prefixes-conflict-skos.expected.owl | 81 ++++++++++ .../prefixes-conflict-skos.meta.yaml | 5 + .../prefixes-conflict-skos.obo | 12 ++ .../prefixes-created_by.obo | 1 - tests/test_cli.py | 8 +- tests/test_converters/test_obo_format.py | 143 ++++++++++++++++-- tests/test_converters/test_obo_graph_to_cx.py | 2 +- tests/test_implementations/test_pronto.py | 17 +++ tests/test_implementations/test_simple_obo.py | 18 +++ 37 files changed, 1031 insertions(+), 20 deletions(-) create mode 100644 tests/input/metadata-map-prefixes-test.obo create mode 100644 tests/input/obo-compliance/expansion-conflict-main-idspace/expansion-conflict-main-idspace.meta.yaml create mode 100644 tests/input/obo-compliance/expansion-conflict-main-idspace/expansion-conflict-main-idspace.obo create mode 100644 tests/input/obo-compliance/gci-is-a/gci-is-a.expected.json create mode 100644 tests/input/obo-compliance/gci-is-a/gci-is-a.expected.obo create mode 100644 tests/input/obo-compliance/gci-is-a/gci-is-a.expected.ofn create mode 100644 tests/input/obo-compliance/gci-is-a/gci-is-a.expected.owl create mode 100644 tests/input/obo-compliance/gci-is-a/gci-is-a.meta.yaml create mode 100644 tests/input/obo-compliance/gci-is-a/gci-is-a.obo create mode 100644 tests/input/obo-compliance/gci-relation/gci-relation.expected.json create mode 100644 tests/input/obo-compliance/gci-relation/gci-relation.expected.obo create mode 100644 tests/input/obo-compliance/gci-relation/gci-relation.expected.ofn create mode 100644 tests/input/obo-compliance/gci-relation/gci-relation.expected.owl create mode 100644 tests/input/obo-compliance/gci-relation/gci-relation.meta.yaml create mode 100644 tests/input/obo-compliance/gci-relation/gci-relation.obo create mode 100644 tests/input/obo-compliance/prefixes-conflict-main-idspace/prefixes-conflict-main-idspace.meta.yaml create mode 100644 tests/input/obo-compliance/prefixes-conflict-main-idspace/prefixes-conflict-main-idspace.obo create mode 100644 tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.json create mode 100644 tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.obo create mode 100644 tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.ofn create mode 100644 tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.owl create mode 100644 tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.meta.yaml create mode 100644 tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.obo create mode 100644 tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.json create mode 100644 tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.obo create mode 100644 tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.ofn create mode 100644 tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.owl create mode 100644 tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.meta.yaml create mode 100644 tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.obo diff --git a/src/oaklib/interfaces/basic_ontology_interface.py b/src/oaklib/interfaces/basic_ontology_interface.py index 78dbe86fb..5f3070b9b 100644 --- a/src/oaklib/interfaces/basic_ontology_interface.py +++ b/src/oaklib/interfaces/basic_ontology_interface.py @@ -2,6 +2,7 @@ import logging from abc import ABC from collections import defaultdict +from copy import copy from dataclasses import dataclass, field from functools import lru_cache from pathlib import Path @@ -164,6 +165,8 @@ class BasicOntologyInterface(OntologyInterface, ABC): _edge_index: Optional[EdgeIndex] = None _entailed_edge_index: Optional[EdgeIndex] = None + _prefix_map: Optional[PREFIX_MAP] = None + def prefix_map(self) -> PREFIX_MAP: """ Return a dictionary mapping all prefixes known to the resource to their URI expansion. @@ -183,7 +186,9 @@ def prefix_map(self) -> PREFIX_MAP: :return: prefix map """ - return get_default_prefix_map() + if not self._prefix_map: + self._prefix_map = copy(get_default_prefix_map()) + return self._prefix_map @deprecated("Replaced by prefix_map") def get_prefix_map(self) -> PREFIX_MAP: @@ -203,7 +208,7 @@ def converter(self) -> curies.Converter: :return: A converter """ if self._converter is None: - self._converter = curies.Converter.from_prefix_map(self.prefix_map()) + self._converter = curies.Converter.from_prefix_map(self.prefix_map(), strict=False) return self._converter def set_metamodel_mappings(self, mappings: Union[str, Path, List[Mapping]]) -> None: diff --git a/tests/input/metadata-map-prefixes-test.obo b/tests/input/metadata-map-prefixes-test.obo new file mode 100644 index 000000000..9ccaad358 --- /dev/null +++ b/tests/input/metadata-map-prefixes-test.obo @@ -0,0 +1,17 @@ +format-version: 1.2 +ontology: test.obo +idspace: dc http://purl.org/dc/elements/1.1/ +idspace: oboInOwl http://www.geneontology.org/formats/oboInOwl# +idspace: owl http://www.w3.org/2002/07/owl# +idspace: rdf http://www.w3.org/1999/02/22-rdf-syntax-ns# +idspace: rdfs http://www.w3.org/2000/01/rdf-schema# +idspace: terms http://purl.org/dc/terms/ +idspace: xml http://www.w3.org/XML/1998/namespace +idspace: xsd http://www.w3.org/2001/XMLSchema# +idspace: HP http://purl.obolibrary.org/obo/HP_ + +[Term] +id: HP:0000001 +name: All +def: "." [PMID:1] + diff --git a/tests/input/obo-compliance.obo b/tests/input/obo-compliance.obo index f70afd914..44e7ee8b3 100644 --- a/tests/input/obo-compliance.obo +++ b/tests/input/obo-compliance.obo @@ -565,6 +565,53 @@ idspace: Y http://example.org/Y/ id: X:1 created_by: Y:1 +!! name: prefixes-conflict-oio +!! description: tests conflicting prefixes/contractions for oboInOwl namespace, where oio might be built-in +!! unstable: true +!! seeAlso: https://github.com/INCATools/ontology-access-kit/issues/760 +idspace: oboInOwl http://www.geneontology.org/formats/oboInOwl# + +[Term] +id: X:1 +name: X:1 +def: "." [] + +!! name: prefixes-conflict-skos +!! description: tests conflicting prefixes for the SKOS namespace +!! unstable: true +!! seeAlso: https://github.com/INCATools/ontology-access-kit/issues/760 +idspace: skos http://example.org/not-skos/ + +[Term] +id: X:1 +property_value: skos:exactMatch Y:1 + +[Typedef] +id: skos:exactMatch +is_metadata_tag: true + +!! name: prefixes-conflict-main-idspace +!! description: tests conflicting prefixes for the main ID space +!! invalid: true +!! seeAlso: https://github.com/INCATools/ontology-access-kit/issues/760 +idspace: X http://example.org/X/ +idspace: FAKEX http://example.org/X/ + +[Term] +id: X:1 +name: X:1 + +!! name: expansion-conflict-main-idspace +!! description: tests conflicting expansions for the main ID space +!! invalid: true +!! seeAlso: https://github.com/INCATools/ontology-access-kit/issues/760 +idspace: X http://example.org/X/ +idspace: X http://example.org/FAKEX/ + +[Term] +id: X:1 +name: X:1 + !! # ######### !! # Headers !! # ######### @@ -658,6 +705,20 @@ subset: S {source="PMID:123464"} id: X:1 disjoint_from: X:2 {source="PMID:123465"} +!! name: gci-relation +!! description: General Class Inclusion relation + +[Term] +id: X:1 +relationship: R:1 X:2 {gci_relation="R:2", gci_filler="X:3"} + +!! name: gci-is-a +!! description: General Class Inclusion is_a + +[Term] +id: X:1 +is_a: X:2 {gci_relation="R:2", gci_filler="X:3"} + !! name: created_by-annotated !! description: Metadata for creator with annotation diff --git a/tests/input/obo-compliance/expansion-conflict-main-idspace/expansion-conflict-main-idspace.meta.yaml b/tests/input/obo-compliance/expansion-conflict-main-idspace/expansion-conflict-main-idspace.meta.yaml new file mode 100644 index 000000000..146b62fb4 --- /dev/null +++ b/tests/input/obo-compliance/expansion-conflict-main-idspace/expansion-conflict-main-idspace.meta.yaml @@ -0,0 +1,5 @@ +name: expansion-conflict-main-idspace +name: expansion-conflict-main-idspace +description: tests conflicting expansions for the main ID space +invalid: true +seeAlso: https://github.com/INCATools/ontology-access-kit/issues/760 \ No newline at end of file diff --git a/tests/input/obo-compliance/expansion-conflict-main-idspace/expansion-conflict-main-idspace.obo b/tests/input/obo-compliance/expansion-conflict-main-idspace/expansion-conflict-main-idspace.obo new file mode 100644 index 000000000..1d96a591a --- /dev/null +++ b/tests/input/obo-compliance/expansion-conflict-main-idspace/expansion-conflict-main-idspace.obo @@ -0,0 +1,10 @@ +format-version: 1.4 +ontology: expansion-conflict-main-idspace +idspace: X http://example.org/X/ +idspace: X http://example.org/FAKEX/ + +[Term] +id: X:1 +name: X:1 + + diff --git a/tests/input/obo-compliance/gci-is-a/gci-is-a.expected.json b/tests/input/obo-compliance/gci-is-a/gci-is-a.expected.json new file mode 100644 index 000000000..3acf8d565 --- /dev/null +++ b/tests/input/obo-compliance/gci-is-a/gci-is-a.expected.json @@ -0,0 +1,31 @@ +{ + "graphs" : [ { + "id" : "http://purl.obolibrary.org/obo/gci-is-a.owl", + "meta" : { + "basicPropertyValues" : [ { + "pred" : "http://www.geneontology.org/formats/oboInOwl#hasOBOFormatVersion", + "val" : "1.4" + } ] + }, + "nodes" : [ { + "id" : "http://www.geneontology.org/formats/oboInOwl#hasOBOFormatVersion", + "lbl" : "has_obo_format_version", + "type" : "PROPERTY" + }, { + "id" : "http://www.geneontology.org/formats/oboInOwl#id", + "lbl" : "id", + "type" : "PROPERTY" + } ], + "edges" : [ { + "sub" : "http://purl.obolibrary.org/obo/X_1", + "pred" : "is_a", + "obj" : "http://purl.obolibrary.org/obo/X_2", + "meta" : { + "basicPropertyValues" : [ { + "pred" : "http://www.geneontology.org/formats/oboInOwl#gci_predicate", + "val" : "R:2" + } ] + } + } ] + } ] +} \ No newline at end of file diff --git a/tests/input/obo-compliance/gci-is-a/gci-is-a.expected.obo b/tests/input/obo-compliance/gci-is-a/gci-is-a.expected.obo new file mode 100644 index 000000000..5d881b925 --- /dev/null +++ b/tests/input/obo-compliance/gci-is-a/gci-is-a.expected.obo @@ -0,0 +1,7 @@ +format-version: 1.2 +ontology: gci-is-a + +[Term] +id: X:1 +is_a: X:2 {gci_predicate="R:2"} + diff --git a/tests/input/obo-compliance/gci-is-a/gci-is-a.expected.ofn b/tests/input/obo-compliance/gci-is-a/gci-is-a.expected.ofn new file mode 100644 index 000000000..acede5c45 --- /dev/null +++ b/tests/input/obo-compliance/gci-is-a/gci-is-a.expected.ofn @@ -0,0 +1,41 @@ +Prefix(:=) +Prefix(owl:=) +Prefix(rdf:=) +Prefix(xml:=) +Prefix(xsd:=) +Prefix(rdfs:=) + + +Ontology( +Annotation( "1.4") + +Declaration(Class()) +Declaration(Class()) +Declaration(AnnotationProperty()) +Declaration(AnnotationProperty()) +Declaration(AnnotationProperty()) +############################ +# Annotation Properties +############################ + +# Annotation Property: (has_obo_format_version) + +AnnotationAssertion(rdfs:label "has_obo_format_version") + +# Annotation Property: (id) + +AnnotationAssertion(rdfs:label "id") + + + +############################ +# Classes +############################ + +# Class: () + +AnnotationAssertion( "X:1") +SubClassOf(Annotation( "R:2") ) + + +) \ No newline at end of file diff --git a/tests/input/obo-compliance/gci-is-a/gci-is-a.expected.owl b/tests/input/obo-compliance/gci-is-a/gci-is-a.expected.owl new file mode 100644 index 000000000..2c52ec4b4 --- /dev/null +++ b/tests/input/obo-compliance/gci-is-a/gci-is-a.expected.owl @@ -0,0 +1,83 @@ + + + + 1.4 + + + + + + + + + + + + + + + + + + + has_obo_format_version + + + + + + + + id + + + + + + + + + + + + + + X:1 + + + + + + R:2 + + + + + + + + + + + + + diff --git a/tests/input/obo-compliance/gci-is-a/gci-is-a.meta.yaml b/tests/input/obo-compliance/gci-is-a/gci-is-a.meta.yaml new file mode 100644 index 000000000..d896fb72f --- /dev/null +++ b/tests/input/obo-compliance/gci-is-a/gci-is-a.meta.yaml @@ -0,0 +1,3 @@ +name: gci-is-a +name: gci-is-a +description: General Class Inclusion is_a \ No newline at end of file diff --git a/tests/input/obo-compliance/gci-is-a/gci-is-a.obo b/tests/input/obo-compliance/gci-is-a/gci-is-a.obo new file mode 100644 index 000000000..953a73231 --- /dev/null +++ b/tests/input/obo-compliance/gci-is-a/gci-is-a.obo @@ -0,0 +1,7 @@ +format-version: 1.4 +ontology: gci-is-a + +[Term] +id: X:1 +is_a: X:2 {gci_relation="R:2", gci_filler="X:3"} + diff --git a/tests/input/obo-compliance/gci-relation/gci-relation.expected.json b/tests/input/obo-compliance/gci-relation/gci-relation.expected.json new file mode 100644 index 000000000..091f684f0 --- /dev/null +++ b/tests/input/obo-compliance/gci-relation/gci-relation.expected.json @@ -0,0 +1,31 @@ +{ + "graphs" : [ { + "id" : "http://purl.obolibrary.org/obo/gci-relation.owl", + "meta" : { + "basicPropertyValues" : [ { + "pred" : "http://www.geneontology.org/formats/oboInOwl#hasOBOFormatVersion", + "val" : "1.4" + } ] + }, + "nodes" : [ { + "id" : "http://www.geneontology.org/formats/oboInOwl#hasOBOFormatVersion", + "lbl" : "has_obo_format_version", + "type" : "PROPERTY" + }, { + "id" : "http://www.geneontology.org/formats/oboInOwl#id", + "lbl" : "id", + "type" : "PROPERTY" + } ], + "edges" : [ { + "sub" : "http://purl.obolibrary.org/obo/X_1", + "pred" : "http://purl.obolibrary.org/obo/R_1", + "obj" : "http://purl.obolibrary.org/obo/X_2", + "meta" : { + "basicPropertyValues" : [ { + "pred" : "http://www.geneontology.org/formats/oboInOwl#gci_predicate", + "val" : "R:2" + } ] + } + } ] + } ] +} \ No newline at end of file diff --git a/tests/input/obo-compliance/gci-relation/gci-relation.expected.obo b/tests/input/obo-compliance/gci-relation/gci-relation.expected.obo new file mode 100644 index 000000000..92d234370 --- /dev/null +++ b/tests/input/obo-compliance/gci-relation/gci-relation.expected.obo @@ -0,0 +1,7 @@ +format-version: 1.2 +ontology: gci-relation + +[Term] +id: X:1 +relationship: R:1 X:2 {gci_predicate="R:2"} + diff --git a/tests/input/obo-compliance/gci-relation/gci-relation.expected.ofn b/tests/input/obo-compliance/gci-relation/gci-relation.expected.ofn new file mode 100644 index 000000000..5a7a8d6dc --- /dev/null +++ b/tests/input/obo-compliance/gci-relation/gci-relation.expected.ofn @@ -0,0 +1,43 @@ +Prefix(:=) +Prefix(owl:=) +Prefix(rdf:=) +Prefix(xml:=) +Prefix(xsd:=) +Prefix(rdfs:=) + + +Ontology( +Annotation( "1.4") + +Declaration(Class()) +Declaration(Class()) +Declaration(ObjectProperty()) +Declaration(AnnotationProperty()) +Declaration(AnnotationProperty()) +Declaration(AnnotationProperty()) +############################ +# Annotation Properties +############################ + +# Annotation Property: (has_obo_format_version) + +AnnotationAssertion(rdfs:label "has_obo_format_version") + +# Annotation Property: (id) + +AnnotationAssertion(rdfs:label "id") + + + + +############################ +# Classes +############################ + +# Class: () + +AnnotationAssertion( "X:1") +SubClassOf(Annotation( "R:2") ObjectSomeValuesFrom( )) + + +) \ No newline at end of file diff --git a/tests/input/obo-compliance/gci-relation/gci-relation.expected.owl b/tests/input/obo-compliance/gci-relation/gci-relation.expected.owl new file mode 100644 index 000000000..826fe43d4 --- /dev/null +++ b/tests/input/obo-compliance/gci-relation/gci-relation.expected.owl @@ -0,0 +1,104 @@ + + + + 1.4 + + + + + + + + + + + + + + + + + + + has_obo_format_version + + + + + + + + id + + + + + + + + + + + + + + + + + + + + + + + + + X:1 + + + + + + + + + + R:2 + + + + + + + + + + + + + diff --git a/tests/input/obo-compliance/gci-relation/gci-relation.meta.yaml b/tests/input/obo-compliance/gci-relation/gci-relation.meta.yaml new file mode 100644 index 000000000..eceb19845 --- /dev/null +++ b/tests/input/obo-compliance/gci-relation/gci-relation.meta.yaml @@ -0,0 +1,3 @@ +name: gci-relation +name: gci-relation +description: General Class Inclusion relation \ No newline at end of file diff --git a/tests/input/obo-compliance/gci-relation/gci-relation.obo b/tests/input/obo-compliance/gci-relation/gci-relation.obo new file mode 100644 index 000000000..84503b0ed --- /dev/null +++ b/tests/input/obo-compliance/gci-relation/gci-relation.obo @@ -0,0 +1,7 @@ +format-version: 1.4 +ontology: gci-relation + +[Term] +id: X:1 +relationship: R:1 X:2 {gci_relation="R:2", gci_filler="X:3"} + diff --git a/tests/input/obo-compliance/prefixes-conflict-main-idspace/prefixes-conflict-main-idspace.meta.yaml b/tests/input/obo-compliance/prefixes-conflict-main-idspace/prefixes-conflict-main-idspace.meta.yaml new file mode 100644 index 000000000..b0a8e87ad --- /dev/null +++ b/tests/input/obo-compliance/prefixes-conflict-main-idspace/prefixes-conflict-main-idspace.meta.yaml @@ -0,0 +1,5 @@ +name: prefixes-conflict-main-idspace +name: prefixes-conflict-main-idspace +description: tests conflicting prefixes for the main ID space +invalid: true +seeAlso: https://github.com/INCATools/ontology-access-kit/issues/760 \ No newline at end of file diff --git a/tests/input/obo-compliance/prefixes-conflict-main-idspace/prefixes-conflict-main-idspace.obo b/tests/input/obo-compliance/prefixes-conflict-main-idspace/prefixes-conflict-main-idspace.obo new file mode 100644 index 000000000..e6e80a0ba --- /dev/null +++ b/tests/input/obo-compliance/prefixes-conflict-main-idspace/prefixes-conflict-main-idspace.obo @@ -0,0 +1,9 @@ +format-version: 1.4 +ontology: prefixes-conflict-main-idspace +idspace: X http://example.org/X/ +idspace: FAKEX http://example.org/X/ + +[Term] +id: X:1 +name: X:1 + diff --git a/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.json b/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.json new file mode 100644 index 000000000..d8b4987c2 --- /dev/null +++ b/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.json @@ -0,0 +1,33 @@ +{ + "graphs" : [ { + "id" : "http://purl.obolibrary.org/obo/prefixes-conflict-oio.owl", + "meta" : { + "basicPropertyValues" : [ { + "pred" : "http://www.geneontology.org/formats/oboInOwl#hasOBOFormatVersion", + "val" : "1.4" + } ] + }, + "nodes" : [ { + "id" : "http://purl.obolibrary.org/obo/IAO_0000115", + "lbl" : "definition", + "type" : "PROPERTY" + }, { + "id" : "http://purl.obolibrary.org/obo/X_1", + "lbl" : "X:1", + "type" : "CLASS", + "meta" : { + "definition" : { + "val" : "." + } + } + }, { + "id" : "http://www.geneontology.org/formats/oboInOwl#hasOBOFormatVersion", + "lbl" : "has_obo_format_version", + "type" : "PROPERTY" + }, { + "id" : "http://www.geneontology.org/formats/oboInOwl#id", + "lbl" : "id", + "type" : "PROPERTY" + } ] + } ] +} \ No newline at end of file diff --git a/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.obo b/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.obo new file mode 100644 index 000000000..99e3b962e --- /dev/null +++ b/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.obo @@ -0,0 +1,9 @@ +format-version: 1.2 +idspace: oboInOwl http://www.geneontology.org/formats/oboInOwl# +ontology: prefixes-conflict-oio + +[Term] +id: X:1 +name: X:1 +def: "." [] + diff --git a/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.ofn b/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.ofn new file mode 100644 index 000000000..c9cde933e --- /dev/null +++ b/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.ofn @@ -0,0 +1,47 @@ +Prefix(:=) +Prefix(owl:=) +Prefix(rdf:=) +Prefix(xml:=) +Prefix(xsd:=) +Prefix(rdfs:=) +Prefix(oboInOwl:=) + + +Ontology( +Annotation(oboInOwl:hasOBOFormatVersion "1.4") + +Declaration(Class()) +Declaration(AnnotationProperty()) +Declaration(AnnotationProperty(oboInOwl:hasOBOFormatVersion)) +Declaration(AnnotationProperty(oboInOwl:id)) +Declaration(AnnotationProperty(rdfs:label)) +############################ +# Annotation Properties +############################ + +# Annotation Property: (definition) + +AnnotationAssertion(rdfs:label "definition") + +# Annotation Property: oboInOwl:hasOBOFormatVersion (has_obo_format_version) + +AnnotationAssertion(rdfs:label oboInOwl:hasOBOFormatVersion "has_obo_format_version") + +# Annotation Property: oboInOwl:id (id) + +AnnotationAssertion(rdfs:label oboInOwl:id "id") + + + +############################ +# Classes +############################ + +# Class: (X:1) + +AnnotationAssertion( ".") +AnnotationAssertion(oboInOwl:id "X:1") +AnnotationAssertion(rdfs:label "X:1") + + +) \ No newline at end of file diff --git a/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.owl b/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.owl new file mode 100644 index 000000000..4cd6d71c9 --- /dev/null +++ b/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.expected.owl @@ -0,0 +1,81 @@ + + + + 1.4 + + + + + + + + + + + + + definition + + + + + + + + has_obo_format_version + + + + + + + + id + + + + + + + + + + + + + + + + + + + . + X:1 + X:1 + + + + + + + diff --git a/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.meta.yaml b/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.meta.yaml new file mode 100644 index 000000000..af55397b2 --- /dev/null +++ b/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.meta.yaml @@ -0,0 +1,5 @@ +name: prefixes-conflict-oio +name: prefixes-conflict-oio +description: tests conflicting prefixes/contractions for oboInOwl namespace, where oio might be built-in +unstable: true +seeAlso: https://github.com/INCATools/ontology-access-kit/issues/760 \ No newline at end of file diff --git a/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.obo b/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.obo new file mode 100644 index 000000000..ef80fa07b --- /dev/null +++ b/tests/input/obo-compliance/prefixes-conflict-oio/prefixes-conflict-oio.obo @@ -0,0 +1,9 @@ +format-version: 1.4 +ontology: prefixes-conflict-oio +idspace: oboInOwl http://www.geneontology.org/formats/oboInOwl# + +[Term] +id: X:1 +name: X:1 +def: "." [] + diff --git a/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.json b/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.json new file mode 100644 index 000000000..c2e92992c --- /dev/null +++ b/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.json @@ -0,0 +1,38 @@ +{ + "graphs" : [ { + "id" : "http://purl.obolibrary.org/obo/prefixes-conflict-skos.owl", + "meta" : { + "basicPropertyValues" : [ { + "pred" : "http://www.geneontology.org/formats/oboInOwl#hasOBOFormatVersion", + "val" : "1.4" + } ] + }, + "nodes" : [ { + "id" : "http://example.org/not-skos/exactMatch", + "type" : "PROPERTY", + "meta" : { + "basicPropertyValues" : [ { + "pred" : "http://www.geneontology.org/formats/oboInOwl#is_metadata_tag", + "val" : "true" + } ] + } + }, { + "id" : "http://purl.obolibrary.org/obo/X_1", + "type" : "CLASS", + "meta" : { + "basicPropertyValues" : [ { + "pred" : "http://example.org/not-skos/exactMatch", + "val" : "http://purl.obolibrary.org/obo/Y_1" + } ] + } + }, { + "id" : "http://www.geneontology.org/formats/oboInOwl#hasOBOFormatVersion", + "lbl" : "has_obo_format_version", + "type" : "PROPERTY" + }, { + "id" : "http://www.geneontology.org/formats/oboInOwl#id", + "lbl" : "id", + "type" : "PROPERTY" + } ] + } ] +} \ No newline at end of file diff --git a/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.obo b/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.obo new file mode 100644 index 000000000..6b4c94b90 --- /dev/null +++ b/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.obo @@ -0,0 +1,12 @@ +format-version: 1.2 +idspace: skos http://example.org/not-skos/ +ontology: prefixes-conflict-skos + +[Term] +id: X:1 +relationship: skos:exactMatch Y:1 + +[Typedef] +id: skos:exactMatch +is_metadata_tag: true + diff --git a/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.ofn b/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.ofn new file mode 100644 index 000000000..94bfacd9f --- /dev/null +++ b/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.ofn @@ -0,0 +1,47 @@ +Prefix(:=) +Prefix(owl:=) +Prefix(rdf:=) +Prefix(xml:=) +Prefix(xsd:=) +Prefix(rdfs:=) +Prefix(skos:=) + + +Ontology( +Annotation( "1.4") + +Declaration(Class()) +Declaration(AnnotationProperty(skos:exactMatch)) +Declaration(AnnotationProperty()) +Declaration(AnnotationProperty()) +Declaration(AnnotationProperty()) +############################ +# Annotation Properties +############################ + +# Annotation Property: skos:exactMatch (skos:exactMatch) + +AnnotationAssertion( skos:exactMatch "skos:exactMatch") +AnnotationAssertion( skos:exactMatch "true"^^xsd:boolean) + +# Annotation Property: (has_obo_format_version) + +AnnotationAssertion(rdfs:label "has_obo_format_version") + +# Annotation Property: (id) + +AnnotationAssertion(rdfs:label "id") + + + +############################ +# Classes +############################ + +# Class: () + +AnnotationAssertion(skos:exactMatch ) +AnnotationAssertion( "X:1") + + +) \ No newline at end of file diff --git a/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.owl b/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.owl new file mode 100644 index 000000000..389b2d20a --- /dev/null +++ b/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.expected.owl @@ -0,0 +1,81 @@ + + + + 1.4 + + + + + + + + + + + + + skos:exactMatch + true + + + + + + + + has_obo_format_version + + + + + + + + id + + + + + + + + + + + + + + + + + + + + X:1 + + + + + + + diff --git a/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.meta.yaml b/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.meta.yaml new file mode 100644 index 000000000..dd532151b --- /dev/null +++ b/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.meta.yaml @@ -0,0 +1,5 @@ +name: prefixes-conflict-skos +name: prefixes-conflict-skos +description: tests conflicting prefixes for the SKOS namespace +unstable: true +seeAlso: https://github.com/INCATools/ontology-access-kit/issues/760 \ No newline at end of file diff --git a/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.obo b/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.obo new file mode 100644 index 000000000..829a2173f --- /dev/null +++ b/tests/input/obo-compliance/prefixes-conflict-skos/prefixes-conflict-skos.obo @@ -0,0 +1,12 @@ +format-version: 1.4 +ontology: prefixes-conflict-skos +idspace: skos http://example.org/not-skos/ + +[Term] +id: X:1 +property_value: skos:exactMatch Y:1 + +[Typedef] +id: skos:exactMatch +is_metadata_tag: true + diff --git a/tests/input/obo-compliance/prefixes-created_by/prefixes-created_by.obo b/tests/input/obo-compliance/prefixes-created_by/prefixes-created_by.obo index d74ef1fbb..e58492d16 100644 --- a/tests/input/obo-compliance/prefixes-created_by/prefixes-created_by.obo +++ b/tests/input/obo-compliance/prefixes-created_by/prefixes-created_by.obo @@ -6,4 +6,3 @@ idspace: Y http://example.org/Y/ id: X:1 created_by: Y:1 - diff --git a/tests/test_cli.py b/tests/test_cli.py index da59c5979..fc55d3914 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1142,7 +1142,13 @@ def test_lexmatch(self): self.assertIn(intracellular_match, contents) msdf = parse_sssom_table(outfile) msd = to_mapping_set_document(msdf) - self.assertEqual("http://purl.obolibrary.org/obo/XX_", msd.prefix_map["XX"]) + self.assertIn( + msd.prefix_map["XX"], + [ + "http://purl.obolibrary.org/obo/XX_", + "http://w3id.org/sssom/unknown_prefix/xx/", + ], + ) cases = [ (nucleus_match, NUCLEUS, SKOS_EXACT_MATCH), (intracellular_match, INTRACELLULAR, SKOS_CLOSE_MATCH), diff --git a/tests/test_converters/test_obo_format.py b/tests/test_converters/test_obo_format.py index 33a28df33..ccf000b96 100644 --- a/tests/test_converters/test_obo_format.py +++ b/tests/test_converters/test_obo_format.py @@ -8,13 +8,77 @@ Note: some of these tests may migrate from OAK to a more central location. -Currently these tests do two things: +Currently the tests in this suite do two things: -1. Compile a compliance suite by converting from a source .obo file +1. Compile a compliance suite by converting from a source .obo file (COMPILED_OBO_FILE) - writes to tests/input/obo-compliance - these may eventually be moved to a separate repo 2. Test that conversion to and from .obo matches these files - generates files in tests/output/obo-compliance + +Step 1: Generate the compliance suite +-------------------------------------- + +Step 1 is intended to be run relatively infrequently. It generates the source-of-truth "expected" files. +Note there is some bootstrapping here. We trust a certain version of robot/obographs/owlapi to generate the +canonical files. For the first iteration these should be manually inspected to see if they align to the spec. +Once we are happy with these, in general they should not change again. + +The source file COMPILED_OBO_FILE looks like this: + +.. code-block:: obo + + !! name: name + !! description: rdfs:label + + [Term] + id: X:1 + name: x1 + + !! name: invalid-name-duplicate + !! description: max 1 name + !! invalid: true + + [Term] + id: X:1 + name: x1 + name: x2 + + !! name: namespace + !! description: oio:namespace + + [Term] + id: X:1 + namespace: NS1 + + !! name: xref + !! description: rdfs:label + +This is designed for easy editing. New tests can be added by providing !! separators +and name/description metadata. + +If new tests are added, test_generate_canonical_files in unskip mode. This will generate the directories in +tests/input/obo-compliance + +E.g. + +.. code-block:: bash + + alt_id/ + alt_id.obo + alt_id.meta.yaml + +Next, robot will be run from the command line to generate the expected files: + +- alt_id.expected.json +- alt_id.expected.obo +- alt_id.expected.ofn +- alt_id.expected.owl + +Step 2: Checking current behavior against the compliance suite +-------------------------------------------------------------- + + """ import difflib @@ -24,7 +88,7 @@ import subprocess from functools import lru_cache from pathlib import Path -from typing import Optional +from typing import Optional, Tuple, Union import pytest import rdflib @@ -202,15 +266,18 @@ def test_generate_canonical_files(split_compiled_obo, output_format): make_filename(ontology_id, "expected", output_format, parent=OBO_COMPLIANCE_DIR) ) if canonical_path.exists(): - logger.info(f"(skipping) Comparing {output_path} to {canonical_path}") - # compare_output(path, ontology_id, "robot", output_format) + print(f"Comparing {output_path} to {canonical_path}") + compare_output(output_path, canonical_path, output_format, strict=True) else: canonical_path.parent.mkdir(exist_ok=True, parents=True) # copy the output to the input dir # logger.info(f"Copying {output_path} to {canonical_path}") shutil.copy(output_path, canonical_path) + shutil.copy(mk_version_path(output_path), mk_version_path(canonical_path)) else: assert ok is None + print(f"DID NOT CONVERT {path} to {output_path}") + raise AssertionError(f"Could not convert {path} to {output_path}") @pytest.mark.parametrize( @@ -226,6 +293,9 @@ def test_oak_loaders_dumpers(split_compiled_obo, output_format, wrapper): """ Tests that conversion via OAK generates files that are compliant. + This tests the conversion from .obo format (these are generated in advance from source, see + docs above) into other formats using OAK. + :param split_compiled_obo: :param output_format: :param wrapper: @@ -255,23 +325,33 @@ def test_oak_loaders_dumpers(split_compiled_obo, output_format, wrapper): # non-canonical forms are not expected to be identical continue canonical = canonical_path(ontology_id, output_format) - _, _, fatal = compare_output( - output_path, canonical, output_format, metadata=metadata[ontology_id] + compare_output( + output_path, canonical, output_format, metadata=metadata[ontology_id], strict=False ) - if output_format == "obo": - assert not fatal def compare_output( - generated_path: str, canonical_path: str, format: str = None, metadata: dict = None -): + generated_path: str, + canonical_path: str, + format: str = None, + metadata: dict = None, + strict=False, +) -> Tuple[int, list, bool]: """ Compare the output of OAK loading and dumping vs canonical files. + In all cases difflib is used over ascii representations + + - for obo files, the diff is a simple ascii diff (this sensitive to line ordering) + - for json files, the files are reserialized to canonicalize the order of keys + - for owl files, the files are reserialized using rdflib + + The constant KNOWN_ISSUES holds the list of test names that are known to be problematic. + :param generated_path: :param canonical_path: :param format: - :return: + :return: Tuple of [number of changes, list of diffs, fatal] """ fatal = False if not metadata: @@ -314,10 +394,14 @@ def compare_output( expected = "EXPECTED" else: if name in KNOWN_ISSUES: - expected = "TOD" + expected = "TODO" else: expected = "UNEXPECTED" - fatal = False + fatal = True + if strict: + raise ValueError( + f"UNEXPECTED DIFF {format}: {canonical_path} vs {generated_path}" + ) logger.info(f"## {name}:: {expected} DIFF {format}: {canonical_path} vs {generated_path}:") for diff in diffs: logger.info(diff) @@ -394,6 +478,16 @@ def canonical_path(ontology_id: str, output_format: str) -> str: return str(OBO_COMPLIANCE_DIR / ontology_id / f"{ontology_id}.expected.{output_format}") +def mk_version_path(path: Union[str, Path]) -> str: + """ + Make a version path for a given path. + + :param path: + :return: + """ + return f"{path}.versioninfo" + + def robot_convert(input_path: str, output_path: str) -> Optional[bool]: """ Convert an ontology using robot. @@ -403,24 +497,43 @@ def robot_convert(input_path: str, output_path: str) -> Optional[bool]: :return: """ if not robot_is_on_path(): + logger.warning("ROBOT NOT ON PATH") return None cmd = [ "robot", "convert", "-i", input_path, + # "-t", + # "http://www.geneontology.org/formats/oboInOwl#id", + # "convert", "-o", output_path, ] try: + print(f"Running {cmd}") result = subprocess.run(cmd, check=True, capture_output=True, text=True) if result.stderr: logging.warning(result.stderr) logging.info(result.stdout) - return True except subprocess.CalledProcessError as e: logging.info(f"Robot call failed: {e}") return False + try: + version_meta_file = mk_version_path(output_path) + version_cmd = [ + "robot", + "--version", + ] + with open(version_meta_file, "w") as outfile: + result = subprocess.run(version_cmd, check=True, stdout=outfile, text=True) + if result.stderr: + logging.warning(result.stderr) + logging.info(result.stdout) + return True + except subprocess.CalledProcessError as e: + logging.info(f"Robot --version call failed: {e}") + return False def ogger_convert(input_path: str, output_path: str) -> Optional[bool]: diff --git a/tests/test_converters/test_obo_graph_to_cx.py b/tests/test_converters/test_obo_graph_to_cx.py index 37a5be3df..089e5f4ff 100644 --- a/tests/test_converters/test_obo_graph_to_cx.py +++ b/tests/test_converters/test_obo_graph_to_cx.py @@ -16,7 +16,7 @@ class OboGraphToCXTest(unittest.TestCase): - """Tests OBO JSON -> RDF/OWL.""" + """Tests OBO JSON -> NDEx CX format.""" def setUp(self): self.converter = OboGraphToCXConverter() diff --git a/tests/test_implementations/test_pronto.py b/tests/test_implementations/test_pronto.py index 3c5610fce..694762cd2 100644 --- a/tests/test_implementations/test_pronto.py +++ b/tests/test_implementations/test_pronto.py @@ -86,6 +86,23 @@ def test_custom_prefixes(self): if iri is not None: self.assertEqual(oi.uri_to_curie(iri), curie, f"in contract iri: {iri}") + def test_conflicting_oio_prefixes(self): + """ + See https://github.com/INCATools/ontology-access-kit/issues/702 + """ + resource = OntologyResource( + slug="metadata-map-prefixes-test.obo", directory=INPUT_DIR, local=True + ) + adapter = ProntoImplementation(resource) + m = adapter.entity_metadata_map("HP:0000001") + self.assertIsNotNone(m) + uri = "http://www.geneontology.org/formats/oboInOwl#foo" + curie = adapter.uri_to_curie(uri) + # behavior is currently intentionally undefined + assert curie == "oio:foo" or curie == "oboInOwl:foo" + # must be reversible + assert adapter.curie_to_uri(curie) == uri + def test_relationship_map(self): oi = self.oi rels = oi.outgoing_relationship_map("GO:0005773") diff --git a/tests/test_implementations/test_simple_obo.py b/tests/test_implementations/test_simple_obo.py index f5bce975f..f99b191c9 100644 --- a/tests/test_implementations/test_simple_obo.py +++ b/tests/test_implementations/test_simple_obo.py @@ -103,6 +103,24 @@ def test_custom_prefixes(self): if iri is not None: self.assertEqual(oi.uri_to_curie(iri), curie, f"in contract iri: {iri}") + def test_conflicting_oio_prefixes(self): + """ + See https://github.com/INCATools/ontology-access-kit/issues/702 + """ + # TODO: DRY. This is currently duplicative of a pronto test + resource = OntologyResource( + slug="metadata-map-prefixes-test.obo", directory=INPUT_DIR, local=True + ) + adapter = SimpleOboImplementation(resource) + m = adapter.entity_metadata_map("HP:0000001") + self.assertIsNotNone(m) + uri = "http://www.geneontology.org/formats/oboInOwl#foo" + curie = adapter.uri_to_curie(uri) + # behavior is currently intentionally undefined + assert curie == "oio:foo" or curie == "oboInOwl:foo" + # must be reversible + assert adapter.curie_to_uri(curie) == uri + def test_relationships_extra(self): oi = self.oi rels = oi.outgoing_relationship_map("GO:0005773")