Parse values
This commit is contained in:
glowtables
@ -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)
|
||||
)
|
||||
|
@ -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"):
|
||||
|
Reference in New Issue
Block a user