Skip to content

Commit

Permalink
Merge pull request #15 from EMMC-ASBL/14-persistent-rdflib-storage
Browse files Browse the repository at this point in the history
Added support for simple persistent storage in the rdflib backend
  • Loading branch information
jesper-friis authored Oct 24, 2022
2 parents 23d91f7 + 40e163d commit 0f2ce17
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 8 deletions.
34 changes: 34 additions & 0 deletions tests/test_triplestore.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,40 @@ def cost2(parameter):
assert len(store.function_repo) == 4


def test_backend_rdflib_base_iri(
get_ontology_path: "Callable[[str], Path]", tmp_path: "Path"
) -> None:
"""Test rdflib with `base_iri`.
Parameters:
get_ontology_path: Fixture from `conftest.py` to retrieve a `pathlib.Path`
object pointing to an ontology test file.
tmp_path: Built-in pytest fixture, which returns a `pathlib.Path` object
representing a temporary folder.
"""
import shutil

from tripper.triplestore import RDF, Triplestore

ontopath_family = get_ontology_path("family")
tmp_onto = tmp_path / "family.ttl"
shutil.copy(ontopath_family, tmp_onto)

store = Triplestore(backend="rdflib", base_iri=f"file://{tmp_onto}")
FAM = store.bind( # pylint: disable=invalid-name
"fam", "http://onto-ns.com/ontologies/examples/family#"
)
store.add_triples(
[
(":Nils", RDF.type, FAM.Father),
(":Anna", RDF.type, FAM.Dauther),
(":Nils", FAM.hasChild, ":Anna"),
]
)
store.close()


def test_backend_ontopy(get_ontology_path: "Callable[[str], Path]") -> None:
"""Specifically test the ontopy backend Triplestore.
Expand Down
33 changes: 29 additions & 4 deletions tripper/backends/rdflib.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from rdflib import BNode, Graph
from rdflib import Literal as rdflibLiteral
from rdflib import URIRef
from rdflib.util import guess_format

from tripper.triplestore import Literal

Expand Down Expand Up @@ -38,14 +39,32 @@ def astriple(triple: "Triple"):


class RdflibStrategy:
"""Triplestore strategy for rdflib."""

def __init__(self, **kwargs) -> None: # pylint: disable=unused-argument
"""Triplestore strategy for rdflib.
Arguments:
base_iri: If given, initialise the triplestore from this storage.
When `close()` is called, the storage will be overwritten
with the current content of the triplestore.
triplestore_url: Alternative URL to the underlying ontology if
`base_iri` is not resolvable. Defaults to `base_iri`.
format: Format of storage specified with `base_iri`.
"""

def __init__(
self, base_iri: str = None, triplestore_url: str = None, format: str = None
): # pylint: disable=redefined-builtin
self.graph = Graph()
self.base_iri = base_iri
self.triplestore_url = triplestore_url if triplestore_url else base_iri
if self.triplestore_url is not None:
if format is None:
format = guess_format(self.triplestore_url)
self.parse(location=self.triplestore_url, format=format)
self.base_format = format

def triples(self, triple: "Triple") -> "Generator":
"""Returns a generator over matching triples."""
for (s, p, o,) in self.graph.triples( # pylint: disable=not-an-iterable
for s, p, o in self.graph.triples( # pylint: disable=not-an-iterable
astriple(triple)
):
yield (
Expand All @@ -66,6 +85,12 @@ def remove(self, triple: "Triple"):
self.graph.remove(astriple(triple))

# Optional methods
def close(self):
"""Close the internal RDFLib graph."""
if self.triplestore_url:
self.serialize(destination=self.triplestore_url, format=self.base_format)
self.graph.close()

def parse(
self,
source=None,
Expand Down
19 changes: 15 additions & 4 deletions tripper/triplestore.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def __init__(

if need_triplestore and triplestore is None:
url = triplestore_url if triplestore_url else iri
triplestore = Triplestore("rdflib", base_iri=iri)
triplestore = Triplestore("rdflib", base_iri=iri, triplestore_url=url)
triplestore.parse(url)

self._cache = {} if cachemode != Namespace.NO_CACHE else None
Expand Down Expand Up @@ -378,9 +378,10 @@ def __init__(self, backend: str, base_iri: str = None, **kwargs):
module = import_module(
backend if "." in backend else f"tripper.backends.{backend}"
)
cls = getattr(module, backend.title() + "Strategy")
cls = getattr(module, f"{backend.title()}Strategy")
self.base_iri = base_iri
self.namespaces: "Dict[str, Namespace]" = {}
self.closed = False
self.backend_name = backend
self.backend = cls(base_iri=base_iri, **kwargs)
# Keep functions in the triplestore for convienence even though
Expand All @@ -405,6 +406,16 @@ def remove(self, triple: "Triple"):

# Methods optionally implemented by backend
# -----------------------------------------
def close(self):
"""Calls the backend close() method if it is implemented.
Otherwise, this method has no effect.
"""
# It should be ok to call close() regardless of whether the backend
# implements this method or not. Hence, don't call _check_method().
if not self.closed and hasattr(self.backend, "close"):
self.backend.close()
self.closed = True

def parse(
self, source=None, format=None, **kwargs # pylint: disable=redefined-builtin
):
Expand Down Expand Up @@ -496,9 +507,9 @@ def add(self, triple: "Triple"):
"""Add `triple` to triplestore."""
self.add_triples([triple])

def value(
def value( # pylint: disable=redefined-builtin
self, subject=None, predicate=None, object=None, default=None, any=False
): # pylint: disable=redefined-builtin
):
"""Return the value for a pair of two criteria.
Useful if one knows that there may only be one value.
Expand Down

0 comments on commit 0f2ce17

Please sign in to comment.