Skip to content

Commit

Permalink
Decouple the parsers actions (#658)
Browse files Browse the repository at this point in the history
* Decouple the following operations that the parser was responsible for: get the graph of the ontology, put the ontology in the namespace registry.

* Modify the unit tests to accommodate the new changes.

* Make the `Ontology` class directly accesible from the `ontology` module.
  • Loading branch information
kysrpex authored Jun 16, 2021
1 parent 55ac8a2 commit 415c676
Show file tree
Hide file tree
Showing 33 changed files with 1,578 additions and 1,400 deletions.
2 changes: 1 addition & 1 deletion examples/ontology_example.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""An example explaining how to access an installed ontology."""

from osp.core.ontology.oclass_composition import Composition # Used later.
from osp.core.ontology.oclass_composition import Composition

# Please install the both the city ontology: $pico install city,
# and the European Materials & Modelling Ontology (EMMO): $pico install emmo.
Expand Down
2 changes: 1 addition & 1 deletion osp/core/namespaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@


try:
_namespace_registry.load(_path)
_namespace_registry.load_graph_file(_path)
except RuntimeError:
_logger.critical("Could not load installed ontologies.", exc_info=1)

Expand Down
9 changes: 6 additions & 3 deletions osp/core/ontology/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from osp.core.ontology.installation import OntologyInstallationManager
from osp.core.ontology.parser.parser import Parser
from osp.core.ontology.oclass import OntologyClass
from osp.core.ontology.ontology import Ontology
from osp.core.ontology.attribute import OntologyAttribute
from osp.core.ontology.entity import OntologyEntity
from osp.core.ontology.relationship import OntologyRelationship
from osp.core.ontology.attribute import OntologyAttribute
from osp.core.ontology.parser import Parser
from osp.core.ontology.installation import OntologyInstallationManager
from osp.core.ontology.oclass_composition import Composition
from osp.core.ontology.oclass_restriction import Restriction
28 changes: 13 additions & 15 deletions osp/core/ontology/installation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
import logging
import shutil
import tempfile
from osp.core.ontology.parser import Parser
from osp.core.ontology.parser.parser import OntologyParser

logger = logging.getLogger(__name__)


class OntologyInstallationManager():
class OntologyInstallationManager:
"""This class handles the installation of ontologies."""

def __init__(self, namespace_registry=None, path=None):
Expand Down Expand Up @@ -81,7 +81,7 @@ def _get_remaining_packages(self, remove_packages):
installed_pkgs = dict(self.get_installed_packages(return_path=True))
for pkg in remove_packages:
if pkg.endswith(".yml") and os.path.exists(pkg):
pkg = Parser.get_identifier(pkg)
pkg = OntologyParser.get_parser(pkg).identifier
if pkg in installed_pkgs:
remove_pkgs.add(pkg)
else:
Expand All @@ -105,7 +105,7 @@ def _get_replaced_packages(self, new_packages):
"""
installed = dict(self.get_installed_packages(return_path=True))
for pkg in new_packages:
installed[Parser.get_identifier(pkg)] = pkg
installed[OntologyParser.get_parser(pkg).identifier] = pkg
return installed.values()

def _get_new_packages(self, packages):
Expand All @@ -120,7 +120,7 @@ def _get_new_packages(self, packages):
result = set(packages)
installed = set(self.get_installed_packages())
for pkg in packages:
identifier = Parser.get_identifier(pkg)
identifier = OntologyParser.get_parser(pkg).identifier
if identifier in installed:
logger.info("Skipping package %s with identifier %s, "
"because it is already installed."
Expand All @@ -142,16 +142,15 @@ def _install(self, files, filter_func, clear):
if clear:
self.namespace_registry.clear()
installed = set()
shutil.rmtree(self.path)
os.makedirs(self.path)
files = self._sort_for_installation(filter_func(files), installed)
parser = Parser(parser_namespace_registry=self.namespace_registry)
for file in files:
parser.parse(file)
parser = OntologyParser.get_parser(file)
self.namespace_registry.load_parser(parser)
# serialize the result
parser.install(self.path)
self.namespace_registry.update_namespaces()
# serialize the result
if clear:
shutil.rmtree(self.path)
os.makedirs(self.path)
parser.store(self.path)
self.namespace_registry.store(self.path)

def _sort_for_installation(self, files, installed):
Expand All @@ -167,8 +166,8 @@ def _sort_for_installation(self, files, installed):
List[str]: The sorted list of file paths.
"""
result = list()
files = {Parser.get_identifier(f): f for f in files}
requirements = {n: Parser.get_requirements(f) for
files = {OntologyParser.get_parser(f).identifier: f for f in files}
requirements = {n: OntologyParser.get_parser(f).requirements for
n, f in files.items()}

# order the files
Expand Down Expand Up @@ -210,7 +209,6 @@ def pico_migrate(namespace_registry, path):
files.append(os.path.join(d, file))
shutil.rmtree(path)
os.mkdir(path)

installer = OntologyInstallationManager(namespace_registry, path)
namespace_registry._load_cuba()
installer.install(*files)
Expand Down
2 changes: 1 addition & 1 deletion osp/core/ontology/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
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.yml.case_insensitivity import \
from osp.core.ontology.parser.yml.case_insensitivity import \
get_case_insensitive_alternative as alt

logger = logging.getLogger(__name__)
Expand Down
68 changes: 62 additions & 6 deletions osp/core/ontology/namespace_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
from osp.core.ontology.oclass import OntologyClass
from osp.core.ontology.relationship import OntologyRelationship
from osp.core.ontology.attribute import OntologyAttribute
from osp.core.ontology.ontology import Ontology
from osp.core.ontology.parser.parser import OntologyParser
from functools import lru_cache

logger = logging.getLogger(__name__)


class NamespaceRegistry():
class NamespaceRegistry:
"""Stores all the loaded namespaces."""

def __init__(self):
Expand All @@ -27,7 +29,7 @@ def __init__(self):
self._namespaces = dict()

def __iter__(self):
"""Iterate over the installed namespace.
"""Iterate over the installed namespaces.
:return: An iterator over the namespaces.
:rtype: Iterator[OntologyNamespace]
Expand Down Expand Up @@ -178,8 +180,10 @@ def from_bnode(self, bnode, btype=None):
bnode (BNode): A blank node in the triple store.
btype (URIRef): The rdf.type of the blank node.
"""
from osp.core.ontology.oclass_composition import get_composition
from osp.core.ontology.oclass_restriction import get_restriction
from osp.core.ontology.oclass_composition import \
get_composition
from osp.core.ontology.oclass_restriction import \
get_restriction
t = btype or self._graph.value(bnode, rdflib.RDF.type)

if t == rdflib.OWL.Restriction:
Expand Down Expand Up @@ -309,15 +313,15 @@ def store(self, path):
for name, iri in self._namespaces.items():
print("%s\t%s" % (name, iri), file=f)

def load(self, path):
def load_graph_file(self, path):
"""Load the installed graph from at the given path.
Args:
path (Path): path to directory where the ontology has been
installed.
"""
# Migrate old ontology formats if needed.
if os.path.exists(os.path.join(path, "yml")):
if os.path.exists(os.path.join(path, "parser/yml")):
from osp.core.ontology.installation import pico_migrate
pico_migrate(self, path)
# Migrate to 3.5.3.1 format if needed.
Expand Down Expand Up @@ -356,5 +360,57 @@ def _load_cuba(self):
self.bind("cuba",
rdflib.URIRef("http://www.osp-core.com/cuba#"))

def load_parser(self, parser: OntologyParser):
"""Load new namespace(s) from a parser object.
Args:
parser (OntologyParser): the ontology parser from where to load
the new namespaces
"""
namespaces = parser.namespaces.items() \
if isinstance(parser.namespaces, dict) else dict()
logger.info("Loading namespaces %s." %
'; '.join([f'{name}, {uri}'
for name, uri in namespaces]))
ontology = Ontology(from_parser=parser)
# Force default relationships to be installed before installing a new
# ontology.
self._check_default_relationship_installed(ontology)
# Merge ontology graph.
self._graph += ontology.graph
# Bind namespaces.
for name, iri in namespaces:
if not (
iri.endswith("#") or iri.endswith("/")
):
iri += "#"
self.bind(name, iri)

def _check_default_relationship_installed(self, ontology: Ontology,
allow_types=frozenset(
{rdflib.OWL.ObjectProperty,
})
):
found = False
# Check if it is in the namespace to be installed.
for s, p, o in ontology.graph.triples((ontology.default_relationship,
rdflib.RDF.type,
None)):
if o in allow_types:
found = True
break
# If not, found, find it in the namespace registry.
if not found:
try:
self.from_iri(ontology.default_relationship)
found = True
except KeyError:
pass
if not found:
raise ValueError(f'The default relationship '
f'{ontology.default_relationship} defined for '
f'the ontology package {ontology.identifier} '
f'is not installed.')


namespace_registry = NamespaceRegistry()
Loading

0 comments on commit 415c676

Please sign in to comment.