Skip to content

Commit

Permalink
turtle, jsonld, browse fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
aaxelb committed Nov 9, 2023
1 parent eae25a1 commit 9b7dc6d
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 147 deletions.
5 changes: 2 additions & 3 deletions trove/derive/osfmap_json.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import json

from trove.render.jsonld import RdfJsonldRenderer
from trove.vocab.osfmap import OSFMAP_VOCAB, osfmap_labeler
from trove.render.osfmap_jsonld import RdfOsfmapJsonldRenderer
from trove.vocab.trove import TROVE
from ._base import IndexcardDeriver

Expand All @@ -19,7 +18,7 @@ def should_skip(self) -> bool:
# abstract method from IndexcardDeriver
def derive_card_as_text(self):
return json.dumps(
RdfJsonldRenderer(OSFMAP_VOCAB, osfmap_labeler).tripledict_as_nested_jsonld(
RdfOsfmapJsonldRenderer().tripledict_as_nested_jsonld(
self.data.tripledict,
self.focus_iri,
)
Expand Down
7 changes: 4 additions & 3 deletions trove/render/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

from .jsonapi import RdfJsonapiRenderer
from .html_browse import RdfHtmlBrowseRenderer
from .turtle import RdfTurtleRenderer
from .jsonld import RdfJsonldRenderer


RENDERER_BY_MEDIATYPE = {
_renderer_cls.MEDIATYPE: _renderer_cls
for _renderer_cls in (
RdfHtmlBrowseRenderer,
RdfJsonapiRenderer,
# TODO:
# RdfJsonldRenderer,
# RdfTurtleRenderer,
RdfTurtleRenderer,
RdfJsonldRenderer,
)
}

Expand Down
7 changes: 3 additions & 4 deletions trove/render/html_browse.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from trove.util.iris import get_sufficiently_unique_iri
from trove.vocab.jsonapi import JSONAPI_MEDIATYPE
from trove.vocab.namespaces import TROVE, RDF
from trove.vocab.trove import trove_browse_link
from ._base import BaseRenderer


Expand Down Expand Up @@ -59,7 +60,7 @@ def __render_alt_links(self):
with self.__nest('nav', attrs={'class': 'VisibleNest Browse__card'}):
self.__leaf('header', text='alternate mediatypes')
# TODO: more mediatypes
for _mediatype in [JSONAPI_MEDIATYPE]:
for _mediatype in _shuffled((JSONAPI_MEDIATYPE, 'text/turtle', 'application/ld+json')):
_qparams = self.request.GET.copy()
_qparams['acceptMediatype'] = _mediatype
_href = urlunsplit((
Expand Down Expand Up @@ -249,9 +250,7 @@ def __leaf_link(self, iri: str):
def __href_for_iri(self, iri: str):
if self.request and (self.request.get_host() == urlsplit(iri).netloc):
return iri
return reverse('trovetrove:browse-iri', kwargs={
'iri': get_sufficiently_unique_iri(iri),
})
return trove_browse_link(iri)

def __label_for_iri(self, iri: str):
# TODO: get actual label in requested language
Expand Down
127 changes: 9 additions & 118 deletions trove/render/jsonld.py
Original file line number Diff line number Diff line change
@@ -1,127 +1,18 @@
import datetime
import json

from primitive_metadata import primitive_rdf

from trove.util.iri_labeler import IriLabeler
from trove.vocab.namespaces import RDF, OWL
from ._base import BaseRenderer


class RdfJsonldRenderer: # TODO: inherit BaseRenderer, add to get_renderer
def __init__(self, vocabulary: primitive_rdf.RdfTripleDictionary, labeler: IriLabeler):
self.vocabulary = vocabulary
self.labeler = labeler
class RdfJsonldRenderer(BaseRenderer):
MEDIATYPE = 'application/ld+json'

def render_document(self, data: primitive_rdf.RdfTripleDictionary, focus_iri: str) -> str:
_rendered = self.tripledict_as_nested_jsonld(data, focus_iri)
_rendered['@context'] = self.simple_jsonld_context()
return json.dumps(_rendered, indent=2, sort_keys=True)

def simple_jsonld_context(self):
return self.labeler.all_iris_by_label()

def tripledict_as_nested_jsonld(self, tripledict: primitive_rdf.RdfTripleDictionary, focus_iri: str):
self.__nestvisiting_iris = set()
return self.__nested_rdfobject_as_jsonld(tripledict, focus_iri)

def rdfobject_as_jsonld(self, rdfobject: primitive_rdf.RdfObject) -> dict:
if isinstance(rdfobject, frozenset):
return self.twopledict_as_jsonld(
primitive_rdf.twopledict_from_twopleset(rdfobject),
)
elif isinstance(rdfobject, primitive_rdf.Literal):
if not rdfobject.datatype_iris:
return {'@value': rdfobject.unicode_value}
if RDF.JSON in rdfobject.datatype_iris:
# NOTE: does not reset jsonld context (is that a problem?)
return json.loads(rdfobject.unicode_value)
_language_tag = rdfobject.language
if _language_tag: # standard language tag
return {
'@value': rdfobject.unicode_value,
'@language': _language_tag,
}
# datatype iri (or non-standard language iri)
return {
'@value': rdfobject.unicode_value,
'@type': (
list(rdfobject.datatype_iris)
if len(rdfobject.datatype_iris) > 1
else next(iter(rdfobject.datatype_iris))
),
}
elif isinstance(rdfobject, str):
return {'@id': self.labeler.get_label_or_iri(rdfobject)}
elif isinstance(rdfobject, (float, int)):
return {'@value': rdfobject}
elif isinstance(rdfobject, datetime.date):
# just "YYYY-MM-DD"
return {'@value': datetime.date.isoformat(rdfobject)}
elif isinstance(rdfobject, tuple):
return {'@list': [
self.rdfobject_as_jsonld(_obj)
for _obj in rdfobject
]}
raise ValueError(f'unrecognized RdfObject (got {rdfobject})')

def twopledict_as_jsonld(self, twopledict: primitive_rdf.RdfTwopleDictionary) -> dict:
_jsonld = {}
for _pred, _objectset in twopledict.items():
if _objectset:
_key = self.labeler.get_label_or_iri(_pred)
_jsonld[_key] = self._list_or_single_value(_pred, [
self.rdfobject_as_jsonld(_obj)
for _obj in _objectset
])
return _jsonld

def __nested_rdfobject_as_jsonld(
self,
tripledict: primitive_rdf.RdfTripleDictionary,
rdfobject: primitive_rdf.RdfObject,
):
_yes_nest = (
isinstance(rdfobject, str)
and (rdfobject not in self.__nestvisiting_iris)
and (rdfobject in tripledict)
)
if not _yes_nest:
return self.rdfobject_as_jsonld(rdfobject)
self.__nestvisiting_iris.add(rdfobject)
_nested_obj = (
{}
if rdfobject.startswith('_:') # HACK: non-blank blank nodes (stop that)
else {'@id': rdfobject}
)
for _pred, _objectset in tripledict[rdfobject].items():
_label = self.labeler.get_label_or_iri(_pred)
if _objectset:
_nested_obj[_label] = self._list_or_single_value(
_pred,
[ # recursion:
self.__nested_rdfobject_as_jsonld(tripledict, _obj)
for _obj in _objectset
],
)
self.__nestvisiting_iris.discard(rdfobject)
return _nested_obj

def _list_or_single_value(self, predicate_iri, objectset):
_only_one_object = OWL.FunctionalProperty in (
self.vocabulary
.get(predicate_iri, {})
.get(RDF.type, ())
_jsonld_serializer = primitive_rdf.JsonldSerializer(self.iri_shorthand)
# TODO: use focus_iri
return json.dumps(
_jsonld_serializer.tripledict_as_jsonld(data, with_context=True),
indent=2,
sort_keys=True,
)
if _only_one_object:
if len(objectset) > 1:
raise ValueError((
f'expected at most one object for <{predicate_iri}>'
f' (got {objectset})'
))
try:
(_only_obj,) = objectset
except ValueError:
return None
else:
return _only_obj
return list(objectset)
131 changes: 131 additions & 0 deletions trove/render/osfmap_jsonld.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import datetime
import json

from primitive_metadata import primitive_rdf

from trove.vocab.osfmap import (
OSFMAP_VOCAB,
osfmap_labeler,
)
from trove.vocab.namespaces import RDF, OWL
from ._base import BaseRenderer


# TODO: use RdfJsonldRenderer instead
class RdfOsfmapJsonldRenderer(BaseRenderer):
vocabulary = OSFMAP_VOCAB
labeler = osfmap_labeler

def render_document(self, data: primitive_rdf.RdfTripleDictionary, focus_iri: str) -> str:
_rendered = self.tripledict_as_nested_jsonld(data, focus_iri)
_rendered['@context'] = self.simple_jsonld_context()
return json.dumps(_rendered, indent=2, sort_keys=True)

def simple_jsonld_context(self):
return self.labeler.all_iris_by_label()

def tripledict_as_nested_jsonld(self, tripledict: primitive_rdf.RdfTripleDictionary, focus_iri: str):
self.__nestvisiting_iris = set()
return self.__nested_rdfobject_as_jsonld(tripledict, focus_iri)

def rdfobject_as_jsonld(self, rdfobject: primitive_rdf.RdfObject) -> dict:
if isinstance(rdfobject, frozenset):
return self.twopledict_as_jsonld(
primitive_rdf.twopledict_from_twopleset(rdfobject),
)
elif isinstance(rdfobject, primitive_rdf.Literal):
if not rdfobject.datatype_iris:
return {'@value': rdfobject.unicode_value}
if RDF.JSON in rdfobject.datatype_iris:
# NOTE: does not reset jsonld context (is that a problem?)
return json.loads(rdfobject.unicode_value)
_language_tag = rdfobject.language
if _language_tag: # standard language tag
return {
'@value': rdfobject.unicode_value,
'@language': _language_tag,
}
# datatype iri (or non-standard language iri)
return {
'@value': rdfobject.unicode_value,
'@type': (
list(rdfobject.datatype_iris)
if len(rdfobject.datatype_iris) > 1
else next(iter(rdfobject.datatype_iris))
),
}
elif isinstance(rdfobject, str):
return {'@id': self.labeler.get_label_or_iri(rdfobject)}
elif isinstance(rdfobject, (float, int)):
return {'@value': rdfobject}
elif isinstance(rdfobject, datetime.date):
# just "YYYY-MM-DD"
return {'@value': datetime.date.isoformat(rdfobject)}
elif isinstance(rdfobject, tuple):
return {'@list': [
self.rdfobject_as_jsonld(_obj)
for _obj in rdfobject
]}
raise ValueError(f'unrecognized RdfObject (got {rdfobject})')

def twopledict_as_jsonld(self, twopledict: primitive_rdf.RdfTwopleDictionary) -> dict:
_jsonld = {}
for _pred, _objectset in twopledict.items():
if _objectset:
_key = self.labeler.get_label_or_iri(_pred)
_jsonld[_key] = self._list_or_single_value(_pred, [
self.rdfobject_as_jsonld(_obj)
for _obj in _objectset
])
return _jsonld

def __nested_rdfobject_as_jsonld(
self,
tripledict: primitive_rdf.RdfTripleDictionary,
rdfobject: primitive_rdf.RdfObject,
):
_yes_nest = (
isinstance(rdfobject, str)
and (rdfobject not in self.__nestvisiting_iris)
and (rdfobject in tripledict)
)
if not _yes_nest:
return self.rdfobject_as_jsonld(rdfobject)
self.__nestvisiting_iris.add(rdfobject)
_nested_obj = (
{}
if rdfobject.startswith('_:') # HACK: non-blank blank nodes (stop that)
else {'@id': rdfobject}
)
for _pred, _objectset in tripledict[rdfobject].items():
_label = self.labeler.get_label_or_iri(_pred)
if _objectset:
_nested_obj[_label] = self._list_or_single_value(
_pred,
[ # recursion:
self.__nested_rdfobject_as_jsonld(tripledict, _obj)
for _obj in _objectset
],
)
self.__nestvisiting_iris.discard(rdfobject)
return _nested_obj

def _list_or_single_value(self, predicate_iri, objectset):
_only_one_object = OWL.FunctionalProperty in (
self.vocabulary
.get(predicate_iri, {})
.get(RDF.type, ())
)
if _only_one_object:
if len(objectset) > 1:
raise ValueError((
f'expected at most one object for <{predicate_iri}>'
f' (got {objectset})'
))
try:
(_only_obj,) = objectset
except ValueError:
return None
else:
return _only_obj
return list(objectset)
10 changes: 10 additions & 0 deletions trove/render/turtle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from primitive_metadata.primitive_rdf import turtle_from_tripledict

from ._base import BaseRenderer


class RdfTurtleRenderer(BaseRenderer):
MEDIATYPE = 'text/turtle'

def render_document(self, rdf_graph, focus_iri):
return turtle_from_tripledict(rdf_graph, focus=focus_iri)
6 changes: 3 additions & 3 deletions trove/trovesearch_gathering.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
)
from share.search.search_response import ValuesearchResult
from trove import models as trove_db
from trove.render.jsonld import RdfJsonldRenderer
from trove.render.osfmap_jsonld import RdfOsfmapJsonldRenderer
from trove.vocab.namespaces import RDF, FOAF, DCTERMS, RDFS
from trove.vocab.jsonapi import (
JSONAPI_LINK_OBJECT,
Expand Down Expand Up @@ -298,7 +298,7 @@ def _valuesearch_result_as_indexcard_blanknode(result: ValuesearchResult) -> fro

def _osfmap_json(tripledict, focus_iri):
return literal_json(
RdfJsonldRenderer(OSFMAP_VOCAB, osfmap_labeler).tripledict_as_nested_jsonld(
RdfOsfmapJsonldRenderer().tripledict_as_nested_jsonld(
tripledict,
focus_iri,
)
Expand All @@ -307,7 +307,7 @@ def _osfmap_json(tripledict, focus_iri):

def _osfmap_twople_json(twopledict):
return literal_json(
RdfJsonldRenderer(OSFMAP_VOCAB, osfmap_labeler).twopledict_as_jsonld(twopledict),
RdfOsfmapJsonldRenderer().twopledict_as_jsonld(twopledict),
)


Expand Down
1 change: 1 addition & 0 deletions trove/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
path('index-card-search', view=CardsearchView.as_view(), name='index-card-search'),
path('index-value-search', view=ValuesearchView.as_view(), name='index-value-search'),
path('browse///<path:iri>', view=BrowseIriView.as_view(), name='browse-iri'),
path('browse', view=BrowseIriView.as_view(), name='browse-iri'),
path('ingest', view=RdfIngestView.as_view(), name='ingest-rdf'),
path('docs/openapi.json', view=OpenapiJsonView.as_view(), name='docs.openapi-json'),
path('docs/openapi.html', view=OpenapiHtmlView.as_view(), name='docs.openapi-html'),
Expand Down
6 changes: 6 additions & 0 deletions trove/util/iri_labeler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ def __init__(
self.acceptable_prefixes = acceptable_prefixes
self.output_prefix = output_prefix

def build_shorthand(self) -> primitive_rdf.IriShorthand:
return primitive_rdf.IriShorthand({
_label: _iri
for _label, _iri in self.all_iris_by_label()
})

def all_iris_by_label(self) -> dict[str, str]:
try:
return self.__iris_by_label
Expand Down
Loading

0 comments on commit 9b7dc6d

Please sign in to comment.