Parse values

This commit is contained in:
Val Lorentz 2023-05-23 20:00:45 +02:00
parent a4087ac180
commit 44eb8147c8
2 changed files with 35 additions and 11 deletions

View File

@ -23,6 +23,8 @@ from typing import Any, Callable, Generic, Iterator, NewType, Optional, TypeVar
import rdflib
from glowtables.sparql import SparqlBackend
Language = NewType("Language", str)
"""ISO 639-1 code"""
@ -42,6 +44,9 @@ class Field(abc.ABC, Generic[_TFieldValue]):
display_names: dict[Language, str]
"""Localized name for the field (eg. in a table header)"""
parse: Callable[[str], _TFieldValue]
"""Parses a string returned by a SPARQL query to a native Python value."""
@abc.abstractmethod
def sort_key(self, value: _TFieldValue):
"""Function suitable as ``key`` argument to :func:`sorted`.
@ -72,18 +77,18 @@ class Field(abc.ABC, Generic[_TFieldValue]):
@dataclasses.dataclass
class LiteralField(Field[rdflib.Literal]):
class LiteralField(Field[_TFieldValue], Generic[_TFieldValue]):
"""Simplest field: its value is a literal directly on the subject"""
predicate: rdflib.URIRef
default: Optional[rdflib.Literal] = None
default: Optional[_TFieldValue] = 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: Optional[_TFieldValue]) -> Any:
"""Function suitable as ``key`` argument to :func:`sorted`.
Defaults to the identity function."""
@ -176,3 +181,13 @@ class Table:
constraints=constraints,
statements=statements.strip(),
)
def query(self, backend: SparqlBackend) -> Iterator[tuple]:
"""Returns a list of all rows of the table. Each row has exactly one cell for
each column defined in :attr:`fields`.
"""
for row in backend.query(self.sparql()):
yield tuple(
None if cell is None else field.parse(cell)
for (field, cell) in zip(self.fields, row)
)

View File

@ -18,6 +18,7 @@ Test cases use a graph containing a few CPUs, with names and clock frequencies.
"""
import textwrap
from decimal import Decimal
import pytest
import rdflib
@ -64,6 +65,7 @@ def test_single_literal(rdflib_sparql: SparqlBackend) -> None:
name_field = LiteralField(
"display_name",
{Language("en"): "Name"},
str,
rdflib.URIRef("http://example.org/display-name"),
)
table = Table(
@ -81,7 +83,7 @@ def test_single_literal(rdflib_sparql: SparqlBackend) -> None:
"""
)
assert set(rdflib_sparql.query(table.sparql())) == {
assert set(table.query(rdflib_sparql)) == {
("Grown 1700",),
("Grown 3600",),
}
@ -91,11 +93,13 @@ def test_two_literals(rdflib_sparql: SparqlBackend) -> None:
name_field = LiteralField(
"display_name",
{Language("en"): "Name"},
str,
rdflib.URIRef("http://example.org/display-name"),
)
frequency_field = LiteralField(
"frequency",
{Language("en"): "Clock frequency"},
Decimal,
rdflib.URIRef("http://example.org/clock-frequency"),
)
table = Table(
@ -114,8 +118,8 @@ def test_two_literals(rdflib_sparql: SparqlBackend) -> None:
"""
)
assert set(rdflib_sparql.query(table.sparql())) == {
("Grown 1700", "3000"),
assert set(table.query(rdflib_sparql)) == {
("Grown 1700", 3000),
}
@ -123,14 +127,16 @@ def test_default_value(rdflib_sparql: SparqlBackend) -> None:
name_field = LiteralField(
"display_name",
{Language("en"): "Name"},
str,
rdflib.URIRef("http://example.org/display-name"),
default=rdflib.Literal("Anonymous CPU"),
default="Anonymous CPU",
)
frequency_field = LiteralField(
"frequency",
{Language("en"): "Clock frequency"},
Decimal,
rdflib.URIRef("http://example.org/clock-frequency"),
default=rdflib.Literal(0),
default=Decimal(0),
)
table = Table(
fields=[name_field, frequency_field],
@ -148,10 +154,10 @@ def test_default_value(rdflib_sparql: SparqlBackend) -> None:
"""
)
assert set(rdflib_sparql.query(table.sparql())) == {
("Grown 1700", "3000"),
assert set(table.query(rdflib_sparql)) == {
("Grown 1700", 3000),
("Grown 3600", None),
(None, "9000"),
(None, 9000),
}
@ -159,6 +165,7 @@ def test_field_id_subject() -> None:
name_field = LiteralField(
"subject",
{Language("en"): "Name"},
str,
rdflib.URIRef("http://example.org/display-name"),
)
with pytest.raises(ValueError, match="both subject and a field id"):
@ -172,11 +179,13 @@ def test_field_id_clash() -> None:
name_field = LiteralField(
"name",
{Language("en"): "Name"},
str,
rdflib.URIRef("http://example.org/name"),
)
display_name_field = LiteralField(
"name",
{Language("en"): "Display Name"},
str,
rdflib.URIRef("http://example.org/display-name"),
)
with pytest.raises(ValueError, match="has duplicate field ids: name"):