Make RemoteSparqlBackend actually work and use it in Field/Table tests

This commit is contained in:
Val Lorentz 2023-05-21 21:29:05 +02:00
parent fb02fb3841
commit a4087ac180
4 changed files with 82 additions and 10 deletions

View File

@ -15,6 +15,7 @@
"""Abstraction over SPARQL backends, primarily meant to be mocked by tests.""" """Abstraction over SPARQL backends, primarily meant to be mocked by tests."""
import abc import abc
import urllib.parse
from typing import Iterable from typing import Iterable
import requests import requests
@ -41,8 +42,14 @@ class RemoteSparqlBackend(SparqlBackend):
self._session.headers["User-Agent"] = agent self._session.headers["User-Agent"] = agent
def query(self, query: str) -> Iterable[tuple]: def query(self, query: str) -> Iterable[tuple]:
params = {"format": "json", "query": query} headers = {
resp = self._session.post(self._url, params=params).json() "Content-Type": "application/sparql-query",
"Accept": "application/json",
}
params = {"query": query}
resp = self._session.post(
self._url, headers=headers, data=urllib.parse.urlencode(params)
).json()
variables = resp["head"]["vars"] variables = resp["head"]["vars"]
for result in resp["results"]["bindings"]: for result in resp["results"]["bindings"]:
yield tuple(result.get(variable) for variable in variables) yield tuple(result.get(variable) for variable in variables)

View File

@ -78,6 +78,10 @@ class LiteralField(Field[rdflib.Literal]):
predicate: rdflib.URIRef predicate: rdflib.URIRef
default: Optional[rdflib.Literal] = None default: Optional[rdflib.Literal] = None
"""If this is not :const:`None`, allows subjects without a statement for this field;
and use this value instead when sorting.
This is only used when sorting, and isn't displayed."""
def sort_key(self, value: rdflib.Literal) -> Any: def sort_key(self, value: rdflib.Literal) -> Any:
"""Function suitable as ``key`` argument to :func:`sorted`. """Function suitable as ``key`` argument to :func:`sorted`.

View File

@ -16,6 +16,8 @@
# pylint: disable=redefined-outer-name # pylint: disable=redefined-outer-name
import urllib.parse
import pytest import pytest
import rdflib import rdflib
@ -33,11 +35,15 @@ def rdflib_sparql(requests_mock, rdflib_graph: rdflib.Graph) -> RemoteSparqlBack
"""Returns a SPARQL backend instance for ``rdflib_graph``.""" """Returns a SPARQL backend instance for ``rdflib_graph``."""
def json_callback(request, context): def json_callback(request, context):
result = rdflib_graph.query(request.json()) params = urllib.parse.parse_qs(request.text)
(query,) = params["query"]
results = rdflib_graph.query(query)
context.status_code = 200 context.status_code = 200
return { return {
"head": {"vars": result.vars}, "head": {"vars": results.vars},
"results": list(result), "results": {
"bindings": [dict(zip(results.vars, result)) for result in results]
},
} }
requests_mock.register_uri("POST", "mock://sparql.example.org/", json=json_callback) requests_mock.register_uri("POST", "mock://sparql.example.org/", json=json_callback)

View File

@ -12,6 +12,11 @@
# You should have received a copy of the GNU Affero General Public License along with # You should have received a copy of the GNU Affero General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>. # this program. If not, see <http://www.gnu.org/licenses/>.
"""Tests :mod:`glowtables.table`.
Test cases use a graph containing a few CPUs, with names and clock frequencies.
"""
import textwrap import textwrap
import pytest import pytest
@ -20,6 +25,40 @@ import rdflib
from glowtables.sparql import SparqlBackend from glowtables.sparql import SparqlBackend
from glowtables.table import Language, LiteralField, Table from glowtables.table import Language, LiteralField, Table
CPU1_URI = rdflib.URIRef("http://example.org/grown-1700")
CPU2_URI = rdflib.URIRef("http://example.org/grown-3600")
CPU3_URI = rdflib.URIRef("http://example.org/secret-project")
CG_URI = rdflib.URIRef("http://example.org/likestone-underdog")
CPU = rdflib.URIRef("http://example.org/CPU")
DISPLAY_NAME = rdflib.URIRef("http://example.org/display-name")
FREQUENCY = rdflib.URIRef("http://example.org/clock-frequency")
TYPE = rdflib.URIRef("http://example.org/type")
@pytest.fixture()
def rdflib_graph() -> rdflib.Graph:
graph = rdflib.Graph()
# A CPU with all the properties
graph.add((CPU1_URI, DISPLAY_NAME, rdflib.Literal("Grown 1700")))
graph.add((CPU1_URI, TYPE, CPU))
graph.add((CPU1_URI, FREQUENCY, rdflib.Literal(3000)))
# Another CPU, without a frequency
graph.add((CPU2_URI, DISPLAY_NAME, rdflib.Literal("Grown 3600")))
graph.add((CPU2_URI, TYPE, CPU))
# Another CPU, without a name
graph.add((CPU3_URI, TYPE, CPU))
graph.add((CPU3_URI, FREQUENCY, rdflib.Literal(9000)))
# Add a graphics card; which should be excluded from CPU searches
graph.add((CG_URI, DISPLAY_NAME, rdflib.Literal("LikeStone Underdog")))
graph.add((CG_URI, FREQUENCY, rdflib.Literal(1626)))
graph.add((CG_URI, TYPE, rdflib.URIRef("http://example.org/graphics-card")))
return graph
def test_single_literal(rdflib_sparql: SparqlBackend) -> None: def test_single_literal(rdflib_sparql: SparqlBackend) -> None:
name_field = LiteralField( name_field = LiteralField(
@ -42,7 +81,10 @@ def test_single_literal(rdflib_sparql: SparqlBackend) -> None:
""" """
) )
rdflib_sparql.query(table.sparql()) assert set(rdflib_sparql.query(table.sparql())) == {
("Grown 1700",),
("Grown 3600",),
}
def test_two_literals(rdflib_sparql: SparqlBackend) -> None: def test_two_literals(rdflib_sparql: SparqlBackend) -> None:
@ -72,7 +114,9 @@ def test_two_literals(rdflib_sparql: SparqlBackend) -> None:
""" """
) )
rdflib_sparql.query(table.sparql()) assert set(rdflib_sparql.query(table.sparql())) == {
("Grown 1700", "3000"),
}
def test_default_value(rdflib_sparql: SparqlBackend) -> None: def test_default_value(rdflib_sparql: SparqlBackend) -> None:
@ -82,22 +126,33 @@ def test_default_value(rdflib_sparql: SparqlBackend) -> None:
rdflib.URIRef("http://example.org/display-name"), rdflib.URIRef("http://example.org/display-name"),
default=rdflib.Literal("Anonymous CPU"), default=rdflib.Literal("Anonymous CPU"),
) )
frequency_field = LiteralField(
"frequency",
{Language("en"): "Clock frequency"},
rdflib.URIRef("http://example.org/clock-frequency"),
default=rdflib.Literal(0),
)
table = Table( table = Table(
fields=[name_field], fields=[name_field, frequency_field],
constraints="?subject <http://example.org/type> <http://example.org/CPU>.", constraints="?subject <http://example.org/type> <http://example.org/CPU>.",
) )
assert table.sparql() == textwrap.dedent( assert table.sparql() == textwrap.dedent(
""" """
SELECT ?display_name SELECT ?display_name ?frequency
WHERE { WHERE {
?subject <http://example.org/type> <http://example.org/CPU>. ?subject <http://example.org/type> <http://example.org/CPU>.
OPTIONAL { ?subject <http://example.org/display-name> ?display_name. }. OPTIONAL { ?subject <http://example.org/display-name> ?display_name. }.
OPTIONAL { ?subject <http://example.org/clock-frequency> ?frequency. }.
} }
""" """
) )
rdflib_sparql.query(table.sparql()) assert set(rdflib_sparql.query(table.sparql())) == {
("Grown 1700", "3000"),
("Grown 3600", None),
(None, "9000"),
}
def test_field_id_subject() -> None: def test_field_id_subject() -> None: