diff --git a/cloud_registry/api/20201108.e0358db.openapi.yaml b/cloud_registry/api/20201108.e0358db.openapi.yaml index 4b8b6d1..6d0d091 100644 --- a/cloud_registry/api/20201108.e0358db.openapi.yaml +++ b/cloud_registry/api/20201108.e0358db.openapi.yaml @@ -20,15 +20,32 @@ paths: operationId: getServices tags: - services + parameters: + - name: page + in: query + required: false + schema: + type: integer + minimum: 1 + example: 1 + - name: page_size + in: query + required: false + schema: + type: integer + minimum: 1 + example: 10 responses: '200': description: 'List of services' content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/ExternalService' + oneOf: + - type: array + items: + $ref: '#/components/schemas/ExternalService' + - $ref: '#/components/schemas/PaginatedExternalService' '401': $ref: '#/components/responses/Unauthorized' '403': @@ -80,9 +97,11 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/ServiceType' + oneOf: + - type: array + items: + $ref: '#/components/schemas/ServiceType' + - $ref: '#/components/schemas/PaginatedServiceType' '401': $ref: '#/components/responses/Unauthorized' '403': @@ -198,3 +217,46 @@ components: required: - status - title + PaginationMetadata: + description: 'Pagination Metadata' + type: object + required: + - page + - page_size + - total_count + - total_pages + properties: + page: + type: integer + description: "Current page of the response" + example: 1 + page_size: + type: integer + description: "Number of entries in a page" + example: 10 + total_count: + type: integer + description: "Total number of entries for the query" + example: 30 + total_pages: + type: integer + description: "Total number of entries for the query" + example: 30 + PaginatedExternalService: + type: object + properties: + results: + type: array + items: + $ref: '#/components/schemas/ExternalService' + pagination: + $ref: '#/components/schemas/PaginationMetadata' + PaginatedServiceType: + type: object + properties: + results: + type: array + items: + $ref: '#/components/schemas/ServiceType' + pagination: + $ref: '#/components/schemas/PaginationMetadata' diff --git a/cloud_registry/ga4gh/registry/server.py b/cloud_registry/ga4gh/registry/server.py index fac812a..b314d57 100644 --- a/cloud_registry/ga4gh/registry/server.py +++ b/cloud_registry/ga4gh/registry/server.py @@ -1,34 +1,72 @@ """Controllers for service endpoints.""" import logging -from typing import Dict, List, Tuple +from math import ceil +from typing import Dict, List, Tuple, Union +from cloud_registry.exceptions import BadRequest, NotFound +from cloud_registry.ga4gh.registry.service import RegisterService +from cloud_registry.ga4gh.registry.service_info import RegisterServiceInfo from flask import current_app, request from foca.utils.logging import log_traffic -from cloud_registry.exceptions import NotFound, BadRequest -from cloud_registry.ga4gh.registry.service_info import RegisterServiceInfo -from cloud_registry.ga4gh.registry.service import RegisterService logger = logging.getLogger(__name__) # GET /services @log_traffic -def getServices(**kwargs) -> List: +def getServices(**kwargs) -> Union[List, Dict]: """List all services. Returns: - List of services. + List of services or Paginated list of services. """ + + # Get pagination data + page = request.args.get("page", type=int) + page_size = request.args.get("page_size", type=int) + foca_conf = current_app.config.foca # type: ignore[attr-defined] db_collection_service = ( foca_conf.db.dbs["serviceStore"].collections["services"].client ) - records = db_collection_service.find( - filter={}, - projection={"_id": False}, + + # return list if no pagination query found + if page is None and page_size is None: + records = db_collection_service.find( + filter={}, + projection={"_id": False}, + ) + return list(records) + + # Return paginated response + page = page or 1 + page_size = page_size or 10 + total_count = db_collection_service.count_documents({}) + total_pages = ceil(total_count / page_size) + + if page < 1 or (total_count > 0 and page > total_pages): + raise BadRequest + + skip_items = (page - 1) * page_size + records = ( + db_collection_service.find( + filter={}, + projection={"_id": False}, + ) + .skip(skip_items) + .limit(page_size) ) - return list(records) + + return { + "results": list(records), + "pagination": { + "page": page, + "page_size": page_size, + "total_count": total_count, + "total_pages": total_pages, + }, + } # GET /services/{serviceId} @@ -55,17 +93,57 @@ def getServiceById(serviceId: str, **kwargs) -> Dict: # GET /services/types @log_traffic -def getServiceTypes(**kwargs) -> List: +def getServiceTypes(**kwargs) -> Union[List, Dict]: """List types of services. Returns: List of distinct service types. """ - services = getServices.__wrapped__() + + # Get pagination data + page = request.args.get("page", type=int) + page_size = request.args.get("page_size", type=int) + + # get all the services + foca_conf = current_app.config.foca # type: ignore[attr-defined] + db_collection_service = ( + foca_conf.db.dbs["serviceStore"].collections["services"].client + ) + services = list( + db_collection_service.find( + filter={}, + projection={"_id": False}, + ) + ) types = [s["type"] for s in services] uniq_types = [dict(t) for t in {tuple(sorted(d.items())) for d in types}] - return uniq_types + # return list if no pagination query found + if page is None and page_size is None: + return uniq_types + + # return paginated response + page = page or 1 + page_size = page_size or 10 + total_count = len(uniq_types) + total_pages = ceil(total_count / page_size) + + if page < 1 or (total_count > 0 and page > total_pages): + raise BadRequest + + skip_items = (page - 1) * page_size + end = skip_items + page_size + paginated_types = uniq_types[skip_items:end] + + return { + "results": paginated_types, + "pagination": { + "page": page, + "page_size": page_size, + "total_count": total_count, + "total_pages": total_pages, + }, + } # GET /service-info diff --git a/tests/ga4gh/registry/test_server.py b/tests/ga4gh/registry/test_server.py index 5d65364..97f5371 100644 --- a/tests/ga4gh/registry/test_server.py +++ b/tests/ga4gh/registry/test_server.py @@ -55,8 +55,11 @@ def test_getServices(): data.append(mock_resp) # check whether getServices returns the same list - with app.app_context(): + with app.test_request_context("services"): res = getServices.__wrapped__() + # For paginated response extract the data present in result field + if "results" in res: + res = res["results"] assert res == data @@ -111,8 +114,10 @@ def test_getServiceTypes_duplicates(): "services" ].client.insert_one(mock_resp) - with app.app_context(): + with app.test_request_context("services"): res = getServiceTypes.__wrapped__() + if "results" in res: + res = res["results"] # All written services have same type, we expect list of length 1 assert res == [MOCK_TYPE] @@ -139,8 +144,11 @@ def test_getServiceTypes_distinct(): "services" ].client.insert_one(mock_resp) - with app.app_context(): + with app.test_request_context("services"): res = getServiceTypes.__wrapped__() + # For paginated response extract the data present in result field + if "results" in res: + res = res["results"] # All written services have distinct types, we expect a list of the # same length as there are entries in the database collection assert len(res) == len(services)