From dcc332e352ded8af25dce7dae635bd62417d2c13 Mon Sep 17 00:00:00 2001 From: Matthew Richards Date: Thu, 13 Jan 2022 17:16:28 +0000 Subject: [PATCH] feat: implement search API endpoints #266, #267, #268 --- datagateway_api/src/search_api/helpers.py | 139 ++++++++++++++++++++-- 1 file changed, 131 insertions(+), 8 deletions(-) diff --git a/datagateway_api/src/search_api/helpers.py b/datagateway_api/src/search_api/helpers.py index ffc67bb8..50148372 100644 --- a/datagateway_api/src/search_api/helpers.py +++ b/datagateway_api/src/search_api/helpers.py @@ -1,6 +1,10 @@ +import json import logging +from datagateway_api.src.common.exceptions import MissingRecordError from datagateway_api.src.datagateway_api.filter_order_handler import FilterOrderHandler +from datagateway_api.src.search_api.filters import SearchAPIWhereFilter +import datagateway_api.src.search_api.models as models from datagateway_api.src.search_api.query import SearchAPIQuery from datagateway_api.src.search_api.session_handler import ( client_manager, @@ -13,35 +17,154 @@ @client_manager def get_search(entity_name, filters): + """ + Search for data on the given entity, using filters from the request to restrict the + query + + :param entity_name: Name of the entity requested to query against + :type entity_name: :class:`str` + :param filters: The list of Search API filters to be applied to the request/query + :type filters: List of specific implementation :class:`QueryFilter` + :return: List of records (in JSON serialisable format) of the given entity for the + query constructed from that and the request's filters + """ + + log.info("Searching for %s using request's filters", entity_name) log.debug("Entity Name: %s, Filters: %s", entity_name, filters) query = SearchAPIQuery(entity_name) filter_handler = FilterOrderHandler() filter_handler.add_filters(filters) + filter_handler.merge_python_icat_limit_skip_filters() filter_handler.apply_filters(query) - log.debug("Python ICAT Query: %s", query.icat_query.query) - # TODO - `getApiVersion()` used as a placeholder for testing client handling - # Replace with endpoint functionality when implementing the endpoints - return SessionHandler.client.getApiVersion() + log.debug("JPQL Query to be sent/executed in ICAT: %s", query.icat_query.query) + icat_query_data = query.icat_query.execute_query(SessionHandler.client, True) + + panosc_data = [] + for icat_data in icat_query_data: + panosc_model = getattr(models, entity_name) + panosc_record = panosc_model.from_icat(icat_data).json(by_alias=True) + panosc_data.append(json.loads(panosc_record)) + + return panosc_data @client_manager def get_with_pid(entity_name, pid, filters): - pass + """ + Get a particular record of data from the specified entity + + These will only be called with entity names of Dataset, Document and Instrument. + Each of these entities have a PID attribute, so we can assume the identifier will be + persistent (or `pid`) rather than an ordinary identifier (`id`) + + :param entity_name: Name of the entity requested to query against + :type entity_name: :class:`str` + :param pid: Persistent identifier of the data to find + :type pid: :class:`str` + :param filters: The list of Search API filters to be applied to the request/query + :type filters: List of specific implementation :class:`QueryFilter` + :return: The (in JSON serialisable format) record of the specified PID + :raises MissingRecordError: If no results can be found for the query + """ + + log.info("Getting %s from ID %s", entity_name, pid) + log.debug("Entity Name: %s, Filters: %s", entity_name, filters) + + filters.append(SearchAPIWhereFilter("pid", "eq", pid)) + + query = SearchAPIQuery(entity_name) + + filter_handler = FilterOrderHandler() + filter_handler.add_filters(filters) + filter_handler.merge_python_icat_limit_skip_filters() + filter_handler.apply_filters(query) + + log.debug("JPQL Query to be sent/executed in ICAT: %s", query.icat_query.query) + icat_query_data = query.icat_query.execute_query(SessionHandler.client, True) + + if not icat_query_data: + raise MissingRecordError("No result found") + else: + return icat_query_data[0] @client_manager def get_count(entity_name, filters): - pass + """ + Get the number of results of a given entity, with filters provided in the request to + restrict the search + + :param entity_name: Name of the entity requested to query against + :type entity_name: :class:`str` + :param filters: The list of Search API filters to be applied to the request/query + :type filters: List of specific implementation :class:`QueryFilter` + :return: Dict containing the number of records returned from the query + """ + + log.info("Getting number of results for %s, using request's filters", entity_name) + log.debug("Entity Name: %s, Filters: %s", entity_name, filters) + + query = SearchAPIQuery(entity_name, aggregate="COUNT") + + filter_handler = FilterOrderHandler() + filter_handler.add_filters(filters) + filter_handler.merge_python_icat_limit_skip_filters() + filter_handler.apply_filters(query) + + log.debug("Python ICAT Query: %s", query.icat_query.query) + + log.debug("JPQL Query to be sent/executed in ICAT: %s", query.icat_query.query) + icat_query_data = query.icat_query.execute_query(SessionHandler.client, True) + + return {"count": icat_query_data[0]} @client_manager def get_files(entity_name, pid, filters): - pass + """ + Using the PID of a dataset, find all of its associated files and return them + + :param entity_name: Name of the entity requested to query against + :type entity_name: :class:`str` + :param pid: Persistent identifier of the dataset + :type pid: :class:`str` + :param filters: The list of Search API filters to be applied to the request/query + :type filters: List of specific implementation :class:`QueryFilter` + :return: List of file records for the dataset given by PID + """ + + log.info("Getting files of dataset (PID: %s), using request's filters", pid) + log.debug( + "Entity Name: %s, Filters: %s", entity_name, filters, + ) + + filters.append(SearchAPIWhereFilter("dataset.pid", pid, "eq")) + return get_search(entity_name, filters) @client_manager def get_files_count(entity_name, filters, pid): - pass + """ + Using the PID of a dataset, find the number of associated files + + :param entity_name: Name of the entity requested to query against + :type entity_name: :class:`str` + :param pid: Persistent identifier of the data to find + :type pid: :class:`str` + :param filters: The list of Search API filters to be applied to the request/query + :type filters: List of specific implementation :class:`QueryFilter` + :return: Dict containing the number of files for the dataset given by PID + """ + + log.info( + "Getting number of files for dataset (PID: %s), using request's filters", pid, + ) + log.debug( + "Entity Name: %s, Filters: %s", entity_name, filters, + ) + + filters.append(SearchAPIWhereFilter("dataset.pid", pid, "eq")) + return get_count(entity_name, filters)