Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "web-algebra"
version = "1.4.0"
version = "1.4.1"
description = "Composable RDF operations in JSON"
readme = "README.md"
license = "Apache-2.0"
Expand Down
9 changes: 7 additions & 2 deletions src/web_algebra/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,18 +222,23 @@ def to_graph(data: Any, *, base: Optional[str] = None) -> Graph:
already a `Graph` (e.g. the output of an upstream op such as CONSTRUCT)
passes through unchanged.

Both shapes of JSON-LD document are accepted: a single node object
(`dict`) and a top-level array of nodes (`list`) — the latter is what
SPARQL DESCRIBE/CONSTRUCT over multiple subjects returns. rdflib's
JSON-LD parser handles both natively.

`base` is the document's base IRI — typically the target URL of the
enclosing op — used to resolve any relative IRIs in the JSON-LD.
"""
if isinstance(data, Graph):
return data
if isinstance(data, dict):
if isinstance(data, (dict, list)):
graph = Graph()
graph.parse(data=json.dumps(data), format="json-ld", publicID=base)
return graph
raise TypeError(
f"Cannot convert {type(data).__name__} to Graph; "
"expected a JSON-LD dict or an rdflib.Graph"
"expected a JSON-LD dict/list or an rdflib.Graph"
)

@staticmethod
Expand Down
16 changes: 16 additions & 0 deletions tests/unit/test_to_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@

import pytest
from rdflib import Graph, URIRef
from rdflib.namespace import RDF

from web_algebra.operation import Operation

DOC_URI = URIRef("https://example.org/doc/")
PROV_GENERATED_BY = URIRef("http://www.w3.org/ns/prov#wasGeneratedBy")
ACTIVITY_URI = URIRef("https://example.org/activity/#this")
DOC_TYPE = URIRef("https://example.org/T")


class TestToGraph:
Expand All @@ -30,6 +32,20 @@ def test_dict_is_parsed_as_jsonld(self):
assert isinstance(graph, Graph)
assert (DOC_URI, PROV_GENERATED_BY, ACTIVITY_URI) in graph

def test_top_level_array_is_parsed_as_jsonld(self):
# SPARQL DESCRIBE/CONSTRUCT over multiple subjects returns a top-level
# JSON-LD array of nodes; to_graph must accept it, not raise.
data = [
{"@id": str(DOC_URI), "@type": [str(DOC_TYPE)]},
{"@id": str(ACTIVITY_URI), str(PROV_GENERATED_BY): {"@id": str(DOC_URI)}},
]

graph = Operation.to_graph(data)

assert isinstance(graph, Graph)
assert (DOC_URI, RDF.type, DOC_TYPE) in graph
assert (ACTIVITY_URI, PROV_GENERATED_BY, DOC_URI) in graph

def test_graph_passes_through_unchanged(self):
# An upstream op (e.g. CONSTRUCT) already produced a Graph — identity.
g = Graph()
Expand Down
Loading