Source code for osp.core.ontology.namespace

"""A namespace in the ontology."""


from collections.abc import Iterable
import rdflib
import logging
import itertools
from osp.core.ontology.entity import OntologyEntity
from osp.core.ontology.relationship import OntologyRelationship
from osp.core.ontology.cuba import rdflib_cuba
from osp.core.ontology.parser.yml.case_insensitivity import \
    get_case_insensitive_alternative as alt

logger = logging.getLogger(__name__)


[docs]class OntologyNamespace(): """A namespace in the ontology.""" def __init__(self, name, namespace_registry, iri): """Initialize the namespace. Args: name (str): The name of the namespace. namespace_registry (OntologyNamespace): The namespace registry. iri (rdflib.URIRef): The IRI of the namespace. """ self._name = name self._namespace_registry = namespace_registry self._iri = rdflib.URIRef(str(iri)) self._default_rel = -1 self._reference_by_label = \ namespace_registry._get_reference_by_label(self._iri) def __dir__(self): """Attributes available for the OntologyNamespace class. Returns: Iterable: the available attributes, which include the methods and the ontology entities in the namespace. """ entity_autocompletion = self._iter_labels() \ if self._reference_by_label else self._iter_suffixes() return itertools.chain(dir(super()), entity_autocompletion) def __str__(self): """Transform the namespace to a human readable string. Returns: str: The resulting string. """ return "%s (%s)" % (self._name, self._iri) def __repr__(self): """Transform the namespace to a string. Returns: str: The resulting string. """ return "<%s: %s>" % (self._name, self._iri)
[docs] def __eq__(self, other): """Check whether the two namespace objects are the same. Args: other (OntologyNamespace): The namespace to compare with. Returns: bool: Whether the given namespace is the same. """ return self._name == other._name and self._iri == other._iri \ and self._namespace_registry is other._namespace_registry
def __hash__(self): """Compute a has value.""" return hash(str(self))
[docs] def get_name(self): """Get the name of the namespace.""" return self._name
@property def _graph(self): """Return the graph of the namespace registry.""" return self._namespace_registry._graph
[docs] def get_default_rel(self): """Get the default relationship of the namespace.""" if self._default_rel == -1: self._default_rel = None for s, p, o in self._graph.triples((self._iri, rdflib_cuba._default_rel, None)): self._default_rel = self._namespace_registry.from_iri(o) return self._default_rel
[docs] def get_iri(self): """Get the IRI of the namespace.""" return self._iri
[docs] def __getattr__(self, name): """Get an ontology entity from the registry by label or suffix. Args: name (str): The label or namespace suffix of the ontology entity. Raises: AttributeError: Unknown label or suffix. Returns: OntologyEntity: The ontology entity. """ if name and name.startswith("INVERSE_OF_"): # Backwards compatibility try: return getattr(self, name[11:]).inverse except AttributeError: pass if self._reference_by_label: try: return self._get_from_label(name) except KeyError as e: raise AttributeError(str(e)) from e else: try: return self.get_from_suffix(name) except KeyError as e: raise AttributeError(str(e)) from e
[docs] def __getitem__(self, label): """Get an ontology entity from the registry by label. Useful for entities whose labels contains characters which are not compatible with the Python syntax. Args: label (str): The label of the ontology entity. Raises: KeyError: Unknown label. Returns: OntologyEntity: The ontology entity. """ if type(label) is str: lang = None elif isinstance(label, Iterable): contents = tuple(label) if len(contents) == 2: label = contents[0] lang = contents[1] else: raise TypeError(f'{type(self).capitalize()} indices must be of ' f'type {str} or (label: str, lang: str).') return self._get_from_label(label, lang, case_sensitive=True)
[docs] def get(self, name, fallback=None): """Get an ontology entity from the registry by suffix or label. Args: name (str): The label or suffix of the ontology entity. default (Any): The value to return if it doesn't exist. fallback (Any): The fallback value, defaults to None. Returns: OntologyEntity: The ontology entity """ try: return getattr(self, name) except AttributeError: return fallback
[docs] def get_from_iri(self, iri, _name=None): """Get an ontology entity directly from its IRI. For consistency, this method only returns entities from this namespace. Args: iri (Union[str, rdlib.URIRef]): The iri of the ontology entity. _name (str): Not mean to be provided by the user. Just passed to the `from_iri` method of the namespace registry. Returns: OntologyEntity: The ontology entity. Raises: KeyError: When the iri does not belong to the namespace. """ if rdflib.URIRef(str(iri)) in self: return self._namespace_registry.from_iri(str(iri), _name=_name) else: raise KeyError(f"The IRI {iri} does not belong to the namespace" f"{self}.")
[docs] def get_from_suffix(self, suffix, case_sensitive=False): """Get an ontology entity from its namespace suffix. Args: suffix (str): Suffix of the ontology entity. case_sensitive (bool): Whether to search also for the same suffix with different capitalization. By default, such a search is performed. """ iri = rdflib.URIRef(str(self._iri) + suffix) try: return self.get_from_iri(iri, _name=suffix) except KeyError as e: if not case_sensitive: return self._get_case_insensitive(suffix, self.get_from_suffix) raise e
def _get_from_label(self, label, lang=None, case_sensitive=False): """Get an ontology entity from the registry by label. Args: label (str): The label of the ontology entity. Raises: KeyError: Unknown label. Returns: OntologyEntity: The ontology entity. """ results = [] inverse = label.startswith("INVERSE_OF_") label = label if not inverse else label[11:] for iri in self._get_namespace_subjects(): entity_labels = self._get_labels_for_iri(iri, lang=lang) if case_sensitive is False: entity_labels = (label.lower() for label in entity_labels) comp_label = label.lower() else: comp_label = label if comp_label in entity_labels: _name = label if self._reference_by_label else None results.append(self.get_from_iri(iri, _name=_name)) if len(results) == 0: error = "No element with label %s was found in namespace %s."\ % (label, self) if inverse: error += f' Therefore, INVERSE_OF_{label} could not be found.' raise KeyError(error) elif len(results) >= 2: element_suffixes = (r._iri_suffix for r in results) error = (f"There are multiple elements " f"({', '.join(element_suffixes)}) with label" f" {label} for namespace {self}." f"\n" f"Please refer to a specific element of the " f"list by calling get_from_iri(IRI) for " f"namespace {self} for one of the following " f"IRIs: " + "{iris}.")\ .format(iris=', '.join(entity.iri for entity in results)) if inverse: error += f' Therefore, INVERSE_OF_{label} could not be found.' raise KeyError(error) if inverse: if type(results[0]) is not OntologyRelationship: raise KeyError(f"The entity {label} is not an ontology " f"relationship. Therefore INVERSE_OF_{label} " f"does not exist.") results[0] = results[0].inverse return results[0] def _iter_iris(self): """Iterate over the IRIs of the ontology entities in the namespace. :return: An iterator over the entity IRIs. :rtype: Iterator[rdflib.URIRef] """ types = [rdflib.OWL.DatatypeProperty, rdflib.OWL.ObjectProperty, rdflib.OWL.Class] return (s for t in types for s, _, _ in self._graph.triples((None, rdflib.RDF.type, t)) if s in self)
[docs] def __iter__(self): """Iterate over the ontology entities in the namespace. :return: An iterator over the entities. :rtype: Iterator[OntologyEntity] """ return (self._namespace_registry.from_iri(iri) for iri in self._iter_iris())
def _iter_labels(self): """Iterate over the labels of the ontology entities in the namespace. :return: An iterator over the entity labels. :rtype: Iterator[str] """ return itertools.chain(*(self._get_labels_for_iri(iri) for iri in self._iter_iris())) def _iter_suffixes(self): """Iterate over suffixes of the ontology entities in the namespace. :return: An iterator over the entity suffixes. :rtype: Iterator[str] """ return (str(iri)[len(str(self._iri)):] for iri in self._iter_iris())
[docs] def __contains__(self, item): """Check whether the given entity is part of the namespace. Args: item (Union[str, rdflib.URIRef, OntologyEntity, rdflib.BNode]): The name, IRI of an entity, the entity itself or a blank node. Returns: bool: Whether the given entity name or IRI is part of the namespace. Blank nodes are never part of a namespace. """ if type(item) is str: iri_suffix = item item = rdflib.URIRef(str(self._iri) + iri_suffix) elif isinstance(item, OntologyEntity): item = item.iri if not isinstance(item, (rdflib.URIRef, rdflib.BNode)): raise TypeError(f'in {type(self)} requires {str}, ' f'{rdflib.URIRef}, {OntologyEntity} or ' f'{rdflib.BNode} as left operand, ' f'not {type(item)}.') if isinstance(item, rdflib.BNode): return False elif str(item).startswith(self._iri): return True else: return False
# TODO: Cache or write a more efficient algorithm. def _get_namespace_subjects(self, unique=True): """Returns all the subjects in the namespace. Args: unique (bool): When true, does not return duplicates. This is the default option. Returns: iter: An iterator that goes through all the subjects belonging to the namespace. """ subjects = (subject for subject in self._graph.subjects() if subject in self) if unique: return set(subjects) else: return subjects def _get_labels_for_iri(self, iri, lang=None, _return_literal=False, _return_label_property=False): """Returns all the available labels for the given IRI. Args: iri (rdflib.URIRef): the target iri. lang (str): retrieve labels only on a speific language. _return_literal: return rdflib.Literal instead of str, so that the language of the labels is known to the caller. Returns: Union[str, rdflib.Literal]: Either the text of the label or the rdflib.Literal representing the label. """ def filter_language(literal): if lang is None: return True elif lang == "": return literal.language is None else: return literal.language == lang labels = filter(lambda label_tuple: filter_language(label_tuple[1]), ((prop, literal) for prop in self._label_properties for literal in self._graph.objects(iri, prop)) ) if not _return_literal: labels = ((prop, literal.toPython()) for prop, literal in labels) if not _return_label_property: return (literal for prop, literal in labels) else: return labels _label_properties = (rdflib.SKOS.prefLabel, rdflib.RDFS.label) # Backwards compatibility. # ↓----------------------↓ def _get(self, name, _case_sensitive=False, _force_by_iri=False): """Get an ontology entity from the registry by name. Args: name(str): The name of the ontology entity _case_sensitive(bool): Name should be case sensitive, defaults to False _force_by_iri(bool): Name is IRI suffix, defaults to False Returns: OntologyEntity: The ontology entity """ if _force_by_iri is True or self._reference_by_label is False: return self.get_from_suffix(name) else: return self._get_from_label(name, case_sensitive=_case_sensitive) def _get_case_insensitive(self, name, method): """Get by trying alternative naming convention of given name. Args: name(str): The name of the entity. Returns: OntologyEntity: The Entity to return Raises: KeyError: Reference to unknown entity. """ alternative = alt(name, self._name == "cuba") if alternative is None: raise KeyError( f"Unknown entity '{name}' in namespace {self._name}." ) r = None exception = None try: r = method(alternative, case_sensitive=True) except KeyError as e: if name and name.startswith("INVERSE_OF_"): try: r = method(name[11:], case_sensitive=False).inverse except KeyError: raise e exception = e if r is not None: logger.warning( f"{alternative} is referenced with '{name}'. " f"Note that referencing entities will be case sensitive " f"in future releases. Additionally, entity names defined " f"in YAML ontology are no longer required to be ALL_CAPS. " f"You can use the yaml2camelcase " f"commandline tool to transform entity names to CamelCase." ) else: raise KeyError( f"Unknown entity '{name}' in namespace {self._name}. " f"For backwards compatibility reasons we also " f"looked for {alternative} and failed." ) from exception return r