From 5d5905c85c0de5fd1ffbd7256f6a26d67c68711e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Tue, 30 Jun 2026 11:07:08 +0200 Subject: [PATCH 1/2] Accept top-level JSON-LD arrays in Operation.to_graph MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit to_graph only accepted dict/Graph, but a JSON-LD document may be a top-level array of nodes — which is exactly what SPARQL DESCRIBE/CONSTRUCT over multiple subjects returns. client.query handed that list straight to to_graph, so every DESCRIBE/CONSTRUCT pipeline broke with "TypeError: Cannot convert list to Graph" (regression in 1.4.0, when parsing was centralized into to_graph). Accept list alongside dict — rdflib's JSON-LD parser handles top-level arrays natively. Add a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/web_algebra/operation.py | 9 +++++++-- tests/unit/test_to_graph.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/web_algebra/operation.py b/src/web_algebra/operation.py index fd26ea9..2cae5b5 100644 --- a/src/web_algebra/operation.py +++ b/src/web_algebra/operation.py @@ -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 diff --git a/tests/unit/test_to_graph.py b/tests/unit/test_to_graph.py index 53009cd..461681b 100644 --- a/tests/unit/test_to_graph.py +++ b/tests/unit/test_to_graph.py @@ -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: @@ -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() From 0cec0df8d3d5a44fa9eabd5d72366dda16f69323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Tue, 30 Jun 2026 11:07:19 +0200 Subject: [PATCH 2/2] Bump version to 1.4.1 Co-Authored-By: Claude Opus 4.8 (1M context) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 91b0b2e..a417f21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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"