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 import rdflib
from glowtables.sparql import SparqlBackend
Language = NewType("Language", str) Language = NewType("Language", str)
"""ISO 639-1 code""" """ISO 639-1 code"""
@ -42,6 +44,9 @@ class Field(abc.ABC, Generic[_TFieldValue]):
display_names: dict[Language, str] display_names: dict[Language, str]
"""Localized name for the field (eg. in a table header)""" """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 @abc.abstractmethod
def sort_key(self, value: _TFieldValue): def sort_key(self, value: _TFieldValue):
"""Function suitable as ``key`` argument to :func:`sorted`. """Function suitable as ``key`` argument to :func:`sorted`.
@ -72,18 +77,18 @@ class Field(abc.ABC, Generic[_TFieldValue]):
@dataclasses.dataclass @dataclasses.dataclass
class LiteralField(Field[rdflib.Literal]): class LiteralField(Field[_TFieldValue], Generic[_TFieldValue]):
"""Simplest field: its value is a literal directly on the subject""" """Simplest field: its value is a literal directly on the subject"""
predicate: rdflib.URIRef 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; """If this is not :const:`None`, allows subjects without a statement for this field;
and use this value instead when sorting. and use this value instead when sorting.
This is only used when sorting, and isn't displayed.""" 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`. """Function suitable as ``key`` argument to :func:`sorted`.
Defaults to the identity function.""" Defaults to the identity function."""
@ -176,3 +181,13 @@ class Table:
constraints=constraints, constraints=constraints,
statements=statements.strip(), 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 import textwrap
from decimal import Decimal
import pytest import pytest
import rdflib import rdflib
@ -64,6 +65,7 @@ def test_single_literal(rdflib_sparql: SparqlBackend) -> None:
name_field = LiteralField( name_field = LiteralField(
"display_name", "display_name",
{Language("en"): "Name"}, {Language("en"): "Name"},
str,
rdflib.URIRef("http://example.org/display-name"), rdflib.URIRef("http://example.org/display-name"),
) )
table = Table( 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 1700",),
("Grown 3600",), ("Grown 3600",),
} }
@ -91,11 +93,13 @@ def test_two_literals(rdflib_sparql: SparqlBackend) -> None:
name_field = LiteralField( name_field = LiteralField(
"display_name", "display_name",
{Language("en"): "Name"}, {Language("en"): "Name"},
str,
rdflib.URIRef("http://example.org/display-name"), rdflib.URIRef("http://example.org/display-name"),
) )
frequency_field = LiteralField( frequency_field = LiteralField(
"frequency", "frequency",
{Language("en"): "Clock frequency"}, {Language("en"): "Clock frequency"},
Decimal,
rdflib.URIRef("http://example.org/clock-frequency"), rdflib.URIRef("http://example.org/clock-frequency"),
) )
table = Table( table = Table(
@ -114,8 +118,8 @@ def test_two_literals(rdflib_sparql: SparqlBackend) -> None:
""" """
) )
assert set(rdflib_sparql.query(table.sparql())) == { assert set(table.query(rdflib_sparql)) == {
("Grown 1700", "3000"), ("Grown 1700", 3000),
} }
@ -123,14 +127,16 @@ def test_default_value(rdflib_sparql: SparqlBackend) -> None:
name_field = LiteralField( name_field = LiteralField(
"display_name", "display_name",
{Language("en"): "Name"}, {Language("en"): "Name"},
str,
rdflib.URIRef("http://example.org/display-name"), rdflib.URIRef("http://example.org/display-name"),
default=rdflib.Literal("Anonymous CPU"), default="Anonymous CPU",
) )
frequency_field = LiteralField( frequency_field = LiteralField(
"frequency", "frequency",
{Language("en"): "Clock frequency"}, {Language("en"): "Clock frequency"},
Decimal,
rdflib.URIRef("http://example.org/clock-frequency"), rdflib.URIRef("http://example.org/clock-frequency"),
default=rdflib.Literal(0), default=Decimal(0),
) )
table = Table( table = Table(
fields=[name_field, frequency_field], fields=[name_field, frequency_field],
@ -148,10 +154,10 @@ def test_default_value(rdflib_sparql: SparqlBackend) -> None:
""" """
) )
assert set(rdflib_sparql.query(table.sparql())) == { assert set(table.query(rdflib_sparql)) == {
("Grown 1700", "3000"), ("Grown 1700", 3000),
("Grown 3600", None), ("Grown 3600", None),
(None, "9000"), (None, 9000),
} }
@ -159,6 +165,7 @@ def test_field_id_subject() -> None:
name_field = LiteralField( name_field = LiteralField(
"subject", "subject",
{Language("en"): "Name"}, {Language("en"): "Name"},
str,
rdflib.URIRef("http://example.org/display-name"), rdflib.URIRef("http://example.org/display-name"),
) )
with pytest.raises(ValueError, match="both subject and a field id"): 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_field = LiteralField(
"name", "name",
{Language("en"): "Name"}, {Language("en"): "Name"},
str,
rdflib.URIRef("http://example.org/name"), rdflib.URIRef("http://example.org/name"),
) )
display_name_field = LiteralField( display_name_field = LiteralField(
"name", "name",
{Language("en"): "Display Name"}, {Language("en"): "Display Name"},
str,
rdflib.URIRef("http://example.org/display-name"), rdflib.URIRef("http://example.org/display-name"),
) )
with pytest.raises(ValueError, match="has duplicate field ids: name"): with pytest.raises(ValueError, match="has duplicate field ids: name"):