Skip to content

Commit

Permalink
fix: Add to_dict method to the JSON-LD Context class. (#2310)
Browse files Browse the repository at this point in the history
`Context.to_dict` is used in JSON-LD serialization, but it was not implemented.
This change adds the method.

- Closes <#2138>.

---------

Co-authored-by: Marc-Antoine Parent <maparent@acm.org>
  • Loading branch information
aucampia and maparent committed Mar 25, 2023
1 parent 3faa01b commit d7883eb
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 2 deletions.
37 changes: 35 additions & 2 deletions rdflib/plugins/shared/jsonld/context.py
Expand Up @@ -85,7 +85,7 @@ def __init__(
self.terms: Dict[str, Any] = {}
# _alias maps NODE_KEY to list of aliases
self._alias: Dict[str, List[str]] = {}
self._lookup: Dict[Tuple[str, Any, Union[Defined, str], bool], Any] = {}
self._lookup: Dict[Tuple[str, Any, Union[Defined, str], bool], Term] = {}
self._prefixes: Dict[str, Any] = {}
self.active = False
self.parent: Optional[Context] = None
Expand Down Expand Up @@ -243,8 +243,10 @@ def add_term(

if isinstance(container, (list, set, tuple)):
container = set(container)
else:
elif container is not UNDEF:
container = set([container])
else:
container = set()

term = Term(
idref,
Expand Down Expand Up @@ -617,6 +619,37 @@ def _get_source_id(self, source: Dict[str, Any], key: str) -> Optional[str]:
term = term.get(ID)
return term

def _term_dict(self, term: Term) -> Union[Dict[str, Any], str]:
tdict: Dict[str, Any] = {}
if term.type != UNDEF:
tdict[TYPE] = self.shrink_iri(term.type)
if term.container:
tdict[CONTAINER] = list(term.container)
if term.language != UNDEF:
tdict[LANG] = term.language
if term.reverse:
tdict[REV] = term.id
else:
tdict[ID] = term.id
if tdict.keys() == {ID}:
return tdict[ID]
return tdict

def to_dict(self) -> Dict[str, Any]:
"""
Returns a dictionary representation of the context that can be
serialized to JSON.
:return: a dictionary representation of the context.
"""
r = {v: k for (k, v) in self._prefixes.items()}
r.update({term.name: self._term_dict(term) for term in self._lookup.values()})
if self.base:
r[BASE] = self.base
if self.language:
r[LANG] = self.language
return r


Term = namedtuple(
"Term",
Expand Down
51 changes: 51 additions & 0 deletions test/jsonld/test_context.py
Expand Up @@ -2,10 +2,12 @@
JSON-LD Context Spec
"""

import json
from functools import wraps
from pathlib import Path
from typing import Any, Dict

from rdflib.namespace import PROV, XSD, Namespace
from rdflib.plugins.shared.jsonld import context, errors
from rdflib.plugins.shared.jsonld.context import Context

Expand Down Expand Up @@ -234,3 +236,52 @@ def test_dict_source(tmp_path: Path) -> None:
file.write_text(r"""{ "@context": { "ex": "http://example.com/" } }""")
ctx = Context(source=[{"@context": file.as_uri()}])
assert "http://example.com/" == ctx.terms["ex"].id


EG = Namespace("https://example.com/")

DIVERSE_CONTEXT = json.loads(
"""
{
"@context": {
"ex": "https://example.com/",
"generatedAt": { "@id": "http://www.w3.org/ns/prov#generatedAtTime", "@type": "http://www.w3.org/2001/XMLSchema#dateTime" },
"graphMap": { "@id": "https://example.com/graphMap", "@container": ["@graph", "@id"] },
"occupation_en": { "@id": "https://example.com/occupation", "@language": "en" },
"children": { "@reverse": "https://example.com/parent" }
}
}
"""
)


def test_parsing() -> None:
"""
A `Context` can be parsed from a dict.
"""
ctx = Context(DIVERSE_CONTEXT)
assert f"{EG}" == ctx.terms["ex"].id
assert f"{PROV.generatedAtTime}" == ctx.terms["generatedAt"].id
assert f"{XSD.dateTime}" == ctx.terms["generatedAt"].type
assert f"{EG.graphMap}" == ctx.terms["graphMap"].id
assert {"@graph", "@id"} == ctx.terms["graphMap"].container
assert f"{EG.occupation}" == ctx.terms["occupation_en"].id
assert "en" == ctx.terms["occupation_en"].language
assert False is ctx.terms["occupation_en"].reverse
assert True is ctx.terms["children"].reverse
assert f"{EG.parent}" == ctx.terms["children"].id


def test_to_dict() -> None:
"""
A `Context` can be converted to a dictionary.
"""
ctx = Context()
ctx.add_term("ex", f"{EG}")
ctx.add_term("generatedAt", f"{PROV.generatedAtTime}", coercion=f"{XSD.dateTime}")
ctx.add_term("graphMap", f"{EG.graphMap}", container=["@graph", "@id"])
ctx.add_term("occupation_en", f"{EG.occupation}", language="en")
ctx.add_term("children", f"{EG.parent}", reverse=True)
result = ctx.to_dict()
result["graphMap"]["@container"] = sorted(result["graphMap"]["@container"])
assert DIVERSE_CONTEXT["@context"] == result
44 changes: 44 additions & 0 deletions test/test_serializers/test_serializer_jsonld.py
@@ -0,0 +1,44 @@
import json
import logging
import pprint
from typing import Any, Dict, Union

import pytest

from rdflib import Graph
from rdflib.namespace import Namespace
from rdflib.plugins.shared.jsonld.context import Context

EG = Namespace("http://example.org/")


@pytest.mark.parametrize(
["input"],
[
(
Context(
{
"eg": f"{EG}",
}
),
),
({"eg": f"{EG}"},),
],
)
def test_serialize_context(input: Union[Dict[str, Any], Context]) -> None:
"""
The JSON-LD serializer accepts and correctly serializes the context argument to the output.
"""
graph = Graph()
graph.add((EG.subject, EG.predicate, EG.object0))
graph.add((EG.subject, EG.predicate, EG.object1))
context = Context(
{
"eg": f"{EG}",
}
)
logging.debug("context = %s", pprint.pformat(vars(context)))
data = graph.serialize(format="json-ld", context=context)
logging.debug("data = %s", data)
obj = json.loads(data)
assert obj["@context"] == {"eg": f"{EG}"}

0 comments on commit d7883eb

Please sign in to comment.