async def featured_result_query(self, request: ONSRequest):
        """
        Executes the ONS featured result query using the given SearchEngine class
        :param request:
        :return:
        """
        engine: AbstractSearchEngine = self.get_search_engine_instance()

        # Perform the query
        search_term = request.get_search_term()

        try:
            engine: AbstractSearchEngine = engine.featured_result_query(
                search_term)

            logger.debug(request.request_id,
                         "Executing featured result query",
                         extra={"query": engine.to_dict()})
            response: ONSResponse = await engine.execute()
        except ConnectionError as e:
            message = "Unable to connect to Elasticsearch cluster to perform featured result query request"
            logger.error(request.request_id, message, exc_info=e)
            raise ServerError(message)
        except RequestSizeExceededException as e:
            # Log and raise a 400 BAD_REQUEST
            message = "Requested page size exceeds max allowed: '{0}'".format(
                e)
            logger.error(request.request_id, message, exc_info=e)
            raise InvalidUsage(message)

        search_result: SearchResult = response.to_featured_result_query_search_result(
        )

        return search_result
    def get_type_filters(self, list_type: ListType) -> List[TypeFilter]:
        """
        Returns requested type filters or the defaults for the desired ListType
        :param list_type:
        :return:
        """
        if hasattr(self, "json") and isinstance(self.json, dict):
            type_filters_raw = self.json.get("filter", None)

            if type_filters_raw is not None:
                if isinstance(type_filters_raw, str):
                    type_filters_raw = loads(type_filters_raw)

                if not isinstance(type_filters_raw, list):
                    type_filters_raw = [type_filters_raw]

                try:
                    type_filters: List[
                        TypeFilter] = AvailableTypeFilters.from_string_list(
                            type_filters_raw)
                    return type_filters
                except UnknownTypeFilter as e:
                    # Import logger here to prevent circular dependency on module import
                    message = "Received unknown type filter: '{0}'".format(
                        e.unknown_type_filter)
                    logger.error(self.request_id, message, exc_info=e)
                    raise InvalidUsage(message)

        return list_type.to_type_filters()
    async def departments_query(self, request: ONSRequest) -> SearchResult:
        """
        Executes the ONS departments query using the given SearchEngine class
        :param request:
        :return:
        """
        # Initialise the search engine
        engine: AbstractSearchEngine = self.get_search_engine_instance()

        # Perform the query
        search_term = request.get_search_term()
        page = request.get_current_page()
        page_size = request.get_page_size()

        try:
            engine: AbstractSearchEngine = engine.departments_query(
                search_term, page, page_size)

            logger.debug(request.request_id,
                         "Executing departments query",
                         extra={"query": engine.to_dict()})
            response: ONSResponse = await engine.execute()
        except ConnectionError as e:
            message = "Unable to connect to Elasticsearch cluster to perform departments query request"
            logger.error(request.request_id, message, exc_info=e)
            raise ServerError(message)

        search_result: SearchResult = response.to_departments_query_search_result(
            page, page_size)

        return search_result
async def health_check(request: ONSRequest):
    """
    API to check health of the app (i.e connection status to Elasticsearch)
    :param request:
    :return:
    """
    # Ping elasticsearch to get cluster health
    app: SearchApp = request.app

    client: Elasticsearch = app.elasticsearch.client

    # Send the request
    try:
        health = client.cluster.health()
        if isawaitable(health):
            health = await health

        code = 200 if 'status' in health and health['status'] in [
            'yellow', 'green'
        ] else 500

        if code != 200:
            logger.error(request.request_id,
                         "Healthcheck results in non-200 response",
                         extra={"health": health})
        return json(request, health, code)
    except Exception as e:
        logger.error(request.request_id,
                     "Unable to get Elasticsearch cluster health",
                     exc_info=e)
        body = {"elasticsearch": "unavailable"}
        return json(request, body, 500)
async def spell_check(request: ONSRequest):
    """
    API for spell checking
    :param request:
    :return:
    """
    search_term = request.get_search_term()

    # Get spell checker
    app: SearchApp = request.app
    spell_checker: SpellChecker = app.spell_checker

    # Generate the tokens
    tokens = search_term.split()
    if len(tokens) > 0:
        # Get the result
        result = spell_checker.correct_spelling(tokens)

        # Return the json response
        return json(request, result, 200)

    # No input tokens - raise a 400 BAD_REQUEST
    message = "Found no input tokens in query: %s" % search_term
    logger.error(request.request_id, message)
    return json(request, message, 400)
Exemple #6
0
async def ons_content_query(request: ONSRequest,
                            list_type: str) -> HTTPResponse:
    """
    Handles content queries to the <list_type> API.
    :param request:
    :param list_type: The list_type to query against (i.e ons, onsdata or onspublications; see api.search.list_type.py)
    :return:
    """
    # Parse list_type to enum
    if ListType.is_list_type(list_type):
        list_type_enum: ListType = ListType.from_str(list_type)
        # Initialise the search engine
        sanic_search_engine = SanicSearchEngine(request.app, SearchEngine,
                                                Index.ONS)

        # Perform the request
        search_result: SearchResult = await sanic_search_engine.content_query(
            request, list_type_enum)

        return json(request, search_result.to_dict(), 200)
    else:
        # Log and return 404
        message = "Received content query request for unknown list type: '{0}'".format(
            list_type)
        logger.error(request.request_id, message)
        return json(request, message, 404)
 def get_search_term(self) -> str:
     """
     Parses the request to extract a search term
     :return:
     """
     search_term = self.args.get("q", None)
     if search_term is None:
         logger.error(self.request_id,
                      "Search term not specified",
                      extra={"status": 400})
         raise InvalidUsage("Search term not specified")
     return search_term
    async def content_query(self, request: ONSRequest,
                            list_type: ListType) -> SearchResult:
        """
        Executes the ONS content query using the given SearchEngine class
        :param request:
        :param list_type:
        :return:
        """
        # Initialise the search engine
        engine: AbstractSearchEngine = self.get_search_engine_instance()

        # Perform the query
        search_term = request.get_search_term()
        page = request.get_current_page()
        page_size = request.get_page_size()
        sort_by: SortField = request.get_sort_by()
        type_filters: List[TypeFilter] = request.get_type_filters(list_type)

        # Build filter functions
        filter_functions: List[AvailableContentTypes] = []
        for type_filter in type_filters:
            filter_functions.extend(type_filter.get_content_types())

        try:
            engine: AbstractSearchEngine = engine.content_query(
                search_term,
                page,
                page_size,
                sort_by=sort_by,
                filter_functions=filter_functions,
                type_filters=type_filters)

            logger.debug(request.request_id,
                         "Executing content query",
                         extra={"query": engine.to_dict()})
            response: ONSResponse = await engine.execute()
        except ConnectionError as e:
            message = "Unable to connect to Elasticsearch cluster to perform content query request"
            logger.error(request.request_id, message, exc_info=e)
            raise ServerError(message)
        except RequestSizeExceededException as e:
            # Log and raise a 400 BAD_REQUEST
            message = "Requested page size exceeds max allowed: '{0}'".format(
                e)
            logger.error(request.request_id, message, exc_info=e)
            raise InvalidUsage(message)

        search_result: SearchResult = response.to_content_query_search_result(
            page, page_size, sort_by)

        return search_result
    def get_elasticsearch_query(self) -> dict:
        """
        Parse the request body for Elasticsearch query JSON and return as dict
        :return:
        """
        body = self.json

        if body is not None and isinstance(body, dict) and "query" in body:
            return body
        else:
            # Raise InvalidUsage (400) and log error
            # Import logger here to prevent circular dependency on module import
            message = "Invalid request body whilst trying to parse for Elasticsearch query"
            logger.error(self.request_id, message, extra={"body": body})
            raise InvalidUsage(message)
    def get_page_size(self) -> int:
        """
        Returns the requested page size (min of 1). Defaults to the value set by the paginator.
        :return:
        """
        page_size = self.args.get("size", SEARCH_CONFIG.results_per_page)
        if isinstance(page_size, str):
            if page_size.isdigit():
                page_size = int(page_size)
            else:
                # Log error parsing param to int
                message = "Unable to convert size param to int"
                logger.error(self.request_id,
                             message,
                             extra={"size": page_size})
                raise InvalidUsage(message)

        if isinstance(page_size, int) and page_size > 0:
            return page_size

        # Raise InvalidUsage (400) and log error
        message = "Invalid request [size={size}]".format(size=page_size)
        logger.error(self.request_id, message, extra={"size": page_size})
        raise InvalidUsage(message)
    async def proxy(self, request: ONSRequest) -> SearchResult:
        """
        Proxy an Elasticsearch query over HTTP
        :param request:
        :return:
        """
        # Initialise the search engine
        engine: AbstractSearchEngine = self.get_search_engine_instance()

        # Parse the request body for a valid Elasticsearch query
        body: dict = request.get_elasticsearch_query()

        # Parse query and filters
        query: dict = loads(body.get("query"))
        type_filters_raw = body.get("filter")

        # Update the search engine with the query JSON
        engine.update_from_dict(query)

        # Extract paginator params
        page = request.get_current_page()
        page_size = request.get_page_size()
        sort_by = request.get_sort_by()

        try:
            engine: AbstractSearchEngine = engine.paginate(page, page_size)
        except RequestSizeExceededException as e:
            # Log and raise a 400 BAD_REQUEST
            message = "Requested page size exceeds max allowed: '{0}'".format(
                e)
            logger.error(request.request_id, message, exc_info=e)
            raise InvalidUsage(message)

        # Add any type filters
        if type_filters_raw is not None:
            if not isinstance(type_filters_raw, list):
                type_filters_raw = [type_filters_raw]
            try:
                type_filters = AvailableTypeFilters.from_string_list(
                    type_filters_raw)
                engine: AbstractSearchEngine = engine.type_filter(type_filters)
            except UnknownTypeFilter as e:
                message = "Received unknown type filter: '{0}'".format(
                    e.unknown_type_filter)
                logger.error(request.request_id, message, exc_info=e)
                raise InvalidUsage(message)

        # Execute
        try:
            logger.debug(request.request_id,
                         "Executing proxy query",
                         extra={"query": engine.to_dict()})
            response: ONSResponse = await engine.execute()
        except ConnectionError as e:
            message = "Unable to connect to Elasticsearch cluster to perform proxy query request"
            logger.error(request.request_id, message, e)
            raise ServerError(message)

        search_result: SearchResult = response.to_content_query_search_result(
            page, page_size, sort_by)

        return search_result