Skip to content

Commit

Permalink
Polish tools module (#786)
Browse files Browse the repository at this point in the history
* General polishing and revision of tools (including semantic2dot).

* semantic2dot: allow plotting namespaces, sessions and individuals on the same graph.

* semantic2dot: add IPython rendering.

* Use graphviz's own `_repr_mimebundle_` function for integration of `semantic2dot` with IPython.
  • Loading branch information
kysrpex authored Jul 14, 2022
1 parent ef5e5fd commit b2b4667
Show file tree
Hide file tree
Showing 25 changed files with 1,727 additions and 1,334 deletions.
3 changes: 1 addition & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@
},
"console_scripts": {
"pico = simphony_osp.tools.pico:terminal",
"semantic2dot = simphony_osp.tools.semantic2dot"
":run_from_terminal",
"semantic2dot = simphony_osp.tools.semantic2dot:terminal",
},
},
install_requires=[
Expand Down
9 changes: 9 additions & 0 deletions simphony_osp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,12 @@
from importlib import import_module as _import_module

_import_module("simphony_osp.utils.pico") # Load installed ontologies

__all__ = [
"ontology",
"session",
"tools",
"development",
"namespaces",
"wrappers",
]
File renamed without changes.
26 changes: 18 additions & 8 deletions simphony_osp/ontology/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
Set,
Tuple,
Type,
TypeVar,
Union,
)

Expand Down Expand Up @@ -139,7 +140,9 @@ def session(self, value: Session) -> None:

@property
@lru_cache_timestamp(lambda self: self.session.entity_cache_timestamp)
def direct_superclasses(self) -> FrozenSet[OntologyEntity]:
def direct_superclasses(
self: ONTOLOGY_ENTITY,
) -> FrozenSet[ONTOLOGY_ENTITY]:
"""Get the direct superclasses of the entity.
Returns:
Expand All @@ -149,7 +152,7 @@ def direct_superclasses(self) -> FrozenSet[OntologyEntity]:

@property
@lru_cache_timestamp(lambda self: self.session.entity_cache_timestamp)
def direct_subclasses(self) -> FrozenSet[OntologyEntity]:
def direct_subclasses(self: ONTOLOGY_ENTITY) -> FrozenSet[ONTOLOGY_ENTITY]:
"""Get the direct subclasses of the entity.
Returns:
Expand All @@ -159,7 +162,7 @@ def direct_subclasses(self) -> FrozenSet[OntologyEntity]:

@property
@lru_cache_timestamp(lambda self: self.session.entity_cache_timestamp)
def superclasses(self) -> FrozenSet[OntologyEntity]:
def superclasses(self: ONTOLOGY_ENTITY) -> FrozenSet[ONTOLOGY_ENTITY]:
"""Get the superclass of the entity.
Returns:
Expand All @@ -170,7 +173,7 @@ def superclasses(self) -> FrozenSet[OntologyEntity]:

@property
@lru_cache_timestamp(lambda self: self.session.entity_cache_timestamp)
def subclasses(self) -> FrozenSet[OntologyEntity]:
def subclasses(self: ONTOLOGY_ENTITY) -> FrozenSet[ONTOLOGY_ENTITY]:
"""Get the subclasses of the entity.
Returns:
Expand Down Expand Up @@ -375,22 +378,26 @@ def graph(self) -> Graph:
return self.session.graph if self.session is not None else self.__graph

@abstractmethod
def _get_direct_superclasses(self) -> Iterable[OntologyEntity]:
def _get_direct_superclasses(
self: ONTOLOGY_ENTITY,
) -> Iterable[ONTOLOGY_ENTITY]:
"""Direct superclass getter specific to the type of ontology entity."""
pass

@abstractmethod
def _get_direct_subclasses(self) -> Iterable[OntologyEntity]:
def _get_direct_subclasses(
self: ONTOLOGY_ENTITY,
) -> Iterable[ONTOLOGY_ENTITY]:
"""Direct subclass getter specific to the type of ontology entity."""
pass

@abstractmethod
def _get_superclasses(self) -> Iterable[OntologyEntity]:
def _get_superclasses(self: ONTOLOGY_ENTITY) -> Iterable[ONTOLOGY_ENTITY]:
"""Superclass getter specific to the type of ontology entity."""
pass

@abstractmethod
def _get_subclasses(self) -> Iterable[OntologyEntity]:
def _get_subclasses(self: ONTOLOGY_ENTITY) -> Iterable[ONTOLOGY_ENTITY]:
"""Subclass getter specific to the type of ontology entity."""
pass

Expand Down Expand Up @@ -464,3 +471,6 @@ def __init__(
# Otherwise, it is None -> do not change what is stored.
self._session = session
self.__graph = None


ONTOLOGY_ENTITY = TypeVar("ONTOLOGY_ENTITY", bound=OntologyEntity)
37 changes: 24 additions & 13 deletions simphony_osp/ontology/individual.py
Original file line number Diff line number Diff line change
Expand Up @@ -2217,7 +2217,10 @@ def relationships_iter(
rel: Optional[OntologyRelationship] = None,
oclass: Optional[OntologyClass] = None,
return_rel: bool = False,
) -> Iterator[OntologyIndividual]:
) -> Union[
Iterator[OntologyIndividual],
Iterator[Tuple[OntologyIndividual, OntologyRelationship]],
]:
"""Iterate over the connected ontology individuals.
Args:
Expand All @@ -2231,31 +2234,39 @@ def relationships_iter(
Returns:
Iterator with the queried ontology individuals.
"""
entities_and_relationships = (
(
self.session.from_identifier(o),
self.session.ontology.from_identifier(p),
)

def individuals_and_relationships() -> Iterator[
OntologyIndividual, OntologyEntity
]:
for s, p, o in self.session.graph.triples(
(
self.identifier,
rel.identifier if rel is not None else None,
None,
)
)
if not (isinstance(o, Literal) or p == RDF.type)
)
):
if isinstance(o, Literal) or p == RDF.type:
continue
prop = self.session.ontology.from_identifier(p)
if not isinstance(prop, OntologyRelationship):
continue
individual = self.session.from_identifier_typed(
o, typing=OntologyIndividual
)
yield individual, prop

individuals_and_relationships = individuals_and_relationships()
if oclass:
entities_and_relationships = (
individuals_and_relationships = (
(entity, relationship)
for entity, relationship in entities_and_relationships
for entity, relationship in individuals_and_relationships
if oclass == entity
)

if return_rel:
yield from entities_and_relationships
yield from individuals_and_relationships
else:
yield from map(lambda x: x[0], entities_and_relationships)
yield from map(lambda x: x[0], individuals_and_relationships)

# ↑ ----------------- ↑
# Relationship handling
2 changes: 1 addition & 1 deletion simphony_osp/ontology/operations/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from simphony_osp.ontology.operations.operations import Operations
from simphony_osp.session.session import Environment, Session
from simphony_osp.utils import simphony_namespace
from simphony_osp.utils.simphony_namespace import simphony_namespace

if TYPE_CHECKING:
from simphony_osp.ontology.individual import OntologyIndividual
Expand Down
2 changes: 1 addition & 1 deletion simphony_osp/ontology/operations/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from rdflib.term import URIRef

from simphony_osp.ontology.operations import Operations
from simphony_osp.utils import simphony_namespace
from simphony_osp.utils.simphony_namespace import simphony_namespace

logger = logging.getLogger(__name__)

Expand Down
6 changes: 6 additions & 0 deletions simphony_osp/session/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
"""Module aimed at providing an ontology lager on top of the RDF layer."""

from simphony_osp.session.session import Session

__all__ = ["Session", "core_session"]

core_session = Session.get_default_session()
30 changes: 21 additions & 9 deletions simphony_osp/session/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
)

from rdflib import OWL, RDF, RDFS, SKOS, BNode, Graph, Literal, URIRef
from rdflib.graph import ReadOnlyGraphAggregate
from rdflib.graph import ModificationException, ReadOnlyGraphAggregate
from rdflib.plugins.sparql.processor import SPARQLResult
from rdflib.query import ResultRow
from rdflib.term import Identifier, Node, Variable
Expand Down Expand Up @@ -469,6 +469,10 @@ def ontology(self) -> Session:
@ontology.setter
def ontology(self, value: Optional[Session]) -> None:
"""Set the T-Box of this session."""
if not isinstance(value, (Session, type(None))):
raise TypeError(
f"Expected {Session} or {type(None)}, not type {value}."
)
self._ontology = value

label_properties: Tuple[URIRef] = (SKOS.prefLabel, RDFS.label)
Expand Down Expand Up @@ -513,8 +517,8 @@ def ontology(self, value: Optional[Session]) -> None:
def commit(self) -> None:
"""Commit pending changes to the session's graph."""
self._graph.commit()
if self.ontology is not self:
self.ontology.commit()
# if self.ontology is not self:
# self.ontology.commit()
self.creation_set = set()

def compute(self, **kwargs) -> None:
Expand Down Expand Up @@ -878,9 +882,10 @@ def delete(
self._graph.remove((entity, None, None))
self._graph.remove((None, None, entity))

def clear(self):
def clear(self, force: bool = False):
"""Clear all the data stored in the session."""
self._graph.remove((None, None, None))
graph = self._graph_writable if force else self._graph
graph.remove((None, None, None))
self._namespaces.clear()
self.entity_cache_timestamp = datetime.now()
self.from_identifier.cache_clear()
Expand Down Expand Up @@ -1105,9 +1110,12 @@ def __init__(
self._environment_references.add(self)
# Base the session graph either on a store if passed or an empty graph.
if base is not None:
self._graph_writable = base
self._graph = base

else:
graph = Graph("SimpleMemory")
graph = Graph()
self._graph_writable = graph
self._graph = graph

self._interface_driver = driver
Expand All @@ -1116,11 +1124,12 @@ def __init__(
if isinstance(ontology, Session):
self.ontology = ontology
elif ontology is True:
self._graph = ReadOnlyGraphAggregate([self._graph_writable])
self.ontology = self
elif ontology is not None:
raise TypeError(
f"Invalid ontology argument: {ontology}."
f"Expected either a `Session` or `bool` object, "
f"Expected either a {Session} or {bool} object, "
f"got {type(ontology)} instead."
)

Expand Down Expand Up @@ -1224,7 +1233,7 @@ def bind(self, name: Optional[str], iri: Union[str, URIRef]):
)
else:
self.ontology._namespaces[iri] = name
self.ontology._graph.bind(name, iri)
self.ontology._graph_writable.bind(name, iri)

def unbind(self, name: Union[str, URIRef]):
"""Unbind a namespace from this session.
Expand Down Expand Up @@ -1343,7 +1352,9 @@ def load_parser(self, parser: OntologyParser):
Args:
parser: the ontology parser from where to load the new namespaces.
"""
self.ontology._graph += parser.graph
if self.ontology is not self:
raise ModificationException()
self._graph_writable += parser.graph
for name, iri in parser.namespaces.items():
self.bind(name, iri)
self.entity_cache_timestamp = datetime.now()
Expand Down Expand Up @@ -1538,6 +1549,7 @@ def _update_and_merge_helper(
creation_set: Set[Identifier]
_namespaces: Dict[URIRef, str]
_graph: Graph
_graph_writable: Graph
_driver: Optional[Interface] = None

@property
Expand Down
30 changes: 15 additions & 15 deletions simphony_osp/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
"""Module containing tools for the users of the SimPhoNy OSP."""

import simphony_osp.utils.pico as pico
from simphony_osp.tools.general import branch, get_relationships_between
from simphony_osp.tools.import_export import export_cuds, import_cuds
from simphony_osp.tools.general import branch, relationships_between
from simphony_osp.tools.import_export import export_file, import_file
from simphony_osp.tools.pretty_print import pretty_print
from simphony_osp.tools.remote import host
from simphony_osp.tools.search import (
find_cuds_object,
find_cuds_object_by_uid,
find_cuds_objects_by_attribute,
find_cuds_objects_by_oclass,
find,
find_by_attribute,
find_by_class,
find_by_identifier,
find_relationships,
sparql,
)
from simphony_osp.tools.semantic2dot import Semantic2Dot
from simphony_osp.tools.semantic2dot import semantic2dot

__all__ = [
# simphony_osp.tools.import_export
"export_cuds",
"import_cuds",
"export_file",
"import_file",
# simphony_osp.tools.pico
"pico",
# simphony_osp.tools.pretty_print
"pretty_print",
# simphony_osp.tools.search
"sparql",
"find_cuds_object",
"find_cuds_objects_by_attribute",
"find_cuds_objects_by_oclass",
"find_cuds_object_by_uid",
"find",
"find_by_attribute",
"find_by_class",
"find_by_identifier",
"find_relationships",
# simphony_osp.tools.semantic2dot
"Semantic2Dot",
"semantic2dot",
# simphony_osp.tools.general
"branch",
"get_relationships_between",
"relationships_between",
# simphony_osp.utils.remote
"host",
]
8 changes: 4 additions & 4 deletions simphony_osp/tools/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""

import logging
from typing import Optional, Set
from typing import Set

from simphony_osp.ontology.individual import OntologyIndividual
from simphony_osp.ontology.relationship import OntologyRelationship
Expand All @@ -13,12 +13,12 @@

__all__ = [
"branch",
"get_relationships_between",
"relationships_between",
]


def branch(
individual, *individuals, rel: Optional[OntologyRelationship] = None
individual, *individuals, rel: OntologyRelationship
) -> OntologyIndividual:
"""Like `connect`, but returns the element you connect to.
Expand All @@ -39,7 +39,7 @@ def branch(
return individual


def get_relationships_between(
def relationships_between(
subj: OntologyIndividual, obj: OntologyIndividual
) -> Set[OntologyRelationship]:
"""Get the set of relationships between two cuds objects.
Expand Down
Loading

0 comments on commit b2b4667

Please sign in to comment.