From df033789ae904783a5a6072f8f0f06b7f310cee3 Mon Sep 17 00:00:00 2001 From: Abram Booth Date: Mon, 20 May 2024 10:28:01 -0400 Subject: [PATCH] simplest simple json --- share/search/search_params.py | 4 ++ trove/render/__init__.py | 2 + trove/render/simple_json.py | 100 ++++++++++++++++++++++++++++++++++ trove/views/indexcard.py | 2 +- trove/views/search.py | 2 +- 5 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 trove/render/simple_json.py diff --git a/share/search/search_params.py b/share/search/search_params.py index 8eabf6a0b..1794d3c28 100644 --- a/share/search/search_params.py +++ b/share/search/search_params.py @@ -59,6 +59,7 @@ class BaseTroveParams: iri_shorthand: primitive_rdf.IriShorthand include: frozenset[tuple[str, ...]] + accept_mediatype: str | None @classmethod def from_querystring(cls, querystring: str) -> 'BaseTroveParams': # TODO py3.11: typing.Self @@ -74,6 +75,7 @@ def parse_queryparams(cls, queryparams: QueryparamDict) -> dict: return { 'iri_shorthand': cls._gather_shorthand(queryparams), 'include': cls._gather_include(queryparams.get('include', [])), + 'accept_mediatype': _get_single_value(queryparams, QueryparamName('acceptMediatype')), } def to_querystring(self) -> str: @@ -82,6 +84,8 @@ def to_querystring(self) -> str: def to_querydict(self) -> QueryDict: # subclasses should override and add their fields to super().to_querydict() _querydict = QueryDict(mutable=True) + if self.accept_mediatype: + _querydict['acceptMediatype'] = self.accept_mediatype # TODO: self.iri_shorthand, self.include return _querydict diff --git a/trove/render/__init__.py b/trove/render/__init__.py index 46fdbea8f..27f4f941b 100644 --- a/trove/render/__init__.py +++ b/trove/render/__init__.py @@ -4,6 +4,7 @@ from .html_browse import RdfHtmlBrowseRenderer from .turtle import RdfTurtleRenderer from .jsonld import RdfJsonldRenderer +from .simple_json import TrovesearchSimpleJsonRenderer RENDERER_BY_MEDIATYPE = { @@ -13,6 +14,7 @@ RdfJsonapiRenderer, RdfTurtleRenderer, RdfJsonldRenderer, + TrovesearchSimpleJsonRenderer, ) } diff --git a/trove/render/simple_json.py b/trove/render/simple_json.py new file mode 100644 index 000000000..960f2cfda --- /dev/null +++ b/trove/render/simple_json.py @@ -0,0 +1,100 @@ +import json + +from primitive_metadata import primitive_rdf as rdf + +from trove.vocab.jsonapi import ( + JSONAPI_LINK_OBJECT, + JSONAPI_MEMBERNAME, +) +from trove.vocab.namespaces import TROVE, RDF +from ._base import BaseRenderer + + +class TrovesearchSimpleJsonRenderer(BaseRenderer): + '''for "simple json" search api -- very entangled with trove/trovesearch_gathering.py + ''' + MEDIATYPE = 'application/json' + + def render_document(self, data: rdf.RdfTripleDictionary, focus_iri: str) -> str: + _focustypes = data[focus_iri][RDF.type] + _graph = rdf.RdfGraph(data) + if TROVE.Cardsearch in _focustypes: + _jsonable = self._render_cardsearch(_graph, focus_iri) + elif TROVE.Valuesearch in _focustypes: + _jsonable = self._render_valuesearch(_graph, focus_iri) + elif TROVE.Indexcard in _focustypes: + _jsonable = self._render_card(_graph, focus_iri) + else: + raise NotImplementedError(f'simplejson not implemented for any of {_focustypes}') + # TODO: links, total in 'meta' + return json.dumps({ + 'data': _jsonable, + 'links': self._render_links(_graph, focus_iri), + 'meta': self._render_meta(_graph, focus_iri), + }, indent=2) + + def _render_cardsearch(self, graph: rdf.RdfGraph, cardsearch_iri: str): + return self._render_searchresultpage(graph, cardsearch_iri) + + def _render_valuesearch(self, graph: rdf.RdfGraph, valuesearch_iri: str): + return self._render_searchresultpage(graph, valuesearch_iri) + + def _render_searchresultpage(self, graph: rdf.RdfGraph, focus_iri: str): + # just each card's contents + _results_sequence = next( + _page + for _page in graph.q(focus_iri, TROVE.searchResultPage) + if rdf.is_container(_page) # filter out page links + ) + return [ + self._render_result(graph, _search_result_blanknode) + for _search_result_blanknode in rdf.sequence_objects_in_order(_results_sequence) + ] + + def _render_result(self, graph: rdf.RdfGraph, search_result_blanknode: rdf.RdfBlanknode): + _card = next( + _obj + for _pred, _obj in search_result_blanknode + if _pred == TROVE.indexCard + ) + return self._render_card(graph, _card) + + def _render_card(self, graph: rdf.RdfGraph, card: str | rdf.RdfBlanknode): + # just the card contents + if isinstance(card, str): + _card_contents = next(graph.q(card, TROVE.resourceMetadata)) + elif isinstance(card, frozenset): + _card_contents = next( + _obj + for _pred, _obj in card + if _pred == TROVE.resourceMetadata + ) + else: + raise NotImplementedError + assert isinstance(_card_contents, rdf.Literal) + assert RDF.JSON in _card_contents.datatype_iris + return json.loads(_card_contents.unicode_value) + + def _render_meta(self, graph: rdf.RdfGraph, focus_iri: str): + _meta = {} + try: + _total = next(graph.q(focus_iri, TROVE.totalResultCount)) + if isinstance(_total, int): + _meta['total'] = _total + elif isinstance(_total, rdf.Literal): + _meta['total'] = int(_total.unicode_value) + elif _total == TROVE['ten-thousands-and-more']: + _meta['total'] = 'trove:ten-thousands-and-more' + except StopIteration: + pass + return _meta + + def _render_links(self, graph: rdf.RdfGraph, focus_iri: str): + _links = {} + for _pagelink in graph.q(focus_iri, TROVE.searchResultPage): + _twopledict = rdf.twopledict_from_twopleset(_pagelink) + if JSONAPI_LINK_OBJECT in _twopledict.get(RDF.type, ()): + (_membername,) = _twopledict[JSONAPI_MEMBERNAME] + (_link_url,) = _twopledict[RDF.value] + _links[_membername.unicode_value] = _link_url + return _links diff --git a/trove/views/indexcard.py b/trove/views/indexcard.py index a385a2db5..8371939cc 100644 --- a/trove/views/indexcard.py +++ b/trove/views/indexcard.py @@ -14,7 +14,7 @@ def get(self, request, indexcard_uuid): # TODO (gather): allow omitting kwargs that go unused 'search_params': None, 'specific_index': None, - 'use_osfmap_json': (_renderer.MEDIATYPE == JSONAPI_MEDIATYPE) + 'use_osfmap_json': (_renderer.MEDIATYPE in {'application/json', JSONAPI_MEDIATYPE}) }) _indexcard_iri = trove_indexcard_iri(indexcard_uuid) _search_gathering.ask( diff --git a/trove/views/search.py b/trove/views/search.py index e6ae3e992..b9c7af23e 100644 --- a/trove/views/search.py +++ b/trove/views/search.py @@ -77,6 +77,6 @@ def _parse_request(request: http.HttpRequest, search_params_dataclass): _search_gathering = trovesearch_by_indexstrategy.new_gathering({ 'search_params': _search_params, 'specific_index': _specific_index, - 'use_osfmap_json': (_renderer.MEDIATYPE == JSONAPI_MEDIATYPE), + 'use_osfmap_json': (_renderer.MEDIATYPE in {'application/json', JSONAPI_MEDIATYPE}) }) return (_search_iri, _search_gathering, _renderer)