Source code for hpotk.model._term

import hpotk
import abc
import enum
import typing

from ._base import Identified, Named
from ._term_id import TermId


[docs] class MinimalTerm(Identified, Named, metaclass=abc.ABCMeta): """ `MinimalTerm` is a data object with the minimal useful information about an ontology concept. Each term has: * `identifier` - a :class:`TermId` of the term (thanks to inheriting from :class:`Identified`) * `name` - a human-friendly name of the term (thanks to inheriting from :class:`Named`) * `alt_term_ids` - a sequence of *alternate identifiers* (IDs of obsolete terms that should be replaced by this term) * `is_obsolete` - the *obsoletion status* Most of the time, you should get terms from an :class:`hpotk.ontology.MinimalOntology`. However, `MinimalTerm` can also be created from scratch using :func:`create_minimal_term` if you must do that from whatever reason. """
[docs] @staticmethod def create_minimal_term( term_id: typing.Union[TermId, str], name: str, alt_term_ids: typing.Iterable[typing.Union[TermId, str]], is_obsolete: bool, ): """ Create `MinimalTerm` from the components. .. doctest:: >>> seizure = MinimalTerm.create_minimal_term(term_id='HP:0001250', name='Seizure', ... alt_term_ids=('HP:0002279', 'HP:0002391'), ... is_obsolete=False) :param term_id: a `TermId` or a CURIE `str` (e.g. 'HP:0001250'). :param name: term name (e.g. Seizure) . :param alt_term_ids: an iterable with term IDs that represent the alternative IDs of the term. :param is_obsolete: `True` if the `MinimalTerm` has been obsoleted, or `False` otherwise. :return: the created term. """ return DefaultMinimalTerm(term_id, name, alt_term_ids, is_obsolete)
@property @abc.abstractmethod def alt_term_ids(self) -> typing.Sequence[TermId]: """ Get a sequence of identifiers of the ontology concepts that were obsoleted and should be replaced by the concept represented by this `Term`. """ pass @property def is_current(self) -> bool: """ Return `True` if the term is current (*not* obsolete) and `False` otherwise. """ return not self.is_obsolete @property @abc.abstractmethod def is_obsolete(self) -> bool: """ Return `True` if the term is obsolete (*not* current) and `False` otherwise. """ pass def __eq__(self, other): return ( isinstance(other, MinimalTerm) and self.identifier == other.identifier and self.name == other.name and self.alt_term_ids == other.alt_term_ids and self.is_obsolete == other.is_obsolete ) def __str__(self): return ( f"MinimalTerm(" f'identifier="{self.identifier}", ' f'name="{self.name}", ' f"alt_term_ids={self.alt_term_ids}, " f"is_obsolete={self.is_obsolete})" )
[docs] class SynonymCategory(enum.Enum): """ An enumeration of the synonym categories. """ EXACT = enum.auto() RELATED = enum.auto() BROAD = enum.auto() NARROW = enum.auto()
[docs] class SynonymType(enum.Enum): """ An enumeration of the synonym types, 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()
[docs] def is_obsolete(self) -> bool: """ Returns `True` if the synonym is obsolete (not current) and `False` otherwise. """ return self == SynonymType.OBSOLETE_SYNONYM
[docs] def is_current(self) -> bool: """ Returns `True` if the synonym is current (not obsolete) and `False` otherwise. """ return not self.is_obsolete()
[docs] class Synonym(Named): """ `Synonym` represents the information regarding a synonym of an ontology concept. """ def __init__( self, name: str, synonym_category: typing.Optional[SynonymCategory] = None, synonym_type: typing.Optional[SynonymType] = None, xrefs: typing.Optional[typing.Sequence[TermId]] = None, ): self._name = name self._scat = synonym_category self._stype = synonym_type self._xrefs = xrefs @property def name(self) -> str: """ Get the name of the synonym """ return self._name @property def category(self) -> typing.Optional[SynonymCategory]: """ Get the synonym category - an instance of :class:`SynonymCategory` or ``None``. """ return self._scat @property def synonym_type(self) -> typing.Optional[SynonymType]: """ Get synonym type - an instance of :class:`SynonymType` or ``None``. """ return self._stype @property def xrefs(self) -> typing.Optional[typing.Sequence[TermId]]: """ Get a sequence of identifiers of the cross-references of the ontology concept or ``None`` if there are none. """ return self._xrefs def __eq__(self, other): return ( isinstance(other, Synonym) and self.name == other.name 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"category={self.category}, " f"synonym_type={self.synonym_type}, " f'xrefs="{self.xrefs}")' ) def __repr__(self): return str(self)
[docs] class Definition: """ `Definition` includes a definition and the cross-references. :param definition: a definition, e.g. *Abnormally long and slender fingers ("spider fingers").* for *Arachnodactyly*. :param xrefs: an iterable with definition cross-references, e.g. `('https://orcid.org/0000-0002-0736-9199',)` for *Arachnodactyly*. """ def __init__( self, definition: str, xrefs: typing.Iterable[str], ): self._definition = hpotk.util.validate_instance(definition, str, "definition") self._xrefs = tuple(xrefs) @property def definition(self) -> str: """ Get a `str` with the term definition. For instance, *Abnormally long and slender fingers ("spider fingers").* for *Arachnodactyly*. """ return self._definition @property def xrefs(self) -> typing.Sequence[str]: """ Get definition of cross-references of a definition. For instance, `('https://orcid.org/0000-0002-0736-9199',)` for *Arachnodactyly*. """ return self._xrefs def __eq__(self, other): return isinstance(other, Definition) and self.definition == other.definition and self.xrefs == other.xrefs def __str__(self): return f'Definition(definition="{self.definition}", xrefs={self.xrefs}")' def __repr__(self): return str(self)
[docs] class Term(MinimalTerm, metaclass=abc.ABCMeta): """ A comprehensive representation of an ontology concept. `Term` has all attributes of the :class:`MinimalTerm` plus the following: * `definition` - an optional definition of the term, including a comprehensive description and cross-references * `comment` - an optional comment * `synonyms` - an optional sequence of term synonyms * `cross-references` - an optional sequence of cross-references Most of the time, you should be getting terms from :class:`hpotk.ontology.Ontology`. However, if you absolutely must craft a term or two by hand, use :func:`create_term` function. """ # TODO - add the remaining attributes from phenol's Term?
[docs] @staticmethod def create_term( identifier: typing.Union[TermId, str], name: str, alt_term_ids: typing.Iterable[typing.Union[TermId, str]], is_obsolete: bool, definition: typing.Optional[typing.Union[Definition, str]], comment: typing.Optional[str], synonyms: typing.Optional[typing.Iterable[Synonym]], xrefs: typing.Optional[typing.Iterable[TermId]], ): """ Create a `MinimalTerm` from the components. :param identifier: a `TermId` or a CURIE (e.g. 'HP:0001250'). :param name: term name (e.g. Seizure). :param alt_term_ids: an iterable with term IDs that represent the alternative IDs of the term. :param is_obsolete: `True` if the `MinimalTerm` has been obsoleted, or `False` otherwise. :param definition: an optional `str` with a definition of the term or a :class:`Definition` with the full info. :param comment: an optional comment of the term. :param synonyms: an optional iterable with all synonyms of the term. :param xrefs: an optional iterable with all the cross-references. :return: the created term. """ if isinstance(definition, str): definition = Definition(definition, ()) return DefaultTerm( identifier, name, alt_term_ids, is_obsolete, definition, comment, synonyms, xrefs, )
@property @abc.abstractmethod def definition(self) -> typing.Optional[Definition]: """ Get the definition of the ontology concept. """ pass @property @abc.abstractmethod def comment(self) -> typing.Optional[str]: """ Get the comment string of the ontology concept. """ pass @property @abc.abstractmethod def synonyms(self) -> typing.Optional[typing.Sequence[Synonym]]: """ Get a sequence of all synonyms (including obsolete) of the ontology concept or `None` if the concept has no synonyms. """ pass
[docs] def current_synonyms(self) -> typing.Iterable[Synonym]: """ Get an iterable with *current* synonyms of the ontology concept. The iterable is empty if the concept has no current synonyms. """ return self._synonyms_iter(lambda synonym: synonym.synonym_type is None or synonym.synonym_type.is_current())
[docs] def obsolete_synonyms(self) -> typing.Iterable[Synonym]: """ Get an iterable with *obsolete* synonyms of the ontology concept. The iterable is empty if the concept has no obsolete synonyms. """ 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.Sequence[TermId]]: """ Get a sequence of the cross-references of the ontology concept. """ 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.synonyms == other.synonyms and self.xrefs == other.xrefs ) def __str__(self): return ( f"Term(" f"identifier={self.identifier}, " 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}")' )
def map_to_term_id(value: typing.Union[TermId, str]) -> TermId: if isinstance(value, TermId): return value elif isinstance(value, str): return TermId.from_curie(value) else: raise ValueError(f"Expected a `TermId` or `str` but got {type(value)}") def validate_name(name: typing.Optional[str]) -> str: # Some obsolete nodes do not have labels in the Obographs format. # We assign an empty string. if name is None: return "" else: return hpotk.util.validate_instance(name, str, "name") class DefaultMinimalTerm(MinimalTerm): def __init__( self, identifier: typing.Union[TermId, str], name: str, alt_term_ids: typing.Iterable[typing.Union[TermId, str]], is_obsolete: bool, ): self._id = hpotk.util.validate_instance(map_to_term_id(identifier), TermId, "identifier") self._name = validate_name(name) self._alts = tuple(map(map_to_term_id, alt_term_ids)) self._is_obsolete = hpotk.util.validate_instance(is_obsolete, bool, "is_obsolete") @property def identifier(self) -> TermId: return self._id @property def name(self) -> str: return self._name @property def alt_term_ids(self) -> typing.Sequence[TermId]: return self._alts @property 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}")' ) def validate_synonyms(synonyms: typing.Optional[typing.Iterable[Synonym]]): if synonyms is None: return None else: validated = [] for i, s in enumerate(synonyms): validated.append(hpotk.util.validate_instance(s, Synonym, f"synonym #{i}")) return tuple(validated) class DefaultTerm(DefaultMinimalTerm, Term): def __init__( self, identifier: typing.Union[TermId, str], name: str, alt_term_ids: typing.Iterable[typing.Union[TermId, str]], is_obsolete: bool, definition: typing.Optional[Definition], comment: typing.Optional[str], synonyms: typing.Optional[typing.Iterable[Synonym]], xrefs: typing.Optional[typing.Iterable[typing.Union[TermId, str]]], ): DefaultMinimalTerm.__init__( self, identifier=identifier, name=name, alt_term_ids=alt_term_ids, is_obsolete=is_obsolete, ) self._definition = hpotk.util.validate_optional_instance(definition, Definition, "definition") self._comment = hpotk.util.validate_optional_instance(comment, str, "comment") self._synonyms = validate_synonyms(synonyms) self._xrefs = tuple(map(map_to_term_id, xrefs)) if xrefs is not None else None @property def definition(self) -> typing.Optional[Definition]: return self._definition @property def comment(self) -> typing.Optional[str]: return self._comment @property def synonyms(self) -> typing.Optional[typing.Sequence[Synonym]]: return self._synonyms @property def xrefs(self) -> typing.Optional[typing.Sequence[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._alts}")' )