Skip to content
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
6 changes: 3 additions & 3 deletions docs/session.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,21 @@ GraphDBTest:
backend: sparqlwrapper
base_iri: http://localhost:7200/repositories/test_repo
update_iri: http://localhost:7200/repositories/test_repo/statements
check_url: http://localhost:7200/repositories
check_iri: http://localhost:7200/repositories

FusekiTest:
backend: sparqlwrapper
base_iri: http://localhost:3030/test_repo
update_iri: http://localhost:3030/test_repo/update
check_url: http://localhost:3030
check_iri: http://localhost:3030
username: admin
password: admin0

MyKB:
backend: sparqlwrapper
base_iri: https://graphdb.myproject.eu/repositories/test_repo
update_iri: https://graphdb.myproject.eu/repositories/test_repo/statements
check_url: https://graphdb.myproject.eu/repositories
check_iri: https://graphdb.myproject.eu/repositories
username: myname
password: KEYRING
```
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""
Test the sparqlwrapper backend with GraphDB.
Note that this requires to have a running graphDB instance set up
as described in the for_developers documentation on
https://emmc-asbl.github.io/tripper/latest/developers/.
A general test for all backends supporting SPARQL.

Note that this test expects that that you have a Fuseki and a graphDB
instance running.

See https://emmc-asbl.github.io/tripper/latest/developers/
"""

from pathlib import Path
Expand All @@ -20,6 +22,7 @@


# if True:
# sessionName = "RdflibTest"
# sessionName = "GraphDBTest"
# sessionName = "FusekiTest"
def populate_and_search(sessionName): # pylint: disable=too-many-statements
Expand All @@ -30,7 +33,7 @@ def populate_and_search(sessionName): # pylint: disable=too-many-statements
from tripper.datadoc import acquire, save_datadoc, search

ts = session.get_triplestore(sessionName)
if not ts.available(timeout=1):
if not ts.is_available(timeout=1):
pytest.skip(f"{sessionName} service not available; skipping test.")

datasetinput = thisdir / "datadocumentation_sample.yaml"
Expand Down Expand Up @@ -183,3 +186,9 @@ def test_fuseki():
"""Test the sparqlwrapper backend using Fuseki."""
# Use service configured in tests/input/session.yaml
populate_and_search("FusekiTest")


def test_rdflib():
"""Test the sparqlwrapper backend using rdflib."""
# Use service configured in tests/input/session.yaml
populate_and_search("RdflibTest")
16 changes: 9 additions & 7 deletions tests/datadoc/test_prefixes.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,19 @@ def test_prefixes():

save_prefixes(ts, prefixes1)
r2 = load_prefixes(ts)
assert set(r2) == set(prefixes1.items())
assert set((str(p), str(ns)) for p, ns in r2) == set(prefixes1.items())

save_prefixes(ts, prefixes2)
r3 = load_prefixes(ts)
assert set(r3) == set(prefixes1.items()).union(prefixes2.items())
assert set((str(p), str(ns)) for p, ns in r3) == set(
prefixes1.items()
).union(prefixes2.items())

save_prefixes(ts, prefixes3)
r4 = load_prefixes(ts)
assert set(r4) == set(prefixes1.items()).union(prefixes2.items()).union(
prefixes3.items()
)
assert set((str(p), str(ns)) for p, ns in r4) == set(
prefixes1.items()
).union(prefixes2.items()).union(prefixes3.items())

save_prefixes(ts, prefixes3) # Save the same prefixes twice
r5 = load_prefixes(ts)
Expand All @@ -66,7 +68,7 @@ def test_prefixes():
assert r6 == [("owl", prefixes2["owl"])]

r7 = load_prefixes(ts, prefix="dcat")
assert set(r7) == {
assert set((str(p), str(ns)) for p, ns in r7) == {
("dcat", prefixes2["dcat"]),
("dcat", prefixes3["dcat"]),
}
Expand All @@ -75,7 +77,7 @@ def test_prefixes():
assert r8 == [("adms", prefixes1["adms"])]

r9 = load_prefixes(ts, namespace=prefixes3["dct"])
assert set(r9) == {
assert set((str(p), str(ns)) for p, ns in r9) == {
("dcterms", prefixes1["dcterms"]),
("dct", prefixes3["dct"]),
}
4 changes: 2 additions & 2 deletions tests/input/session.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ FusekiTest:
backend: sparqlwrapper
base_iri: http://localhost:3030/test_repo
update_iri: http://localhost:3030/test_repo/update
check_url: http://localhost:3030
check_iri: http://localhost:3030
username: admin
password: admin0

GraphDBTest:
backend: sparqlwrapper
base_iri: http://localhost:7200/repositories/test_repo
update_iri: http://localhost:7200/repositories/test_repo/statements
check_url: http://localhost:7200/repositories
check_iri: http://localhost:7200/repositories
7 changes: 5 additions & 2 deletions tests/test_sparql.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def test_sparql_select2():
pytest.importorskip("rdflib")
from textwrap import dedent

from tripper import Triplestore
from tripper import Literal, Triplestore

data = dedent(
"""
Expand Down Expand Up @@ -128,7 +128,10 @@ def test_sparql_select2():
ts.parse(data=data)
r = ts.query(query)

assert set(r) == {("Alice", "Bob", "None"), ("Alice", "Clare", "CT")}
assert set(r) == {
(Literal("Alice"), Literal("Bob"), "None"),
(Literal("Alice"), Literal("Clare"), Literal("CT")),
}


# if True:
Expand Down
34 changes: 15 additions & 19 deletions tripper/backends/rdflib.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ def totriple(triple: "Triple"):
return tordflib(s), tordflib(p), tordflib(o)


def fromrdflib(
value: "Union[URIRef, rdflibLiteral, BNode]",
) -> "Union[str, Literal]":
"""Help function converting an rdflib value to corresponding tripper value."""
if isinstance(value, rdflibLiteral):
return parse_literal(value)
if isinstance(value, BNode) and not value.startswith("_:"):
return f"_:{value}"
return str(value)


class RdflibStrategy:
"""Triplestore strategy for rdflib.

Expand Down Expand Up @@ -208,7 +219,8 @@ def query(
return result # type: ignore

if resulttype == "SELECT":
return [tuple(str(v) for v in row) for row in result] # type: ignore
# type: ignore
return [tuple(fromrdflib(v) for v in row) for row in result]
if resulttype == "ASK":
return bool(result)
if resulttype in ("CONSTRUCT", "DESCRIBE"):
Expand Down Expand Up @@ -258,21 +270,5 @@ def namespaces(self) -> dict:
def _convert_triples_to_tripper(triples) -> "Generator[Triple, None, None]":
"""Help function that converts a iterator/generator of rdflib triples
to tripper triples."""
for s, p, o in triples: ### p ylint: disable=not-an-iterable
yield (
(
f"_:{s}"
if isinstance(s, BNode) and not s.startswith("_:")
else str(s)
),
str(p),
(
parse_literal(o)
if isinstance(o, rdflibLiteral)
else (
f"_:{o}"
if isinstance(o, BNode) and not o.startswith("_:")
else str(o)
)
),
)
for s, p, o in triples: # pylint: disable=not-an-iterable
yield (fromrdflib(s), str(p), fromrdflib(o))
36 changes: 33 additions & 3 deletions tripper/backends/sparqlwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from tripper import Literal
from tripper.backends.rdflib import _convert_triples_to_tripper
from tripper.errors import TripperError
from tripper.utils import check_service_availability

try:
from SPARQLWrapper import GET, JSON, POST, TURTLE, SPARQLWrapper
Expand All @@ -31,7 +32,10 @@ class SparqlwrapperStrategy:
Arguments:
base_iri: SPARQL endpoint.
update_iri: Update SPARQL endpoint. For some triplestores (e.g.
GraphDB), update endpoint is different from base endpoint.
GraphDB), update endpoint is different from base endpoint.
Defaults to base_iri.
check_iri: IRI to use for checking that the triplestore is available.
Defaults to base_iri.
username: User name.
password: Password.
kwargs: Additional arguments passed to the SPARQLWrapper constructor.
Expand All @@ -44,10 +48,14 @@ def __init__(
self,
base_iri: str,
update_iri: "Optional[str]" = None,
check_iri: "Optional[str]" = None,
username: "Optional[str]" = None,
password: "Optional[str]" = None,
**kwargs,
) -> None:
self.update_iri = update_iri if update_iri else base_iri
self.check_iri = check_iri if check_iri else base_iri

kwargs.pop(
"database", None
) # database is not used in the SPARQLWrapper backend
Expand All @@ -57,8 +65,6 @@ def __init__(
if username and password:
self.sparql.setCredentials(username, password)

self.update_iri = update_iri

@property
def update_iri(self) -> "Optional[str]":
"""Getter for the update IRI."""
Expand Down Expand Up @@ -178,6 +184,30 @@ def update(
self.sparql.setQuery(update_object)
self.sparql.query()

def is_available(self, timeout: float = 5, interval: float = 1) -> bool:
"""Checks if the backend is available.

This is done by sending a request to the URL specified
in the `check_iri` attribute and checking for the response.

Arguments:
timeout: Total time in seconds to wait for a response.
interval: Internal time interval in seconds between checking if
the service has responded.

Returns:
Returns true if the service responds with code 200,
otherwise false is returned.

"""
if self.check_iri is None:
raise ValueError(
"`check_iri` must be assigned before calling is_available()"
)
return check_service_availability(
self.check_iri, timeout=timeout, interval=interval
)

def triples(self, triple: "Triple") -> "Generator[Triple, None, None]":
"""Returns a generator over matching triples."""
variables = [
Expand Down
2 changes: 1 addition & 1 deletion tripper/datadoc/keywords.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ def write_doc_keywords(self, outfile: "FileLoc") -> None:
"Edit the keywords.yaml file instead. -->",
"",
f"# Keywords{theme}",
f"The tables below lists the keywords the theme {self.theme}.",
f"The tables below lists the keywords for the theme {self.theme}.",
"",
"The meaning of the columns are as follows:",
"",
Expand Down
12 changes: 12 additions & 0 deletions tripper/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,18 @@ def bind(self, prefix: str, namespace: str) -> Namespace:
Should only be defined if the backend supports namespaces.
"""

def is_available(self, timeout: float = 5, interval: float = 1) -> bool:
"""Checks if the backend is available.

Arguments:
timeout: Total time in seconds to wait for a response.
interval: Internal time interval in seconds between checking if
the service has responded.

Returns:
Returns true if the backend is available.
"""

def namespaces(self) -> dict:
"""Returns a dict mapping prefixes to namespaces.

Expand Down
42 changes: 37 additions & 5 deletions tripper/triplestore.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def __init__(
base_iri: "Optional[str]" = None,
database: "Optional[str]" = None,
package: "Optional[str]" = None,
check_url: "Optional[str]" = None,
check_url: "Optional[str]" = None, # Deprecated
**kwargs,
) -> None:
"""Initialise triplestore using the backend with the given name.
Expand All @@ -162,8 +162,9 @@ def __init__(
supports it).
package: Required when `backend` is a relative module. In that
case, it is relative to `package`.
check_url: A URL to use for checking that the backend is available.
Defaults to `base_iri`.
check_url: Deprecated. A URL for checking whether the backend
is available. Use the optional keyword argument `check_iri`
instead. Defaults to `base_iri`.
kwargs: Keyword arguments passed to the backend's __init__()
method.

Expand All @@ -175,7 +176,6 @@ def __init__(
namespaces: Dict mapping namespace prefixes to IRIs.
package: Name of Python package if the backend is implemented as
a relative module. Assigned to the `package` argument.
check_url: The value of the `check_url` argument.

Notes:
If the backend establishes a connection that should be closed
Expand All @@ -189,6 +189,15 @@ def __init__(
This ensures that the connection is automatically closed when the
context manager exits.
"""
if check_url:
warnings.warn(
"Argument `check_url` is deprecated. It defaults to "
"`base_iri` and is rarely needed. If needed, `check_iri` "
"instead.",
DeprecationWarning,
stacklevel=2,
)

backend_name = backend.rsplit(".", 1)[-1]
module = self._load_backend(backend, package)
cls = getattr(module, f"{backend_name.title()}Strategy")
Expand All @@ -198,7 +207,7 @@ def __init__(
self.backend_name = backend_name
self.database = database
self.package = package
self.check_url = check_url if check_url else base_iri
self.check_url = check_url if check_url else base_iri # Deprecated
self.kwargs = kwargs.copy()
self.backend = cls(base_iri=base_iri, database=database, **kwargs)

Expand Down Expand Up @@ -583,6 +592,23 @@ def bind(
self.namespaces[prefix] = ns
return ns

def is_available(self, timeout: float = 5, interval: float = 1) -> bool:
"""Checks if the backend is available.

Arguments:
timeout: Total time in seconds to wait for a response.
interval: Internal time interval in seconds between checking if
the service has responded.

Returns:
Returns true if the backend is available.
"""
if hasattr(self.backend, "is_available"):
return self.backend.is_available(
timeout=timeout, interval=interval
)
return True

@classmethod
def create_database(cls, backend: str, database: str, **kwargs):
"""Create a new database in backend.
Expand Down Expand Up @@ -1085,6 +1111,12 @@ def available(self, timeout: float = 5, interval: float = 1) -> bool:
otherwise false is returned.

"""
warnings.warn(
"Method `Triplestore.available()` is deprecated. "
"Use `Triplestore.is_available()` instead.",
DeprecationWarning,
stacklevel=2,
)
if self.check_url is None:
raise ValueError(
"`check_url` must be assigned before calling available()"
Expand Down