# coding: utf-8
import functools
import typing
import weakref
from typing import Optional, Set, FrozenSet, Iterable
from .xref import Xref
from .utils.impl import set
from .utils.meta import roundrepr, typechecked
if typing.TYPE_CHECKING:
from .ontology import Ontology
_SCOPES = frozenset({"EXACT", "RELATED", "BROAD", "NARROW"})
[docs]@functools.total_ordering
@roundrepr
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 is not None and 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]@functools.total_ordering
@roundrepr
class SynonymData(object):
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 is not None and 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):
_ontology: "weakref.ReferenceType[Ontology]"
_data: "weakref.ReferenceType[SynonymData]"
def __init__(self, ontology: "Ontology", syndata: "SynonymData"):
if syndata.type is not None:
types = ontology.metadata.synonymtypedefs
if not any(t.id == syndata.type for t in types):
raise ValueError(f"undeclared synonym type: {syndata.type}")
self._ontology = weakref.ref(ontology)
self._data = weakref.ref(syndata)
[docs] def __eq__(self, other):
if isinstance(other, Synonym):
return self._data() == other._data()
return False
[docs] def __lt__(self, other):
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
@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.metadata.synonymtypedefs if t.id == syndata.type)
return None
@type.setter
@typechecked(property=True)
def type(self, type_: Optional[SynonymType]) -> None:
synonyms = self._ontology().metadata.synonymtypedefs
if type is not None and type_.id not 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
@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) -> FrozenSet[Xref]:
return frozenset(self._data().xrefs)