Source code for pronto.entity

import collections.abc
import datetime
import weakref
import typing
from typing import Any, Iterable, Optional, Set, FrozenSet

from .definition import Definition
from .synonym import Synonym, SynonymData, SynonymType
from .pv import PropertyValue
from .xref import Xref
from .utils.meta import roundrepr, typechecked
from .utils.impl import set

if typing.TYPE_CHECKING:
    from .ontology import Ontology


class EntityData:

    id: str
    alternate_ids: Set[str]
    annotations: Set[PropertyValue]
    anonymous: bool
    builtin: bool
    comment: Optional[str]
    created_by: Optional[str]
    creation_date: Optional[datetime.datetime]
    definition: Optional[Definition]
    equivalent_to: Set[str]
    name: Optional[str]
    namespace: Optional[str]
    obsolete: bool
    subsets: Set[str]
    synonyms: Set[SynonymData]
    xrefs: Set[Xref]

    __slots__ = ("__weakref__",) + tuple(__annotations__)  # noqa: E0602


[docs]class Entity: """An entity in the ontology graph. With respects to the OBO semantics, an `Entity` is either a term or a relationship in the ontology graph. Any entity has a unique identifier as well as some common properties. """ if typing.TYPE_CHECKING: __ontology: "weakref.ReferenceType[Ontology]" __data: "weakref.ReferenceType[EntityData]" __slots__: Iterable[str] = ("__weakref__",) def __init__(self, ontology: "Ontology", data: "EntityData"): self.__ontology = weakref.ref(ontology) self.__data = weakref.ref(data) self.__id = data.id def _data(self) -> "EntityData": rdata = self.__data() if rdata is None: raise RuntimeError("entity data was deallocated") return rdata def _ontology(self) -> "Ontology": ontology = self.__ontology() if ontology is None: raise RuntimeError("referenced ontology was deallocated") return ontology else: __slots__: Iterable[str] = ("__weakref__", "_ontology", "_data") def __init__(self, ontology: "Ontology", data: "EntityData"): self._ontology = weakref.ref(ontology) self._data = weakref.ref(data) self.__id = data.id # --- Magic Methods ------------------------------------------------------
[docs] def __eq__(self, other: Any) -> bool: if isinstance(other, Entity): return self.id == other.id return False
[docs] def __lt__(self, other): if isinstance(other, Entity): return self.id < other.id return NotImplemented
[docs] def __le__(self, other): if isinstance(other, Entity): return self.id <= other.id return NotImplemented
[docs] def __gt__(self, other): if isinstance(other, Entity): return self.id > other.id return NotImplemented
[docs] def __ge__(self, other): if isinstance(other, Entity): return self.id >= other.id return NotImplemented
[docs] def __hash__(self): return hash((self.id))
[docs] def __repr__(self): return roundrepr.make(type(self).__name__, self.id, name=(self.name, None))
# --- Data descriptors --------------------------------------------------- @property def alternate_ids(self) -> FrozenSet[str]: """`frozenset` of `str`: A set of alternate IDs for this entity. """ return frozenset(self._data().alternate_ids) @alternate_ids.setter @typechecked(property=True) def alternate_ids(self, ids: FrozenSet[str]): self._data().alternate_ids = set(ids) @property def annotations(self) -> FrozenSet[PropertyValue]: """`frozenset` of `PropertyValue`: Annotations relevant to the entity. """ return frozenset(self._data().annotations) @property def anonymous(self) -> bool: """`bool`: Whether or not the entity has an anonymous id. Semantics of anonymous entities are the same as B-Nodes in RDF. """ return self._data().anonymous @anonymous.setter def anonymous(self, value: bool): self._data().anonymous = value @property def builtin(self) -> bool: """`bool`: Whether or not the entity is built-in to the OBO format. ``pronto`` uses this tag on the ``is_a`` relationship, which is the axiomatic to the OBO language but treated as a relationship in the library. """ return self._data().builtin @builtin.setter @typechecked(property=True) def builtin(self, value: bool): self._data().builtin = value @property def comment(self) -> Optional[str]: """`str` or `None`: A comment about the current entity. Comments in ``comment`` clauses are guaranteed to be conserved by OBO parsers and serializers, unlike bang comments. A non `None` `comment` is semantically equivalent to a ``rdfs:comment`` in OWL2. When parsing from OWL, several RDF comments will be merged together into a single ``comment`` clause spanning over multiple lines. """ return self._data().comment @comment.setter def comment(self, value: Optional[str]): self._data().comment = value @property def created_by(self) -> Optional[str]: """`str` or `None`: the name of the creator of the entity, if any. This property gets translated to a ``dc:creator`` annotation in OWL2, which has very broad semantics. Some OBO ontologies may instead use other annotation properties such as the ones found in `Information Interchange Ontology <http://www.obofoundry.org/ontology/iao.html>`_, which can be accessed in the `annotations` attribute of the entity, if any. """ return self._data().created_by @created_by.setter @typechecked(property=True) def created_by(self, value: Optional[str]): self._data().created_by = value @property def creation_date(self) -> Optional[datetime.datetime]: """`~datetime.datetime` or None: the date the entity was created. """ return self._data().creation_date @creation_date.setter @typechecked(property=True) def creation_date(self, value: Optional[datetime.datetime]): self._data().creation_date = value @property def definition(self) -> Optional[Definition]: """`str` or None: the textual definition of the current entity. Definitions in OBO are intended to be human-readable text describing the entity, with some additional cross-references if possible. """ return self._data().definition @definition.setter @typechecked(property=True) def definition(self, definition: Optional[Definition]): self._data().definition = definition @property def equivalent_to(self) -> FrozenSet[str]: return frozenset(self._data().equivalent_to) @equivalent_to.setter def equivalent_to(self, equivalent_to: FrozenSet[str]): self._data().equivalent_to = set(equivalent_to) @property def id(self): """`str`: The OBO identifier of the entity. Identifiers can be either prefixed (e.g. ``MS:1000031``), unprefixed (e.g. ``part_of``) or given as plain URLs. Identifiers cannot be edited. """ return self.__id @property def name(self) -> Optional[str]: """`str` or `None`: The name of the entity. Names are formally equivalent to ``rdf:label`` in OWL2. The OBO format version 1.4 made names optional to improve OWL interoperability, as labels are optional in OWL. """ return self._data().name @name.setter @typechecked(property=True) def name(self, value: Optional[str]): self._data().name = value @property def namespace(self) -> Optional[str]: """`str` or `None`: the namespace this entity is defined in. """ return self._data().namespace @namespace.setter @typechecked(property=True) def namespace(self, ns: Optional[str]): self._data().namespace = ns @property def obsolete(self) -> bool: """`bool`: whether or not the entity is obsolete. """ return self._data().obsolete @obsolete.setter @typechecked(property=True) def obsolete(self, value: bool): self._data().obsolete = value @property def subsets(self) -> FrozenSet[str]: """`frozenset` of `str`: the subsets containing this entity. """ return frozenset(self._data().subsets) @subsets.setter @typechecked(property=True) def subsets(self, subsets: FrozenSet[str]): declared = set(s.id for s in self._ontology().metadata.subsetdefs) for subset in subsets: if subset not in declared: raise ValueError(f"undeclared subset: {subset!r}") self._data().subsets = set(subsets) @property def synonyms(self) -> FrozenSet[Synonym]: """`frozenset` of `Synonym`: a set of synonyms for this entity. """ ontology, termdata = self._ontology(), self._data() return frozenset(Synonym(ontology, s) for s in termdata.synonyms) @synonyms.setter @typechecked(property=True) def synonyms(self, synonyms: FrozenSet[Synonym]): self._data().synonyms = set(synonyms) @property def xrefs(self) -> FrozenSet[Xref]: """`frozenset` of `Xref`: a set of database cross-references. Xrefs can be used to describe an analogous entity in another vocabulary, such as a database or a semantic knowledge base. """ return frozenset(self._data().xrefs) @xrefs.setter @typechecked(property=True) def xrefs(self, xrefs: FrozenSet[Xref]): self._data().xrefs = set(xrefs) # --- Conveniene methods -------------------------------------------------
[docs] def add_synonym( self, description: str, scope: Optional[str] = None, type: Optional[SynonymType] = None, xrefs: Optional[Iterable[Xref]] = None, ) -> Synonym: """Add a new synonym to the current entity. Arguments: description (`str`): The alternate definition of the entity, or a related human-readable synonym. scope (`str` or `None`): An optional synonym scope. Must be either **EXACT**, **RELATED**, **BROAD** or **NARROW** if given. type (`~pronto.SynonymType` or `None`): An optional synonym type. Must be declared in the header of the current ontology. xrefs (iterable of `Xref`, or `None`): A collections of database cross-references backing the origin of the synonym. Raises: ValueError: when given an invalid synonym type or scope. Returns: `~pronto.Synonym`: A new synonym for the terms. The synonym is already added to the `Entity.synonyms` collection. """ type_id = type.id if type is not None else None data = SynonymData(description, scope, type_id, xrefs=xrefs) synonym = Synonym(self._ontology(), data) self._data().synonyms.add(data) return synonym