Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functionality for setting name part of IRI to prefLabel #399

Merged
merged 9 commits into from
Apr 27, 2022
5 changes: 5 additions & 0 deletions docs/tools-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,3 +356,8 @@ Since the catalog file will be overwritten in the above example writing output t
```console
ontoconvert --recursive emmo.ttl owl/emmo.owl
```


### Bugs
Since parsing the results from the reasoner is currently broken in Owlready2 (v0.37), a workaround has been added to ontoconvert.
This workaround only only supports FaCT++. Hence, HermiT and Pellet are currently not available.
17 changes: 12 additions & 5 deletions ontopy/factpluspluswrapper/sync_factpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@
RDF.type: "individual",
OWL.equivalentClass: "class",
OWL.equivalentProperty: "property",
OWL.sameAs: "individual",
}


def sync_reasoner_factpp( # pylint: disable=too-many-locals,too-many-branches
def sync_reasoner_factpp(
ontology_or_world=None, infer_property_values=False, debug=1
):
"""Run FaCT++ reasoner and load the inferred relations back into
Expand All @@ -41,6 +42,7 @@ def sync_reasoner_factpp( # pylint: disable=too-many-locals,too-many-branches
debug : bool
Whether to print debug info to standard output.
"""
# pylint: disable=too-many-locals,too-many-branches,too-many-statements
if isinstance(ontology_or_world, World):
world = ontology_or_world
elif isinstance(ontology_or_world, Ontology):
Expand All @@ -62,7 +64,8 @@ def sync_reasoner_factpp( # pylint: disable=too-many-locals,too-many-branches
world.graph.release_write_lock() # Not needed during reasoning

try:
print("*** Prepare graph")
if debug:
print("*** Prepare graph")
# Exclude owl:imports because they are not needed and can
# cause trouble when loading the inferred ontology
graph1 = rdflib.Graph()
Expand All @@ -72,10 +75,12 @@ def sync_reasoner_factpp( # pylint: disable=too-many-locals,too-many-branches
if predicate != OWL.imports:
graph1.add((subject, predicate, obj))

print("*** Run FaCT++ reasoner (and postprocess)")
if debug:
print("*** Run FaCT++ reasoner (and postprocess)")
graph2 = FaCTPPGraph(graph1).inferred_graph()

print("*** Load inferred ontology")
if debug:
print("*** Load inferred ontology")
# Check all rdfs:subClassOf relations in the inferred graph and add
# them to the world if they are missing
new_parents = defaultdict(list)
Expand Down Expand Up @@ -117,7 +122,9 @@ def sync_reasoner_factpp( # pylint: disable=too-many-locals,too-many-branches
if locked:
world.graph.acquire_write_lock() # re-lock when applying results

print("*** Applying reasoning results")
if debug:
print("*** Applying reasoning results")

_apply_reasoning_results(
world, ontology, debug, new_parents, new_equivs, entity_2_type
)
Expand Down
199 changes: 160 additions & 39 deletions ontopy/ontology.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
import uuid
import tempfile
import types
import pathlib
from typing import Union
from pathlib import Path
from collections import defaultdict
from collections.abc import Iterable

Expand All @@ -32,6 +32,7 @@
from ontopy.utils import (
asstring,
read_catalog,
write_catalog,
infer_version,
convert_imported,
FMAP,
Expand Down Expand Up @@ -84,11 +85,7 @@ def get_ontology(self, base_iri="emmo-inferred"):
- "emmo-development": load latest inferred development version
of EMMO
"""
base_iri = (
base_iri.as_uri()
if isinstance(base_iri, pathlib.Path)
else base_iri
)
base_iri = base_iri.as_uri() if isinstance(base_iri, Path) else base_iri

if base_iri == "emmo":
base_iri = (
Expand Down Expand Up @@ -305,10 +302,10 @@ def get_by_label(
return self.namespaces[label]

if label_annotations is None:
annotations = (_.name for _ in self.label_annotations)
annotations = (a.name for a in self.label_annotations)
else:
annotations = (
_.name if hasattr(_, "storid") else _ for _ in label_annotations
a.name if hasattr(a, "storid") else a for a in label_annotations
)
for key in annotations:
entity = self.search_one(**{key: label})
Expand Down Expand Up @@ -374,7 +371,7 @@ def remove_label_annotation(self, iri):
raise ValueError(f"IRI not in ontology: {iri}")
self._label_annotations.remove(label_annotation)

def load( # pylint: disable=too-many-arguments
def load( # pylint: disable=too-many-arguments,arguments-renamed
self,
only_local=False,
filename=None,
Expand Down Expand Up @@ -460,7 +457,7 @@ def _load( # pylint: disable=too-many-arguments,too-many-locals,too-many-branch
catalog_file="catalog-v001.xml",
**kwargs,
):
"""Help function for _load()."""
"""Help function for load()."""
web_protocol = "http://", "https://", "ftp://"

url = filename if filename else self.base_iri.rstrip("/#")
Expand Down Expand Up @@ -612,19 +609,52 @@ def getmtime(path):
)

def save(
self, filename=None, format=None, overwrite=False, **kwargs
): # pylint: disable=redefined-builtin
self,
filename=None,
format=None,
dir=".",
mkdir=False,
overwrite=False,
recursive=False,
squash=False,
write_catalog_file=False,
append_catalog=False,
catalog_file="catalog-v001.xml",
):
"""Writes the ontology to file.

If `overwrite` is `True` and filename exists, it will be removed
before saving. The default is to append an existing ontology.
Parameters
----------
filename: None | str | Path
Name of file to write to. If None, it defaults to the name
of the ontology with `format` as file extension.
format: str
Output format. The default is to infer it from `filename`.
dir: str | Path
If `filename` is a relative path, it is a relative path to `dir`.
mkdir: bool
Whether to create output directory if it does not exists.
owerwrite: bool
If true and `filename` exists, remove the existing file before
saving. The default is to append to an existing ontology.
recursive: bool
Whether to save imported ontologies recursively. This is
commonly combined with `filename=None`, `dir` and `mkdir`.
squash: bool
If true, rdflib will be used to save the current ontology
together with all its sub-ontologies into `filename`.
It make no sense to combine this with `recursive`.
write_catalog_file: bool
Whether to also write a catalog file to disk.
append_catalog: bool
Whether to append to an existing catalog file.
catalog_file: str | Path
Name of catalog file. If not an absolute path, it is prepended
to `dir`.
"""
if overwrite and filename and os.path.exists(filename):
os.remove(filename)

if not format:
format = guess_format(filename, fmap=FMAP)

# pylint: disable=redefined-builtin,too-many-arguments
# pylint: disable=too-many-statements,too-many-branches
# pylint: disable=too-many-locals,arguments-renamed
if not _validate_installed_version(
package="rdflib", min_version="6.0.0"
) and format == FMAP.get("ttl", ""):
Expand All @@ -642,16 +672,104 @@ def save(
)
)

if format in OWLREADY2_FORMATS:
revmap = {value: key for key, value in FMAP.items()}
super().save(file=filename, format=revmap[format], **kwargs)
revmap = {value: key for key, value in FMAP.items()}

if filename is None:
if format:
fmt = revmap.get(format, format)
filename = f"{self.name}.{fmt}"
else:
TypeError("`filename` and `format` cannot both be None.")
filename = os.path.join(dir, filename)
dir = Path(filename).resolve().parent

if mkdir:
outdir = Path(filename).parent.resolve()
if not outdir.exists():
outdir.mkdir(parents=True)

if not format:
format = guess_format(filename, fmap=FMAP)
fmt = revmap.get(format, format)

if overwrite and filename and os.path.exists(filename):
os.remove(filename)

EMMO = rdflib.Namespace( # pylint:disable=invalid-name
"http://emmo.info/emmo#"
)

if recursive:
if squash:
raise ValueError(
"`recursive` and `squash` should not both be true"
)
base = self.base_iri.rstrip("#/")
for onto in self.imported_ontologies:
obase = onto.base_iri.rstrip("#/")
newdir = Path(dir) / os.path.relpath(obase, base)
onto.save(
filename=None,
format=format,
dir=newdir.resolve(),
mkdir=mkdir,
overwrite=overwrite,
recursive=recursive,
squash=squash,
write_catalog_file=write_catalog_file,
append_catalog=append_catalog,
catalog_file=catalog_file,
)

if squash:
from rdflib import ( # pylint:disable=import-outside-toplevel
URIRef,
RDF,
OWL,
)

graph = self.world.as_rdflib_graph()
graph.namespace_manager.bind("emmo", EMMO)

# Remove anonymous namespace and imports
graph.remove((URIRef("http://anonymous"), RDF.type, OWL.Ontology))
imports = list(graph.triples((None, OWL.imports, None)))
for triple in imports:
graph.remove(triple)

graph.serialize(destination=filename, format=format)
elif format in OWLREADY2_FORMATS:
super().save(file=filename, format=fmt)
else:
with tempfile.NamedTemporaryFile(suffix=".owl") as handle:
tmpname = handle.name
super().save(file=tmpname, format="rdfxml", **kwargs)
graph = rdflib.Graph()
graph.parse(tmpname, format="xml")
graph.serialize(destination=filename, format=format)
try:
super().save(file=tmpname, format="rdfxml")
graph = rdflib.Graph()
graph.namespace_manager.bind("emmo", EMMO)
graph.parse(tmpname, format="xml")
graph.serialize(destination=filename, format=format)
finally:
os.remove(tmpname)

if write_catalog_file:
mappings = {}
base = self.base_iri.rstrip("#/")

def append(onto):
obase = onto.base_iri.rstrip("#/")
newdir = Path(dir) / os.path.relpath(obase, base)
newpath = newdir.resolve() / f"{onto.name}.{fmt}"
relpath = os.path.relpath(newpath, dir)
mappings[onto.get_version(as_iri=True)] = str(relpath)
for imported in onto.imported_ontologies:
append(imported)

if recursive:
append(self)
write_catalog(
mappings, output=catalog_file, dir=dir, append=append_catalog
)

def get_imported_ontologies(self, recursive=False):
"""Return a list with imported ontologies.
Expand Down Expand Up @@ -837,23 +955,27 @@ def sync_reasoner(

Keyword arguments are passed to the underlying owlready2 function.
"""
if reasoner == "Pellet":
if reasoner == "FaCT++":
sync = sync_reasoner_factpp
elif reasoner == "Pellet":
sync = owlready2.sync_reasoner_pellet
elif reasoner == "HermiT":
sync = owlready2.sync_reasoner_hermit
elif reasoner == "FaCT++":
sync = sync_reasoner_factpp
else:
raise ValueError(
f"unknown reasoner {reasoner!r}. Supported reasoners are "
'"Pellet", "HermiT" and "FaCT++".'
f"unknown reasoner {reasoner!r}. Supported reasoners "
'are "Pellet", "HermiT" and "FaCT++".'
)

if include_imported:
with self:
sync(**kwargs)
else:
sync([self], **kwargs)
# For some reason we must visit all entities once before running
# the reasoner...
list(self.get_entities())

with self:
if include_imported:
sync(self.world, **kwargs)
else:
sync(self, **kwargs)

def sync_attributes( # pylint: disable=too-many-branches
self,
Expand Down Expand Up @@ -914,11 +1036,10 @@ class prefLabel(owlready2.label):
if not hasattr(ind, "prefLabel"):
# no prefLabel - create new annotation property..
with self:

# pylint: disable=invalid-name,missing-class-docstring
# pylint: disable=function-redefined
class prefLabel(owlready2.label):
pass
iri = "http://www.w3.org/2004/02/skos/core#prefLabel"

ind.prefLabel = [locstr(ind.name, lang="en")]
elif not ind.prefLabel:
Expand Down
Loading