Skip to content

Commit

Permalink
Allow controling ontology IRI when saving (#700)
Browse files Browse the repository at this point in the history
# Description
Allow separating the IRI of the ontology from `base_iri` when saving
using rdflib.

Owlready2 doesn't allow to separate the IRI of the
ontology from the `base_iri` (the namespace of the entities in the
ontology). In many ontologies, e.g. EMMO, are these not the same.

Further work: Also assign the ontology IRI when loading an ontology. 

## Type of change
<!-- Put an `x` in the box that applies. -->
- [ ] Bug fix.
- [x] New feature.
- [ ] Documentation update.
- [ ] Test update.

## Checklist
<!-- Put an `x` in the boxes that apply. You can also fill these out
after creating the PR. -->

This checklist can be used as a help for the reviewer.

- [ ] Is the code easy to read and understand?
- [ ] Are comments for humans to read, not computers to disregard?
- [ ] Does a new feature has an accompanying new test (in the CI or unit
testing schemes)?
- [ ] Has the documentation been updated as necessary?
- [ ] Does this close the issue?
- [ ] Is the change limited to the issue?
- [ ] Are errors handled for all outcomes?
- [ ] Does the new feature provide new restrictions on dependencies, and
if so is this documented?

## Comments
<!-- Additional comments here, including clarifications on checklist if
applicable. -->

---------

Co-authored-by: Francesca L. Bleken <48128015+francescalb@users.noreply.github.com>
  • Loading branch information
jesper-friis and francescalb authored Jan 19, 2024
1 parent ada4ea5 commit 9df0345
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 12 deletions.
45 changes: 33 additions & 12 deletions ontopy/ontology.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,21 @@ def get_unabbreviated_triples(


class Ontology(owlready2.Ontology): # pylint: disable=too-many-public-methods
"""A generic class extending owlready2.Ontology."""
"""A generic class extending owlready2.Ontology.
Additional attributes:
iri: IRI of this ontology. Currently only used for serialisation
with rdflib. Defaults to None, meaning `base_iri` will be used
instead.
label_annotations: List of label annotations, i.e. annotations
that are recognised by the get_by_label() method. Defaults
to `[skos:prefLabel, rdf:label, skos:altLabel]`.
prefix: Prefix for this ontology. Defaults to None.
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.iri = None
self.label_annotations = DEFAULT_LABEL_ANNOTATIONS[:]
self.prefix = None

Expand Down Expand Up @@ -934,10 +945,6 @@ def save(
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(
Expand Down Expand Up @@ -984,14 +991,14 @@ def save(
)

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

URIRef, RDF, OWL = rdflib.URIRef, rdflib.RDF, rdflib.OWL
graph = self.world.as_rdflib_graph()
graph.namespace_manager.bind("emmo", EMMO)
graph.namespace_manager.bind("", rdflib.Namespace(self.base_iri))
if self.iri:
base_iri = URIRef(self.base_iri)
for s, p, o in graph.triples((base_iri, None, None)):
graph.remove((s, p, o))
graph.add((URIRef(self.iri), p, o))

# Remove anonymous namespace and imports
graph.remove((URIRef("http://anonymous"), RDF.type, OWL.Ontology))
Expand All @@ -1015,6 +1022,20 @@ def save(
super().save(tmpfile, format="ntriples")
graph = rdflib.Graph()
graph.parse(tmpfile, format="ntriples")
graph.namespace_manager.bind(
"", rdflib.Namespace(self.base_iri)
)
if self.iri:
base_iri = rdflib.URIRef(self.base_iri)
for (
s,
p,
o,
) in graph.triples( # pylint: disable=not-an-iterable
(base_iri, None, None)
):
graph.remove((s, p, o))
graph.add((rdflib.URIRef(self.iri), p, o))
graph.serialize(destination=filename, format=format)
finally:
os.remove(tmpfile)
Expand Down
26 changes: 26 additions & 0 deletions tests/ontopy_tests/test_iri.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import re
from pathlib import Path

from ontopy import get_ontology


# if True:
def test_iri():
thisdir = Path(__file__).resolve().parent
ontodir = thisdir.parent / "testonto"
outdir = thisdir.parent / "output"

onto = get_ontology(ontodir / "testonto.ttl").load()
onto.base_iri = "http://example.com/onto"
onto.iri = "http://example.com/onto/testonto"
ontofile = outdir / "testonto.ttl"
if ontofile.exists():
ontofile.unlink()
onto.save(outdir / "testonto.ttl")

# Load saved ontology and make sure that base_iri and iri are stored
# correctly
with open(outdir / "testonto.ttl", mode="r") as f:
ttl = f.read()
assert re.findall(r"@prefix : (\S+) \.", ttl) == [f"<{onto.base_iri}>"]
assert re.findall(r"(\S+) a owl:Ontology", ttl) == [f"<{onto.iri}>"]
1 change: 1 addition & 0 deletions tests/output/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.ttl

0 comments on commit 9df0345

Please sign in to comment.