Skip to content
Merged
15 changes: 12 additions & 3 deletions docs/developers.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@

See [interface.py], which defines the interface of a backend and may serve as a template for creating new backends.

### Developing the sparqlwrapper

## Setting up local GraphDB and Fuseki services for testing
Tripper comes with an inbuilt backend to the SPARQLWrapper. In order
to test this properly a real triplestore is needed. This is not done in the
automatic workflows on github. However, a local graphDB can be setup as described below and tested with test_sparqlwrapper_graphdb.py.
automatic workflows on github. However, local graphDB and Fuseki services
can be setup as described below and tested with
`tests/backends/test_sparqlwrapper_graphdb_fuseki.py`.

The backend configurations corresponding to the local GraphDB and Fuseki services
can be found in `[tests/input/session.yaml]`.


### Setting up GraphDB service
To create the local instance of graphdb:
```bash
docker pull ontotext/graphdb:10.8.3 # latest tag 17.02.2025
Expand All @@ -30,14 +37,15 @@ You can now run the test test_sparqlwrapper_graphdb_fuseki.py with graphdb.
Note that if the graphdb instance is not found the test will just be skipped.


### Setting up Fuseki service
Similarly a jena-fuseki instance can be tested locally as follows:

```bash
docker pull stain/jena-fuseki
docker run -d --name fuseki -p 3030:3030 -e ADMIN_PASSWORD=admin0 -e=FUSEKI_DATASET_1=test_repo stain/jena-fuseki
```

You can now run the test test_sparqlwrapper_graphdb_fuseki.py with fuseki.
You can now run the test `test_sparqlwrapper_graphdb_fuseki.py` with fuseki.

Note that if the fuseki instance is not found the test will just be skipped.

Expand Down Expand Up @@ -75,3 +83,4 @@ Then open http://127.0.0.1:8000/tripper/ in your browser.

[interface.py]: https://github.com/EMMC-ASBL/tripper/blob/master/tripper/interface.py
[mkdocs]: https://www.mkdocs.org/
[tests/input/session.yaml]: https://github.com/EMMC-ASBL/tripper/blob/master/tests/input/session.yaml)
23 changes: 18 additions & 5 deletions docs/session.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,47 @@ The default location of this configuration file depends on the system:
- Windows: `$HOME/AppData/Local/tripper/Config/session.yaml`
- Darwin: `$HOME/Library/Config/tripper/session.yaml`

Add some default
The schema of the YAML file is simple.
A session should have a name that identifies it and should be followed by keyword arguments accepted by the `Triplestore` constructor.

Here is an example of a possible session file:

```
---

RdflibTest:
backend: rdflib

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

FusekiTest:
backend: sparqlwrapper
base_iri: http://localhost:3030/test_repo
update_iri: http://localhost:3030/test_repo/update
check_url: 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
username: myname
password: KEYRING
```

The two first entries correspond to the GraphDB and Fuseki services
that can be started with docker as described in the [developers]
section.
The first entry is an in-memory rdflib backend.

The second and third entries correspond to GraphDB and Fuseki services,
respectively.
These can be started with docker as described in the [developers] section.

The third entry is just a dummy example, showing how to use [keyring].
The fourth entry is just a dummy example, showing how to use [keyring].

Each entry starts with the name identifying the configured triplestore.
The keywords following it, correspond to the keyword arguments passed to the
Expand Down
78 changes: 21 additions & 57 deletions tests/backends/test_sparqlwrapper_graphdb_fuseki.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,57 +5,37 @@
https://emmc-asbl.github.io/tripper/latest/developers/.
"""

from pathlib import Path

import pytest

from tripper import Session

pytest.importorskip("pyld")

# URL to check if GraphDB is running.
GRAPHDB_CHECK_URL = "http://localhost:7200/repositories"
FUSEKI_CHECK_URL = "http://localhost:3030"


def get_triplestore(tsname: str) -> "Triplestore":
"""Help function that returns a new triplestore object."""
from tripper import Triplestore

if tsname == "GraphDB":
ts = Triplestore(
backend="sparqlwrapper",
base_iri="http://localhost:7200/repositories/test_repo",
update_iri=(
"http://localhost:7200/repositories/test_repo/statements"
),
)
elif tsname == "Fuseki":
ts = Triplestore(
backend="sparqlwrapper",
base_iri=f"{FUSEKI_CHECK_URL}/test_repo",
update_iri=f"{FUSEKI_CHECK_URL}/test_repo/update",
username="admin",
password="admin0",
)
else:
raise ValueError(f"Unsupported triplestore name: {tsname}")

return ts
thisdir = Path(__file__).resolve().parent
indir = thisdir.parent / "input"

session = Session(config=indir / "session.yaml")


# if True:
# tsname = "Fuseki"
def populate_and_search(tsname): # pylint: disable=too-many-statements
# sessionName = "GraphDBTest"
# sessionName = "FusekiTest"
def populate_and_search(sessionName): # pylint: disable=too-many-statements
"""Do the test on the desried backend."""
# pylint: disable=too-many-locals

from pathlib import Path

from tripper import Literal
from tripper.datadoc import acquire, save_datadoc, search

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

datasetinput = thisdir / "datadocumentation_sample.yaml"
datasetinput2 = thisdir / "datadocumentation_sample2.yaml"

ts = get_triplestore(tsname)
EX = ts.bind("ex", "http://www.example.org/")

# Test DELETE query - clear the triplestore
Expand Down Expand Up @@ -194,28 +174,12 @@ def populate_and_search(tsname): # pylint: disable=too-many-statements


def test_graphdb():
"""
Test the sparqlwrapper backend using GraphDB.
"""
# Check if GraphDB is available and write a warning if it is not.
from tripper.utils import check_service_availability

if not check_service_availability(GRAPHDB_CHECK_URL, timeout=1):
pytest.skip("GraphDB instance not available locally; skipping tests.")

print("Testing graphdb")
populate_and_search("GraphDB")
"""Test the sparqlwrapper backend using GraphDB."""
# Use service configured in tests/input/session.yaml
populate_and_search("GraphDBTest")


def test_fuseki():
"""
Test the sparqlwrapper backend using Fuseki.
"""
# Check if Fuseki is available and write a warning if it is not.
from tripper.utils import check_service_availability

if not check_service_availability(FUSEKI_CHECK_URL, timeout=1):
pytest.skip("Fuseki instance not available locally; skipping tests.")

print("Testing fuseki")
populate_and_search("Fuseki")
"""Test the sparqlwrapper backend using Fuseki."""
# Use service configured in tests/input/session.yaml
populate_and_search("FusekiTest")
8 changes: 8 additions & 0 deletions tests/input/session.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
# Default sessions used for testing
#
# See https://emmc-asbl.github.io/tripper/latest/developers/ for how to set
# up local instances of GraphDB and Fuseki corresponding to the settings below.


RdflibTest:
backend: rdflib

FusekiTest:
backend: sparqlwrapper
base_iri: http://localhost:3030/test_repo
update_iri: http://localhost:3030/test_repo/update
check_url: 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
12 changes: 12 additions & 0 deletions tests/test_triplestore.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,18 @@ def test_restriction() -> None: # pylint: disable=too-many-statements
]


def test_availability():
"""Test availability()."""
# Already tested in backends/test_sparqlwrapper_graphdb_fuseki.py
# Just add test for missing `check_url`
pytest.importorskip("rdflib")
from tripper.triplestore import Triplestore

ts = Triplestore("rdflib")
with pytest.raises(ValueError):
ts.available()


def test_backend_rdflib(expected_function_triplestore: str) -> None:
"""Specifically test the rdflib backend Triplestore.

Expand Down
29 changes: 29 additions & 0 deletions tripper/triplestore.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
)
from tripper.utils import (
bnode_iri,
check_service_availability,
en,
expand_iri,
function_id,
Expand Down Expand Up @@ -135,6 +136,7 @@ def __init__(
base_iri: "Optional[str]" = None,
database: "Optional[str]" = None,
package: "Optional[str]" = None,
check_url: "Optional[str]" = None,
**kwargs,
) -> None:
"""Initialise triplestore using the backend with the given name.
Expand All @@ -160,6 +162,7 @@ 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.
kwargs: Keyword arguments passed to the backend's __init__()
method.

Expand All @@ -171,6 +174,7 @@ 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 @@ -193,6 +197,7 @@ def __init__(
self.backend_name = backend_name
self.database = database
self.package = package
self.check_url = check_url
self.kwargs = kwargs.copy()
self.backend = cls(base_iri=base_iri, database=database, **kwargs)

Expand Down Expand Up @@ -1063,6 +1068,30 @@ def _get_restriction_dict(self, iri):
"value": dct[p],
}

def 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_url` 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_url is None:
raise ValueError(
"`check_url` must be assigned before calling available()"
)
return check_service_availability(
self.check_url, timeout=timeout, interval=interval
)

def map(
self,
source: str,
Expand Down
7 changes: 5 additions & 2 deletions tripper/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -834,13 +834,16 @@ def get_entry_points(group: str):
return eps


def check_service_availability(url: str, timeout=5, interval=1) -> bool:
def check_service_availability(
url: str, timeout: float = 5, interval: float = 1
) -> bool:
"""Check whether the service with given URL is available.

Arguments:
url: URL of the service to check.
timeout: Total time in seconds to wait for a respond.
interval: Interval for checking response.
interval: Internal time interval in seconds between checking if the
service has responded.

Returns:
Returns true if the service responds with code 200,
Expand Down