diff --git a/glowtables/sparql.py b/glowtables/sparql.py
new file mode 100644
index 0000000..8c6b713
--- /dev/null
+++ b/glowtables/sparql.py
@@ -0,0 +1,45 @@
+# This file is part of the Glowtables software
+# Copyright (C) 2023 Valentin Lorentz
+#
+# This program is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Affero General Public License version 3, as published by the
+# Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License along with
+# this program. If not, see .
+
+"""Abstraction over SPARQL backends, primarily meant to be mocked by tests."""
+
+import abc
+from typing import Iterable
+
+import SPARQLWrapper
+
+
+class SparqlBackend(abc.ABC):
+ """Abstract class for SPARQL clients"""
+
+ @abc.abstractmethod
+ def query(self, query: str) -> Iterable[tuple]:
+ """Sends a SPARQL query, and returns an iterable of results."""
+
+
+class RemoteSparqlBackend(SparqlBackend):
+ """Queries a SPARQL API over HTTP."""
+
+ def __init__(self, url: str, agent: str):
+ """
+ :param url: Base URL of the endpoint
+ :param agent: User-Agent to use in HTTP requests
+ """
+ self._url = url
+ self._agent = agent
+
+ def query(self, query: str) -> Iterable[tuple]:
+ sparql = SPARQLWrapper.SPARQLWrapper(self._url, agent=self._agent)
+ sparql.setQuery(query)
+ return sparql.query()
diff --git a/glowtables/tests/conftest.py b/glowtables/tests/conftest.py
new file mode 100644
index 0000000..319f9c3
--- /dev/null
+++ b/glowtables/tests/conftest.py
@@ -0,0 +1,39 @@
+# This file is part of the Glowtables software
+# Copyright (C) 2023 Valentin Lorentz
+#
+# This program is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Affero General Public License version 3, as published by the
+# Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License along with
+# this program. If not, see .
+
+from typing import Iterable
+
+import pytest
+import rdflib
+
+from glowtables.sparql import SparqlBackend
+
+
+class RdflibSparqlBackend(SparqlBackend):
+ def __init__(self, graph: rdflib.Graph):
+ self._graph = graph
+
+ def query(self, query: str) -> Iterable[tuple]:
+ ret = self._graph.query(query)
+ return ret
+
+
+@pytest.fixture()
+def rdflib_graph():
+ return rdflib.Graph()
+
+
+@pytest.fixture()
+def rdflib_sparql(rdflib_graph: rdflib.Graph):
+ return RdflibSparqlBackend(rdflib_graph)
diff --git a/pyproject.toml b/pyproject.toml
index 2160f26..afd4a04 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,6 +29,7 @@ python_version = "3.9"
[[tool.mypy.overrides]]
module = [
"requests_mock",
+ "SPARQLWrapper",
]
ignore_missing_imports = true