From ccd7d35a90397353ade465ef43eeab30dece78cd Mon Sep 17 00:00:00 2001 From: Daniel Danis Date: Wed, 8 Mar 2023 16:01:27 -0500 Subject: [PATCH 1/8] Next development iteration `0.1.4dev0`. --- recipe/meta.yaml | 2 +- src/hpotk/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/recipe/meta.yaml b/recipe/meta.yaml index 731b9be..317c345 100644 --- a/recipe/meta.yaml +++ b/recipe/meta.yaml @@ -1,5 +1,5 @@ {% set name = "hpo-toolkit" %} -{% set version = "0.1.3" %} +{% set version = "0.1.4dev0" %} package: name: {{ name|lower }} diff --git a/src/hpotk/__init__.py b/src/hpotk/__init__.py index 759cc05..662d17c 100644 --- a/src/hpotk/__init__.py +++ b/src/hpotk/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.3" +__version__ = "0.1.4dev0" from . import model from . import constants From ce2c4053c99ea016b88fec9d29225edcd199f6b9 Mon Sep 17 00:00:00 2001 From: Daniel Danis Date: Thu, 13 Apr 2023 22:13:00 -0400 Subject: [PATCH 2/8] Add synonyms and xrefs into `Term`. --- src/hpotk/model/__init__.py | 2 +- src/hpotk/model/_term.py | 174 +++++++++++++++++- src/hpotk/ontology/load/obographs/_factory.py | 106 ++++++++++- src/hpotk/ontology/load/obographs/_model.py | 60 +++++- tests/test_obographs.py | 58 ++++++ tests/test_term.py | 33 +++- 6 files changed, 408 insertions(+), 25 deletions(-) diff --git a/src/hpotk/model/__init__.py b/src/hpotk/model/__init__.py index a1f47a3..267ab3a 100644 --- a/src/hpotk/model/__init__.py +++ b/src/hpotk/model/__init__.py @@ -2,7 +2,7 @@ from ._term_id import TermId from ._base import Identified, Named, Versioned -from ._term import MinimalTerm, Term +from ._term import MinimalTerm, Term, Synonym, SynonymType, SynonymCategory # Types ID = typing.TypeVar('ID', bound=TermId) diff --git a/src/hpotk/model/_term.py b/src/hpotk/model/_term.py index 84f632c..1956efe 100644 --- a/src/hpotk/model/_term.py +++ b/src/hpotk/model/_term.py @@ -1,4 +1,5 @@ import abc +import enum import typing from ._base import Identified, Named @@ -58,6 +59,95 @@ def __str__(self): f'is_obsolete={self.is_obsolete})' +class SynonymCategory(enum.Enum): + """ + Synonym category. + """ + EXACT = enum.auto() + RELATED = enum.auto() + BROAD = enum.auto() + NARROW = enum.auto() + + +class SynonymType(enum.Enum): + """ + Synonym type, as provided by Obographs. + """ + LAYPERSON_TERM = enum.auto() + ABBREVIATION = enum.auto() + UK_SPELLING = enum.auto() + OBSOLETE_SYNONYM = enum.auto() + PLURAL_FORM = enum.auto() + ALLELIC_REQUIREMENT = enum.auto() + + # We may not need these, unless we support ECTO + # IUPAC_NAME = enum.auto() + # INN = enum.auto() + # BRAND_NAME = enum.auto() + # IN_PART = enum.auto() + # SYNONYM = enum.auto() + # BLAST_NAME = enum.auto() + # GENBANK_COMMON_NAME = enum.auto() + # COMMON_NAME = enum.auto() + + def is_obsolete(self): + return self == SynonymType.OBSOLETE_SYNONYM + + def is_current(self): + return not self.is_obsolete() + + +class Synonym(Named): + + def __init__(self, name: str, + synonym_category: typing.Optional[SynonymCategory], + synonym_type: typing.Optional[SynonymType], + xrefs: typing.Optional[typing.List[TermId]] = None): + self._name = name + self._scat = synonym_category + self._stype = synonym_type + self._xrefs = xrefs + + @property + def name(self) -> str: + return self._name + + @property + def synonym_category(self) -> typing.Optional[SynonymCategory]: + """ + :return: an instance of :class:`SynonymCategory` or ``None``. + """ + return self._scat + + @property + def synonym_type(self) -> typing.Optional[SynonymType]: + """ + :return: an instance of :class:`SynonymType` or ``None``. + """ + return self._stype + + @property + def xrefs(self) -> typing.Optional[typing.List[TermId]]: + return self._xrefs + + def __eq__(self, other): + isinstance(other, Synonym) \ + and self.name == other.name \ + and self.synonym_category == other.synonym_category \ + and self.synonym_type == other.synonym_type \ + and self.xrefs == other.xrefs + + def __str__(self): + return f'Synonym(' \ + f'name="{self.name}", ' \ + f'synonym_category={self.synonym_category}, ' \ + f'synonym_type={self.synonym_type}, ' \ + f'xrefs="{self.xrefs}")' + + def __repr__(self): + return str(self) + + class Term(MinimalTerm, metaclass=abc.ABCMeta): """ A representation of an ontology concept. @@ -71,8 +161,10 @@ def create_term(identifier: TermId, alt_term_ids: typing.Sequence[TermId], is_obsolete: typing.Optional[bool], definition: typing.Optional[str], - comment: typing.Optional[str]): - return DefaultTerm(identifier, name, alt_term_ids, is_obsolete, definition, comment) + comment: typing.Optional[str], + synonyms: typing.Optional[typing.List[Synonym]], + xrefs: typing.Optional[typing.List[TermId]]): + return DefaultTerm(identifier, name, alt_term_ids, is_obsolete, definition, comment, synonyms, xrefs) @property @abc.abstractmethod @@ -90,13 +182,53 @@ def comment(self) -> str: """ pass - # TODO - add Xrefs and dbXrefs + @property + @abc.abstractmethod + def synonyms(self) -> typing.Optional[typing.List[Synonym]]: + """ + :return: all synonyms (including obsolete) of the term. + """ + pass + + def current_synonyms(self) -> typing.Iterable[Synonym]: + """ + :return: iterable over current (non-obsolete) synonyms of the term. + """ + return self._synonyms_iter(lambda synonym: + synonym.synonym_type is None + or synonym.synonym_type.is_current()) + + def obsolete_synonyms(self) -> typing.Iterable[Synonym]: + """ + :return: iterable over obsolete synonyms of the term. + """ + return self._synonyms_iter(lambda synonym: + synonym.synonym_type is not None + and synonym.synonym_type.is_obsolete()) + + def _synonyms_iter(self, filter_f): + if self.synonyms is None: + return () + else: + for synonym in self.synonyms: + if filter_f(synonym): + yield synonym + + @property + @abc.abstractmethod + def xrefs(self) -> typing.Optional[typing.List[TermId]]: + """ + :return: term's cross-references + """ + pass def __eq__(self, other): return MinimalTerm.__eq__(self, other) \ and isinstance(other, Term) \ and self.definition == other.definition \ - and self.comment == other.comment + and self.comment == other.comment \ + and self.synonyms == other.synonyms \ + and self.xrefs == other.xrefs def __str__(self): return f'Term(' \ @@ -104,6 +236,8 @@ def __str__(self): f'name="{self.name}", ' \ f'definition={self.definition}, ' \ f'comment={self.comment}, ' \ + f'synonyms={self.synonyms}, ' \ + f'xrefs={self.xrefs}, ' \ f'is_obsolete={self.is_obsolete}, ' \ f'alt_term_ids="{self.alt_term_ids}")' @@ -135,6 +269,13 @@ def alt_term_ids(self) -> typing.Sequence[TermId]: def is_obsolete(self) -> bool: return self._is_obsolete + def __repr__(self): + return f'DefaultMinimalTerm(' \ + f'identifier={self._id},' \ + f' name="{self._name}",' \ + f' is_obsolete={self._is_obsolete},' \ + f' alt_term_ids="{self._alts}")' + class DefaultTerm(Term): @@ -143,13 +284,17 @@ def __init__(self, identifier: TermId, alt_term_ids: typing.Sequence[TermId], is_obsolete: typing.Optional[bool], definition: typing.Optional[str], - comment: typing.Optional[str]): + comment: typing.Optional[str], + synonyms: typing.Optional[typing.List[Synonym]], + xrefs: typing.Optional[typing.List[TermId]]): self._id = identifier self._name = name self._alt_term_ids = alt_term_ids self._is_obsolete = False if is_obsolete is None else is_obsolete self._definition = definition self._comment = comment + self._synonyms = synonyms + self._xrefs = xrefs @property def identifier(self) -> TermId: @@ -174,3 +319,22 @@ def definition(self) -> str: @property def comment(self) -> str: return self._comment + + @property + def synonyms(self) -> typing.Optional[typing.List[Synonym]]: + return self._synonyms + + @property + def xrefs(self) -> typing.Optional[typing.List[TermId]]: + return self._xrefs + + def __repr__(self): + return f'DefaultTerm(' \ + f'identifier={self._id}, ' \ + f'name="{self._name}", ' \ + f'definition={self._definition}, ' \ + f'comment={self._comment}, ' \ + f'synonyms={self._synonyms}, ' \ + f'xrefs={self._xrefs}, ' \ + f'is_obsolete={self._is_obsolete}, ' \ + f'alt_term_ids="{self._alt_term_ids}")' diff --git a/src/hpotk/ontology/load/obographs/_factory.py b/src/hpotk/ontology/load/obographs/_factory.py index a859e16..dd55875 100644 --- a/src/hpotk/ontology/load/obographs/_factory.py +++ b/src/hpotk/ontology/load/obographs/_factory.py @@ -1,12 +1,19 @@ import abc +import logging +import re import typing -from hpotk.model import TermId, Term, MinimalTerm +from hpotk.model import TermId, Term, MinimalTerm, Synonym, SynonymType, SynonymCategory +from ._model import Node, Meta, SynonymPropertyValue -from ._model import Node +logger = logging.getLogger(__name__) MINIMAL_TERM = typing.TypeVar('MINIMAL_TERM', bound=MinimalTerm) +OBO_PURL_PT = re.compile(r'^http://purl\.obolibrary\.org/obo/(?P.+)$') +HP_VAL_PT = re.compile(r'^hp(.*)#(?P.+)$') +ORCID_PT = re.compile(r'.*orcid\.org/(?P\d{4}-\d{4}-\d{4}-\d{4})$') + def create_alt_term_ids(node: Node) -> typing.List[TermId]: alt_term_ids = [] @@ -19,6 +26,91 @@ def create_alt_term_ids(node: Node) -> typing.List[TermId]: return alt_term_ids +def create_synonyms(meta: Meta) -> typing.Optional[typing.List[Synonym]]: + if len(meta.synonyms) == 0: + return None + else: + return [parse_synonym(s) for s in meta.synonyms] + + +def parse_synonym(spv: SynonymPropertyValue) -> Synonym: + synonym_category: typing.Optional[SynonymCategory] = parse_synonym_category(spv.pred) + synonym_type: typing.Optional[SynonymType] = parse_synonym_type(spv.synonym_type) + if len(spv.xrefs) != 0: + xrefs = [] + for xref in spv.xrefs: + parsed = parse_synonym_xref(xref) + if parsed is not None: + xrefs.append(parsed) + xrefs = list(xrefs) if len(xrefs) != 0 else None # shrink to fit + else: + xrefs = None + + return Synonym(name=spv.val, synonym_category=synonym_category, synonym_type=synonym_type, xrefs=xrefs) + + +def parse_synonym_category(synonym_category: str) -> typing.Optional[SynonymCategory]: + if synonym_category == 'hasRelatedSynonym': + return SynonymCategory.RELATED + elif synonym_category == 'hasExactSynonym': + return SynonymCategory.EXACT + elif synonym_category == 'hasBroadSynonym': + return SynonymCategory.BROAD + elif synonym_category == 'hasNarrowSynonym': + return SynonymCategory.NARROW + else: + logger.debug(f"Unknown synonym category {synonym_category}") + return None + + +def parse_synonym_type(synonym_type: str) -> typing.Optional[SynonymType]: + if synonym_type is None or len(synonym_type) == 0: + return None + hp_obo_matcher = OBO_PURL_PT.match(synonym_type) + if hp_obo_matcher: + value = hp_obo_matcher.group('value') + hp_matcher = HP_VAL_PT.match(value) + if hp_matcher: + value = hp_matcher.group('value') + if value in ('layperson', 'layperson term'): + return SynonymType.LAYPERSON_TERM + elif value == 'abbreviation': + return SynonymType.ABBREVIATION + elif value == 'uk_spelling': + return SynonymType.UK_SPELLING + elif value == 'obsolete_synonym': + return SynonymType.OBSOLETE_SYNONYM + elif value == 'plural_form': + return SynonymType.PLURAL_FORM + else: + if value in ('HP_0034334', 'allelic_requirement'): + return SynonymType.ALLELIC_REQUIREMENT + + logger.debug(f"Unknown synonym type {synonym_type}") + return None + + +def parse_synonym_xref(xref) -> typing.Optional[TermId]: + orcid_matcher = ORCID_PT.match(xref) + if orcid_matcher: + return TermId.from_curie(f'ORCID:{orcid_matcher.group("orcid")}') + else: + try: + # TODO: this can contain many things. Investigate.. + return TermId.from_curie(xref) + except ValueError: + logger.debug(f'Unable to create a synonym xref from {xref}') + return None + + +def create_xrefs(meta: Meta) -> typing.Optional[typing.List[TermId]]: + if len(meta.xrefs) == 0: + return None + else: + # TODO: Expecting that all xrefs are CURIES may be a bit too naive. Investigate.. + return [TermId.from_curie(xref.val) for xref in meta.xrefs] + + class ObographsTermFactory(typing.Generic[MINIMAL_TERM], metaclass=abc.ABCMeta): """ Term factory turns `TermId` and obographs `Node` into an ontology term. @@ -49,9 +141,13 @@ def create_term(self, term_id: TermId, node: Node) -> typing.Optional[Term]: definition = node.meta.definition.val if node.meta.definition else None comment = ', '.join(node.meta.comments) if len(node.meta.comments) > 0 else None alt_term_ids = create_alt_term_ids(node) + synonyms = create_synonyms(node.meta) + xrefs = create_xrefs(node.meta) return Term.create_term(term_id, name=node.lbl, alt_term_ids=alt_term_ids, - is_obsolete=node.meta.is_deprecated, definition=definition, comment=comment) + is_obsolete=node.meta.is_deprecated, definition=definition, comment=comment, + synonyms=synonyms, xrefs=xrefs) else: - return Term.create_term(term_id, name=node.lbl, alt_term_ids=[], is_obsolete=False, definition=None, - comment=None) + return Term.create_term(term_id, name=node.lbl, alt_term_ids=[], + is_obsolete=False, definition=None, comment=None, + synonyms=None, xrefs=None) diff --git a/src/hpotk/ontology/load/obographs/_model.py b/src/hpotk/ontology/load/obographs/_model.py index 6ebc803..db81876 100644 --- a/src/hpotk/ontology/load/obographs/_model.py +++ b/src/hpotk/ontology/load/obographs/_model.py @@ -16,24 +16,30 @@ def create_property_value(cls, data: typing.Dict): if cls == PropertyValue: return PropertyValue(pred=get_attr_or_none(data, 'pred'), val=get_attr_or_none(data, 'val'), - xrefs=[xref for xref in data['xrefs']] if 'xrefs' in data else [], + xrefs=data['xrefs'] if 'xrefs' in data else [], meta=create_meta(data['meta']) if 'meta' in data else None) elif cls == DefinitionPropertyValue: return DefinitionPropertyValue(pred=get_attr_or_none(data, 'pred'), val=get_attr_or_none(data, 'val'), - xrefs=[xref for xref in data['xrefs']] if 'xrefs' in data else [], + xrefs=data['xrefs'] if 'xrefs' in data else [], meta=create_meta(data['meta']) if 'meta' in data else None) elif cls == BasicPropertyValue: return BasicPropertyValue(pred=get_attr_or_none(data, 'pred'), val=get_attr_or_none(data, 'val'), - xrefs=[xref for xref in data['xrefs']] if 'xrefs' in data else [], + xrefs=data['xrefs'] if 'xrefs' in data else [], meta=create_meta(data['meta']) if 'meta' in data else None) elif cls == XrefPropertyValue: return XrefPropertyValue(lbl=get_attr_or_none(data, 'lbl'), pred=get_attr_or_none(data, 'pred'), val=get_attr_or_none(data, 'val'), - xrefs=[xref for xref in data['xrefs']] if 'xrefs' in data else [], + xrefs=data['xrefs'] if 'xrefs' in data else [], meta=create_meta(data['meta']) if 'meta' in data else None) + elif cls == SynonymPropertyValue: + return SynonymPropertyValue(synonym_type=get_attr_or_none(data, 'synonymType'), + pred=get_attr_or_none(data, 'pred'), + val=get_attr_or_none(data, 'val'), + xrefs=data['xrefs'] if 'xrefs' in data else [], + meta=create_meta(data['meta']) if 'meta' in data else None) else: return None @@ -92,10 +98,6 @@ def __repr__(self): class XrefPropertyValue(PropertyValue): - @staticmethod - def create_xref_property_value(data: typing.Dict): - return - def __init__(self, lbl: typing.Optional[str], pred: typing.Optional[str], val: typing.Optional[str], @@ -115,14 +117,42 @@ def __repr__(self): return str(self) +class SynonymPropertyValue(PropertyValue): + + def __init__(self, synonym_type: typing.Optional[str], + pred: typing.Optional[str], + val: typing.Optional[str], + xrefs: typing.Sequence[str], + meta): + super().__init__(pred, val, xrefs, meta) + self._synonym_type = synonym_type + + @property + def synonym_type(self) -> typing.Optional[str]: + return self._synonym_type + + def __str__(self): + return f'SynonymPropertyValue(' \ + f'synonym_type={self.synonym_type},' \ + f' pred={self.pred},' \ + f' val={self.val},' \ + f' xrefs={self.xrefs},' \ + f' meta={self.meta})' + + def __repr__(self): + return str(self) + + class Meta: def __init__(self, definition: typing.Optional[DefinitionPropertyValue], + synonyms: typing.Sequence[SynonymPropertyValue], comments: typing.Sequence[str], basic_property_values: typing.Sequence[BasicPropertyValue], xrefs: typing.Sequence[XrefPropertyValue], is_deprecated: bool): self._definition = definition + self._synonyms = synonyms self._comments = comments self._property_values = basic_property_values self._xrefs = xrefs @@ -132,6 +162,10 @@ def __init__(self, definition: typing.Optional[DefinitionPropertyValue], def definition(self) -> typing.Optional[DefinitionPropertyValue]: return self._definition + @property + def synonyms(self) -> typing.Sequence[SynonymPropertyValue]: + return self._synonyms + @property def comments(self) -> typing.Sequence[str]: return self._comments @@ -149,7 +183,12 @@ def is_deprecated(self) -> bool: return self._is_deprecated def __str__(self): - return f'Meta(definition={self.definition}, comments={self.comments}, basic_property_values={self.basic_property_values}, xrefs={self.xrefs}, is_deprecated={self.is_deprecated})' + return f'Meta(definition={self.definition},' \ + f' synonyms={self.synonyms},' \ + f' comments={self.comments},' \ + f' basic_property_values={self.basic_property_values},' \ + f' xrefs={self.xrefs},' \ + f' is_deprecated={self.is_deprecated})' def __repr__(self): return str(self) @@ -240,10 +279,11 @@ def create_meta(data) -> typing.Optional[Meta]: comments = data['comments'] if 'comments' in data else [] basic_property_values = [create_property_value(BasicPropertyValue, d) for d in data['basicPropertyValues']] if 'basicPropertyValues' in data else [] + synonyms = [create_property_value(SynonymPropertyValue, x) for x in data['synonyms']] if 'synonyms' in data else [] xrefs = [create_property_value(XrefPropertyValue, x) for x in data['xrefs']] if 'xrefs' in data else [] is_deprecated = 'deprecated' in data - return Meta(definition, comments, basic_property_values, xrefs, is_deprecated) + return Meta(definition, synonyms, comments, basic_property_values, xrefs, is_deprecated) def create_node(data) -> typing.Optional[Node]: diff --git a/tests/test_obographs.py b/tests/test_obographs.py index 41fc10b..ac74584 100644 --- a/tests/test_obographs.py +++ b/tests/test_obographs.py @@ -82,3 +82,61 @@ def _get_current_nodes(nodes): else: result.append(node) return result + + +class TestTerms(unittest.TestCase): + """ + We only load the ontology once, and we test the properties of the loaded data. + """ + + ONTOLOGY: hp.ontology.Ontology = None + + @classmethod + def setUpClass(cls) -> None: + cls.ONTOLOGY = load_ontology(TOY_HPO) + + def test_term_properties(self): + # Test properties of a Term + term = TestTerms.ONTOLOGY.get_term('HP:0001626') + + self.assertEqual(term.identifier.value, 'HP:0001626') + self.assertEqual(term.name, 'Abnormality of the cardiovascular system') + self.assertEqual(term.definition, 'Any abnormality of the cardiovascular system.') + self.assertEqual(term.comment, 'The cardiovascular system consists of the heart, vasculature, and the ' + 'lymphatic system.') + self.assertEqual(term.is_obsolete, False) + self.assertListEqual(term.alt_term_ids, [TermId.from_curie('HP:0003116')]) + + synonyms = term.synonyms + self.assertEqual(len(synonyms), 3) + + one = synonyms[0] + self.assertEqual(one.name, 'Cardiovascular disease') + self.assertEqual(one.synonym_category, hp.model.SynonymCategory.RELATED) + self.assertEqual(one.synonym_type, hp.model.SynonymType.LAYPERSON_TERM) + self.assertIsNone(one.xrefs) + + two = synonyms[1] + self.assertEqual(two.name, 'Cardiovascular abnormality') + self.assertEqual(two.synonym_category, hp.model.SynonymCategory.EXACT) + self.assertEqual(two.synonym_type, hp.model.SynonymType.LAYPERSON_TERM) + self.assertIsNone(two.xrefs) + + three = synonyms[2] + self.assertEqual(three.name, 'Abnormality of the cardiovascular system') + self.assertEqual(three.synonym_category, hp.model.SynonymCategory.EXACT) + self.assertEqual(three.synonym_type, hp.model.SynonymType.LAYPERSON_TERM) + self.assertIsNone(three.xrefs) + + self.assertEqual(term.xrefs, [TermId.from_curie(curie) for curie in ('UMLS:C0243050', 'UMLS:C0007222', + 'MSH:D018376', 'SNOMEDCT_US:49601007', + 'MSH:D002318')]) + + def test_synonym_properties(self): + term = TestTerms.ONTOLOGY.get_term('HP:0001627') + synonym = term.synonyms[7] + self.assertEqual(synonym.name, 'Abnormally shaped heart') + self.assertEqual(synonym.synonym_category, hp.model.SynonymCategory.EXACT) + self.assertEqual(synonym.synonym_type, hp.model.SynonymType.LAYPERSON_TERM) + self.assertEqual(synonym.xrefs, [TermId.from_curie('ORCID:0000-0001-5208-3432')]) + diff --git a/tests/test_term.py b/tests/test_term.py index be8b706..3472a11 100644 --- a/tests/test_term.py +++ b/tests/test_term.py @@ -32,19 +32,22 @@ class TestTerm(unittest.TestCase): def test_equal_terms_are_equal(self): one_id = TermId.from_curie("HP:1234567") + one_syn = Synonym('F', SynonymCategory.EXACT, None, [TermId.from_curie('ORCID:1234-5678-1234-5678')]) one = Term.create_term(identifier=one_id, name="First", alt_term_ids=[TermId.from_curie("HP:1111111")], is_obsolete=False, definition="First term definition", - comment="First comment") + comment="First comment", + synonyms=[one_syn], xrefs=[TermId.from_curie("SNOMED_CT:123456")]) two_id = TermId.from_curie("HP:1234567") two = Term.create_term(identifier=two_id, name="First", alt_term_ids=[TermId.from_curie("HP:1111111")], is_obsolete=False, definition="First term definition", - comment="First comment") + comment="First comment", + synonyms=[one_syn], xrefs=[TermId.from_curie("SNOMED_CT:123456")]) self.assertEqual(one, two) def test_not_equal_terms_are_not_equal(self): @@ -54,12 +57,34 @@ def test_not_equal_terms_are_not_equal(self): alt_term_ids=[TermId.from_curie("HP:2222222")], is_obsolete=False, definition="First term definition", - comment="Other comment") + comment="Other comment", + synonyms=None, + xrefs=None) two_id = TermId.from_curie("HP:1111111") two = Term.create_term(identifier=two_id, name="First", alt_term_ids=[TermId.from_curie("HP:2222222")], is_obsolete=False, definition="First term definition", - comment="First comment") + comment="First comment", + synonyms=None, + xrefs=None) self.assertNotEqual(one, two) + + def test_current_obsolete_synonyms(self): + current_one = Synonym('A', SynonymCategory.EXACT, SynonymType.LAYPERSON_TERM, None) + current_two = Synonym('B', SynonymCategory.EXACT, SynonymType.LAYPERSON_TERM, None) + obsolete_two = Synonym('C', SynonymCategory.EXACT, SynonymType.OBSOLETE_SYNONYM, None) + term = Term.create_term(identifier=TermId.from_curie('HP:1111111'), + name="First", + alt_term_ids=[TermId.from_curie("HP:2222222")], + is_obsolete=False, + definition="First term definition", + comment="Other comment", + synonyms=[current_one, current_two, obsolete_two], + xrefs=None) + + current = list(map(lambda s: s.name, term.current_synonyms())) + self.assertEqual(current, ['A', 'B']) + obsolete = list(map(lambda s: s.name, term.obsolete_synonyms())) + self.assertEqual(obsolete, ['C']) From 7d39b0de36ed1d75e1fdec389a73d70a56c3ec5a Mon Sep 17 00:00:00 2001 From: Daniel Danis Date: Fri, 14 Apr 2023 09:00:42 -0400 Subject: [PATCH 3/8] Use `typing.Sequence` instead of `typing.List` is more appropriate since the `Term`s should be immutable. --- src/hpotk/model/_term.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/hpotk/model/_term.py b/src/hpotk/model/_term.py index 1956efe..9285716 100644 --- a/src/hpotk/model/_term.py +++ b/src/hpotk/model/_term.py @@ -102,7 +102,7 @@ class Synonym(Named): def __init__(self, name: str, synonym_category: typing.Optional[SynonymCategory], synonym_type: typing.Optional[SynonymType], - xrefs: typing.Optional[typing.List[TermId]] = None): + xrefs: typing.Optional[typing.Sequence[TermId]] = None): self._name = name self._scat = synonym_category self._stype = synonym_type @@ -127,7 +127,7 @@ def synonym_type(self) -> typing.Optional[SynonymType]: return self._stype @property - def xrefs(self) -> typing.Optional[typing.List[TermId]]: + def xrefs(self) -> typing.Optional[typing.Sequence[TermId]]: return self._xrefs def __eq__(self, other): @@ -162,8 +162,8 @@ def create_term(identifier: TermId, is_obsolete: typing.Optional[bool], definition: typing.Optional[str], comment: typing.Optional[str], - synonyms: typing.Optional[typing.List[Synonym]], - xrefs: typing.Optional[typing.List[TermId]]): + synonyms: typing.Optional[typing.Sequence[Synonym]], + xrefs: typing.Optional[typing.Sequence[TermId]]): return DefaultTerm(identifier, name, alt_term_ids, is_obsolete, definition, comment, synonyms, xrefs) @property @@ -184,7 +184,7 @@ def comment(self) -> str: @property @abc.abstractmethod - def synonyms(self) -> typing.Optional[typing.List[Synonym]]: + def synonyms(self) -> typing.Optional[typing.Sequence[Synonym]]: """ :return: all synonyms (including obsolete) of the term. """ @@ -216,7 +216,7 @@ def _synonyms_iter(self, filter_f): @property @abc.abstractmethod - def xrefs(self) -> typing.Optional[typing.List[TermId]]: + def xrefs(self) -> typing.Optional[typing.Sequence[TermId]]: """ :return: term's cross-references """ @@ -285,8 +285,8 @@ def __init__(self, identifier: TermId, is_obsolete: typing.Optional[bool], definition: typing.Optional[str], comment: typing.Optional[str], - synonyms: typing.Optional[typing.List[Synonym]], - xrefs: typing.Optional[typing.List[TermId]]): + synonyms: typing.Optional[typing.Sequence[Synonym]], + xrefs: typing.Optional[typing.Sequence[TermId]]): self._id = identifier self._name = name self._alt_term_ids = alt_term_ids @@ -321,11 +321,11 @@ def comment(self) -> str: return self._comment @property - def synonyms(self) -> typing.Optional[typing.List[Synonym]]: + def synonyms(self) -> typing.Optional[typing.Sequence[Synonym]]: return self._synonyms @property - def xrefs(self) -> typing.Optional[typing.List[TermId]]: + def xrefs(self) -> typing.Optional[typing.Sequence[TermId]]: return self._xrefs def __repr__(self): From ea37e7723ec1919cfa6c78ae6e3a00fb7a5efe09 Mon Sep 17 00:00:00 2001 From: Daniel Danis Date: Fri, 14 Apr 2023 09:03:15 -0400 Subject: [PATCH 4/8] Rename `synonym_category` to `category`. --- src/hpotk/model/_term.py | 6 +++--- tests/test_obographs.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/hpotk/model/_term.py b/src/hpotk/model/_term.py index 9285716..649ac4f 100644 --- a/src/hpotk/model/_term.py +++ b/src/hpotk/model/_term.py @@ -113,7 +113,7 @@ def name(self) -> str: return self._name @property - def synonym_category(self) -> typing.Optional[SynonymCategory]: + def category(self) -> typing.Optional[SynonymCategory]: """ :return: an instance of :class:`SynonymCategory` or ``None``. """ @@ -133,14 +133,14 @@ def xrefs(self) -> typing.Optional[typing.Sequence[TermId]]: def __eq__(self, other): isinstance(other, Synonym) \ and self.name == other.name \ - and self.synonym_category == other.synonym_category \ + and self.category == other.category \ and self.synonym_type == other.synonym_type \ and self.xrefs == other.xrefs def __str__(self): return f'Synonym(' \ f'name="{self.name}", ' \ - f'synonym_category={self.synonym_category}, ' \ + f'category={self.category}, ' \ f'synonym_type={self.synonym_type}, ' \ f'xrefs="{self.xrefs}")' diff --git a/tests/test_obographs.py b/tests/test_obographs.py index ac74584..746ed75 100644 --- a/tests/test_obographs.py +++ b/tests/test_obographs.py @@ -112,19 +112,19 @@ def test_term_properties(self): one = synonyms[0] self.assertEqual(one.name, 'Cardiovascular disease') - self.assertEqual(one.synonym_category, hp.model.SynonymCategory.RELATED) + self.assertEqual(one.category, hp.model.SynonymCategory.RELATED) self.assertEqual(one.synonym_type, hp.model.SynonymType.LAYPERSON_TERM) self.assertIsNone(one.xrefs) two = synonyms[1] self.assertEqual(two.name, 'Cardiovascular abnormality') - self.assertEqual(two.synonym_category, hp.model.SynonymCategory.EXACT) + self.assertEqual(two.category, hp.model.SynonymCategory.EXACT) self.assertEqual(two.synonym_type, hp.model.SynonymType.LAYPERSON_TERM) self.assertIsNone(two.xrefs) three = synonyms[2] self.assertEqual(three.name, 'Abnormality of the cardiovascular system') - self.assertEqual(three.synonym_category, hp.model.SynonymCategory.EXACT) + self.assertEqual(three.category, hp.model.SynonymCategory.EXACT) self.assertEqual(three.synonym_type, hp.model.SynonymType.LAYPERSON_TERM) self.assertIsNone(three.xrefs) @@ -136,7 +136,7 @@ def test_synonym_properties(self): term = TestTerms.ONTOLOGY.get_term('HP:0001627') synonym = term.synonyms[7] self.assertEqual(synonym.name, 'Abnormally shaped heart') - self.assertEqual(synonym.synonym_category, hp.model.SynonymCategory.EXACT) + self.assertEqual(synonym.category, hp.model.SynonymCategory.EXACT) self.assertEqual(synonym.synonym_type, hp.model.SynonymType.LAYPERSON_TERM) self.assertEqual(synonym.xrefs, [TermId.from_curie('ORCID:0000-0001-5208-3432')]) From f2b011386f2e71c44b49c8c538aa928e55ad2277 Mon Sep 17 00:00:00 2001 From: Daniel Danis Date: Fri, 14 Apr 2023 09:47:36 -0400 Subject: [PATCH 5/8] Update `SimpleHpoaDiseaseLoader` to parse the novel HPO annotation file. --- src/hpotk/annotations/load/hpoa/_impl.py | 19 +++++--- tests/annotations/__init__.py | 0 tests/annotations/load/__init__.py | 0 tests/annotations/load/test_hpoa.py | 44 ++++++++++++++----- tests/data/phenotype.fake.novel.hpoa | 16 +++++++ ...pe.fake.hpoa => phenotype.fake.older.hpoa} | 8 ++-- 6 files changed, 66 insertions(+), 21 deletions(-) create mode 100644 tests/annotations/__init__.py create mode 100644 tests/annotations/load/__init__.py create mode 100644 tests/data/phenotype.fake.novel.hpoa rename tests/data/{phenotype.fake.hpoa => phenotype.fake.older.hpoa} (86%) diff --git a/src/hpotk/annotations/load/hpoa/_impl.py b/src/hpotk/annotations/load/hpoa/_impl.py index aec3f24..028947c 100644 --- a/src/hpotk/annotations/load/hpoa/_impl.py +++ b/src/hpotk/annotations/load/hpoa/_impl.py @@ -42,13 +42,22 @@ def __init__(self, hpo: MinimalOntology, def load(self, file: typing.Union[typing.IO, str]) -> HpoDiseases: data = defaultdict(list) version = None + expecting_to_see_header_line = True with open_text_io_handle(file) as fh: for line in fh: - if line.startswith('#'): - # header - version_matcher = HPOA_VERSION_PATTERN.match(line) - if version_matcher: - version = version_matcher.group('version') + if expecting_to_see_header_line: + if line.startswith('#'): + # header + if line.startswith('#DatabaseID'): + # The older HPOA format + expecting_to_see_header_line = False + else: + version_matcher = HPOA_VERSION_PATTERN.match(line) + if version_matcher: + version = version_matcher.group('version') + else: + if line.startswith('database_id'): + expecting_to_see_header_line = False continue else: # corpus diff --git a/tests/annotations/__init__.py b/tests/annotations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/annotations/load/__init__.py b/tests/annotations/load/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/annotations/load/test_hpoa.py b/tests/annotations/load/test_hpoa.py index b9f3952..23da78f 100644 --- a/tests/annotations/load/test_hpoa.py +++ b/tests/annotations/load/test_hpoa.py @@ -6,32 +6,56 @@ from pkg_resources import resource_filename TOY_HPO = resource_filename(__name__, os.path.join('../../data', 'hp.toy.json')) -TOY_HPOA = resource_filename(__name__, os.path.join('../../data', 'phenotype.fake.hpoa')) +TOY_HPOA_OLDER = resource_filename(__name__, os.path.join('../../data', 'phenotype.fake.older.hpoa')) +TOY_HPOA = resource_filename(__name__, os.path.join('../../data', 'phenotype.fake.novel.hpoa')) -class TestHpoaLoader(unittest.TestCase): - +class TestHpoaLoaderBase(unittest.TestCase): HPO: hpotk.ontology.MinimalOntology @classmethod def setUpClass(cls) -> None: cls.HPO = hpotk.ontology.load.obographs.load_minimal_ontology(TOY_HPO) + +class TestHpoaLoader(TestHpoaLoaderBase): + def setUp(self) -> None: - self.loader = hpotk.annotations.load.hpoa.SimpleHpoaDiseaseLoader(self.HPO) + self.loader = hpotk.annotations.load.hpoa.SimpleHpoaDiseaseLoader(TestHpoaLoaderBase.HPO) - def test_toy_phenotypic_features(self): + def test_load_hpo_annotations(self): diseases = self.loader.load(TOY_HPOA) self.assertIsInstance(diseases, hpotk.annotations.HpoDiseases) self.assertEqual(2, len(diseases)) self.assertSetEqual({'ORPHA:123456', 'OMIM:987654'}, set(map(lambda d: d.value, diseases.disease_ids))) + self.assertEqual(diseases.version, '2021-08-02') + + def test_load_older_hpo_annotations(self): + diseases = self.loader.load(TOY_HPOA_OLDER) + self.assertIsInstance(diseases, hpotk.annotations.HpoDiseases) + + self.assertEqual(2, len(diseases)) + self.assertSetEqual({'ORPHA:123456', 'OMIM:987654'}, set(map(lambda d: d.value, diseases.disease_ids))) + + +class TestHpoaDiseaseProperties(TestHpoaLoaderBase): + LOADER: hpotk.annotations.load.hpoa.SimpleHpoaDiseaseLoader + HPO_DISEASES: hpotk.annotations.HpoDiseases + + @classmethod + def setUpClass(cls) -> None: + TestHpoaLoaderBase.setUpClass() + cls.LOADER = hpotk.annotations.load.hpoa.SimpleHpoaDiseaseLoader(TestHpoaLoaderBase.HPO) + cls.HPO_DISEASES = cls.LOADER.load(TOY_HPOA) - omim = diseases['OMIM:987654'] + def test_hpoa_disease_properties(self): + omim = TestHpoaDiseaseProperties.HPO_DISEASES['OMIM:987654'] self.assertEqual('Made-up OMIM disease, autosomal recessive', omim.name) self.assertEqual(2, len(omim.annotations)) - omim_annotations: typing.List[hpotk.annotations.HpoDiseaseAnnotation] = list(sorted(omim.annotations, key=lambda a: a.identifier.value)) + omim_annotations: typing.List[hpotk.annotations.HpoDiseaseAnnotation] = list( + sorted(omim.annotations, key=lambda a: a.identifier.value)) first = omim_annotations[0] self.assertEqual(first.identifier.value, 'HP:0001167') self.assertEqual(first.is_present(), True) @@ -44,9 +68,5 @@ def test_toy_phenotypic_features(self): self.assertEqual(second.identifier.value, 'HP:0001238') self.assertEqual(second.is_absent(), True) self.assertEqual(second.ratio.numerator, 0) - self.assertEqual(second.ratio.denominator, self.loader.cohort_size) + self.assertEqual(second.ratio.denominator, TestHpoaDiseaseProperties.LOADER.cohort_size) self.assertEqual(len(second.references), 1) - - def test_disease_data_has_version(self): - diseases: hpotk.annotations.HpoDiseases = self.loader.load(TOY_HPOA) - self.assertEqual(diseases.version, '2021-08-02') diff --git a/tests/data/phenotype.fake.novel.hpoa b/tests/data/phenotype.fake.novel.hpoa new file mode 100644 index 0000000..acc2dc7 --- /dev/null +++ b/tests/data/phenotype.fake.novel.hpoa @@ -0,0 +1,16 @@ +#description: Fake HPO annotation file for testing `HpoDiseaseLoader` implementations with descriptions in line. +#date: 2021-08-02 +#tracker: https://github.com/obophenotype/human-phenotype-ontology +#HPO-version: http://purl.obolibrary.org/obo/hp.obo/hp/releases/2021-08-02/hp.obo.owl +# ORPHA:123456 is a made up AD disease with Abnormality of finger (HP:0001167) and NOT Slender finger (HP:0001238). +# OMIM:987654 is a made up AR disease with Abnormality of finger (HP:0001167) and NOT Slender finger (HP:0001238). +# Abnormality of finger is observed in 1/8 with childhood onset and in 4/5 with pediatric onset. +# Slender finger is occasionally (HP:0040283) absent. +database_id disease_name qualifier hpo_id reference evidence onset frequency sex modifier aspect biocuration +ORPHA:123456 Made-up Orphanet disease, autosomal dominant HP:0001167 ORPHA:166035 TAS HP:0003581 HP:0040283 P ORPHA:orphadata[2021-08-02] +ORPHA:123456 Made-up Orphanet disease, autosomal dominant NOT HP:0001238 ORPHA:166035 TAS P ORPHA:orphadata[2021-08-02] +ORPHA:123456 Made-up Orphanet disease, autosomal dominant HP:0000006 ORPHA:166035 TAS HP:0040280 I ORPHA:orphadata[2021-08-02] +OMIM:987654 Made-up OMIM disease, autosomal recessive HP:0001167 PMID:20375004 PCS HP:0011463 1/8 P HPO:probinson[2021-05-26] +OMIM:987654 Made-up OMIM disease, autosomal recessive HP:0001167 PMID:22736615 PCS HP:0410280 4/5 HP:0012832;HP:0012828 P HPO:probinson[2021-05-26] +OMIM:987654 Made-up OMIM disease, autosomal recessive NOT HP:0001238 PMID:20375004 PCS HP:0040283 P HPO:skoehler[2010-06-20];HPO:probinson[2021-05-26] +OMIM:987654 Made-up OMIM disease, autosomal recessive HP:0000007 PMID:20375004 PCS I HPO:iea[2009-02-17];HPO:probinson[2021-05-26] diff --git a/tests/data/phenotype.fake.hpoa b/tests/data/phenotype.fake.older.hpoa similarity index 86% rename from tests/data/phenotype.fake.hpoa rename to tests/data/phenotype.fake.older.hpoa index d18b4e0..39f1f46 100644 --- a/tests/data/phenotype.fake.hpoa +++ b/tests/data/phenotype.fake.older.hpoa @@ -2,14 +2,14 @@ #date: 2021-08-02 #tracker: https://github.com/obophenotype/human-phenotype-ontology #HPO-version: http://purl.obolibrary.org/obo/hp.obo/hp/releases/2021-08-02/hp.obo.owl +# ORPHA:123456 is a made up AD disease with Abnormality of finger (HP:0001167) and NOT Slender finger (HP:0001238). +# OMIM:987654 is a made up AR disease with Abnormality of finger (HP:0001167) and NOT Slender finger (HP:0001238). +# Abnormality of finger is observed in 1/8 with childhood onset and in 4/5 with pediatric onset. +# Slender finger is occasionally (HP:0040283) absent. #DatabaseID DiseaseName Qualifier HPO_ID Reference Evidence Onset Frequency Sex Modifier Aspect Biocuration -# Made up AD disease with Abnormality of finger (HP:0001167) and NOT Slender finger (HP:0001238). ORPHA:123456 Made-up Orphanet disease, autosomal dominant HP:0001167 ORPHA:166035 TAS HP:0003581 HP:0040283 P ORPHA:orphadata[2021-08-02] ORPHA:123456 Made-up Orphanet disease, autosomal dominant NOT HP:0001238 ORPHA:166035 TAS P ORPHA:orphadata[2021-08-02] ORPHA:123456 Made-up Orphanet disease, autosomal dominant HP:0000006 ORPHA:166035 TAS HP:0040280 I ORPHA:orphadata[2021-08-02] -# Made up AR disease with Abnormality of finger (HP:0001167) and NOT Slender finger (HP:0001238). -# Abnormality of finger is observed in 1/8 with childhood onset and in 4/5 with pediatric onset. -# Slender finger is occasionally (HP:0040283) absent. OMIM:987654 Made-up OMIM disease, autosomal recessive HP:0001167 PMID:20375004 PCS HP:0011463 1/8 P HPO:probinson[2021-05-26] OMIM:987654 Made-up OMIM disease, autosomal recessive HP:0001167 PMID:22736615 PCS HP:0410280 4/5 HP:0012832;HP:0012828 P HPO:probinson[2021-05-26] OMIM:987654 Made-up OMIM disease, autosomal recessive NOT HP:0001238 PMID:20375004 PCS HP:0040283 P HPO:skoehler[2010-06-20];HPO:probinson[2021-05-26] From a88da517402de9e69ec0546ef2448458543cb2f8 Mon Sep 17 00:00:00 2001 From: Daniel Danis Date: Fri, 14 Apr 2023 09:55:44 -0400 Subject: [PATCH 6/8] Update the tutorial notebook. --- notebooks/Tutorial.ipynb | 131 +++++++++++++++------------------------ 1 file changed, 51 insertions(+), 80 deletions(-) diff --git a/notebooks/Tutorial.ipynb b/notebooks/Tutorial.ipynb index e28e352..18bf723 100644 --- a/notebooks/Tutorial.ipynb +++ b/notebooks/Tutorial.ipynb @@ -27,7 +27,9 @@ { "cell_type": "markdown", "id": "6e41fb89-d16e-427f-8e53-ec92710af8c8", - "metadata": {}, + "metadata": { + "tags": [] + }, "source": [ "# Load HPO\n", "\n", @@ -49,7 +51,7 @@ "from hpotk.ontology import Ontology\n", "from hpotk.ontology.load.obographs import load_ontology\n", "\n", - "o: Ontology = load_ontology('https://raw.githubusercontent.com/obophenotype/human-phenotype-ontology/master/hp.json')" + "o: Ontology = load_ontology('http://purl.obolibrary.org/obo/hp.json')" ] }, { @@ -65,7 +67,9 @@ { "cell_type": "markdown", "id": "a98ea987-741a-42fa-8ae1-5924d77647ff", - "metadata": {}, + "metadata": { + "tags": [] + }, "source": [ "# Ontology data model and functionality\n", "\n", @@ -77,8 +81,11 @@ " - `name`, `str` (e.g. `Arachnodactyly`)\n", " - `is_current`/`is_obsolete`, whether or not the concept has been obsoleted\n", " - `alt_term_ids`, a sequence of obsolete `TermId`s that represented the term previously\n", - "- `hpotk.model.Term` - the complete info regarding the ontology concept. The `Term` has all attributes of the `MinimalTerm` plus `definition` and `comment`\n", - "\n", + "- `hpotk.model.Term` - the complete info regarding the ontology concept. The `Term` has all attributes of the `MinimalTerm` plus the following:\n", + " - `definition` - an optional description of the term in slightly more verbiage\n", + " - `comment` - additional comment (optional)\n", + " - `synonyms` - alternative designations of the `Term` (optional)\n", + " - `xrefs` - a sequence of cross-references between the `Term` and concepts from different databases\n", "- `hpotk.ontology.MinimalOntology` - the container for ontology data that uses `MinimalTerm`s\n", "- `hpotk.ontology.Ontology` - the ontology data container that contains `Term`s\n", "- `hpotk.graph.OntologyGraph` - a specification of graph for storing the ontology concept hierarchy and the required graph functionality. As long as the graph implements the methods, it can work with the rest of the toolkit framework\n", @@ -132,7 +139,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Term(identifier=HP:0000001, name=\"All\", definition=None, comment=Root of all terms in the Human Phenotype Ontology., is_obsolete=False, alt_term_ids=\"[]\")\n" + "Term(identifier=HP:0000001, name=\"All\", definition=None, comment=Root of all terms in the Human Phenotype Ontology., synonyms=None, xrefs=[DefaultTermId(idx=4, value=UMLS:C0444868)], is_obsolete=False, alt_term_ids=\"[]\")\n" ] } ], @@ -157,7 +164,7 @@ { "data": { "text/plain": [ - "16874" + "17138" ] }, "execution_count": 4, @@ -252,7 +259,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Term(identifier=HP:0001166, name=\"Arachnodactyly\", definition=Abnormally long and slender fingers (\"spider fingers\")., comment=None, is_obsolete=False, alt_term_ids=\"[DefaultTermId(idx=2, value=HP:0001505)]\")\n" + "Term(identifier=HP:0001166, name=\"Arachnodactyly\", definition=Abnormally long and slender fingers (\"spider fingers\")., comment=None, synonyms=[Synonym(name=\"Long slender fingers\", category=SynonymCategory.EXACT, synonym_type=SynonymType.LAYPERSON_TERM, xrefs=\"None\"), Synonym(name=\"Long, slender fingers\", category=SynonymCategory.EXACT, synonym_type=None, xrefs=\"None\"), Synonym(name=\"Spider fingers\", category=SynonymCategory.EXACT, synonym_type=SynonymType.LAYPERSON_TERM, xrefs=\"None\")], xrefs=[DefaultTermId(idx=3, value=MSH:D054119), DefaultTermId(idx=11, value=SNOMEDCT_US:62250003), DefaultTermId(idx=4, value=UMLS:C0003706)], is_obsolete=False, alt_term_ids=\"[DefaultTermId(idx=2, value=HP:0001505)]\")\n" ] } ], @@ -261,42 +268,6 @@ "print(arachnodactyly)" ] }, - { - "cell_type": "markdown", - "id": "68f50a4f-bef6-4cfc-afbe-ebeb1dbdc298", - "metadata": {}, - "source": [ - "As described above, each term has:\n", - "- `identifier` - a `hpotk.model.TermId` corresponding to term's CURIE \n", - "- `name` - term's name (e.g. *\"Hypertension\"*)\n", - "- `alt_term_ids` - alternative term IDs - term ids of obsoleted terms that have been replaced by this term\n", - "- `is_obsolete` - obsoletion status" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "b9285dbf-4248-4fe1-821c-732dfe1c90f8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ID: HP:0001166\n", - "Name: Arachnodactyly\n", - "Alt ids: ['HP:0001505']\n", - "Is obsolete: False\n" - ] - } - ], - "source": [ - "print(f'ID: {arachnodactyly.identifier.value}')\n", - "print(f'Name: {arachnodactyly.name}')\n", - "print(f'Alt ids: {[str(at) for at in arachnodactyly.alt_term_ids]}')\n", - "print(f'Is obsolete: {arachnodactyly.is_obsolete}')" - ] - }, { "cell_type": "markdown", "id": "70d5ee3d-2329-4094-8b9a-1c09dde7147f", @@ -307,7 +278,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "7763a3f8-9487-4ccc-ac20-06333fe3fd37", "metadata": {}, "outputs": [], @@ -331,7 +302,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "id": "fd90ffa7-9cd4-4557-8c36-e7c1ba7b5c64", "metadata": {}, "outputs": [ @@ -344,21 +315,21 @@ "HP:0001238 - Slender finger\n", "\n", "#################### Ancestors ##################\n", - "HP:0000118 - Phenotypic abnormality\n", "HP:0000924 - Abnormality of the skeletal system\n", + "HP:0000001 - All\n", + "HP:0040064 - Abnormality of limbs\n", + "HP:0002817 - Abnormality of the upper limb\n", "HP:0040068 - Abnormality of limb bone\n", - "HP:0011842 - Abnormal skeletal morphology\n", - "HP:0011844 - Abnormal appendicular skeleton morphology\n", + "HP:0001167 - Abnormal finger morphology\n", + "HP:0033127 - Abnormality of the musculoskeletal system\n", "HP:0100807 - Long fingers\n", - "HP:0001167 - Abnormality of finger\n", - "HP:0000001 - All\n", + "HP:0011844 - Abnormal appendicular skeleton morphology\n", + "HP:0001238 - Slender finger\n", + "HP:0002813 - Abnormality of limb bone morphology\n", + "HP:0000118 - Phenotypic abnormality\n", "HP:0001155 - Abnormality of the hand\n", - "HP:0033127 - Abnormality of the musculoskeletal system\n", - "HP:0002817 - Abnormality of the upper limb\n", "HP:0011297 - Abnormal digit morphology\n", - "HP:0002813 - Abnormality of limb bone morphology\n", - "HP:0040064 - Abnormality of limbs\n", - "HP:0001238 - Slender finger\n" + "HP:0011842 - Abnormal skeletal morphology\n" ] } ], @@ -386,7 +357,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "id": "e74d9f8c-20af-4a1d-a8b2-81ec90708b30", "metadata": { "tags": [] @@ -397,8 +368,8 @@ "output_type": "stream", "text": [ "#################### Children ####################\n", - "HP:0001166 - Arachnodactyly\n", "HP:0001182 - Tapered finger\n", + "HP:0001166 - Arachnodactyly\n", "\n", "#################### Descendants #################\n", "HP:0006216 - Single interphalangeal crease of fifth finger\n", @@ -439,7 +410,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "id": "3b2f58cc-e008-4153-96b5-1abbd4db9e47", "metadata": {}, "outputs": [], @@ -487,7 +458,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "id": "0e668a54-c146-47be-921e-72ab29ee7d28", "metadata": {}, "outputs": [], @@ -531,7 +502,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "id": "b575229e-1c08-4d02-aaa3-012c9a8c80bd", "metadata": {}, "outputs": [], @@ -571,7 +542,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "id": "654026c8-0e98-45ee-b245-0fa72f8498c3", "metadata": {}, "outputs": [ @@ -616,7 +587,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "id": "215903d1-5c3f-4b0f-8fa0-486ef272f5a8", "metadata": {}, "outputs": [ @@ -645,7 +616,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "id": "0b1e1b30-e70e-4ec9-89c9-5c702d24ea25", "metadata": {}, "outputs": [ @@ -672,7 +643,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "id": "5c4bf517-3e4c-4444-9ada-8bdd9cdcbbbe", "metadata": { "tags": [] @@ -684,7 +655,7 @@ "HpoFrequency(identifier=HP:0040283, lower_bound=0.05, upper_bound=0.29)" ] }, - "execution_count": 19, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -705,7 +676,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "id": "03e7bc93-a58e-422e-a9e1-b04f40956dfb", "metadata": { "tags": [] @@ -728,7 +699,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "id": "7daaab3e-6ead-4230-88d5-b8c080b3730b", "metadata": { "tags": [] @@ -759,7 +730,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 21, "id": "d7bf2281-d8ed-4373-b094-ef7df0c60aa8", "metadata": { "tags": [] @@ -790,7 +761,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 22, "id": "2ddccd09-43d8-448d-8df1-f55ae8eeb658", "metadata": { "tags": [] @@ -821,7 +792,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 23, "id": "0c183a9c-802b-43a1-8bf9-b0e81e5a26f1", "metadata": { "tags": [] @@ -867,7 +838,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 24, "id": "9b5e5216-abde-4b8e-888b-fd12b43ce92e", "metadata": { "tags": [] @@ -876,10 +847,10 @@ { "data": { "text/plain": [ - "'Parsed 12606 diseases'" + "'Parsed 12429 diseases'" ] }, - "execution_count": 25, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -905,7 +876,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 25, "id": "b4303dc6-a653-4720-8a50-0600d655b679", "metadata": { "tags": [] @@ -915,7 +886,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "HpoDisease(identifier=OMIM:203400, name=Corticosterone methyloxidase type I deficiency, n_annotations=12)\n" + "HpoDisease(identifier=OMIM:619340, name=Developmental and epileptic encephalopathy 96, n_annotations=9)\n" ] } ], @@ -933,7 +904,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 26, "id": "45035d68-baad-4e69-ab51-5e9c9e0aad86", "metadata": { "tags": [] @@ -943,7 +914,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "OMIM:203400\n" + "OMIM:619340\n" ] } ], @@ -962,7 +933,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 27, "id": "e061b854-2218-4355-8a9f-d6ef9988f588", "metadata": { "tags": [] @@ -995,7 +966,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 28, "id": "2faad640-4765-49c5-9eba-535018782e5b", "metadata": { "tags": [] From d95cfb96a471130e2af367cf9909d0e1863acad4 Mon Sep 17 00:00:00 2001 From: Daniel Danis Date: Fri, 14 Apr 2023 10:04:49 -0400 Subject: [PATCH 7/8] Add `setUpClass` hook to `TestHpoaLoader`. --- tests/annotations/load/test_hpoa.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/annotations/load/test_hpoa.py b/tests/annotations/load/test_hpoa.py index 23da78f..ad92d46 100644 --- a/tests/annotations/load/test_hpoa.py +++ b/tests/annotations/load/test_hpoa.py @@ -20,6 +20,10 @@ def setUpClass(cls) -> None: class TestHpoaLoader(TestHpoaLoaderBase): + @classmethod + def setUpClass(cls) -> None: + TestHpoaLoaderBase.setUpClass() + def setUp(self) -> None: self.loader = hpotk.annotations.load.hpoa.SimpleHpoaDiseaseLoader(TestHpoaLoaderBase.HPO) From 9c1d10ed76c822db70b63da8fa3200abedaad717 Mon Sep 17 00:00:00 2001 From: Daniel Danis Date: Fri, 14 Apr 2023 12:11:23 -0400 Subject: [PATCH 8/8] Prepare release `v0.1.4` --- recipe/meta.yaml | 2 +- src/hpotk/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/recipe/meta.yaml b/recipe/meta.yaml index 317c345..022a181 100644 --- a/recipe/meta.yaml +++ b/recipe/meta.yaml @@ -1,5 +1,5 @@ {% set name = "hpo-toolkit" %} -{% set version = "0.1.4dev0" %} +{% set version = "0.1.4" %} package: name: {{ name|lower }} diff --git a/src/hpotk/__init__.py b/src/hpotk/__init__.py index bbbfdae..0330441 100644 --- a/src/hpotk/__init__.py +++ b/src/hpotk/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.4dev0" +__version__ = "0.1.4" from . import model from . import constants