From 921e067fad94516a7d11f1f6aac07aacdbebad43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Mon, 15 Jun 2026 15:17:56 +0200 Subject: [PATCH 1/2] Recognise top-level JSON-LD in Operation.process_json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A dict carrying any JSON-LD reserved key (@context, @graph, @id, @type) is now parsed to an rdflib.Graph by Operation.process_json itself, ahead of the existing generic-dict recursion. Previously every consuming op (POST, PUT, Merge, ldh-Create*/Add*) re-parsed the same JSON-LD payload in its own execute_json — the runtime treated bare JSON-LD as opaque JSON to recurse into, only the ops at the boundary knew its semantics. Symmetric with the bare-value auto-wrap (json_to_rdflib): a JSON scalar becomes a Literal, a JSON-LD object becomes a Graph. Unblocks the chat-extraction case: an LLM that emits JSON-LD as its final answer (image metadata, structured extraction) gets a real Graph result the renderer can lay out, rather than a dict of Literals that renders as raw text. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/web_algebra/operation.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/web_algebra/operation.py b/src/web_algebra/operation.py index d82bd6d..70902c7 100644 --- a/src/web_algebra/operation.py +++ b/src/web_algebra/operation.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +import json import logging from typing import Type, Dict, Optional, Any, List, ClassVar, Union from pydantic import BaseModel, Field, ConfigDict @@ -9,6 +10,13 @@ from rdflib.query import Result +# JSON-LD keyword set used to recognise a top-level dict as RDF data rather +# than generic JSON to recurse into. Presence of any of these at the root is +# a strong, unambiguous signal — they are JSON-LD reserved terms with no +# legitimate meaning in non-RDF JSON. +_JSONLD_KEYS = ("@context", "@graph", "@id", "@type") + + class Operation(ABC, BaseModel): """ Abstract base class for all operations with dual execution paths: @@ -91,7 +99,24 @@ def process_json( # Return RDFLib objects as-is for operation chaining return result - # 🔁 Recurse into each value — allows nested @op inside JSON-LD and SPARQL bindings + # JSON-LD shape recognition — a dict carrying any JSON-LD reserved + # key is RDF data, not generic JSON to recurse into. Parse it + # once at the runtime layer so every consuming op (POST, PUT, + # Merge, ldh-Create*/Add*) receives an `rdflib.Graph` directly + # instead of re-parsing identically inside its own + # `execute_json`. Symmetric with the bare-value auto-wrap + # below: that branch turns a JSON scalar into the matching + # RDFLib term; this branch turns a JSON-LD object into the + # matching RDFLib graph. + if any(k in json_data for k in _JSONLD_KEYS): + graph = Graph() + graph.parse(data=json.dumps(json_data), format="json-ld") + return graph + + # 🔁 Recurse into each value — allows nested @op inside generic + # JSON structures (e.g. SPARQL binding objects), without + # collapsing pure-data dicts that JSON-LD detection above + # already handled. return { k: cls.process_json(settings, v, context, variable_stack) for k, v in json_data.items() From 2b0d6f30848face25eaef41d8c56a1e2a634e37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Mon, 15 Jun 2026 15:17:56 +0200 Subject: [PATCH 2/2] Bump version to 1.3.0 Co-Authored-By: Claude Opus 4.7 (1M context) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4ed40e9..7ea64ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web-algebra" -version = "1.2.0" +version = "1.3.0" description = "Composable RDF operations in JSON" readme = "README.md" license = "Apache-2.0"