Source code for pronto.synonym

# coding: utf-8

import functools
import typing
import weakref
from typing import Iterable, Optional, Set

from .utils.meta import roundrepr, typechecked
from .xref import Xref

if typing.TYPE_CHECKING:
    from .ontology import Ontology


_SCOPES = frozenset({"EXACT", "RELATED", "BROAD", "NARROW", None})
__all__ = ["SynonymType", "SynonymData", "Synonym"]


[docs] @roundrepr @functools.total_ordering class SynonymType(object): """A user-defined synonym type.""" id: str description: str scope: Optional[str] __slots__ = ("__weakref__", "id", "description", "scope") @typechecked() def __init__(self, id: str, description: str, scope: Optional[str] = None): if scope not in _SCOPES: raise ValueError(f"invalid synonym scope: {scope}") self.id = id self.description = description self.scope = scope
[docs] def __eq__(self, other): if isinstance(other, SynonymType): return self.id == other.id return False
[docs] def __lt__(self, other): if isinstance(other, SynonymType): if self.id < other.id: return True return self.id == other.id and self.description < other.description return NotImplemented
[docs] def __hash__(self): return hash((SynonymType, self.id))
[docs] @roundrepr @functools.total_ordering class SynonymData(object): """Internal data storage of `Synonym` information.""" description: str scope: Optional[str] type: Optional[str] xrefs: Set[Xref] __slots__ = ("__weakref__", "description", "type", "xrefs", "scope")
[docs] def __eq__(self, other): if isinstance(other, SynonymData): return self.description == other.description and self.scope == other.scope return False
[docs] def __lt__(self, other): # FIXME? if not isinstance(other, SynonymData): return NotImplemented if self.type is not None and other.type is not None: return (self.description, self.scope, self.type, frozenset(self.xrefs)) < ( self.description, self.scope, other.type, frozenset(other.xrefs), ) else: return (self.description, self.scope, frozenset(self.xrefs)) < ( self.description, self.scope, frozenset(other.xrefs), )
[docs] def __hash__(self): return hash((self.description, self.scope))
def __init__( self, description: str, scope: Optional[str] = None, type: Optional[str] = None, xrefs: Optional[Iterable[Xref]] = None, ): if scope not in _SCOPES: raise ValueError(f"invalid synonym scope: {scope}") self.description = description self.scope = scope self.type = type self.xrefs = set(xrefs) if xrefs is not None else set()
[docs] @functools.total_ordering class Synonym(object): """A synonym for an entity, with respect to the OBO terminology.""" __ontology: "Ontology" if typing.TYPE_CHECKING: __data: "weakref.ReferenceType[SynonymData]" def __init__(self, ontology: "Ontology", data: "SynonymData"): self.__data = weakref.ref(data) self.__ontology = ontology def _data(self) -> SynonymData: rdata = self.__data() if rdata is None: raise RuntimeError("synonym data was deallocated") return rdata else: __slots__: Iterable[str] = ("__weakref__", "__ontology", "_data") def __init__(self, ontology: "Ontology", syndata: "SynonymData"): if syndata.type is not None: if not any(t.id == syndata.type for t in ontology.synonym_types()): raise ValueError(f"undeclared synonym type: {syndata.type}") self._data = weakref.ref(syndata) self.__ontology = ontology
[docs] def __eq__(self, other: object): if isinstance(other, Synonym): return self._data() == other._data() return False
[docs] def __lt__(self, other: object): if not isinstance(other, Synonym): return False return self._data().__lt__(other._data())
[docs] def __hash__(self): return hash(self._data())
[docs] def __repr__(self): return roundrepr.make( "Synonym", self.description, scope=(self.scope, None), type=(self.type, None), xrefs=(self.xrefs, set()), )
@property def description(self) -> str: return self._data().description @description.setter # type: ignore @typechecked(property=True) def description(self, description: str) -> None: self._data().description = description @property def type(self) -> Optional[SynonymType]: ontology, syndata = self.__ontology, self._data() if syndata.type is not None: return next(t for t in ontology.synonym_types() if t.id == syndata.type) return None @type.setter # type: ignore @typechecked(property=True) def type(self, type_: Optional[SynonymType]) -> None: synonyms: Iterable[SynonymType] = self.__ontology.synonym_types() if type_ is not None and not any(type_.id == s.id for s in synonyms): raise ValueError(f"undeclared synonym type: {type_.id}") self._data().type = type_.id if type_ is not None else None @property def scope(self) -> Optional[str]: return self._data().scope @scope.setter # type: ignore @typechecked(property=True) def scope(self, scope: Optional[str]): if scope not in _SCOPES: raise ValueError(f"invalid synonym scope: {scope}") self._data().scope = scope @property def xrefs(self) -> Set[Xref]: return self._data().xrefs @xrefs.setter def xrefs(self, xrefs: Iterable[Xref]): self._data().xrefs = set(xrefs)