Skip to content

Commit

Permalink
Update save recursive and layout (#710)
Browse files Browse the repository at this point in the history
# Description
    Update layout and fix recursive saving of ontology

    * save now returns the path to the saved ontology.
      This change does not change the previous usage.
      This is practical when the filename is generated and not given.

    * generated layout is updated so that the root is (protocol+domain)
if there are more of these. Otherwise the generated layout is as before.

* updated test_save to check emmo-structure, recursive saving, also for
emmo based domain ontologies that import ontologies with various starts
of iris

## Type of change
<!-- Put an `x` in the box that applies. -->
- [x] Bug fix.
- [x] New feature.
- [x] Documentation update.
- [x] 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: Jesper Friis <jesper-friis@users.noreply.github.com>
  • Loading branch information
francescalb and jesper-friis authored Jan 26, 2024
1 parent d59e983 commit 1e02982
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 65 deletions.
66 changes: 42 additions & 24 deletions ontopy/ontology.py
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,7 @@ def save(
write_catalog_file=False,
append_catalog=False,
catalog_file="catalog-v001.xml",
):
) -> Path:
"""Writes the ontology to file.
Parameters
Expand All @@ -891,17 +891,26 @@ def save(
recursive: bool
Whether to save imported ontologies recursively. This is
commonly combined with `filename=None`, `dir` and `mkdir`.
Note that depending on the structure of the ontology and
all imports the ontology might end up in a subdirectory.
If filename is given, the ontology is saved to the given
directory.
The path to the final location is returned.
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`.
It makes 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`.
Returns
-------
The path to the saved ontology.
"""
# pylint: disable=redefined-builtin,too-many-arguments
# pylint: disable=too-many-statements,too-many-branches
Expand All @@ -922,36 +931,44 @@ def save(
"'Known issues' section of the README."
)
)

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}"
file = f"{self.name}.{fmt}"
else:
raise TypeError("`filename` and `format` cannot both be None.")
filename = os.path.join(dir, filename)
dir = Path(filename).resolve().parent
else:
file = filename
filepath = os.path.join(dir, file)
returnpath = filepath

dir = Path(filepath).resolve().parent

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

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

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

if recursive:
if squash:
raise ValueError(
"`recursive` and `squash` should not both be true"
)
layout = directory_layout(self)

if filename:
layout[self] = file.rstrip(f".{fmt}")
# Update path to where the ontology is saved
# Note that filename should include format
# when given
returnpath = Path(dir) / f"{layout[self]}.{fmt}"
for onto, path in layout.items():
fname = Path(dir) / f"{path}.{fmt}"
onto.save(
Expand Down Expand Up @@ -981,16 +998,7 @@ def save(
directory=dir,
append=append_catalog,
)

elif write_catalog_file:
write_catalog(
{self.get_version(as_iri=True): filename},
output=catalog_file,
directory=dir,
append=append_catalog,
)

if squash:
elif squash:
URIRef, RDF, OWL = rdflib.URIRef, rdflib.RDF, rdflib.OWL
iri = self.iri if self.iri else self.base_iri
graph = self.world.as_rdflib_graph()
Expand All @@ -1010,9 +1018,9 @@ def save(
graph.remove((s, p, o))
graph.add((URIRef(self.iri), p, o))

graph.serialize(destination=filename, format=format)
graph.serialize(destination=filepath, format=format)
elif format in OWLREADY2_FORMATS:
super().save(file=filename, format=fmt)
super().save(file=filepath, format=fmt)
else:
# The try-finally clause is needed for cleanup and because
# we have to provide delete=False to NamedTemporaryFile
Expand Down Expand Up @@ -1040,10 +1048,20 @@ def save(
):
graph.remove((s, p, o))
graph.add((rdflib.URIRef(self.iri), p, o))
graph.serialize(destination=filename, format=format)
graph.serialize(destination=filepath, format=format)
finally:
os.remove(tmpfile)

if write_catalog_file and not recursive:
write_catalog(
{self.get_version(as_iri=True): filepath},
output=catalog_file,
directory=dir,
append=append_catalog,
)

return Path(returnpath)

def get_imported_ontologies(self, recursive=False):
"""Return a list with imported ontologies.
Expand Down
24 changes: 20 additions & 4 deletions ontopy/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,14 @@ def directory_layout(onto):
where `ontoA`, `ontoB` and `ontoC` are imported Ontology objects.
"""
all_imported = [
imported.base_iri for imported in onto.indirectly_imported_ontologies()
]
# get protocol and domain of all imported ontologies
namespace_roots = set()
for iri in all_imported:
protocol, domain, *_ = urllib.parse.urlsplit(iri)
namespace_roots.add("://".join([protocol, domain]))

def recur(o):
baseiri = o.base_iri.rstrip("/#")
Expand All @@ -811,10 +819,18 @@ def recur(o):

layout = {}
recur(onto)

# Strip off initial common prefix from all paths
prefix = os.path.commonprefix(list(layout.values()))
for o, path in layout.items():
layout[o] = path[len(prefix) :].lstrip("/")
if len(namespace_roots) == 1:
prefix = os.path.commonprefix(list(layout.values()))
for o, path in layout.items():
layout[o] = path[len(prefix) :].lstrip("/")
else:
for o, path in layout.items():
for namespace_root in namespace_roots:
if path.startswith(namespace_root):
layout[o] = (
urllib.parse.urlsplit(namespace_root)[1]
+ path[len(namespace_root) :]
)

return layout
23 changes: 0 additions & 23 deletions tests/test_ontology_squash.py

This file was deleted.

Loading

0 comments on commit 1e02982

Please sign in to comment.