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."""
import abc
import urllib.parse
from typing import Iterable
import requests
@ -41,8 +42,14 @@ class RemoteSparqlBackend(SparqlBackend):
self._session.headers["User-Agent"] = agent
def query(self, query: str) -> Iterable[tuple]:
params = {"format": "json", "query": query}
resp = self._session.post(self._url, params=params).json()
headers = {
"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"]
for result in resp["results"]["bindings"]:
yield tuple(result.get(variable) for variable in variables)

View File

@ -78,6 +78,10 @@ class LiteralField(Field[rdflib.Literal]):
predicate: rdflib.URIRef
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:
"""Function suitable as ``key`` argument to :func:`sorted`.

View File

@ -16,6 +16,8 @@
# pylint: disable=redefined-outer-name
import urllib.parse
import pytest
import rdflib
@ -33,11 +35,15 @@ def rdflib_sparql(requests_mock, rdflib_graph: rdflib.Graph) -> RemoteSparqlBack
"""Returns a SPARQL backend instance for ``rdflib_graph``."""
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
return {
"head": {"vars": result.vars},
"results": list(result),
"head": {"vars": results.vars},
"results": {
"bindings": [dict(zip(results.vars, result)) for result in results]
},
}
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
# 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 pytest
@ -20,6 +25,40 @@ import rdflib
from glowtables.sparql import SparqlBackend
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:
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:
@ -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:
@ -82,22 +126,33 @@ def test_default_value(rdflib_sparql: SparqlBackend) -> None:
rdflib.URIRef("http://example.org/display-name"),
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(
fields=[name_field],
fields=[name_field, frequency_field],
constraints="?subject <http://example.org/type> <http://example.org/CPU>.",
)
assert table.sparql() == textwrap.dedent(
"""
SELECT ?display_name
SELECT ?display_name ?frequency
WHERE {
?subject <http://example.org/type> <http://example.org/CPU>.
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: