Source code for kuha_osmh_repo_handler.handlers

#!/usr/bin/env python3
# Author(s): Toni Sissala
# Copyright 2020 Finnish Social Science Data Archive FSD / University of Tampere
# Licensed under the EUPL. See LICENSE.txt for full license.
"""Define handlers for responding to HTTP-requests.

:note: OSMH protocol only supports HTTP-GET
"""

#: OSMH requires returning a list,
#: which is potentially dangerous
#: and thus not supported by tornados encoder.
#: Use built-in json module for list encoding.
import json

from kuha_common.server import RequestHandler
from kuha_common.query import QueryController

from kuha_osmh_repo_handler.response import RecordsResponse
from kuha_osmh_repo_handler.osmh import records


[docs]class BaseHandler(RequestHandler): """BaseHandler class for handling OSMH requests. Derived from :class:`kuha_common.server.RequestHandler`. Defines common attributes. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._records_response = None self._query_ctrl = None self._api_version = None self._query_limit = 0
[docs] async def prepare(self): """Prepare for responding to request. Set output content type. Initiate :obj:`kuha_osmh_repo_handler.response.RecordsResponse` and :obj:`kuha_common.query.QueryController` as instance attributes. """ await super().prepare() self.set_output_content_type(self.CONTENT_TYPE_JSON) self._records_response = RecordsResponse() self._query_limit = self.application.settings['kuha_settings'].query_limit self._query_ctrl = QueryController( headers=self._correlation_id.as_header(), record_limit=self._query_limit ) self._api_version = self.application.settings['kuha_settings'].osmh_repo_handler_api_version
[docs]class ListRecordHeadersHandler(BaseHandler): """Handle list responses. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._stream_response = None self._ongoing_response = None self._get_response_callback = None
[docs] async def prepare(self): """Prepare for each request. Depending on stream-configuration choose which response callback to use. If streaming is enabled write output once a single record has been built. Otherwise store all records in a list and write output when all records are built. :note: With streaming enabled memory consumption will be lower since the records will not be gathered in a single list and encoded to JSON all at once. When dealing with large repositories the amount of memory consumed without streaming could be an issue. """ await super().prepare() self._stream_response = self.application.settings['kuha_settings'].stream_response if self._stream_response: self._get_response_callback = self._get_response_streamer else: self._get_response_callback = self._records_response.get_payload_appender self._ongoing_response = False
def _get_response_streamer(self, constructor): def _streaming_response(ds_payload): if not ds_payload: self.flush() return if self._ongoing_response: self.write(', ') else: self._ongoing_response = True record = constructor(ds_payload) self.write(record.get_payload()) self.flush() return _streaming_response async def _query_recordtype(self, record_type): record_class = records.get_osmh_record_for_type(record_type) fields = record_class.fields_for_header() _call = self._get_response_callback(record_class.for_header_response) return await self._query_ctrl.query_multiple( record_class.get_query_document(), _call, fields=fields )
[docs] async def get(self, record_type=None): """Handles HTTP-GET requests to endpoint. :param record_type: Optional record_type parameter defines if the request limits the list to a certain OSMH type. If the parameter is None, shall output every record in repository. Valid values are defined in handler configuration. :type record_type: str or None """ if self._stream_response: self.write('[') if not record_type: for _type in records.OSMH_RECORD_TYPES: flush_queue = await self._query_recordtype(_type) else: flush_queue = await self._query_recordtype(record_type) if self._query_limit: await flush_queue() if self._stream_response: self.finish(']') else: self.finish(json.dumps(self._records_response.get_response()))
[docs]class GetRecordHandler(BaseHandler): """Handle responses for single record. """
[docs] async def get(self, record_type, identifier): """Handle HTTP-GET requests to endpoint. Gathers the needed information by querying Document Store. Builds the OSMH record response. :param record_type: requested OSMH record type. :type record_type: str :param identifier: requested record identifier. :type identifier: str """ record_class = records.get_osmh_record_for_type(record_type) has_relative_records = record_class.requires_relative_queries_for_record() if has_relative_records: _call = self._records_response.get_record_appender( record_class.for_record_response ) else: _call = self._records_response.get_payload_appender( record_class.for_record_response ) await self._query_ctrl.query_single(record_class.query_document, _filter=record_class.query_filter_for_record( identifier ), fields=record_class.fields_for_record(), on_record=_call) if has_relative_records: flush_queue = None for record in self._records_response.iterate_records(): _filter = record.get_secondary_query_filter_for_record() fields = record.get_secondary_query_fields_for_record() flush_queue = await self._query_ctrl.query_multiple( record.get_secondary_query_document(), record.build_relative_record_payload, _filter=_filter, fields=fields, ) if self._query_limit and flush_queue: await flush_queue() self.finish(self._records_response.get_single_response())
[docs]class ListSupportedRecordTypesHandler(BaseHandler): """Handle response to endpoint that lists supported record types. """
[docs] async def get(self): """Handle HTTP-GET request. :note: returning object will be a list, so will encode to JSON by using build-in json-module. """ self.finish(json.dumps(records.OSMH_RECORD_TYPES))
[docs]class SupportedVersionsHandler(BaseHandler): """Handle response to endpoint that lists supported api version. :note: Currently only single version is supported at a time. """
[docs] async def get(self): """Handle HTTP-GET request. :note: returning object will be a list, so will encode to JSON by using build-in json-module. """ self.finish(json.dumps([self._api_version]))