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

Added support for simple persistent storage in the rdflib backend #15

Merged
merged 5 commits into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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