Source code for httk.optimade.endpoints.partial_data

"""The partial data endpoint, streaming large property values as JSON Lines.

See the OPTIMADE specification appendix "OPTIMADE JSON Lines partial data
format". A backend exposes a large or sliceable property value as a
:class:`~httk.optimade.backend.partial.PartialValue`; this endpoint fetches it
in chunks of ``config.partial_data_chunk_size`` outer items and emits the
JSON Lines document, with a next-marker when more data remains.
"""

import json
from typing import Any
from urllib.parse import quote

from httk.core import FilterAst

from ..backend.partial import PartialValue
from ..model.config import OptimadeConfig
from ..model.errors import OptimadeError
from ..model.request import EndpointResponse, ValidatedRequest
from ..model.results import QueryFunction
from ..schema.served import ServedSchema


[docs] def generate_partial_data_reply( request: ValidatedRequest, config: OptimadeConfig, query_function: QueryFunction, schema: ServedSchema, ) -> EndpointResponse: """Produce the JSON Lines partial data reply for a single property value.""" assert request.partial_data_parts is not None entry, entry_id, prop = request.partial_data_parts filter_ast: FilterAst = ('=', ('Identifier', 'id'), ('String', entry_id)) results = query_function([entry], ['id', 'type', prop], [], 1, 0, filter_ast) row = None for result in results: row = result break if row is None: raise OptimadeError("Entry not found.", 404, "Not Found") value = row.values.get(prop) if not isinstance(value, PartialValue): raise OptimadeError("Property is not available as partial data.", 404, "Not Found") outer_length = value.dimensions[0].length if outer_length is None: raise OptimadeError("Partial data length is not available for this property.", 501, "Not implemented") offset = request.partial_data_offset chunk = config.partial_data_chunk_size remaining_slices = tuple(slice(None) for _ in value.dimensions[1:]) items = value.fetch((slice(offset, offset + chunk),) + remaining_slices) header: dict[str, Any] = { "optimade-partial-data": {"format": "1.2"}, "layout": "dense", "property_name": prop, "entry": {"id": entry_id, "type": entry}, "has_references": False, } if items: # returned_ranges is RECOMMENDED and cannot be expressed for an empty # chunk (an offset at or beyond the data produces no items). header["returned_ranges"] = [{"start": offset, "stop": offset + len(items) - 1, "step": 1}] lines = [json.dumps(header)] for item in items: lines.append(json.dumps(item)) next_offset = offset + len(items) if next_offset < outer_length: quoted = f"partial_data/{quote(entry, safe='')}/{quote(entry_id, safe='')}/{quote(prop, safe='')}" next_link = request.baseurl + quoted + f"?offset={next_offset}" lines.append(json.dumps(["PARTIAL-DATA-NEXT", [next_link]])) else: lines.append(json.dumps(["PARTIAL-DATA-END", [""]])) content = "\n".join(lines) + "\n" return EndpointResponse(content=content, content_type='application/jsonlines')