psims

Source code for psims.controlled_vocabulary.entity

from collections import deque
from typing import Any, Dict, List, Union, TYPE_CHECKING

from collections.abc import Mapping, MutableSequence

from psims.utils import ensure_iterable

from .type_definition import parse_xsdtype, TypeDefinition, ListOfType

if TYPE_CHECKING:
    from psims.controlled_vocabulary.controlled_vocabulary import ControlledVocabulary
    from psims.controlled_vocabulary.relationship import Relationship, Reference

class PredicateList(MutableSequence):
    def __init__(self, members, parent):
        self.members = list(members)
        self.parent = parent

    def __getitem__(self, i):
        return self.members[i]

    def __setitem__(self, i, v):
        v = self.parent.add_relationship(v)
        self.members[i] = v

    def __delitem__(self, i):
        rel = self.members[i]
        self.parent.remove_relationship(rel)
        del self.members[i]

    def __iter__(self):
        return iter(self.members)

    def __len__(self):
        return len(self.members)

    def insert(self, v):
        self.members.insert(v)


class ValueTypeOf(object):
    def __init__(self, entity):
        self.entity = entity

    def __repr__(self):
        return "{self.__class__.__name__}({self.entity})".format(self=self)

    def __call__(self, value):
        return self.parse(value)

    def parse(self, value):
        value_types = self.entity.get('has_value_type')
        if value_types:
            for value_type in value_types:
                try:
                    return value_type(value)
                except (ValueError, TypeError, NotImplementedError):
                    continue
        return value

    def format(self, value):
        value_types = self.entity.get('has_value_type')
        if value_types:
            for value_type in value_types:
                try:
                    return value_type.format(value)
                except (ValueError, TypeError, NotImplementedError):
                    continue
        return str(value)


class KeyOrAttributeError(KeyError, AttributeError):
    pass


[docs]class Entity(Mapping): '''Represent a term in a controlled vocabulary. While this type implements the :class:`~collections.abc.Mapping`, it supports attribute access notation for keys. Attributes ---------- children : list of :class:`Entity` Additional entities derived from this one data : :class:`dict` An arbitrary attribute store representing key-value pairs vocabulary : :class:`~.ControlledVocabulary` The source vocabulary. May be used for upward references id : str The CURI-style identifier of this entity, the accession of the term. definition : str The "def" field of a term. ''' data: Dict[str, Any] children: List['Entity'] vocabulary: 'ControlledVocabulary' def __init__(self, vocabulary=None, **attributes): self.data = dict(attributes) self.children = [] self.vocabulary = vocabulary
[docs] def get(self, key, default=None): return self.data.get(key, default)
def __contains__(self, key): return key in self.data def __getitem__(self, key): return self.data[key] def __setitem__(self, key, value): self.data[key] = value def __getattr__(self, key): try: return self[key] except KeyError: raise KeyOrAttributeError(key) from None def __setattr__(self, key, value): if key in ("vocabulary", "children", "data"): object.__setattr__(self, key, value) else: self[key] = value def add_relationship(self, relationship: Union[str, 'Relationship']) -> 'Relationship': from .relationship import Relationship if isinstance(relationship, str): relationship = Relationship.fromstring(relationship) self.setdefault(relationship.predicate, []) self[relationship.predicate].append(relationship) relationships = self.get('relationship') if isinstance(relationships, list): relationships.append(relationship) elif relationships is not None: relationships = [relationships, relationship] self['relationship'] = relationships else: self['relationship'] = relationship return relationship def remove_relationship(self, relationship: Union[str, 'Relationship']): from .relationship import Relationship if isinstance(relationship, str): relationship = Relationship.fromstring(relationship) predicate_members = self.get(relationship.predicate, []) predicate_members.remove(relationship) relationships = self.get('relationship') if isinstance(relationships, list): relationships.remove(relationship) elif relationship == relationships: self.pop('relationship') else: raise ValueError("Could not find %r" % relationship) def __dir__(self): keys = set(self.keys()) if hasattr(object, '__dir__'): keys |= set(object.__dir__(self)) else: keys |= set(self.__dict__.keys()) keys |= set(self.__class__.__dict__.keys()) return sorted(keys) def __len__(self): return len(self.data) def __iter__(self): return iter(self.keys())
[docs] def keys(self): return self.data.keys()
[docs] def values(self): return self.data.values()
[docs] def items(self): return self.data.items()
def setdefault(self, key, value): self.data.setdefault(key, value) @property def definition(self): return self.data.get("def", '') @definition.setter def definition(self, value): self.data['def'] = value
[docs] def parent(self) -> Union[None, 'Entity', List['Entity']]: '''Fetch the parent or parents of this :class:`Entity` in the bound controlled vocabulary. Returns ------- :class:`Entity` or :class:`list` of :class:`Entity` ''' try: reference = self.is_a except KeyError: return None try: return self.vocabulary[reference] except TypeError: return [self.vocabulary[r] for r in reference]
def __repr__(self): template = 'Entity({self.id!r}, {self.name!r}, {self.definition!r})' return template.format(self=self)
[docs] def is_of_type(self, tp: Union[str, 'Entity']) -> bool: '''Test if `tp` is an ancestor of this :class:`Entity` Parameters ---------- tp : str The identifier for the entity to test Returns ------- bool ''' if isinstance(tp, str): try: tp = self.vocabulary[tp] except KeyError: return False stack = deque([self]) while stack: ref = stack.pop() if ref == tp: return True stack.extend(ensure_iterable(ref.parent())) return False
def as_value_type(self) -> Union[ListOfType, TypeDefinition]: if self.id in self.vocabulary.type_definitions: return self.vocabulary.type_definitions[self.id] is_list_of = self.is_of_type('list of type') value_type = self.value_type if not value_type: value_type = str if is_list_of: type_def = ListOfType(self.id, self.name, value_type) else: type_def = TypeDefinition(self.id, self.name, value_type) self.vocabulary.type_definitions[self.id] = type_def return type_def @property def value_type(self): return ValueTypeOf(self)