From be6235f22a862715c4cd68392ab4ac90b9770ab2 Mon Sep 17 00:00:00 2001 From: Charles Tapley Hoyt Date: Wed, 20 Nov 2024 23:25:17 +0100 Subject: [PATCH 1/7] Add struct tests --- src/pyobo/struct/struct.py | 33 ++- tests/test_reader.py | 93 ++++++++ tests/test_struct.py | 430 ++++++++++++++++++++++++++++++++++--- 3 files changed, 524 insertions(+), 32 deletions(-) create mode 100644 tests/test_reader.py diff --git a/src/pyobo/struct/struct.py b/src/pyobo/struct/struct.py index c46648b6..26654a51 100644 --- a/src/pyobo/struct/struct.py +++ b/src/pyobo/struct/struct.py @@ -268,17 +268,26 @@ def append_synonym( *, type: SynonymTypeDef | None = None, specificity: SynonymSpecificity | None = None, + provenance: list[Reference] | None = None, ) -> None: """Add a synonym.""" if isinstance(synonym, str): synonym = Synonym( - synonym, type=type or DEFAULT_SYNONYM_TYPE, specificity=specificity or "EXACT" + synonym, + type=type or DEFAULT_SYNONYM_TYPE, + specificity=specificity or "EXACT", + provenance=provenance or [], ) self.synonyms.append(synonym) def append_alt(self, alt: str | Reference) -> None: """Add an alternative identifier.""" if isinstance(alt, str): + warnings.warn( + "use fully qualified reference when appending alts", + DeprecationWarning, + stacklevel=2, + ) alt = Reference(prefix=self.prefix, identifier=alt) self.alt_ids.append(alt) @@ -293,7 +302,7 @@ def append_see_also(self, reference: ReferenceHint) -> Self: # a literal string. otherwise, raise the error again if isinstance(reference, str): return self.annotate_literal(see_also, reference) - raise + raise RuntimeError from None # this shouldn't/can't happen? else: return self.annotate_object(see_also, _reference) @@ -321,6 +330,10 @@ def extend_parents(self, references: Collection[Reference]) -> None: def get_properties(self, prop) -> list[str]: """Get properties from the given key.""" + # FIXME this instance check should be removed when + # improving property handling + if isinstance(prop, curies.Reference | Referenced): + prop = prop.curie return self.properties[prop] def get_property(self, prop) -> str | None: @@ -458,6 +471,11 @@ def iterate_obo_lines( if self.definition: yield f"def: {self._definition_fp()}" + elif self.provenance: + logger.warning("%s has provenance but no definition, can't write", self.curie) + + for alt in sorted(self.alt_ids): + yield f"alt_id: {alt}" # __str__ bakes in the ! name for xref in sorted(self.xrefs): yield f"xref: {xref}" # __str__ bakes in the ! name @@ -479,17 +497,16 @@ def iterate_obo_lines( def _emit_relations( self, ontology_prefix: str, typedefs: dict[ReferenceTuple, TypeDef] ) -> Iterable[str]: - for typedef, references in sorted(self.relationships.items()): + for typedef, reference in self.iterate_relations(): _typedef_warn(ontology_prefix, typedef.reference, typedefs) - for reference in sorted(references): - s = f"relationship: {typedef.preferred_curie} {reference.preferred_curie}" - if typedef.name or reference.name: - s += " !" + s = f"relationship: {typedef.preferred_curie} {reference.preferred_curie}" + if typedef.name or reference.name: + s += " !" if typedef.name: s += f" {typedef.name}" if reference.name: s += f" {reference.name}" - yield s + yield s def _emit_properties(self, typedefs: dict[ReferenceTuple, TypeDef]) -> Iterable[str]: for prop, value in sorted(self.iterate_properties(), key=_sort_properties): diff --git a/tests/test_reader.py b/tests/test_reader.py new file mode 100644 index 00000000..9214aa57 --- /dev/null +++ b/tests/test_reader.py @@ -0,0 +1,93 @@ +"""Tests for the reader.""" + +import datetime +import unittest +from io import StringIO +from textwrap import dedent + +from obonet import read_obo + +from pyobo import Obo +from pyobo.reader import from_obonet + + +def _read(text: str) -> Obo: + text = dedent(text).strip() + io = StringIO() + io.write(text) + io.seek(0) + graph = read_obo(io) + return from_obonet(graph) + + +class TestReader(unittest.TestCase): + """Test the reader.""" + + def test_unknown_ontology_prefix(self) -> None: + """Test an ontology with an unknown prefix.""" + with self.assertRaises(ValueError) as exc: + _read("""\ + ontology: nope + + [Term] + id: CHEBI:1234 + """) + self.assertEqual("unknown prefix: nope", exc.exception.args[0]) + + def test_missing_date_version(self) -> None: + """Test an ontology with a missing date and version.""" + ontology = _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + """) + self.assertIsNone(ontology.date) + self.assertIsNone(ontology.data_version) + + def test_bad_date_format(self) -> None: + """Test an ontology with a malformed date and no version.""" + ontology = _read("""\ + ontology: chebi + date: aabbccddeee + + [Term] + id: CHEBI:1234 + """) + self.assertIsNone(ontology.date) + self.assertIsNone(ontology.data_version) + + def test_date_no_version(self) -> None: + """Test an ontology with a date but no version.""" + ontology = _read("""\ + ontology: chebi + date: 20:11:2024 18:44 + + [Term] + id: CHEBI:1234 + """) + self.assertEqual(datetime.datetime(2024, 11, 20, 18, 44), ontology.date) + self.assertEqual("2024-11-20", ontology.data_version) + + def test_minimal(self) -> None: + """Test an ontology with a version but no date.""" + ontology = _read("""\ + data-version: 185 + ontology: chebi + + [Term] + id: CHEBI:1234 + name: Test Name + def: "Test definition" [orcid:1234-1234-1234] + xref: drugbank:DB1234567 + """) + self.assertEqual([], ontology.typedefs) + self.assertEqual([], ontology.synonym_typedefs) + terms = list(ontology.iter_terms()) + self.assertEqual(1, len(terms)) + + term = terms[0] + self.assertEqual("Test definition", term.definition) + self.assertEqual(1, len(term.xrefs)) + xref = term.xrefs[0] + self.assertEqual("drugbank:DB1234567", xref.curie) diff --git a/tests/test_struct.py b/tests/test_struct.py index a0ec0a6b..45515e19 100644 --- a/tests/test_struct.py +++ b/tests/test_struct.py @@ -7,6 +7,13 @@ from pyobo import Obo, Reference from pyobo.constants import NCBITAXON_PREFIX from pyobo.struct.struct import BioregistryError, SynonymTypeDef, Term, TypeDef +from pyobo.struct.typedef import exact_match, see_also + +LYSINE_DEHYDROGENASE_ACT = Reference( + prefix="GO", identifier="0050069", name="lysine dehydrogenase activity" +) +RO_DUMMY = TypeDef(reference=Reference.from_curie("RO:1234567")) +CHARLIE = Reference(prefix="orcid", identifier="0000-0003-4423-4370") class Nope(Obo): @@ -21,12 +28,12 @@ def iter_terms(self, force: bool = False): class TestStruct(unittest.TestCase): """Tests for the OBO data structures.""" - def test_invalid_prefix(self): + def test_invalid_prefix(self) -> None: """Test raising an error when an invalid prefix is used.""" with self.assertRaises(BioregistryError): Nope() - def test_reference_validation(self): + def test_reference_validation(self) -> None: """Test validation of prefix.""" with self.assertRaises(ValueError): Reference(prefix="nope", identifier="also_nope") @@ -70,11 +77,15 @@ def test_synonym_typedef(self) -> None: s4 = SynonymTypeDef(reference=r2, specificity="EXACT") self.assertEqual("synonymtypedef: OMO:0003012 EXACT", s4.to_obo()) + +class TestTerm(unittest.TestCase): + """Tests for terms.""" + def assert_lines(self, text: str, lines: Iterable[str]) -> None: """Assert the lines are equal.""" self.assertEqual(dedent(text).strip(), "\n".join(lines).strip()) - def test_species(self): + def test_species(self) -> None: """Test setting and getting species.""" term = Term(reference=Reference(prefix="hgnc", identifier="1234")) term.set_species("9606", "Homo sapiens") @@ -83,13 +94,25 @@ def test_species(self): self.assertEqual(NCBITAXON_PREFIX, species.prefix) self.assertEqual("9606", species.identifier) - def test_term(self): + def test_term_minimal(self) -> None: """Test emitting properties.""" term = Term( reference=Reference( - prefix="GO", identifier="0050069", name="lysine dehydrogenase activity" - ), + prefix=LYSINE_DEHYDROGENASE_ACT.prefix, + identifier=LYSINE_DEHYDROGENASE_ACT.identifier, + ) ) + self.assert_lines( + """\ + [Term] + id: GO:0050069 + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={}), + ) + + def test_term_with_name(self) -> None: + """Test emitting properties.""" + term = Term(reference=LYSINE_DEHYDROGENASE_ACT) self.assert_lines( """\ [Term] @@ -99,39 +122,398 @@ def test_term(self): term.iterate_obo_lines(ontology_prefix="GO", typedefs={}), ) - term = Term( - reference=Reference( - prefix="GO", identifier="0050069", name="lysine dehydrogenase activity" - ), - properties={ - "key": ["value"], - }, + def test_property_literal(self) -> None: + """Test emitting property literals.""" + term = Term(reference=LYSINE_DEHYDROGENASE_ACT) + term.annotate_literal(RO_DUMMY, "value") + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + property_value: RO:1234567 "value" xsd:string + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={}), ) + + def test_property_object(self) -> None: + """Test emitting property literals.""" + term = Term(reference=LYSINE_DEHYDROGENASE_ACT) + term.annotate_object(RO_DUMMY, Reference(prefix="hgnc", identifier="123")) + # TODO make this work with properties! self.assert_lines( """\ [Term] id: GO:0050069 name: lysine dehydrogenase activity - property_value: key "value" xsd:string + relationship: RO:1234567 hgnc:123 """, term.iterate_obo_lines(ontology_prefix="GO", typedefs={}), ) - typedef = TypeDef(reference=Reference.from_curie("RO:1234567")) - term = Term( - reference=Reference( - prefix="GO", identifier="0050069", name="lysine dehydrogenase activity" - ), - relationships={ - typedef: [Reference.from_curie("EC:1.1.1.1")], - }, + def test_relation(self) -> None: + """Test emitting a relationship.""" + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_relationship(RO_DUMMY, Reference(prefix="eccode", identifier="1.4.1.15")) + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + relationship: RO:1234567 eccode:1.4.1.15 + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + def test_xref(self) -> None: + """Test emitting a relationship.""" + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_xref(Reference(prefix="eccode", identifier="1.4.1.15")) + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + xref: eccode:1.4.1.15 + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + def test_parent(self) -> None: + """Test emitting a relationship.""" + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_parent(Reference(prefix="GO", identifier="1234568")) + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + is_a: GO:1234568 + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + def test_append_exact_match(self) -> None: + """Test emitting a relationship.""" + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_exact_match(Reference(prefix="eccode", identifier="1.4.1.15")) + # FIXME exact match should be a property value + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + xref: eccode:1.4.1.15 + relationship: skos:exactMatch eccode:1.4.1.15 ! exact match + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_exact_match( + Reference(prefix="eccode", identifier="1.4.1.15", name="lysine dehydrogenase") + ) + # FIXME + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + xref: eccode:1.4.1.15 ! lysine dehydrogenase + relationship: skos:exactMatch eccode:1.4.1.15 ! exact match lysine dehydrogenase + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_property( + exact_match, + Reference(prefix="eccode", identifier="1.4.1.15", name="lysine dehydrogenase"), + ) + # FIXME + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + xref: eccode:1.4.1.15 ! lysine dehydrogenase + property_value: skos:exactMatch "eccode:1.4.1.15" xsd:string + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + def test_set_species(self) -> None: + """Test emitting a relationship.""" + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.set_species("9606", "Homo sapiens") + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + relationship: RO:0002162 NCBITaxon:9606 ! in taxon Homo sapiens + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + species = term.get_species() + self.assertIsNotNone(species) + self.assertEqual("ncbitaxon", species.prefix) + self.assertEqual("9606", species.identifier) + + def test_comment(self) -> None: + """Test appending a comment.""" + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_comment("I like this record") + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + property_value: rdfs:comment "I like this record" xsd:string + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + def test_replaced_by(self) -> None: + """Test adding a replaced by.""" + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_replaced_by(Reference(prefix="GO", identifier="1234569", name="dummy")) + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + relationship: IAO:0100001 GO:1234569 ! term replaced by dummy + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + def test_alt(self) -> None: + """Test adding an alternate ID.""" + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_alt(Reference(prefix="GO", identifier="1234569", name="dummy")) + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + alt_id: GO:1234569 ! dummy + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_alt("1234569") + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + alt_id: GO:1234569 + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + def test_see_also(self) -> None: + """Test adding a replaced by.""" + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_replaced_by(Reference(prefix="GO", identifier="1234569", name="dummy")) + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + relationship: IAO:0100001 GO:1234569 ! term replaced by dummy + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + def test_append_synonym(self) -> None: + """Test appending a synonym.""" + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_synonym( + "L-lysine:NAD+ oxidoreductase", + ) + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + synonym: "L-lysine:NAD+ oxidoreductase" EXACT [] + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_synonym( + "L-lysine:NAD+ oxidoreductase", + specificity="RELATED", ) self.assert_lines( """\ [Term] id: GO:0050069 name: lysine dehydrogenase activity - relationship: RO:1234567 eccode:1.1.1.1 + synonym: "L-lysine:NAD+ oxidoreductase" RELATED [] """, - term.iterate_obo_lines(ontology_prefix="GO", typedefs={typedef.pair: typedef}), + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_synonym( + "L-lysine:NAD+ oxidoreductase", specificity="RELATED", provenance=[CHARLIE] + ) + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + synonym: "L-lysine:NAD+ oxidoreductase" RELATED [orcid:0000-0003-4423-4370] + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_synonym( + "L-lysine:NAD+ oxidoreductase", + # TODO switch to bare reference + type=SynonymTypeDef(reference=Reference(prefix="OMO", identifier="1234567")), + provenance=[Reference(prefix="orcid", identifier="0000-0003-4423-4370")], ) + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + synonym: "L-lysine:NAD+ oxidoreductase" EXACT OMO:1234567 [orcid:0000-0003-4423-4370] + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), + ) + + def test_definition(self): + """Test adding a definition.""" + term = Term(LYSINE_DEHYDROGENASE_ACT, definition="Something") + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + def: "Something" [] + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={}), + ) + + term = Term(LYSINE_DEHYDROGENASE_ACT, definition="Something") + term.append_provenance(CHARLIE) + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + def: "Something" [orcid:0000-0003-4423-4370] + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={}), + ) + + def test_provenance_no_definition(self) -> None: + """Test when there's provenance but not definition.""" + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_provenance(CHARLIE) + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={}), + ) + + def test_obsolete(self) -> None: + """Test obsolete definition.""" + term = Term(LYSINE_DEHYDROGENASE_ACT, is_obsolete=True) + self.assert_lines( + """\ + [Term] + id: GO:0050069 + is_obsolete: true + name: lysine dehydrogenase activity + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={}), + ) + + term = Term(LYSINE_DEHYDROGENASE_ACT, is_obsolete=False) + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={}), + ) + + def test_see_also_url(self) -> None: + """Test appending see also.""" + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_see_also("https://example.org/test") + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + property_value: rdfs:seeAlso "https://example.org/test" xsd:string + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={}), + ) + + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_see_also("something") + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + property_value: rdfs:seeAlso "something" xsd:string + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={}), + ) + + self.assertEqual( + "something", + term.get_property(see_also), + ) + self.assertEqual( + ["something"], + term.get_properties(see_also), + ) + + term = Term(LYSINE_DEHYDROGENASE_ACT) + term.append_see_also(Reference(prefix="hgnc", identifier="1234")) + term.append_see_also(Reference(prefix="hgnc", identifier="1235")) + # FIXME this should be a property_value + self.assert_lines( + """\ + [Term] + id: GO:0050069 + name: lysine dehydrogenase activity + relationship: rdfs:seeAlso hgnc:1234 ! see also + relationship: rdfs:seeAlso hgnc:1235 ! see also + """, + term.iterate_obo_lines(ontology_prefix="GO", typedefs={}), + ) + + self.assertEqual( + [ + Reference(prefix="hgnc", identifier="1234"), + Reference(prefix="hgnc", identifier="1235"), + ], + term.get_relationships(see_also), + ) + + # because there can be only one + with self.assertRaises(ValueError): + term.get_relationship(see_also) + + self.assertIsNone(term.get_relationship(exact_match)) + self.assertIsNone(term.get_property(exact_match)) + self.assertIsNone(term.get_species()) From 9254adbbccf47b01249ef2bf323a053ee6f2241e Mon Sep 17 00:00:00 2001 From: Charles Tapley Hoyt Date: Thu, 21 Nov 2024 00:27:58 +0100 Subject: [PATCH 2/7] Update reader.py --- src/pyobo/reader.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/pyobo/reader.py b/src/pyobo/reader.py index e1bc7873..b89c0bf6 100644 --- a/src/pyobo/reader.py +++ b/src/pyobo/reader.py @@ -44,9 +44,7 @@ RELATION_REMAPPINGS: Mapping[str, ReferenceTuple] = bioontologies.upgrade.load() -def from_obo_path( - path: str | Path, prefix: str | None = None, *, strict: bool = True, **kwargs -) -> Obo: +def from_obo_path(path: str | Path, prefix: str | None = None, *, strict: bool = True) -> Obo: """Get the OBO graph from a path.""" import obonet @@ -67,7 +65,7 @@ def from_obo_path( _clean_graph_ontology(graph, prefix) # Convert to an Obo instance and return - return from_obonet(graph, strict=strict, **kwargs) + return from_obonet(graph, strict=strict) def from_obonet(graph: nx.MultiDiGraph, *, strict: bool = True) -> Obo: @@ -315,7 +313,7 @@ def iterate_graph_typedefs( elif "identifier" in typedef: curie = typedef["identifier"] else: - raise KeyError + raise KeyError("typedef is missing an `id`") name = typedef.get("name") if name is None: @@ -353,12 +351,12 @@ def _extract_definition( ) -> tuple[None, None] | tuple[str, list[Reference]]: """Extract the definitions.""" if not s.startswith('"'): - raise ValueError("definition does not start with a quote") + raise ValueError(f"[{node.curie}] definition does not start with a quote") try: definition, rest = _quote_split(s) - except ValueError: - logger.warning("[%s] could not parse definition: %s", node.curie, s) + except ValueError as e: + logger.warning("[%s] failed to parse definition quotes: %s", node.curie, str(e)) return None, None if not rest.startswith("[") or not rest.endswith("]"): @@ -380,7 +378,7 @@ def _quote_split(s: str) -> tuple[str, str]: s = s.lstrip('"') i = _get_first_nonquoted(s) if i is None: - raise ValueError + raise ValueError(f"no closing quote found in `{s}`") return _clean_definition(s[:i].strip()), s[i + 1 :].strip() From 6ad6ea1c0c5c2ee6bfb1e01e0c5f6a38b3202f9c Mon Sep 17 00:00:00 2001 From: Charles Tapley Hoyt Date: Thu, 21 Nov 2024 00:28:04 +0100 Subject: [PATCH 3/7] Update test_reader.py --- tests/test_reader.py | 139 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 6 deletions(-) diff --git a/tests/test_reader.py b/tests/test_reader.py index 9214aa57..41984bea 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -7,17 +7,19 @@ from obonet import read_obo -from pyobo import Obo +from pyobo import Obo, Term from pyobo.reader import from_obonet +from pyobo.struct import default_reference +from pyobo.struct.typedef import TypeDef, is_conjugate_base_of -def _read(text: str) -> Obo: +def _read(text: str, *, strict: bool = True) -> Obo: text = dedent(text).strip() io = StringIO() io.write(text) io.seek(0) graph = read_obo(io) - return from_obonet(graph) + return from_obonet(graph, strict=strict) class TestReader(unittest.TestCase): @@ -69,6 +71,12 @@ def test_date_no_version(self) -> None: self.assertEqual(datetime.datetime(2024, 11, 20, 18, 44), ontology.date) self.assertEqual("2024-11-20", ontology.data_version) + def get_only_term(self, ontology: Obo) -> Term: + terms = list(ontology.iter_terms()) + self.assertEqual(1, len(terms)) + term = terms[0] + return term + def test_minimal(self) -> None: """Test an ontology with a version but no date.""" ontology = _read("""\ @@ -83,11 +91,130 @@ def test_minimal(self) -> None: """) self.assertEqual([], ontology.typedefs) self.assertEqual([], ontology.synonym_typedefs) - terms = list(ontology.iter_terms()) - self.assertEqual(1, len(terms)) - term = terms[0] + term = self.get_only_term(ontology) self.assertEqual("Test definition", term.definition) self.assertEqual(1, len(term.xrefs)) xref = term.xrefs[0] self.assertEqual("drugbank:DB1234567", xref.curie) + + def test_relationship_qualified_undefined(self) -> None: + """Test parsing a relationship that's loaded in the defaults.""" + ontology = _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + name: Test Name + relationship: RO:0018033 CHEBI:5678 + """) + term = self.get_only_term(ontology) + reference = term.get_relationship(is_conjugate_base_of) + self.assertIsNotNone(reference) + self.assertEqual("chebi:5678", reference.curie) + + def test_relationship_qualified_defined(self) -> None: + """Test relationship parsing that's defined.""" + ontology = _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + name: Test Name + relationship: RO:0018033 CHEBI:5678 + + [Typedef] + id: RO:0018033 + name: is conjugate base of + """) + term = self.get_only_term(ontology) + reference = term.get_relationship(is_conjugate_base_of) + self.assertIsNotNone(reference) + self.assertEqual("chebi:5678", reference.curie) + + def test_relationship_unqualified(self) -> None: + """Test relationship parsing that relies on default referencing.""" + ontology = _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + name: Test Name + relationship: is_conjugate_base_of CHEBI:5678 + + [Typedef] + id: is_conjugate_base_of + """) + term = self.get_only_term(ontology) + self.assertIsNone(term.get_relationship(is_conjugate_base_of)) + r = default_reference("chebi", "is_conjugate_base_of") + td = TypeDef(reference=r) + reference = term.get_relationship(td) + self.assertIsNotNone(reference) + self.assertEqual("chebi:5678", reference.curie) + + def test_node_unparsable(self) -> None: + """Test loading an ontology with unparsable nodes..""" + ontology = _read( + """\ + ontology: chebi + + [Term] + id: nope:1234 + """, + strict=False, + ) + self.assertEqual(0, len(list(ontology.iter_terms()))) + + def test_malformed_typedef(self) -> None: + """Test loading an ontology with unparsable nodes.""" + with self.assertRaises(KeyError) as exc: + _read("""\ + ontology: chebi + + [Typedef] + name: nope + """) + self.assertEqual("typedef is missing an `id`", exc.exception.args[0]) + + def test_typedef_xref(self) -> None: + """Test loading an ontology with unparsable nodes.""" + ontology = _read("""\ + ontology: chebi + + [Typedef] + id: RO:0018033 + name: is conjugate base of + xref: debio:0000010 + """) + self.assertEqual(1, len(ontology.typedefs)) + self.assertEqual(is_conjugate_base_of.pair, ontology.typedefs[0].pair) + + def test_malformed_definition(self) -> None: + with self.assertRaises(ValueError) as exc: + _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + name: Test Name + def: malformed definition without quotes + """) + self.assertEqual( + "[chebi:1234] definition does not start with a quote", exc.exception.args[0] + ) + + def test_malformed_definition_2(self) -> None: + with self.assertRaises(ValueError) as exc: + _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + name: Test Name + def: "malformed definition without quotes + """) + self.assertEqual( + '[chebi:1234] could not parse definition: "malformed definition without quotes', + exc.exception.args[0], + ) From b5083494371d2c21db5485e5e9faa616b381e498 Mon Sep 17 00:00:00 2001 From: Charles Tapley Hoyt Date: Thu, 21 Nov 2024 00:39:48 +0100 Subject: [PATCH 4/7] Add more tests --- src/pyobo/reader.py | 8 ++++++-- tests/test_reader.py | 25 ++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/pyobo/reader.py b/src/pyobo/reader.py index b89c0bf6..2b035096 100644 --- a/src/pyobo/reader.py +++ b/src/pyobo/reader.py @@ -367,7 +367,11 @@ def _extract_definition( return definition, provenance -def _get_first_nonquoted(s: str) -> int | None: +def get_first_nonescaped_quote(s: str) -> int | None: + """Get the first non-escaped quote.""" + if s[0] == '"': + # special case first position + return 0 for i, (a, b) in enumerate(pairwise(s), start=1): if b == '"' and a != "\\": return i @@ -376,7 +380,7 @@ def _get_first_nonquoted(s: str) -> int | None: def _quote_split(s: str) -> tuple[str, str]: s = s.lstrip('"') - i = _get_first_nonquoted(s) + i = get_first_nonescaped_quote(s) if i is None: raise ValueError(f"no closing quote found in `{s}`") return _clean_definition(s[:i].strip()), s[i + 1 :].strip() diff --git a/tests/test_reader.py b/tests/test_reader.py index 41984bea..96de88d4 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -8,7 +8,7 @@ from obonet import read_obo from pyobo import Obo, Term -from pyobo.reader import from_obonet +from pyobo.reader import from_obonet, get_first_nonescaped_quote from pyobo.struct import default_reference from pyobo.struct.typedef import TypeDef, is_conjugate_base_of @@ -22,6 +22,22 @@ def _read(text: str, *, strict: bool = True) -> Obo: return from_obonet(graph, strict=strict) +class TestUtils(unittest.TestCase): + """Test utilities for the reader.""" + + def test_first_nonescaped_quote(self): + """Test finding the first non-escaped double quote.""" + self.assertEqual(0, get_first_nonescaped_quote('"')) + self.assertEqual(0, get_first_nonescaped_quote('"abc')) + self.assertEqual(0, get_first_nonescaped_quote('"abc"')) + self.assertEqual(2, get_first_nonescaped_quote('\\""')) + self.assertEqual(3, get_first_nonescaped_quote('abc"')) + self.assertEqual(3, get_first_nonescaped_quote('abc""')) + self.assertIsNone(get_first_nonescaped_quote("abc")) + self.assertIsNone(get_first_nonescaped_quote('abc\\"')) + self.assertIsNone(get_first_nonescaped_quote('\\"hello\\"')) + + class TestReader(unittest.TestCase): """Test the reader.""" @@ -72,6 +88,7 @@ def test_date_no_version(self) -> None: self.assertEqual("2024-11-20", ontology.data_version) def get_only_term(self, ontology: Obo) -> Term: + """Assert there is only a single term in the ontology and return it.""" terms = list(ontology.iter_terms()) self.assertEqual(1, len(terms)) term = terms[0] @@ -190,7 +207,8 @@ def test_typedef_xref(self) -> None: self.assertEqual(1, len(ontology.typedefs)) self.assertEqual(is_conjugate_base_of.pair, ontology.typedefs[0].pair) - def test_malformed_definition(self) -> None: + def test_definition_missing_start_quote(self) -> None: + """Test parsing a definition missing a starting quote.""" with self.assertRaises(ValueError) as exc: _read("""\ ontology: chebi @@ -204,7 +222,8 @@ def test_malformed_definition(self) -> None: "[chebi:1234] definition does not start with a quote", exc.exception.args[0] ) - def test_malformed_definition_2(self) -> None: + def test_definition_missing_end_quote(self) -> None: + """Test parsing a definition missing an ending quote.""" with self.assertRaises(ValueError) as exc: _read("""\ ontology: chebi From 0b8fea0b9767dac4aae7c5bd8f69929fab288878 Mon Sep 17 00:00:00 2001 From: Charles Tapley Hoyt Date: Thu, 21 Nov 2024 01:14:14 +0100 Subject: [PATCH 5/7] Add more tests and update property parsing --- src/pyobo/reader.py | 32 +++--- tests/test_get.py | 2 +- tests/test_reader.py | 255 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 257 insertions(+), 32 deletions(-) diff --git a/src/pyobo/reader.py b/src/pyobo/reader.py index 2b035096..b8796b03 100644 --- a/src/pyobo/reader.py +++ b/src/pyobo/reader.py @@ -205,7 +205,7 @@ def from_obonet(graph: nx.MultiDiGraph, *, strict: bool = True) -> Obo: continue n_relations += 1 term.append_relationship(typedef, reference) - for prop, value in iterate_node_properties(data, term=term): + for prop, value, _is_literal in iterate_node_properties(data, term=term): n_properties += 1 term.append_property(prop, value) terms.append(term) @@ -351,7 +351,8 @@ def _extract_definition( ) -> tuple[None, None] | tuple[str, list[Reference]]: """Extract the definitions.""" if not s.startswith('"'): - raise ValueError(f"[{node.curie}] definition does not start with a quote") + logger.warning(f"[{node.curie}] definition does not start with a quote") + return None, None try: definition, rest = _quote_split(s) @@ -360,7 +361,9 @@ def _extract_definition( return None, None if not rest.startswith("[") or not rest.endswith("]"): - logger.warning("[%s] problem with definition: %s", node.curie, s) + logger.warning( + "[%s] missing square brackets in rest of: %s (rest = `%s`)", node.curie, s, rest + ) provenance = [] else: provenance = _parse_trailing_ref_list(rest, strict=strict, node=node) @@ -433,10 +436,9 @@ def _extract_synonym( break if not rest.startswith("[") or not rest.endswith("]"): - logger.warning("[%s] problem with synonym: %s", node.curie, s) - return None - - provenance = _parse_trailing_ref_list(rest, strict=strict, node=node) + provenance = [] + else: + provenance = _parse_trailing_ref_list(rest, strict=strict, node=node) return Synonym( name=name, specificity=specificity or "EXACT", @@ -482,8 +484,8 @@ def iterate_node_synonyms( def iterate_node_properties( - data: Mapping[str, Any], *, property_prefix: str | None = None, term=None -) -> Iterable[tuple[str, str]]: + data: Mapping[str, Any], *, term=None +) -> Iterable[tuple[str, str, bool]]: """Extract properties from a :mod:`obonet` node's data.""" for prop_value_type in data.get("property_value", []): try: @@ -491,16 +493,18 @@ def iterate_node_properties( except ValueError: logger.info("malformed property: %s on %s", prop_value_type, term and term.curie) continue - if property_prefix is not None and prop.startswith(property_prefix): - prop = prop[len(property_prefix) :] - try: value, _ = value_type.rsplit(" ", 1) # second entry is the value type except ValueError: # logger.debug(f'property missing datatype. defaulting to string - {prop_value_type}') value = value_type # could assign type to be 'xsd:string' by default - value = value.strip('"') - yield prop, value + + if value.startswith('"'): + # this is a literal value + value = value.strip('"') + yield prop, value, True + else: + yield prop, value, False def iterate_node_parents( diff --git a/tests/test_get.py b/tests/test_get.py index b1e9e149..2b033a19 100644 --- a/tests/test_get.py +++ b/tests/test_get.py @@ -193,7 +193,7 @@ def test_get_node_properties(self): t_prop = "http://purl.obolibrary.org/obo/chebi/monoisotopicmass" self.assertIn(t_prop, {prop for prop, value in properties}) self.assertEqual(1, sum(prop == t_prop for prop, value in properties)) - value = next(value for prop, value in properties if prop == t_prop) + value = next(value for prop, value, _ in properties if prop == t_prop) self.assertEqual("261.28318", value) def test_get_node_parents(self): diff --git a/tests/test_reader.py b/tests/test_reader.py index 96de88d4..da017792 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -7,11 +7,14 @@ from obonet import read_obo -from pyobo import Obo, Term +from pyobo import Obo, Reference, Term from pyobo.reader import from_obonet, get_first_nonescaped_quote from pyobo.struct import default_reference +from pyobo.struct.struct import DEFAULT_SYNONYM_TYPE from pyobo.struct.typedef import TypeDef, is_conjugate_base_of +CHARLIE = Reference(prefix="orcid", identifier="0000-0003-4423-4370") + def _read(text: str, *, strict: bool = True) -> Obo: text = dedent(text).strip() @@ -170,6 +173,43 @@ def test_relationship_unqualified(self) -> None: self.assertIsNotNone(reference) self.assertEqual("chebi:5678", reference.curie) + def test_relationship_missing(self) -> None: + """Test parsing a relationship that isn't defined.""" + ontology = _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + name: Test Name + relationship: nope CHEBI:5678 + """) + term = self.get_only_term(ontology) + self.assertEqual(0, len(list(term.iterate_relations()))) + + def test_property_malformed(self) -> None: + """Test parsing a malformed property.""" + ontology = _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + property_value: nope + """) + term = self.get_only_term(ontology) + self.assertEqual(0, len(list(term.iterate_properties()))) + + def test_property_literal(self) -> None: + """Test parsing a property with a literal object.""" + ontology = _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + property_value: level "high" + """) + term = self.get_only_term(ontology) + self.assertEqual("high", term.get_property("level")) + def test_node_unparsable(self) -> None: """Test loading an ontology with unparsable nodes..""" ontology = _read( @@ -209,31 +249,212 @@ def test_typedef_xref(self) -> None: def test_definition_missing_start_quote(self) -> None: """Test parsing a definition missing a starting quote.""" - with self.assertRaises(ValueError) as exc: - _read("""\ + ontology = _read("""\ ontology: chebi [Term] id: CHEBI:1234 - name: Test Name def: malformed definition without quotes """) - self.assertEqual( - "[chebi:1234] definition does not start with a quote", exc.exception.args[0] - ) + term = self.get_only_term(ontology) + self.assertIsNone(term.definition) def test_definition_missing_end_quote(self) -> None: """Test parsing a definition missing an ending quote.""" - with self.assertRaises(ValueError) as exc: - _read("""\ - ontology: chebi + ontology = _read("""\ + ontology: chebi - [Term] - id: CHEBI:1234 - name: Test Name - def: "malformed definition without quotes - """) + [Term] + id: CHEBI:1234 + def: "malformed definition without quotes + """) + term = self.get_only_term(ontology) + self.assertIsNone(term.definition) + + def test_definition_no_provenance(self) -> None: + """Test parsing a term with a definition and no provenance brackets.""" + ontology = _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + def: "definition of CHEBI:1234" + """) + term = self.get_only_term(ontology) + self.assertEqual("definition of CHEBI:1234", term.definition) + + def test_definition_empty_provenance(self) -> None: + """Test parsing a term with a definition and empty provenance.""" + ontology = _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + def: "definition of CHEBI:1234" [] + """) + term = self.get_only_term(ontology) + self.assertEqual("definition of CHEBI:1234", term.definition) + + def test_definition_with_provenance(self) -> None: + """Test parsing a term with a definition and provenance.""" + ontology = _read(f"""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + def: "definition of CHEBI:1234" [{CHARLIE.curie}] + """) + term = self.get_only_term(ontology) + self.assertEqual("definition of CHEBI:1234", term.definition) + self.assertEqual(1, len(term.provenance)) + self.assertEqual(CHARLIE, term.provenance[0]) + + def test_synonym_minimal(self) -> None: + """Test parsing a synonym just the text.""" + ontology = _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + synonym: "LTEC I" + """) + term = self.get_only_term(ontology) + self.assertEqual(1, len(term.synonyms)) + synonym = term.synonyms[0] + self.assertEqual("LTEC I", synonym.name) + self.assertEqual("EXACT", synonym.specificity) + self.assertEqual(DEFAULT_SYNONYM_TYPE, synonym.type) + self.assertEqual([], synonym.provenance) + + def test_synonym_with_specificity(self) -> None: + """Test parsing a synonym with specificity.""" + ontology = _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + synonym: "LTEC I" NARROW + """) + term = self.get_only_term(ontology) + self.assertEqual(1, len(term.synonyms)) + synonym = term.synonyms[0] + self.assertEqual("LTEC I", synonym.name) + self.assertEqual("NARROW", synonym.specificity) + self.assertEqual(DEFAULT_SYNONYM_TYPE, synonym.type) + self.assertEqual([], synonym.provenance) + + def test_synonym_with_type_missing_def(self) -> None: + """Test parsing a synonym with type, but missing type def.""" + ontology = _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + synonym: "LTEC I" OMO:1234567 + """) + term = self.get_only_term(ontology) + self.assertEqual(1, len(term.synonyms)) + synonym = term.synonyms[0] + # this is because no typedef existed + self.assertEqual(DEFAULT_SYNONYM_TYPE, synonym.type) + + def test_synonym_with_type(self) -> None: + """Test parsing a synonym with type.""" + ontology = _read("""\ + ontology: chebi + synonymtypedef: OMO:1234567 "" + + [Term] + id: CHEBI:1234 + synonym: "LTEC I" OMO:1234567 + """) + term = self.get_only_term(ontology) + self.assertEqual(1, len(term.synonyms)) + synonym = term.synonyms[0] + self.assertEqual("LTEC I", synonym.name) + self.assertEqual("EXACT", synonym.specificity) + self.assertEqual(Reference(prefix="omo", identifier="1234567"), synonym.type.reference) + self.assertEqual([], synonym.provenance) + + def test_synonym_with_type_and_specificity(self) -> None: + """Test parsing a synonym with specificity and type.""" + ontology = _read("""\ + ontology: chebi + synonymtypedef: OMO:1234567 "" + + [Term] + id: CHEBI:1234 + synonym: "LTEC I" NARROW OMO:1234567 + """) + term = self.get_only_term(ontology) + self.assertEqual(1, len(term.synonyms)) + synonym = term.synonyms[0] + self.assertEqual("LTEC I", synonym.name) + self.assertEqual("NARROW", synonym.specificity) + self.assertEqual(Reference(prefix="omo", identifier="1234567"), synonym.type.reference) + self.assertEqual([], synonym.provenance) + + def test_synonym_with_empty_prov(self) -> None: + """Test parsing a synonym with specificity,type, and explicit empty provenance.""" + ontology = _read("""\ + ontology: chebi + synonymtypedef: OMO:1234567 "" + + [Term] + id: CHEBI:1234 + synonym: "LTEC I" NARROW OMO:1234567 [] + """) + term = self.get_only_term(ontology) + self.assertEqual(1, len(term.synonyms)) + synonym = term.synonyms[0] + self.assertEqual("LTEC I", synonym.name) + self.assertEqual("NARROW", synonym.specificity) + self.assertEqual(Reference(prefix="omo", identifier="1234567"), synonym.type.reference) + self.assertEqual([], synonym.provenance) + + def test_synonym_no_type(self) -> None: + """Test parsing a synonym with specificity and provenance.""" + ontology = _read(f"""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + synonym: "LTEC I" EXACT [Orphanet:93938,{CHARLIE.curie}] + """) + term = self.get_only_term(ontology) + self.assertEqual(1, len(term.synonyms)) + synonym = term.synonyms[0] + self.assertEqual("LTEC I", synonym.name) + self.assertEqual("EXACT", synonym.specificity) + self.assertEqual(DEFAULT_SYNONYM_TYPE, synonym.type) + self.assertEqual( + [ + Reference(prefix="orphanet", identifier="93938"), + CHARLIE, + ], + synonym.provenance, + ) + + def test_synonym_full(self) -> None: + """Test parsing a synonym with specificity, type, and provenance.""" + ontology = _read(f"""\ + ontology: chebi + synonymtypedef: OMO:1234567 "" + + [Term] + id: CHEBI:1234 + synonym: "LTEC I" EXACT OMO:1234567 [Orphanet:93938,{CHARLIE.curie}] + """) + term = self.get_only_term(ontology) + self.assertEqual(1, len(term.synonyms)) + synonym = term.synonyms[0] + self.assertEqual("LTEC I", synonym.name) + self.assertEqual("EXACT", synonym.specificity) + self.assertEqual(Reference(prefix="omo", identifier="1234567"), synonym.type.reference) self.assertEqual( - '[chebi:1234] could not parse definition: "malformed definition without quotes', - exc.exception.args[0], + [ + Reference(prefix="orphanet", identifier="93938"), + CHARLIE, + ], + synonym.provenance, ) From 91a079940516985004812a28f8d1113a31e02564 Mon Sep 17 00:00:00 2001 From: Charles Tapley Hoyt Date: Thu, 21 Nov 2024 01:16:58 +0100 Subject: [PATCH 6/7] Update test_reader.py --- tests/test_reader.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_reader.py b/tests/test_reader.py index da017792..10a71c10 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -186,6 +186,22 @@ def test_relationship_missing(self) -> None: term = self.get_only_term(ontology) self.assertEqual(0, len(list(term.iterate_relations()))) + def test_relationship_bad_target(self) -> None: + """Test an ontology with a version but no date.""" + ontology = _read("""\ + ontology: chebi + + [Term] + id: CHEBI:1234 + relationship: RO:0018033 missing + + [Typedef] + id: RO:0018033 + name: is conjugate base of + """) + term = self.get_only_term(ontology) + self.assertEqual(0, len(list(term.iterate_relations()))) + def test_property_malformed(self) -> None: """Test parsing a malformed property.""" ontology = _read("""\ From f9dd7ea18dcababda9e9580873dce57c146537b6 Mon Sep 17 00:00:00 2001 From: Charles Tapley Hoyt Date: Thu, 21 Nov 2024 01:21:40 +0100 Subject: [PATCH 7/7] Update --- src/pyobo/struct/struct.py | 1 - tests/test_get.py | 4 ++-- tests/test_struct.py | 3 --- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/pyobo/struct/struct.py b/src/pyobo/struct/struct.py index 26654a51..8e9efce1 100644 --- a/src/pyobo/struct/struct.py +++ b/src/pyobo/struct/struct.py @@ -362,7 +362,6 @@ def append_exact_match(self, reference: ReferenceHint) -> Self: """Append an exact match, also adding an xref.""" reference = _ensure_ref(reference) self.annotate_object(exact_match, reference) - self.append_xref(reference) return self def append_xref(self, reference: ReferenceHint) -> None: diff --git a/tests/test_get.py b/tests/test_get.py index 2b033a19..c10552b2 100644 --- a/tests/test_get.py +++ b/tests/test_get.py @@ -191,8 +191,8 @@ def test_get_node_properties(self): data = self.graph.nodes["CHEBI:51990"] properties = list(iterate_node_properties(data)) t_prop = "http://purl.obolibrary.org/obo/chebi/monoisotopicmass" - self.assertIn(t_prop, {prop for prop, value in properties}) - self.assertEqual(1, sum(prop == t_prop for prop, value in properties)) + self.assertIn(t_prop, {prop for prop, value, _ in properties}) + self.assertEqual(1, sum(prop == t_prop for prop, value, _ in properties)) value = next(value for prop, value, _ in properties if prop == t_prop) self.assertEqual("261.28318", value) diff --git a/tests/test_struct.py b/tests/test_struct.py index 45515e19..4f0d3556 100644 --- a/tests/test_struct.py +++ b/tests/test_struct.py @@ -203,7 +203,6 @@ def test_append_exact_match(self) -> None: [Term] id: GO:0050069 name: lysine dehydrogenase activity - xref: eccode:1.4.1.15 relationship: skos:exactMatch eccode:1.4.1.15 ! exact match """, term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), @@ -219,7 +218,6 @@ def test_append_exact_match(self) -> None: [Term] id: GO:0050069 name: lysine dehydrogenase activity - xref: eccode:1.4.1.15 ! lysine dehydrogenase relationship: skos:exactMatch eccode:1.4.1.15 ! exact match lysine dehydrogenase """, term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}), @@ -236,7 +234,6 @@ def test_append_exact_match(self) -> None: [Term] id: GO:0050069 name: lysine dehydrogenase activity - xref: eccode:1.4.1.15 ! lysine dehydrogenase property_value: skos:exactMatch "eccode:1.4.1.15" xsd:string """, term.iterate_obo_lines(ontology_prefix="GO", typedefs={RO_DUMMY.pair: RO_DUMMY}),