From 743b34b4c1b0964b9c0feb9f9d67a0a94e251359 Mon Sep 17 00:00:00 2001 From: Joshua Kitenge Date: Tue, 26 Nov 2024 15:48:39 +0000 Subject: [PATCH 1/5] refactor: remove database backend #489 --- .flake8 | 2 +- datagateway_api/config.yaml.example | 2 - datagateway_api/src/api_start_utils.py | 48 +- datagateway_api/src/common/config.py | 54 +-- datagateway_api/src/common/date_handler.py | 3 +- .../src/common/filter_order_handler.py | 2 +- datagateway_api/src/common/filters.py | 4 +- datagateway_api/src/common/helpers.py | 4 +- .../src/datagateway_api/backend.py | 153 ------- .../src/datagateway_api/backends.py | 29 -- .../src/datagateway_api/database/__init__.py | 0 .../src/datagateway_api/database/backend.py | 125 ------ .../src/datagateway_api/database/filters.py | 220 --------- .../src/datagateway_api/database/helpers.py | 388 ---------------- .../src/datagateway_api/icat/filters.py | 6 +- .../src/datagateway_api/icat/helpers.py | 7 +- .../{database => icat}/models.py | 2 +- .../icat/{backend.py => python_icat.py} | 89 +++- .../datagateway_api/query_filter_factory.py | 43 +- .../src/resources/entities/entity_endpoint.py | 42 +- .../resources/non_entities/ping_endpoint.py | 12 +- .../non_entities/sessions_endpoints.py | 16 +- .../src/swagger/datagateway_api/openapi.yaml | 422 +++++++++--------- test/conftest.py | 2 - test/integration/conftest.py | 11 +- .../datagateway_api/db/conftest.py | 156 ------- .../datagateway_api/db/endpoints/conftest.py | 51 --- .../endpoints/test_count_with_filters_db.py | 29 -- .../db/endpoints/test_create_db.py | 114 ----- .../db/endpoints/test_delete_by_id_db.py | 40 -- .../db/endpoints/test_findone_db.py | 29 -- .../db/endpoints/test_get_by_id_db.py | 43 -- .../db/endpoints/test_get_with_filters.py | 167 ------- .../db/endpoints/test_ping_db.py | 27 -- .../db/endpoints/test_update_by_id_db.py | 66 --- .../db/endpoints/test_update_multiple_db.py | 85 ---- .../db/test_database_filter_utilities.py | 349 --------------- .../datagateway_api/db/test_entity_helper.py | 202 --------- .../db/test_requires_session_id.py | 47 -- .../icat/endpoints/test_ping_icat.py | 6 +- .../icat/test_session_handling.py | 9 +- .../datagateway_api/test_backends.py | 56 --- .../test_query_filter_factory.py | 26 +- test/integration/test_config.py | 9 - .../test_get_filters_from_query.py | 31 -- test/unit/test_config.py | 9 - test/unit/test_get_entity_object.py | 2 +- test/unit/test_query_filter.py | 12 - 48 files changed, 398 insertions(+), 2853 deletions(-) delete mode 100644 datagateway_api/src/datagateway_api/backend.py delete mode 100644 datagateway_api/src/datagateway_api/backends.py delete mode 100644 datagateway_api/src/datagateway_api/database/__init__.py delete mode 100644 datagateway_api/src/datagateway_api/database/backend.py delete mode 100644 datagateway_api/src/datagateway_api/database/filters.py delete mode 100644 datagateway_api/src/datagateway_api/database/helpers.py rename datagateway_api/src/datagateway_api/{database => icat}/models.py (99%) rename datagateway_api/src/datagateway_api/icat/{backend.py => python_icat.py} (54%) delete mode 100644 test/integration/datagateway_api/db/conftest.py delete mode 100644 test/integration/datagateway_api/db/endpoints/conftest.py delete mode 100644 test/integration/datagateway_api/db/endpoints/test_count_with_filters_db.py delete mode 100644 test/integration/datagateway_api/db/endpoints/test_create_db.py delete mode 100644 test/integration/datagateway_api/db/endpoints/test_delete_by_id_db.py delete mode 100644 test/integration/datagateway_api/db/endpoints/test_findone_db.py delete mode 100644 test/integration/datagateway_api/db/endpoints/test_get_by_id_db.py delete mode 100644 test/integration/datagateway_api/db/endpoints/test_get_with_filters.py delete mode 100644 test/integration/datagateway_api/db/endpoints/test_ping_db.py delete mode 100644 test/integration/datagateway_api/db/endpoints/test_update_by_id_db.py delete mode 100644 test/integration/datagateway_api/db/endpoints/test_update_multiple_db.py delete mode 100644 test/integration/datagateway_api/db/test_database_filter_utilities.py delete mode 100644 test/integration/datagateway_api/db/test_entity_helper.py delete mode 100644 test/integration/datagateway_api/db/test_requires_session_id.py delete mode 100644 test/integration/datagateway_api/test_backends.py delete mode 100644 test/integration/test_config.py diff --git a/.flake8 b/.flake8 index 13cd02ab..dffeb8c5 100644 --- a/.flake8 +++ b/.flake8 @@ -10,7 +10,7 @@ per-file-ignores = test/*: S101 util/icat_db_generator.py: S311 datagateway_api/wsgi.py:E402,F401 - datagateway_api/src/datagateway_api/database/models.py: N815,A003 + datagateway_api/src/datagateway_api/icat/models.py: N815,A003 datagateway_api/src/datagateway_api/icat/filters.py: C901 datagateway_api/src/search_api/models.py: B950 enable-extensions=G diff --git a/datagateway_api/config.yaml.example b/datagateway_api/config.yaml.example index 38170d43..67030dc2 100644 --- a/datagateway_api/config.yaml.example +++ b/datagateway_api/config.yaml.example @@ -1,11 +1,9 @@ --- datagateway_api: extension: "/" - backend: "python_icat" client_cache_size: 5 client_pool_init_size: 2 client_pool_max_size: 5 - db_url: "mysql+pymysql://icatdbuser:icatdbuserpw@localhost:3306/icatdb" icat_url: "https://localhost:8181" icat_check_cert: false use_reader_for_performance: diff --git a/datagateway_api/src/api_start_utils.py b/datagateway_api/src/api_start_utils.py index 6859368f..eaee0ca3 100644 --- a/datagateway_api/src/api_start_utils.py +++ b/datagateway_api/src/api_start_utils.py @@ -9,14 +9,8 @@ from flask_swagger_ui import get_swaggerui_blueprint from datagateway_api.src.common.config import Config - -# Only attempt to create a DataGateway API backend if the datagateway_api object -# is present in the config. This ensures that the API does not error on startup -# due to an AttributeError exception being thrown if the object is missing. -if Config.config.datagateway_api: - from datagateway_api.src.datagateway_api.backends import create_backend -from datagateway_api.src.datagateway_api.database.helpers import db # noqa: I202 from datagateway_api.src.datagateway_api.icat.icat_client_pool import create_client_pool +from datagateway_api.src.datagateway_api.icat.python_icat import PythonICAT from datagateway_api.src.resources.entities.entity_endpoint import ( get_count_endpoint, get_endpoint, @@ -112,21 +106,6 @@ def create_app_infrastructure(flask_app): CORS(flask_app) flask_app.url_map.strict_slashes = False api = CustomErrorHandledApi(flask_app) - - if Config.config.datagateway_api is not None: - try: - backend_type = flask_app.config["TEST_BACKEND"] - Config.config.datagateway_api.set_backend_type(backend_type) - except KeyError: - backend_type = Config.config.datagateway_api.backend - - if backend_type == "db": - flask_app.config[ - "SQLALCHEMY_DATABASE_URI" - ] = Config.config.datagateway_api.db_url - flask_app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False - db.init_app(flask_app) - specs = [] if Config.config.datagateway_api is not None: configure_datagateway_api_swaggerui_blueprint(flask_app) @@ -148,25 +127,18 @@ def create_api_endpoints(flask_app, api, specs): datagateway_api_spec = next( (spec for spec in specs if spec.title == "DataGateway API"), None, ) - try: - backend_type = flask_app.config["TEST_BACKEND"] - Config.config.datagateway_api.set_backend_type(backend_type) - except KeyError: - backend_type = Config.config.datagateway_api.backend - backend = create_backend(backend_type) + python_icat = PythonICAT() - icat_client_pool = None - if backend_type == "python_icat": - # Create client pool - icat_client_pool = create_client_pool() + # Create client pool + icat_client_pool = create_client_pool() datagateway_api_extension = Config.config.datagateway_api.extension for entity_name in endpoints: get_endpoint_resource = get_endpoint( entity_name, endpoints[entity_name], - backend, + python_icat, client_pool=icat_client_pool, ) api.add_resource( @@ -179,7 +151,7 @@ def create_api_endpoints(flask_app, api, specs): get_id_endpoint_resource = get_id_endpoint( entity_name, endpoints[entity_name], - backend, + python_icat, client_pool=icat_client_pool, ) api.add_resource( @@ -192,7 +164,7 @@ def create_api_endpoints(flask_app, api, specs): get_count_endpoint_resource = get_count_endpoint( entity_name, endpoints[entity_name], - backend, + python_icat, client_pool=icat_client_pool, ) api.add_resource( @@ -205,7 +177,7 @@ def create_api_endpoints(flask_app, api, specs): get_find_one_endpoint_resource = get_find_one_endpoint( entity_name, endpoints[entity_name], - backend, + python_icat, client_pool=icat_client_pool, ) api.add_resource( @@ -217,7 +189,7 @@ def create_api_endpoints(flask_app, api, specs): # Session endpoint session_endpoint_resource = session_endpoints( - backend, client_pool=icat_client_pool, + python_icat, client_pool=icat_client_pool, ) api.add_resource( session_endpoint_resource, @@ -227,7 +199,7 @@ def create_api_endpoints(flask_app, api, specs): datagateway_api_spec.path(resource=session_endpoint_resource, api=api) # Ping endpoint - ping_resource = ping_endpoint(backend, client_pool=icat_client_pool) + ping_resource = ping_endpoint(python_icat, client_pool=icat_client_pool) api.add_resource(ping_resource, f"{datagateway_api_extension}/ping") datagateway_api_spec.path(resource=ping_resource, api=api) diff --git a/datagateway_api/src/common/config.py b/datagateway_api/src/common/config.py index dbc96571..e44e7031 100644 --- a/datagateway_api/src/common/config.py +++ b/datagateway_api/src/common/config.py @@ -48,16 +48,12 @@ class UseReaderForPerformance(BaseModel): class DataGatewayAPI(BaseModel): """ Configuration model class that implements pydantic's BaseModel class to allow for - validation of the DataGatewayAPI config data using Python type annotations. It takes - the backend into account, meaning only the config options for the backend used are - required. + validation of the DataGatewayAPI config data using Python type annotations. """ - backend: StrictStr client_cache_size: Optional[StrictInt] client_pool_init_size: Optional[StrictInt] client_pool_max_size: Optional[StrictInt] - db_url: Optional[StrictStr] extension: StrictStr icat_check_cert: Optional[StrictBool] icat_url: Optional[StrictStr] @@ -68,23 +64,6 @@ class DataGatewayAPI(BaseModel): def __getitem__(self, item): return getattr(self, item) - @validator("db_url", always=True) - def require_db_config_value(cls, value, values): # noqa: B902, N805 - """ - By default the `db_url` config field is optional so that it does not have to be - present in the config file if `backend` is set to `python_icat`. However, if the - `backend` is set to `db`, this validator esentially makes the `db_url` config - field mandatory. This means that an error is raised, at which point the - application exits, if a `db_url` config value is not present in the config file. - - :param cls: :class:`DataGatewayAPI` pointer - :param value: The value of the given config field - :param values: The config field values loaded before the given config field - """ - if "backend" in values and values["backend"] == "db" and value is None: - raise TypeError("field required") - return value - @validator( "client_cache_size", "client_pool_init_size", @@ -93,44 +72,25 @@ def require_db_config_value(cls, value, values): # noqa: B902, N805 "icat_url", always=True, ) - def require_icat_config_value(cls, value, values): # noqa: B902, N805 + def require_icat_config_value(cls, value): # noqa: B902, N805 """ - By default the above config fields that are passed to the `@validator` decorator - are optional so that they do not have to be present in the config file if - `backend` is set to `db`. However, if the `backend` is set to `python_icat`, - this validator esentially makes these config fields mandatory. This means that - an error is raised, at which point the application exits, if any of these config - values are not present in the config file. + Validates that the required config fields for the `python_icat` + are present and not None. If any of these config values are missing, + an error is raised, causing the application to exit. :param cls: :class:`DataGatewayAPI` pointer :param value: The value of the given config field - :param values: The config field values loaded before the given config field """ - - if "backend" in values and values["backend"] == "python_icat" and value is None: - raise TypeError("field required") + if value is None: + raise TypeError("Field required for `python_icat`.") return value - def set_backend_type(self, backend_type): - """ - This setter is used as a way for automated tests to set the backend type. The - API can detect if the Flask app setup is from an automated test by checking the - app's config for a `TEST_BACKEND`. If this value exists (a KeyError will be - raised when the API is run normally, which will then grab the backend type from - `config.yaml`), it needs to be set using this function. This is required because - creating filters in the `QueryFilterFactory` is backend-specific so the backend - type must be fetched. This must be done using this module (rather than directly - importing and checking the Flask app's config) to avoid circular import issues. - """ - self.backend = backend_type - class Config: """ The behaviour of the BaseModel class can be controlled via this class. """ # Enables assignment validation on the BaseModel fields. Useful for when the - # backend type is changed using the set_backend_type function. validate_assignment = True diff --git a/datagateway_api/src/common/date_handler.py b/datagateway_api/src/common/date_handler.py index 729695aa..15efd50f 100644 --- a/datagateway_api/src/common/date_handler.py +++ b/datagateway_api/src/common/date_handler.py @@ -32,8 +32,7 @@ def is_str_a_date(potential_date): @staticmethod def str_to_datetime_object(data): """ - Convert a string to a `datetime.datetime` object. This is commonly used when - storing user input in ICAT (using the Python ICAT backend). + Convert a string to a `datetime.datetime` object. Python 3.7+ has support for `datetime.fromisoformat()` which would be a more elegant solution to this conversion operation since dates are converted into ISO diff --git a/datagateway_api/src/common/filter_order_handler.py b/datagateway_api/src/common/filter_order_handler.py index ec9a9f22..ce0a9133 100644 --- a/datagateway_api/src/common/filter_order_handler.py +++ b/datagateway_api/src/common/filter_order_handler.py @@ -176,7 +176,7 @@ def clear_python_icat_order_filters(self): def manage_icat_filters(self, filters, query): """ Utility function to call other functions in this class, used to manage filters - when using the Python ICAT backend. These steps are the same with the different + when using the Python ICAT. These steps are the same with the different types of requests that utilise filters, therefore this function helps to reduce code duplication diff --git a/datagateway_api/src/common/filters.py b/datagateway_api/src/common/filters.py index 3b5270c3..72584bc9 100644 --- a/datagateway_api/src/common/filters.py +++ b/datagateway_api/src/common/filters.py @@ -21,8 +21,8 @@ class WhereFilter(QueryFilter): precedence = 1 def __init__(self, field, value, operation): - # The field is set to None as a precaution but this should be set by the - # individual backend since backends deal with this data differently + # The field is set to None as a precaution but this should be set + # when initialising Python ICAT self.field = None self.value = value self.operation = operation diff --git a/datagateway_api/src/common/helpers.py b/datagateway_api/src/common/helpers.py index 79d8c7ec..dd9c3616 100644 --- a/datagateway_api/src/common/helpers.py +++ b/datagateway_api/src/common/helpers.py @@ -17,7 +17,7 @@ FilterError, MissingCredentialsError, ) -from datagateway_api.src.datagateway_api.database import models +from datagateway_api.src.datagateway_api.icat import models from datagateway_api.src.resources.entities.entity_endpoint_dict import endpoints log = logging.getLogger() @@ -137,7 +137,7 @@ def get_entity_object_from_name(entity_name): :param entity_name: Name of the entity to fetch a version from this model :type entity_name: :class:`str` :return: Object of the entity requested (e.g. - :class:`.datagateway_api.database.models.INVESTIGATIONINSTRUMENT`) + :class:`.datagateway_api.icat.models.INVESTIGATIONINSTRUMENT`) :raises: KeyError: If an entity model cannot be found as a class in this model """ try: diff --git a/datagateway_api/src/datagateway_api/backend.py b/datagateway_api/src/datagateway_api/backend.py deleted file mode 100644 index ec9d8e74..00000000 --- a/datagateway_api/src/datagateway_api/backend.py +++ /dev/null @@ -1,153 +0,0 @@ -from abc import ABC, abstractmethod - - -class Backend(ABC): - """ - Abstact base class for implementations of a backend to inherit from - """ - - @abstractmethod - def ping(self): - """ - Endpoint requiring no authentication to check the API is alive and does a basic - check to ensure the connection method to ICAT is working - :returns: String to tell user the API is OK - """ - pass - - @abstractmethod - def login(self, credentials): - """ - Attempt to log a user in using the provided credentials - :param credentials: The user's credentials (including mechanism). Credentials - should take the following format in JSON: - { username: "value", password: "value", mechanism: "value"} - :returns: a session ID - """ - pass - - @abstractmethod - def get_session_details(self, session_id): - """ - Returns the details of a user's session - :param session_id: The user's session ID - :returns: The user's session details - """ - pass - - @abstractmethod - def refresh(self, session_id): - """ - Attempts to refresh a user's session - :param session_id: The user's session ID - :returns: the user's refreshed session ID - """ - pass - - @abstractmethod - def logout(self, session_id): - """ - Logs a user out - :param session_id: The user's session ID - """ - pass - - @abstractmethod - def get_with_filters(self, session_id, entity_type, filters): - """ - Given a list of filters supplied in json format, returns entities that match the - filters for the given entity type - - :param session_id: The session id of the requesting user - :param entity_type: The type of entity - :param filters: The list of filters to be applied - :return: A list of the matching entities in json format - """ - pass - - @abstractmethod - def create(self, session_id, entity_type, data): - """ - Create one or more entities, from the given list containing json. Each entity - must not contain its ID - - :param session_id: The session id of the requesting user - :param entity_type: The type of entity - :param data: The entities to be created - :return: The created entities. - """ - pass - - @abstractmethod - def update(self, session_id, entity_type, data): - """ - Update one or more entities, from the given list containing json. Each entity - must contain its ID - - :param session_id: The session id of the requesting user - :param entity_type: The type of entity - :param data: the list of updated values or a dictionary - :return: The list of updated entities. - """ - pass - - @abstractmethod - def get_one_with_filters(self, session_id, entity_type, filters): - """ - Returns the first entity that matches a given filter, for a given entity type - - :param session_id: The session id of the requesting user - :param entity_type: The type of entity - :param filters: the filter to be applied to the query - :return: the first entity matching the filter - """ - pass - - @abstractmethod - def count_with_filters(self, session_id, entity_type, filters): - """ - Returns the count of the entities that match a given filter for a given entity - type - - :param session_id: The session id of the requesting user - :param entity_type: The type of entity - :param filters: the filters to be applied to the query - :return: int: the count of the entities - """ - pass - - @abstractmethod - def get_with_id(self, session_id, entity_type, id_): - """ - Gets the entity matching the given ID for the given entity type - - :param session_id: The session id of the requesting user - :param entity_type: The type of entity - :param id_: the id of the record to find - :return: the entity retrieved - """ - pass - - @abstractmethod - def delete_with_id(self, session_id, entity_type, id_): - """ - Deletes the row matching the given ID for the given entity type - - :param session_id: The session id of the requesting user - :param table: the table to be searched - :param id_: the id of the record to delete - """ - pass - - @abstractmethod - def update_with_id(self, session_id, entity_type, id_, data): - """ - Updates the row matching the given ID for the given entity type - - :param session_id: The session id of the requesting user - :param entity_type: The type of entity - :param id_: the id of the record to update - :param data: The dictionary that the entity should be updated with - :return: The updated entity. - """ - pass diff --git a/datagateway_api/src/datagateway_api/backends.py b/datagateway_api/src/datagateway_api/backends.py deleted file mode 100644 index a5767792..00000000 --- a/datagateway_api/src/datagateway_api/backends.py +++ /dev/null @@ -1,29 +0,0 @@ -import sys - -from datagateway_api.src.datagateway_api.database.backend import DatabaseBackend -from datagateway_api.src.datagateway_api.icat.backend import PythonICATBackend - - -def create_backend(backend_type): - """ - Create an instance of a backend dependent on the value parsed into the function. The - value will typically be from the contents of `config.yaml`, however when creating a - backend during automated tests the value will be from the Flask app's config (which - will be set in the API's config at `common.config` - - The API will exit if a valid value isn't given. - - :param backend_type: The type of backend that should be created and used for the API - :type backend_type: :class:`str` - :return: Either an instance of `common.database.backend.DatabaseBackend` or - `common.icat.backend.PythonICATBackend` - """ - - if backend_type == "db": - backend = DatabaseBackend() - elif backend_type == "python_icat": - backend = PythonICATBackend() - else: - sys.exit(f"Invalid config value '{backend_type}' for config option backend") - - return backend diff --git a/datagateway_api/src/datagateway_api/database/__init__.py b/datagateway_api/src/datagateway_api/database/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/datagateway_api/src/datagateway_api/database/backend.py b/datagateway_api/src/datagateway_api/database/backend.py deleted file mode 100644 index 2e5e4ac8..00000000 --- a/datagateway_api/src/datagateway_api/database/backend.py +++ /dev/null @@ -1,125 +0,0 @@ -import datetime -import logging -import uuid - -from sqlalchemy import inspect -from sqlalchemy.exc import SQLAlchemyError - -from datagateway_api.src.common.constants import Constants -from datagateway_api.src.common.exceptions import AuthenticationError, DatabaseError -from datagateway_api.src.common.helpers import ( - get_entity_object_from_name, - queries_records, -) -from datagateway_api.src.datagateway_api.backend import Backend -from datagateway_api.src.datagateway_api.database.helpers import ( - create_rows_from_json, - db, - delete_row_by_id, - get_filtered_row_count, - get_first_filtered_row, - get_row_by_id, - get_rows_by_filter, - insert_row_into_table, - patch_entities, - requires_session_id, - update_row_from_id, -) -from datagateway_api.src.datagateway_api.database.models import SESSION - - -log = logging.getLogger() - - -class DatabaseBackend(Backend): - """ - Class that contains functions to access and modify data in an ICAT database directly - """ - - def ping(self, **kwargs): - log.info("Pinging DB connection to ensure API is alive and well") - - try: - inspector = inspect(db.engine) - tables = inspector.get_table_names() - log.debug("Tables on ping: %s", tables) - except SQLAlchemyError as e: - raise DatabaseError(e) - - return Constants.PING_OK_RESPONSE - - def login(self, credentials, **kwargs): - if credentials["username"] == "user" and credentials["password"] == "password": - session_id = str(uuid.uuid1()) - insert_row_into_table( - SESSION, - SESSION( - id=session_id, - username=f"{credentials['mechanism']}/root", - expireDateTime=datetime.datetime.now() + datetime.timedelta(days=1), - ), - ) - return session_id - else: - raise AuthenticationError("Username and password are incorrect") - - @requires_session_id - def get_session_details(self, session_id, **kwargs): - return get_row_by_id(SESSION, session_id).to_dict() - - @requires_session_id - def refresh(self, session_id, **kwargs): - return session_id - - @requires_session_id - @queries_records - def logout(self, session_id, **kwargs): - return delete_row_by_id(SESSION, session_id) - - @requires_session_id - @queries_records - def get_with_filters(self, session_id, entity_type, filters, **kwargs): - table = get_entity_object_from_name(entity_type) - return get_rows_by_filter(table, filters) - - @requires_session_id - @queries_records - def create(self, session_id, entity_type, data, **kwargs): - table = get_entity_object_from_name(entity_type) - return create_rows_from_json(table, data) - - @requires_session_id - @queries_records - def update(self, session_id, entity_type, data, **kwargs): - table = get_entity_object_from_name(entity_type) - return patch_entities(table, data) - - @requires_session_id - @queries_records - def get_one_with_filters(self, session_id, entity_type, filters, **kwargs): - table = get_entity_object_from_name(entity_type) - return get_first_filtered_row(table, filters) - - @requires_session_id - @queries_records - def count_with_filters(self, session_id, entity_type, filters, **kwargs): - table = get_entity_object_from_name(entity_type) - return get_filtered_row_count(table, filters) - - @requires_session_id - @queries_records - def get_with_id(self, session_id, entity_type, id_, **kwargs): - table = get_entity_object_from_name(entity_type) - return get_row_by_id(table, id_).to_dict() - - @requires_session_id - @queries_records - def delete_with_id(self, session_id, entity_type, id_, **kwargs): - table = get_entity_object_from_name(entity_type) - return delete_row_by_id(table, id_) - - @requires_session_id - @queries_records - def update_with_id(self, session_id, entity_type, id_, data, **kwargs): - table = get_entity_object_from_name(entity_type) - return update_row_from_id(table, id_, data) diff --git a/datagateway_api/src/datagateway_api/database/filters.py b/datagateway_api/src/datagateway_api/database/filters.py deleted file mode 100644 index e8a9d85b..00000000 --- a/datagateway_api/src/datagateway_api/database/filters.py +++ /dev/null @@ -1,220 +0,0 @@ -import logging - -from sqlalchemy import asc, desc - -from datagateway_api.src.common.exceptions import FilterError, MultipleIncludeError -from datagateway_api.src.common.filters import ( - DistinctFieldFilter, - IncludeFilter, - LimitFilter, - OrderFilter, - SkipFilter, - WhereFilter, -) -from datagateway_api.src.common.helpers import get_entity_object_from_name - - -log = logging.getLogger() - - -class DatabaseFilterUtilities: - """ - Class containing utility functions used in the WhereFilter and DistinctFilter - - In this class, the terminology of 'included entities' has been made more generic to - 'related entities'. When these functions are used with the WhereFilter, the related - entities are in fact included entities (entities which are also present in the input - of an include filter in the same request). However, when these functions are used - with the DistinctFilter, they are related entities, not included entities as there's - no requirement for the entity names to also be present in an include filter (this is - to match the ICAT backend) - """ - - def __init__(self): - self.field = None - self.related_field = None - self.related_related_field = None - - def extract_filter_fields(self, field): - """ - Extract the related fields names and put them into separate variables - - :param field: ICAT field names, separated by dots - :type field: :class:`str` - :raises ValueError: If the maximum related/included depth is exceeded - """ - - # Flushing fields in case they have been previously set - self.field = None - self.related_field = None - self.related_related_field = None - - fields = field.split(".") - related_depth = len(fields) - - log.debug("Fields: %s, Related Depth: %d", fields, related_depth) - - if related_depth == 1: - self.field = fields[0] - self.distinct_join_flag = True - elif related_depth == 2: - self.field = fields[0] - self.related_field = fields[1] - elif related_depth == 3: - self.field = fields[0] - self.related_field = fields[1] - self.related_related_field = fields[2] - else: - raise ValueError(f"Maximum related depth exceeded. {field}'s depth > 3") - - def add_query_join(self, query): - """ - Adds any required JOINs to the query if any related fields have been used in the - filter - - :param query: The query to have filters applied to - :type query: :class:`.common.datagateway_api.database.helpers.[QUERY]` - """ - - if self.related_related_field: - included_table = get_entity_object_from_name(self.field) - included_included_table = get_entity_object_from_name(self.related_field) - query.base_query = query.base_query.join(included_table).join( - included_included_table, - ) - elif self.related_field: - included_table = get_entity_object_from_name(self.field) - query.base_query = query.base_query.join(included_table) - - def get_entity_model_for_filter(self, query): - """ - Fetches the appropriate entity model based on the contents of the instance - variables of this class - - :param query: The query to have filters applied to - :type query: :class:`.common.datagateway_api.database.helpers.[QUERY]` - :return: Entity model of the field (usually the field relating to the endpoint - the request is coming from) - """ - if self.related_related_field: - included_included_table = get_entity_object_from_name(self.related_field) - field = self._get_field(included_included_table, self.related_related_field) - elif self.related_field: - included_table = get_entity_object_from_name(self.field) - field = self._get_field(included_table, self.related_field) - else: - # No related fields - field = self._get_field(query.table, self.field) - - return field - - def _get_field(self, table, field): - try: - return getattr(table, field) - except AttributeError: - raise FilterError(f"Unknown attribute {field} on table {table.__name__}") - - -class DatabaseWhereFilter(WhereFilter, DatabaseFilterUtilities): - def __init__(self, field, value, operation): - WhereFilter.__init__(self, field, value, operation) - DatabaseFilterUtilities.__init__(self) - - self.extract_filter_fields(field) - - def apply_filter(self, query): - self.add_query_join(query) - field = self.get_entity_model_for_filter(query) - - if self.operation == "eq": - query.base_query = query.base_query.filter(field == self.value) - elif self.operation == "ne": - query.base_query = query.base_query.filter(field != self.value) - elif self.operation == "like": - query.base_query = query.base_query.filter(field.like(f"%{self.value}%")) - elif self.operation == "nlike": - query.base_query = query.base_query.filter(field.notlike(f"%{self.value}%")) - elif self.operation == "lt": - query.base_query = query.base_query.filter(field < self.value) - elif self.operation == "lte": - query.base_query = query.base_query.filter(field <= self.value) - elif self.operation == "gt": - query.base_query = query.base_query.filter(field > self.value) - elif self.operation == "gte": - query.base_query = query.base_query.filter(field >= self.value) - elif self.operation == "in": - query.base_query = query.base_query.filter(field.in_(self.value)) - else: - raise FilterError( - f" Bad operation given to where filter. operation: {self.operation}", - ) - - -class DatabaseDistinctFieldFilter(DistinctFieldFilter, DatabaseFilterUtilities): - def __init__(self, fields): - DistinctFieldFilter.__init__(self, fields) - DatabaseFilterUtilities.__init__(self) - - def apply_filter(self, query): - query.is_distinct_fields_query = True - - try: - distinct_fields = [] - for field_name in self.fields: - self.extract_filter_fields(field_name) - distinct_fields.append(self.get_entity_model_for_filter(query)) - - # Base query must be set to a DISTINCT query before adding JOINs - if these - # actions are done in the opposite order, the JOINs will overwrite the - # SELECT multiple and effectively turn the query into a `SELECT *` - query.base_query = ( - query.session.query(*distinct_fields) - .select_from(query.table) - .distinct() - ) - - for field_name in self.fields: - self.extract_filter_fields(field_name) - self.add_query_join(query) - except AttributeError: - raise FilterError("Bad field requested") - - -class DatabaseOrderFilter(OrderFilter): - def __init__(self, field, direction): - super().__init__(field, direction) - - def apply_filter(self, query): - if self.direction.upper() == "ASC": - query.base_query = query.base_query.order_by(asc(self.field.upper())) - elif self.direction.upper() == "DESC": - query.base_query = query.base_query.order_by(desc(self.field.upper())) - else: - raise FilterError(f" Bad filter: {self.direction}") - - -class DatabaseSkipFilter(SkipFilter): - def __init__(self, skip_value): - super().__init__(skip_value) - - def apply_filter(self, query): - query.base_query = query.base_query.offset(self.skip_value) - - -class DatabaseLimitFilter(LimitFilter): - def __init__(self, limit_value): - self.limit_value = limit_value - - def apply_filter(self, query): - query.base_query = query.base_query.limit(self.limit_value) - - -class DatabaseIncludeFilter(IncludeFilter): - def __init__(self, included_filters): - super().__init__(included_filters) - - def apply_filter(self, query): - if not query.include_related_entities: - query.include_related_entities = True - else: - raise MultipleIncludeError() diff --git a/datagateway_api/src/datagateway_api/database/helpers.py b/datagateway_api/src/datagateway_api/database/helpers.py deleted file mode 100644 index debe10fa..00000000 --- a/datagateway_api/src/datagateway_api/database/helpers.py +++ /dev/null @@ -1,388 +0,0 @@ -from abc import ABC, abstractmethod -import datetime -from functools import wraps -import logging - -from flask_sqlalchemy import SQLAlchemy - -from datagateway_api.src.common.exceptions import ( - AuthenticationError, - BadRequestError, - MissingRecordError, -) -from datagateway_api.src.common.filter_order_handler import FilterOrderHandler -from datagateway_api.src.common.helpers import map_distinct_attributes_to_results -from datagateway_api.src.datagateway_api.database.filters import ( - DatabaseDistinctFieldFilter, - DatabaseIncludeFilter as IncludeFilter, - DatabaseWhereFilter as WhereFilter, -) -from datagateway_api.src.datagateway_api.database.models import SESSION - - -log = logging.getLogger() - -db = SQLAlchemy() - - -def requires_session_id(method): - """ - Decorator for database backend methods that makes sure a valid session_id is - provided. It expects that session_id is the second argument supplied to the function - - :param method: The method for the backend operation - :raises AuthenticationError, if a valid session_id is not provided with the request - """ - - @wraps(method) - def wrapper_requires_session(*args, **kwargs): - log.info(" Authenticating consumer") - session = db.session - query = session.query(SESSION).filter(SESSION.id == args[1]).first() - if query is not None: - log.info(" Closing DB session") - session.close() - log.info(" Consumer authenticated") - return method(*args, **kwargs) - else: - log.info(" Could not authenticate consumer, closing DB session") - session.close() - raise AuthenticationError("Forbidden") - - return wrapper_requires_session - - -class Query(ABC): - """ - The base query class that all other queries extend from. This defines the enter and - exit methods, used to handle sessions. It is expected that all queries would be used - with the 'with' keyword in most cases for this reason. - """ - - @abstractmethod - def __init__(self, table): - self.session = db.session - self.table = table - self.base_query = self.session.query(table) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - log.info("Closing DB session") - self.session.close() - - @abstractmethod - def execute_query(self): - pass - - def commit_changes(self): - """ - Commits all changes to the database and closes the session - """ - log.info(" Committing changes to %s", self.table) - try: - self.session.commit() - except Exception as e: - log.error("Error whilst committing changes to %s, rolling back", self.table) - self.session.rollback() - raise BadRequestError(f"Bad request: {e}") - - -class CountQuery(Query): - def __init__(self, table): - super().__init__(table) - self.include_related_entities = False - - def execute_query(self): - self.commit_changes() - - def get_count(self): - try: - return self.base_query.count() - finally: - self.execute_query() - - -class ReadQuery(Query): - def __init__(self, table): - super().__init__(table) - self.include_related_entities = False - self.is_distinct_fields_query = False - - def commit_changes(self): - log.info("Closing DB session") - - def execute_query(self): - self.commit_changes() - - def get_single_result(self): - result = self.base_query.first() - if result is not None: - return result - raise MissingRecordError(" No result found") - - def get_all_results(self): - results = self.base_query.all() - if results is not None: - return results - raise MissingRecordError(" No results found") - - -class CreateQuery(Query): - def __init__(self, table, row): - super().__init__(table) - self.row = row - self.inserted_row = None - - def execute_query(self): - """ - Determines if the row is a row object or dictionary then commits it to the table - """ - if type(self.row) is not dict: - record = self.row - else: - record = self.table() - record.update_from_dict(self.row) - record.createTime = datetime.datetime.now() - record.modTime = datetime.datetime.now() - record.createId = "user" - record.modId = "user" - self.session.add(record) - self.commit_changes() - self.session.refresh(record) - self.inserted_row = record - - -class UpdateQuery(Query): - def __init__(self, table, row, new_values): - super().__init__(table) - self.row = row - self.new_values = new_values - - def execute_query(self): - log.info(" Updating row in %s", self.table) - self.row.update_from_dict(self.new_values) - self.session.add(self.row) - self.commit_changes() - - -class DeleteQuery(Query): - def __init__(self, table, row): - super().__init__(table) - self.row = row - - def execute_query(self): - log.info(" Deleting row %s from %s", self.row, self.table.__tablename__) - self.session.delete(self.row) - self.commit_changes() - - -def insert_row_into_table(table, row): - """ - Insert the given row into its table - :param table: The table to be inserted to - :param row: The row to be inserted - """ - with CreateQuery(table, row) as create_query: - create_query.execute_query() - - -def create_row_from_json(table, data): - """ - Given a json dictionary create a row in the table from it - :param table: the table for the row to be inserted into - :param data: the dictionary containing the values - :return: The inserted row as a dictionary - """ - with CreateQuery(table, data) as create_query: - create_query.execute_query() - return create_query.inserted_row.to_dict() - - -def create_rows_from_json(table, data): - """ - Given a List containing dictionary representations of entities, or a dictionary - representation of an entity, insert the entities into the table and return the - created entities - - :param table: The table to insert the entities in - :param data: The entities to be inserted - :return: The inserted entities - """ - if type(data) is list: - return [create_row_from_json(table, entity) for entity in data] - return create_row_from_json(table, data) - - -def get_row_by_id(table, id_): - """ - Gets the row matching the given ID from the given table, raises MissingRecordError - if it can not be found - - :param table: the table to be searched - :param id_: the id of the record to find - :return: the record retrieved - """ - with ReadQuery(table) as read_query: - log.info(" Querying %s for record with ID: %s", table.__tablename__, id_) - where_filter = WhereFilter("id", id_, "eq") - where_filter.apply_filter(read_query) - return read_query.get_single_result() - - -def delete_row_by_id(table, id_): - """ - Deletes the row matching the given ID from the given table, raises - MissingRecordError if it can not be found - - :param table: the table to be searched - :param id_: the id of the record to delete - """ - log.info(" Deleting row from %s with ID: %s", table.__tablename__, id_) - row = get_row_by_id(table, id_) - with DeleteQuery(table, row) as delete_query: - delete_query.execute_query() - - -def update_row_from_id(table, id_, new_values): - """ - Updates a record in a table - - :param table: The table the record is in - :param id_: The id of the record - :param new_values: A JSON string containing what columns are to be updated - """ - row = get_row_by_id(table, id_) - with UpdateQuery(table, row, new_values) as update_query: - update_query.execute_query() - - -def get_filtered_read_query_results(filter_handler, filters, query): - """ - Given a filter handler, list of filters and a query. Apply the filters and execute - the query - - :param filter_handler: The filter handler to apply the filters - :param filters: The filters to be applied - :param query: The query for the filters to be applied to - :return: The results of the query as a list of dictionaries - """ - filter_handler.add_filters(filters) - filter_handler.apply_filters(query) - results = query.get_all_results() - if query.is_distinct_fields_query: - return _get_distinct_fields_as_dicts(filters, results) - if query.include_related_entities: - return _get_results_with_include(filters, results) - return list(map(lambda x: x.to_dict(), results)) - - -def _get_results_with_include(filters, results): - """ - Given a list of entities and a list of filters, use the include filter to nest the - included entities requested in the include filter given - - :param filters: The list of filters - :param results: The list of entities - :return: A list of nested dictionaries representing the entity results - """ - for query_filter in filters: - if type(query_filter) is IncludeFilter: - return [x.to_nested_dict(query_filter.included_filters) for x in results] - - -def _get_distinct_fields_as_dicts(filters, results): - """ - Given a list of column results return a list of dictionaries where each column name - is the key and the column value is the dictionary key value - - :param results: A list of sql alchemy result objects - :return: A list of dictionary representations of the sqlalchemy result objects - """ - distinct_fields = [] - for query_filter in filters: - if type(query_filter) is DatabaseDistinctFieldFilter: - distinct_fields.extend(query_filter.fields) - - dictionaries = [] - for result in results: - dictionary = map_distinct_attributes_to_results(distinct_fields, result) - dictionaries.append(dictionary) - - return dictionaries - - -def get_rows_by_filter(table, filters): - """ - Given a list of filters supplied in json format, returns entities that match the - filters from the given table - - :param table: The table to checked - :param filters: The list of filters to be applied - :return: A list of the rows returned in dictionary form - """ - with ReadQuery(table) as query: - filter_handler = FilterOrderHandler() - return get_filtered_read_query_results(filter_handler, filters, query) - - -def get_first_filtered_row(table, filters): - """ - returns the first row that matches a given filter, in a given table - :param table: the table to be checked - :param filters: the filter to be applied to the query - :return: the first row matching the filter - """ - log.info(" Getting first filtered row for %s", table.__tablename__) - try: - result = get_rows_by_filter(table, filters)[0] - except IndexError: - raise MissingRecordError() - return result - - -def get_filtered_row_count(table, filters): - """ - returns the count of the rows that match a given filter in a given table - :param table: the table to be checked - :param filters: the filters to be applied to the query - :return: int: the count of the rows - """ - - log.info(" getting count for %s", table.__tablename__) - with CountQuery(table) as count_query: - filter_handler = FilterOrderHandler() - filter_handler.add_filters(filters) - filter_handler.apply_filters(count_query) - return count_query.get_count() - - -def patch_entities(table, json_list): - """ - Update one or more rows in the given table, from the given list containing json. - Each entity must contain its ID - - :param table: The table of the entities - :param json_list: the list of updated values or a dictionary - :return: The list of updated rows. - """ - log.info(" Patching entities in %s", table.__tablename__) - results = [] - if type(json_list) is dict: - for key in json_list: - if key.upper() == "ID": - update_row_from_id(table, json_list[key], json_list) - result = get_row_by_id(table, json_list[key]) - results.append(result.to_dict()) - else: - for entity in json_list: - for key in entity: - if key.upper() == "ID": - update_row_from_id(table, entity[key], entity) - result = get_row_by_id(table, entity[key]) - results.append(result.to_dict()) - if len(results) == 0: - raise BadRequestError(f" Bad request made, request: {json_list}") - - return results diff --git a/datagateway_api/src/datagateway_api/icat/filters.py b/datagateway_api/src/datagateway_api/icat/filters.py index 80dba767..58a6a76b 100644 --- a/datagateway_api/src/datagateway_api/icat/filters.py +++ b/datagateway_api/src/datagateway_api/icat/filters.py @@ -82,8 +82,7 @@ def create_filter(self): self.value = str(self.value).replace("[", "(").replace("]", ")") # DataGateway Search can send requests with blank lists. Adding NULL to the - # filter prevents the API from returning a 500. An empty list will be - # returned instead, equivalent to the DB backend + # filter prevents the API from returning a 500. if self.value == "()": self.value = "(NULL)" @@ -95,8 +94,7 @@ def create_filter(self): self.value = str(self.value).replace("[", "(").replace("]", ")") # DataGateway Search can send requests with blank lists. Adding NULL to the - # filter prevents the API from returning a 500. An empty list will be - # returned instead, equivalent to the DB backend + # filter prevents the API from returning a 500. if self.value == "()": self.value = "(NULL)" diff --git a/datagateway_api/src/datagateway_api/icat/helpers.py b/datagateway_api/src/datagateway_api/icat/helpers.py index 35922cf1..84e6494e 100644 --- a/datagateway_api/src/datagateway_api/icat/helpers.py +++ b/datagateway_api/src/datagateway_api/icat/helpers.py @@ -37,7 +37,7 @@ def requires_session_id(method): """ - Decorator for Python ICAT backend methods that looks out for session errors when + Decorator for Python ICAT methods that looks out for session errors when using the API. The API call runs and an ICATSessionError may be raised due to an expired session, invalid session ID etc. @@ -50,7 +50,7 @@ def requires_session_id(method): decorator is applied, which is reasonable to assume considering the current method signatures of all the endpoints. - :param method: The method for the backend operation + :param method: The method for the python ICAT operation :raises AuthenticationError: If a valid session_id is not provided with the request """ @@ -61,7 +61,8 @@ def wrapper_requires_session(*args, **kwargs): client = get_cached_client(args[1], client_pool) client.sessionId = args[1] - # Client object put into kwargs so it can be accessed by backend functions + # Client object put into kwargs so it can be accessed by + # python ICAT functions kwargs["client"] = client # Find out if session has expired diff --git a/datagateway_api/src/datagateway_api/database/models.py b/datagateway_api/src/datagateway_api/icat/models.py similarity index 99% rename from datagateway_api/src/datagateway_api/database/models.py rename to datagateway_api/src/datagateway_api/icat/models.py index 33738224..74dbfbe6 100644 --- a/datagateway_api/src/datagateway_api/database/models.py +++ b/datagateway_api/src/datagateway_api/icat/models.py @@ -83,7 +83,7 @@ def _make_serializable(self, field): :return: The converted field """ if isinstance(field, datetime): - # Add timezone info to match ICAT backend + # Add timezone info to match for python ICAT field = field.replace(tzinfo=tzlocal()) return DateHandler.datetime_object_to_str(field) elif isinstance(field, Decimal): diff --git a/datagateway_api/src/datagateway_api/icat/backend.py b/datagateway_api/src/datagateway_api/icat/python_icat.py similarity index 54% rename from datagateway_api/src/datagateway_api/icat/backend.py rename to datagateway_api/src/datagateway_api/icat/python_icat.py index 83990117..f09fe37c 100644 --- a/datagateway_api/src/datagateway_api/icat/backend.py +++ b/datagateway_api/src/datagateway_api/icat/python_icat.py @@ -5,7 +5,6 @@ from datagateway_api.src.common.constants import Constants from datagateway_api.src.common.exceptions import AuthenticationError, PythonICATError from datagateway_api.src.common.helpers import queries_records -from datagateway_api.src.datagateway_api.backend import Backend from datagateway_api.src.datagateway_api.icat.helpers import ( create_entities, delete_entity_by_id, @@ -26,12 +25,17 @@ log = logging.getLogger() -class PythonICATBackend(Backend): +class PythonICAT: """ Class that contains functions to access and modify data in an ICAT database directly """ def ping(self, **kwargs): + """ + Endpoint requiring no authentication to check the API is alive and does a basic + check to ensure the connection method to ICAT is working. + :returns: String to tell user the API is OK. + """ log.info("Pinging ICAT to ensure API is alive and well") client_pool = kwargs.get("client_pool") @@ -46,6 +50,13 @@ def ping(self, **kwargs): return Constants.PING_OK_RESPONSE def login(self, credentials, **kwargs): + """ + Attempt to log a user in using the provided credentials. + :param credentials: The user's credentials (including mechanism). Credentials + should take the following format in JSON: + { username: "value", password: "value", mechanism: "value"} + :returns: a session ID. + """ log.info("Logging in to get session ID") client_pool = kwargs.get("client_pool") @@ -71,56 +82,130 @@ def login(self, credentials, **kwargs): @requires_session_id def get_session_details(self, session_id, **kwargs): + """ + Returns the details of a user's session. + :param session_id: The user's session ID. + :returns: The user's session details. + """ log.info("Getting session details for session: %s", session_id) return get_session_details_helper(kwargs.get("client")) @requires_session_id def refresh(self, session_id, **kwargs): + """ + Attempts to refresh a user's session. + :param session_id: The user's session ID. + :returns: the user's refreshed session ID. + """ log.info("Refreshing session: %s", session_id) return refresh_client_session(kwargs.get("client")) @requires_session_id @queries_records def logout(self, session_id, **kwargs): + """ + Logs a user out. + :param session_id: The user's session ID. + """ log.info("Logging out of the Python ICAT client") return logout_icat_client(kwargs.get("client")) @requires_session_id @queries_records def get_with_filters(self, session_id, entity_type, filters, **kwargs): + """ + Given a list of filters supplied in JSON format, returns entities that match + the filters for the given entity type. + :param session_id: The session ID of the requesting user. + :param entity_type: The type of entity. + :param filters: The list of filters to be applied. + :return: A list of the matching entities in JSON format. + """ return get_entity_with_filters(kwargs.get("client"), entity_type, filters) @requires_session_id @queries_records def create(self, session_id, entity_type, data, **kwargs): + """ + Create one or more entities, from the given list containing JSON. Each entity + must not contain its ID. + :param session_id: The session ID of the requesting user. + :param entity_type: The type of entity. + :param data: The entities to be created. + :return: The created entities. + """ return create_entities(kwargs.get("client"), entity_type, data) @requires_session_id @queries_records def update(self, session_id, entity_type, data, **kwargs): + """ + Update one or more entities, from the given list containing JSON. Each entity + must contain its ID. + :param session_id: The session ID of the requesting user. + :param entity_type: The type of entity. + :param data: The list of updated values or a dictionary. + :return: The list of updated entities. + """ return update_entities(kwargs.get("client"), entity_type, data) @requires_session_id @queries_records def get_one_with_filters(self, session_id, entity_type, filters, **kwargs): + """ + Returns the first entity that matches a given filter, for a given entity type. + :param session_id: The session ID of the requesting user. + :param entity_type: The type of entity. + :param filters: The filter to be applied to the query. + :return: The first entity matching the filter. + """ return get_first_result_with_filters(kwargs.get("client"), entity_type, filters) @requires_session_id @queries_records def count_with_filters(self, session_id, entity_type, filters, **kwargs): + """ + Returns the count of the entities that match a given filter for a given entity + type. + :param session_id: The session ID of the requesting user. + :param entity_type: The type of entity. + :param filters: The filters to be applied to the query. + :return: int: The count of the entities. + """ return get_count_with_filters(kwargs.get("client"), entity_type, filters) @requires_session_id @queries_records def get_with_id(self, session_id, entity_type, id_, **kwargs): + """ + Gets the entity matching the given ID for the given entity type. + :param session_id: The session ID of the requesting user. + :param entity_type: The type of entity. + :param id_: The ID of the record to find. + :return: The entity retrieved. + """ return get_entity_by_id(kwargs.get("client"), entity_type, id_, True) @requires_session_id @queries_records def delete_with_id(self, session_id, entity_type, id_, **kwargs): + """ + Deletes the row matching the given ID for the given entity type. + :param session_id: The session ID of the requesting user. + :param entity_type: The type of entity. + :param id_: The ID of the record to delete. + """ return delete_entity_by_id(kwargs.get("client"), entity_type, id_) @requires_session_id @queries_records def update_with_id(self, session_id, entity_type, id_, data, **kwargs): + """ + Updates the row matching the given ID for the given entity type. + :param session_id: The session ID of the requesting user. + :param entity_type: The type of entity. + :param id_: The ID of the record to update. + :param data: The dictionary that the entity should be updated with. + :return: The updated entity. + """ return update_entity_by_id(kwargs.get("client"), entity_type, id_, data) diff --git a/datagateway_api/src/datagateway_api/query_filter_factory.py b/datagateway_api/src/datagateway_api/query_filter_factory.py index 27241f7a..717e3398 100644 --- a/datagateway_api/src/datagateway_api/query_filter_factory.py +++ b/datagateway_api/src/datagateway_api/query_filter_factory.py @@ -1,10 +1,14 @@ import logging from datagateway_api.src.common.base_query_filter_factory import QueryFilterFactory -from datagateway_api.src.common.config import Config -from datagateway_api.src.common.exceptions import ( - ApiError, - FilterError, +from datagateway_api.src.common.exceptions import FilterError +from datagateway_api.src.datagateway_api.icat.filters import ( + PythonICATDistinctFieldFilter as DistinctFieldFilter, + PythonICATIncludeFilter as IncludeFilter, + PythonICATLimitFilter as LimitFilter, + PythonICATOrderFilter as OrderFilter, + PythonICATSkipFilter as SkipFilter, + PythonICATWhereFilter as WhereFilter, ) log = logging.getLogger() @@ -16,11 +20,6 @@ def get_query_filter(request_filter, entity_name=None): """ Given a filter, return a matching Query filter object - The filters are imported inside this method to enable the unit tests to not rely - on the contents of `config.yaml`. If they're imported at the top of the file, - the backend type won't have been updated if the Flask app has been created from - an automated test (file imports occur before `create_api_endpoints()` executes). - :param request_filter: The filter to create the QueryFilter for :type request_filter: :class:`dict` :param entity_name: Not utilised in DataGateway API implementation of this @@ -29,35 +28,9 @@ def get_query_filter(request_filter, entity_name=None): used for both implementations :type entity_name: :class:`str` :return: The QueryFilter object created - :raises ApiError: If the backend type contains an invalid value :raises FilterError: If the filter name is not recognised """ - backend_type = Config.config.datagateway_api.backend - if backend_type == "db": - from datagateway_api.src.datagateway_api.database.filters import ( - DatabaseDistinctFieldFilter as DistinctFieldFilter, - DatabaseIncludeFilter as IncludeFilter, - DatabaseLimitFilter as LimitFilter, - DatabaseOrderFilter as OrderFilter, - DatabaseSkipFilter as SkipFilter, - DatabaseWhereFilter as WhereFilter, - ) - elif backend_type == "python_icat": - from datagateway_api.src.datagateway_api.icat.filters import ( - PythonICATDistinctFieldFilter as DistinctFieldFilter, - PythonICATIncludeFilter as IncludeFilter, - PythonICATLimitFilter as LimitFilter, - PythonICATOrderFilter as OrderFilter, - PythonICATSkipFilter as SkipFilter, - PythonICATWhereFilter as WhereFilter, - ) - else: - raise ApiError( - "Cannot select which implementation of filters to import, check the" - " config file has a valid backend type", - ) - filter_name = list(request_filter)[0].lower() if filter_name == "where": field = list(request_filter[filter_name].keys())[0] diff --git a/datagateway_api/src/resources/entities/entity_endpoint.py b/datagateway_api/src/resources/entities/entity_endpoint.py index bdbc23db..13d7a42a 100644 --- a/datagateway_api/src/resources/entities/entity_endpoint.py +++ b/datagateway_api/src/resources/entities/entity_endpoint.py @@ -7,7 +7,7 @@ ) -def get_endpoint(name, entity_type, backend, **kwargs): +def get_endpoint(name, entity_type, python_icat, **kwargs): """ Given an entity name, generate a flask_restful `Resource` class. In `create_api_endpoints()`, these generated classes are registered with the API e.g. @@ -17,8 +17,8 @@ def get_endpoint(name, entity_type, backend, **kwargs): :type name: :class:`str` :param entity_type: The entity the endpoint will use in queries :type entity_type: :class:`str` - :param backend: The backend instance used for processing requests - :type backend: :class:`DatabaseBackend` or :class:`PythonICATBackend` + :param python_icat: The python ICAT instance used for processing requests + :type python_icat: :class:`PythonICAT` :return: The generated endpoint class """ @@ -27,7 +27,7 @@ def get_endpoint(name, entity_type, backend, **kwargs): class Endpoint(Resource): def get(self): return ( - backend.get_with_filters( + python_icat.get_with_filters( get_session_id_from_auth_header(), entity_type, get_filters_from_query_string("datagateway_api"), @@ -72,7 +72,7 @@ def get(self): def post(self): return ( - backend.create( + python_icat.create( get_session_id_from_auth_header(), entity_type, request.json, @@ -118,7 +118,7 @@ def post(self): def patch(self): return ( - backend.update( + python_icat.update( get_session_id_from_auth_header(), entity_type, request.json, @@ -166,7 +166,7 @@ def patch(self): return Endpoint -def get_id_endpoint(name, entity_type, backend, **kwargs): +def get_id_endpoint(name, entity_type, python_icat, **kwargs): """ Given an entity name, generate a flask_restful `Resource` class. In `create_api_endpoints()`, these generated classes are registered with the API e.g. @@ -176,8 +176,8 @@ def get_id_endpoint(name, entity_type, backend, **kwargs): :type name: :class:`str` :param entity_type: The entity the endpoint will use in queries :type entity_type: :class:`str` - :param backend: The backend instance used for processing requests - :type backend: :class:`DatabaseBackend` or :class:`PythonICATBackend` + :param python_icat: The python ICAT instance used for processing requests + :type python_icat: :class:`PythonICAT` :return: The generated id endpoint class """ @@ -186,7 +186,7 @@ def get_id_endpoint(name, entity_type, backend, **kwargs): class EndpointWithID(Resource): def get(self, id_): return ( - backend.get_with_id( + python_icat.get_with_id( get_session_id_from_auth_header(), entity_type, id_, **kwargs, ), 200, @@ -223,7 +223,7 @@ def get(self, id_): """ def delete(self, id_): - backend.delete_with_id( + python_icat.delete_with_id( get_session_id_from_auth_header(), entity_type, id_, **kwargs, ) return "", 204 @@ -257,10 +257,10 @@ def delete(self, id_): def patch(self, id_): session_id = get_session_id_from_auth_header() - backend.update_with_id( + python_icat.update_with_id( session_id, entity_type, id_, request.json, **kwargs, ) - return backend.get_with_id(session_id, entity_type, id_, **kwargs), 200 + return python_icat.get_with_id(session_id, entity_type, id_, **kwargs), 200 patch.__doc__ = f""" --- @@ -304,7 +304,7 @@ def patch(self, id_): return EndpointWithID -def get_count_endpoint(name, entity_type, backend, **kwargs): +def get_count_endpoint(name, entity_type, python_icat, **kwargs): """ Given an entity name, generate a flask_restful `Resource` class. In `create_api_endpoints()`, these generated classes are registered with the API e.g. @@ -314,8 +314,8 @@ def get_count_endpoint(name, entity_type, backend, **kwargs): :type name: :class:`str` :param entity_type: The entity the endpoint will use in queries :type entity_type: :class:`str` - :param backend: The backend instance used for processing requests - :type backend: :class:`DatabaseBackend` or :class:`PythonICATBackend` + :param python_icat: The python ICAT instance used for processing requests + :type python_icat: :class:`PythonICAT` :return: The generated count endpoint class """ @@ -323,7 +323,7 @@ class CountEndpoint(Resource): def get(self): filters = get_filters_from_query_string("datagateway_api") return ( - backend.count_with_filters( + python_icat.count_with_filters( get_session_id_from_auth_header(), entity_type, filters, **kwargs, ), 200, @@ -361,7 +361,7 @@ def get(self): return CountEndpoint -def get_find_one_endpoint(name, entity_type, backend, **kwargs): +def get_find_one_endpoint(name, entity_type, python_icat, **kwargs): """ Given an entity name, generate a flask_restful `Resource` class. In `create_api_endpoints()`, these generated classes are registered with the API e.g. @@ -371,8 +371,8 @@ def get_find_one_endpoint(name, entity_type, backend, **kwargs): :type name: :class:`str` :param entity_type: The entity the endpoint will use in queries :type entity_type: :class:`str` - :param backend: The backend instance used for processing requests - :type backend: :class:`DatabaseBackend` or :class:`PythonICATBackend` + :param python_icat: The python ICAT instance used for processing requests + :type python_icat: :class:`PythonICAT` :return: The generated findOne endpoint class """ @@ -382,7 +382,7 @@ class FindOneEndpoint(Resource): def get(self): filters = get_filters_from_query_string("datagateway_api") return ( - backend.get_one_with_filters( + python_icat.get_one_with_filters( get_session_id_from_auth_header(), entity_type, filters, **kwargs, ), 200, diff --git a/datagateway_api/src/resources/non_entities/ping_endpoint.py b/datagateway_api/src/resources/non_entities/ping_endpoint.py index 5b595095..5f69585e 100644 --- a/datagateway_api/src/resources/non_entities/ping_endpoint.py +++ b/datagateway_api/src/resources/non_entities/ping_endpoint.py @@ -1,14 +1,14 @@ from flask_restful import Resource -def ping_endpoint(backend, **kwargs): +def ping_endpoint(python_icat, **kwargs): """ - Generate a flask_restful Resource class using the configured backend. In main.py + Generate a flask_restful Resource class using python ICAT. In main.py these generated classes are registered with the api e.g. `api.add_resource(get_endpoint("Datafiles", DATAFILE), "/datafiles")` - :param backend: The backend instance used for processing requests - :type backend: :class:`DatabaseBackend` or :class:`PythonICATBackend` + :param python_icat: The python ICAT instance used for processing requests + :type python_icat: :class:`PythonICAT` :return: The generated ping endpoint class """ @@ -24,7 +24,7 @@ def get(self): - Ping responses: 200: - description: Success - the API is responsive on the backend configured + description: Success - the API is responsive on the python ICAT server content: application/json: schema: @@ -34,6 +34,6 @@ def get(self): 500: description: Pinging the API's connection method has gone wrong """ - return backend.ping(**kwargs), 200 + return python_icat.ping(**kwargs), 200 return Ping diff --git a/datagateway_api/src/resources/non_entities/sessions_endpoints.py b/datagateway_api/src/resources/non_entities/sessions_endpoints.py index 4e25f988..54a18df3 100644 --- a/datagateway_api/src/resources/non_entities/sessions_endpoints.py +++ b/datagateway_api/src/resources/non_entities/sessions_endpoints.py @@ -10,14 +10,14 @@ log = logging.getLogger() -def session_endpoints(backend, **kwargs): +def session_endpoints(python_icat, **kwargs): """ - Generate a flask_restful Resource class using the configured backend. In main.py + Generate a flask_restful Resource class using python ICAT. In main.py these generated classes are registered with the api e.g. `api.add_resource(get_endpoint("Datafiles", DATAFILE), "/datafiles")` - :param backend: The backend instance used for processing requests - :type backend: :class:`DatabaseBackend` or :class:`PythonICATBackend` + :param python_icat: The python ICAT instance used for processing requests + :type python_icat: :class:`PythonICAT` :return: The generated session endpoint class """ @@ -74,7 +74,7 @@ def post(self): if not ("mechanism" in request.json): request.json["mechanism"] = "simple" try: - return {"sessionID": backend.login(request.json, **kwargs)}, 201 + return {"sessionID": python_icat.login(request.json, **kwargs)}, 201 except AuthenticationError: return "Forbidden", 403 @@ -99,7 +99,7 @@ def delete(self): 404: description: Not Found - Unable to find session ID """ - backend.logout(get_session_id_from_auth_header(), **kwargs) + python_icat.logout(get_session_id_from_auth_header(), **kwargs) return "", 200 def get(self): @@ -137,7 +137,7 @@ def get(self): description: Forbidden - The session ID provided is invalid """ return ( - backend.get_session_details( + python_icat.get_session_details( get_session_id_from_auth_header(), **kwargs, ), 200, @@ -166,6 +166,6 @@ def put(self): 403: description: Forbidden - The session ID provided is invalid """ - return backend.refresh(get_session_id_from_auth_header(), **kwargs), 200 + return python_icat.refresh(get_session_id_from_auth_header(), **kwargs), 200 return Sessions diff --git a/datagateway_api/src/swagger/datagateway_api/openapi.yaml b/datagateway_api/src/swagger/datagateway_api/openapi.yaml index bb80fe0b..f8be981c 100644 --- a/datagateway_api/src/swagger/datagateway_api/openapi.yaml +++ b/datagateway_api/src/swagger/datagateway_api/openapi.yaml @@ -2355,7 +2355,7 @@ info: version: '1.0' openapi: 3.0.3 paths: - /datagateway-api/applications: + /applications: get: description: Retrieves a list of Application objects parameters: @@ -2449,7 +2449,7 @@ paths: summary: Create new Applications tags: - Applications - /datagateway-api/applications/{id_}: + /applications/{id_}: delete: description: Updates Application with the specified ID with details provided in the request body @@ -2536,7 +2536,7 @@ paths: summary: Update Applications by id tags: - Applications - /datagateway-api/applications/count: + /applications/count: get: description: Return the count of the Application objects that would be retrieved given the filters provided @@ -2562,7 +2562,7 @@ paths: summary: Count Applications tags: - Applications - /datagateway-api/applications/findone: + /applications/findone: get: description: Retrieves the first Application objects that satisfies the filters. parameters: @@ -2590,7 +2590,7 @@ paths: summary: Get single Application tags: - Applications - /datagateway-api/affiliations: + /affiliations: get: description: Retrieves a list of Affiliation objects parameters: @@ -2684,7 +2684,7 @@ paths: summary: Create new Affiliations tags: - Affiliations - /datagateway-api/affiliations/{id_}: + /affiliations/{id_}: delete: description: Updates Affiliation with the specified ID with details provided in the request body @@ -2771,7 +2771,7 @@ paths: summary: Update Affiliations by id tags: - Affiliations - /datagateway-api/affiliations/count: + /affiliations/count: get: description: Return the count of the Affiliation objects that would be retrieved given the filters provided @@ -2797,7 +2797,7 @@ paths: summary: Count Affiliations tags: - Affiliations - /datagateway-api/affiliations/findone: + /affiliations/findone: get: description: Retrieves the first Affiliation objects that satisfies the filters. parameters: @@ -2825,7 +2825,7 @@ paths: summary: Get single Affiliation tags: - Affiliations - /datagateway-api/datacollectioninvestigations: + /datacollectioninvestigations: get: description: Retrieves a list of DataCollectionInvestigation objects parameters: @@ -2920,7 +2920,7 @@ paths: summary: Create new DataCollectionInvestigations tags: - DataCollectionInvestigations - /datagateway-api/datacollectioninvestigations/{id_}: + /datacollectioninvestigations/{id_}: delete: description: Updates DataCollectionInvestigation with the specified ID with details provided in the request body @@ -3007,7 +3007,7 @@ paths: summary: Update DataCollectionInvestigations by id tags: - DataCollectionInvestigations - /datagateway-api/datacollectioninvestigations/count: + /datacollectioninvestigations/count: get: description: Return the count of the DataCollectionInvestigation objects that would be retrieved given the filters provided @@ -3033,7 +3033,7 @@ paths: summary: Count DataCollectionInvestigations tags: - DataCollectionInvestigations - /datagateway-api/datacollectioninvestigations/findone: + /datacollectioninvestigations/findone: get: description: Retrieves the first DataCollectionInvestigation objects that satisfies the filters. @@ -3063,7 +3063,7 @@ paths: summary: Get single DataCollectionInvestigation tags: - DataCollectionInvestigations - /datagateway-api/datacollectiondatafiles: + /datacollectiondatafiles: get: description: Retrieves a list of DataCollectionDatafile objects parameters: @@ -3157,7 +3157,7 @@ paths: summary: Create new DataCollectionDatafiles tags: - DataCollectionDatafiles - /datagateway-api/datacollectiondatafiles/{id_}: + /datacollectiondatafiles/{id_}: delete: description: Updates DataCollectionDatafile with the specified ID with details provided in the request body @@ -3244,7 +3244,7 @@ paths: summary: Update DataCollectionDatafiles by id tags: - DataCollectionDatafiles - /datagateway-api/datacollectiondatafiles/count: + /datacollectiondatafiles/count: get: description: Return the count of the DataCollectionDatafile objects that would be retrieved given the filters provided @@ -3270,7 +3270,7 @@ paths: summary: Count DataCollectionDatafiles tags: - DataCollectionDatafiles - /datagateway-api/datacollectiondatafiles/findone: + /datacollectiondatafiles/findone: get: description: Retrieves the first DataCollectionDatafile objects that satisfies the filters. @@ -3300,7 +3300,7 @@ paths: summary: Get single DataCollectionDatafile tags: - DataCollectionDatafiles - /datagateway-api/datacollectiondatasets: + /datacollectiondatasets: get: description: Retrieves a list of DataCollectionDataset objects parameters: @@ -3394,7 +3394,7 @@ paths: summary: Create new DataCollectionDatasets tags: - DataCollectionDatasets - /datagateway-api/datacollectiondatasets/{id_}: + /datacollectiondatasets/{id_}: delete: description: Updates DataCollectionDataset with the specified ID with details provided in the request body @@ -3481,7 +3481,7 @@ paths: summary: Update DataCollectionDatasets by id tags: - DataCollectionDatasets - /datagateway-api/datacollectiondatasets/count: + /datacollectiondatasets/count: get: description: Return the count of the DataCollectionDataset objects that would be retrieved given the filters provided @@ -3507,7 +3507,7 @@ paths: summary: Count DataCollectionDatasets tags: - DataCollectionDatasets - /datagateway-api/datacollectiondatasets/findone: + /datacollectiondatasets/findone: get: description: Retrieves the first DataCollectionDataset objects that satisfies the filters. @@ -3537,7 +3537,7 @@ paths: summary: Get single DataCollectionDataset tags: - DataCollectionDatasets - /datagateway-api/datacollectionparameters: + /datacollectionparameters: get: description: Retrieves a list of DataCollectionParameter objects parameters: @@ -3632,7 +3632,7 @@ paths: summary: Create new DataCollectionParameters tags: - DataCollectionParameters - /datagateway-api/datacollectionparameters/{id_}: + /datacollectionparameters/{id_}: delete: description: Updates DataCollectionParameter with the specified ID with details provided in the request body @@ -3719,7 +3719,7 @@ paths: summary: Update DataCollectionParameters by id tags: - DataCollectionParameters - /datagateway-api/datacollectionparameters/count: + /datacollectionparameters/count: get: description: Return the count of the DataCollectionParameter objects that would be retrieved given the filters provided @@ -3745,7 +3745,7 @@ paths: summary: Count DataCollectionParameters tags: - DataCollectionParameters - /datagateway-api/datacollectionparameters/findone: + /datacollectionparameters/findone: get: description: Retrieves the first DataCollectionParameter objects that satisfies the filters. @@ -3775,7 +3775,7 @@ paths: summary: Get single DataCollectionParameter tags: - DataCollectionParameters - /datagateway-api/datacollections: + /datacollections: get: description: Retrieves a list of DataCollection objects parameters: @@ -3869,7 +3869,7 @@ paths: summary: Create new DataCollections tags: - DataCollections - /datagateway-api/datacollections/{id_}: + /datacollections/{id_}: delete: description: Updates DataCollection with the specified ID with details provided in the request body @@ -3956,7 +3956,7 @@ paths: summary: Update DataCollections by id tags: - DataCollections - /datagateway-api/datacollections/count: + /datacollections/count: get: description: Return the count of the DataCollection objects that would be retrieved given the filters provided @@ -3982,7 +3982,7 @@ paths: summary: Count DataCollections tags: - DataCollections - /datagateway-api/datacollections/findone: + /datacollections/findone: get: description: Retrieves the first DataCollection objects that satisfies the filters. parameters: @@ -4010,7 +4010,7 @@ paths: summary: Get single DataCollection tags: - DataCollections - /datagateway-api/datapublications: + /datapublications: get: description: Retrieves a list of DataPublication objects parameters: @@ -4104,7 +4104,7 @@ paths: summary: Create new DataPublications tags: - DataPublications - /datagateway-api/datapublications/{id_}: + /datapublications/{id_}: delete: description: Updates DataPublication with the specified ID with details provided in the request body @@ -4191,7 +4191,7 @@ paths: summary: Update DataPublications by id tags: - DataPublications - /datagateway-api/datapublications/count: + /datapublications/count: get: description: Return the count of the DataPublication objects that would be retrieved given the filters provided @@ -4217,7 +4217,7 @@ paths: summary: Count DataPublications tags: - DataPublications - /datagateway-api/datapublications/findone: + /datapublications/findone: get: description: Retrieves the first DataPublication objects that satisfies the filters. @@ -4246,7 +4246,7 @@ paths: summary: Get single DataPublication tags: - DataPublications - /datagateway-api/datapublicationdates: + /datapublicationdates: get: description: Retrieves a list of DataPublicationDate objects parameters: @@ -4340,7 +4340,7 @@ paths: summary: Create new DataPublicationDates tags: - DataPublicationDates - /datagateway-api/datapublicationdates/{id_}: + /datapublicationdates/{id_}: delete: description: Updates DataPublicationDate with the specified ID with details provided in the request body @@ -4427,7 +4427,7 @@ paths: summary: Update DataPublicationDates by id tags: - DataPublicationDates - /datagateway-api/datapublicationdates/count: + /datapublicationdates/count: get: description: Return the count of the DataPublicationDate objects that would be retrieved given the filters provided @@ -4453,7 +4453,7 @@ paths: summary: Count DataPublicationDates tags: - DataPublicationDates - /datagateway-api/datapublicationdates/findone: + /datapublicationdates/findone: get: description: Retrieves the first DataPublicationDate objects that satisfies the filters. @@ -4482,7 +4482,7 @@ paths: summary: Get single DataPublicationDate tags: - DataPublicationDates - /datagateway-api/datapublicationfundings: + /datapublicationfundings: get: description: Retrieves a list of DataPublicationFunding objects parameters: @@ -4576,7 +4576,7 @@ paths: summary: Create new DataPublicationFundings tags: - DataPublicationFundings - /datagateway-api/datapublicationfundings/{id_}: + /datapublicationfundings/{id_}: delete: description: Updates DataPublicationFunding with the specified ID with details provided in the request body @@ -4663,7 +4663,7 @@ paths: summary: Update DataPublicationFundings by id tags: - DataPublicationFundings - /datagateway-api/datapublicationfundings/count: + /datapublicationfundings/count: get: description: Return the count of the DataPublicationFunding objects that would be retrieved given the filters provided @@ -4689,7 +4689,7 @@ paths: summary: Count DataPublicationFundings tags: - DataPublicationFundings - /datagateway-api/datapublicationfundings/findone: + /datapublicationfundings/findone: get: description: Retrieves the first DataPublicationFunding objects that satisfies the filters. @@ -4719,7 +4719,7 @@ paths: summary: Get single DataPublicationFunding tags: - DataPublicationFundings - /datagateway-api/datapublicationtypes: + /datapublicationtypes: get: description: Retrieves a list of DataPublicationType objects parameters: @@ -4813,7 +4813,7 @@ paths: summary: Create new DataPublicationTypes tags: - DataPublicationTypes - /datagateway-api/datapublicationtypes/{id_}: + /datapublicationtypes/{id_}: delete: description: Updates DataPublicationType with the specified ID with details provided in the request body @@ -4900,7 +4900,7 @@ paths: summary: Update DataPublicationTypes by id tags: - DataPublicationTypes - /datagateway-api/datapublicationtypes/count: + /datapublicationtypes/count: get: description: Return the count of the DataPublicationType objects that would be retrieved given the filters provided @@ -4926,7 +4926,7 @@ paths: summary: Count DataPublicationTypes tags: - DataPublicationTypes - /datagateway-api/datapublicationtypes/findone: + /datapublicationtypes/findone: get: description: Retrieves the first DataPublicationType objects that satisfies the filters. @@ -4955,7 +4955,7 @@ paths: summary: Get single DataPublicationType tags: - DataPublicationTypes - /datagateway-api/datapublicationusers: + /datapublicationusers: get: description: Retrieves a list of DataPublicationUser objects parameters: @@ -5049,7 +5049,7 @@ paths: summary: Create new DataPublicationUsers tags: - DataPublicationUsers - /datagateway-api/datapublicationusers/{id_}: + /datapublicationusers/{id_}: delete: description: Updates DataPublicationUser with the specified ID with details provided in the request body @@ -5136,7 +5136,7 @@ paths: summary: Update DataPublicationUsers by id tags: - DataPublicationUsers - /datagateway-api/datapublicationusers/count: + /datapublicationusers/count: get: description: Return the count of the DataPublicationUser objects that would be retrieved given the filters provided @@ -5162,7 +5162,7 @@ paths: summary: Count DataPublicationUsers tags: - DataPublicationUsers - /datagateway-api/datapublicationusers/findone: + /datapublicationusers/findone: get: description: Retrieves the first DataPublicationUser objects that satisfies the filters. @@ -5191,7 +5191,7 @@ paths: summary: Get single DataPublicationUser tags: - DataPublicationUsers - /datagateway-api/datafileformats: + /datafileformats: get: description: Retrieves a list of DatafileFormat objects parameters: @@ -5285,7 +5285,7 @@ paths: summary: Create new DatafileFormats tags: - DatafileFormats - /datagateway-api/datafileformats/{id_}: + /datafileformats/{id_}: delete: description: Updates DatafileFormat with the specified ID with details provided in the request body @@ -5372,7 +5372,7 @@ paths: summary: Update DatafileFormats by id tags: - DatafileFormats - /datagateway-api/datafileformats/count: + /datafileformats/count: get: description: Return the count of the DatafileFormat objects that would be retrieved given the filters provided @@ -5398,7 +5398,7 @@ paths: summary: Count DatafileFormats tags: - DatafileFormats - /datagateway-api/datafileformats/findone: + /datafileformats/findone: get: description: Retrieves the first DatafileFormat objects that satisfies the filters. parameters: @@ -5426,7 +5426,7 @@ paths: summary: Get single DatafileFormat tags: - DatafileFormats - /datagateway-api/datafileparameters: + /datafileparameters: get: description: Retrieves a list of DatafileParameter objects parameters: @@ -5520,7 +5520,7 @@ paths: summary: Create new DatafileParameters tags: - DatafileParameters - /datagateway-api/datafileparameters/{id_}: + /datafileparameters/{id_}: delete: description: Updates DatafileParameter with the specified ID with details provided in the request body @@ -5607,7 +5607,7 @@ paths: summary: Update DatafileParameters by id tags: - DatafileParameters - /datagateway-api/datafileparameters/count: + /datafileparameters/count: get: description: Return the count of the DatafileParameter objects that would be retrieved given the filters provided @@ -5633,7 +5633,7 @@ paths: summary: Count DatafileParameters tags: - DatafileParameters - /datagateway-api/datafileparameters/findone: + /datafileparameters/findone: get: description: Retrieves the first DatafileParameter objects that satisfies the filters. @@ -5662,7 +5662,7 @@ paths: summary: Get single DatafileParameter tags: - DatafileParameters - /datagateway-api/datafiles: + /datafiles: get: description: Retrieves a list of Datafile objects parameters: @@ -5756,7 +5756,7 @@ paths: summary: Create new Datafiles tags: - Datafiles - /datagateway-api/datafiles/{id_}: + /datafiles/{id_}: delete: description: Updates Datafile with the specified ID with details provided in the request body @@ -5843,7 +5843,7 @@ paths: summary: Update Datafiles by id tags: - Datafiles - /datagateway-api/datafiles/count: + /datafiles/count: get: description: Return the count of the Datafile objects that would be retrieved given the filters provided @@ -5869,7 +5869,7 @@ paths: summary: Count Datafiles tags: - Datafiles - /datagateway-api/datafiles/findone: + /datafiles/findone: get: description: Retrieves the first Datafile objects that satisfies the filters. parameters: @@ -5897,7 +5897,7 @@ paths: summary: Get single Datafile tags: - Datafiles - /datagateway-api/datasetparameters: + /datasetparameters: get: description: Retrieves a list of DatasetParameter objects parameters: @@ -5991,7 +5991,7 @@ paths: summary: Create new DatasetParameters tags: - DatasetParameters - /datagateway-api/datasetparameters/{id_}: + /datasetparameters/{id_}: delete: description: Updates DatasetParameter with the specified ID with details provided in the request body @@ -6078,7 +6078,7 @@ paths: summary: Update DatasetParameters by id tags: - DatasetParameters - /datagateway-api/datasetparameters/count: + /datasetparameters/count: get: description: Return the count of the DatasetParameter objects that would be retrieved given the filters provided @@ -6104,7 +6104,7 @@ paths: summary: Count DatasetParameters tags: - DatasetParameters - /datagateway-api/datasetparameters/findone: + /datasetparameters/findone: get: description: Retrieves the first DatasetParameter objects that satisfies the filters. @@ -6133,7 +6133,7 @@ paths: summary: Get single DatasetParameter tags: - DatasetParameters - /datagateway-api/datasetinstruments: + /datasetinstruments: get: description: Retrieves a list of DatasetInstrument objects parameters: @@ -6227,7 +6227,7 @@ paths: summary: Create new DatasetInstruments tags: - DatasetInstruments - /datagateway-api/datasetinstruments/{id_}: + /datasetinstruments/{id_}: delete: description: Updates DatasetInstrument with the specified ID with details provided in the request body @@ -6314,7 +6314,7 @@ paths: summary: Update DatasetInstruments by id tags: - DatasetInstruments - /datagateway-api/datasetinstruments/count: + /datasetinstruments/count: get: description: Return the count of the DatasetInstrument objects that would be retrieved given the filters provided @@ -6340,7 +6340,7 @@ paths: summary: Count DatasetInstruments tags: - DatasetInstruments - /datagateway-api/datasetinstruments/findone: + /datasetinstruments/findone: get: description: Retrieves the first DatasetInstrument objects that satisfies the filters. @@ -6369,7 +6369,7 @@ paths: summary: Get single DatasetInstrument tags: - DatasetInstruments - /datagateway-api/datasettechniques: + /datasettechniques: get: description: Retrieves a list of DatasetTechnique objects parameters: @@ -6463,7 +6463,7 @@ paths: summary: Create new DatasetTechniques tags: - DatasetTechniques - /datagateway-api/datasettechniques/{id_}: + /datasettechniques/{id_}: delete: description: Updates DatasetTechnique with the specified ID with details provided in the request body @@ -6550,7 +6550,7 @@ paths: summary: Update DatasetTechniques by id tags: - DatasetTechniques - /datagateway-api/datasettechniques/count: + /datasettechniques/count: get: description: Return the count of the DatasetTechnique objects that would be retrieved given the filters provided @@ -6576,7 +6576,7 @@ paths: summary: Count DatasetTechniques tags: - DatasetTechniques - /datagateway-api/datasettechniques/findone: + /datasettechniques/findone: get: description: Retrieves the first DatasetTechnique objects that satisfies the filters. @@ -6605,7 +6605,7 @@ paths: summary: Get single DatasetTechnique tags: - DatasetTechniques - /datagateway-api/datasettypes: + /datasettypes: get: description: Retrieves a list of DatasetType objects parameters: @@ -6699,7 +6699,7 @@ paths: summary: Create new DatasetTypes tags: - DatasetTypes - /datagateway-api/datasettypes/{id_}: + /datasettypes/{id_}: delete: description: Updates DatasetType with the specified ID with details provided in the request body @@ -6786,7 +6786,7 @@ paths: summary: Update DatasetTypes by id tags: - DatasetTypes - /datagateway-api/datasettypes/count: + /datasettypes/count: get: description: Return the count of the DatasetType objects that would be retrieved given the filters provided @@ -6812,7 +6812,7 @@ paths: summary: Count DatasetTypes tags: - DatasetTypes - /datagateway-api/datasettypes/findone: + /datasettypes/findone: get: description: Retrieves the first DatasetType objects that satisfies the filters. parameters: @@ -6840,7 +6840,7 @@ paths: summary: Get single DatasetType tags: - DatasetTypes - /datagateway-api/datasets: + /datasets: get: description: Retrieves a list of Dataset objects parameters: @@ -6934,7 +6934,7 @@ paths: summary: Create new Datasets tags: - Datasets - /datagateway-api/datasets/{id_}: + /datasets/{id_}: delete: description: Updates Dataset with the specified ID with details provided in the request body @@ -7021,7 +7021,7 @@ paths: summary: Update Datasets by id tags: - Datasets - /datagateway-api/datasets/count: + /datasets/count: get: description: Return the count of the Dataset objects that would be retrieved given the filters provided @@ -7047,7 +7047,7 @@ paths: summary: Count Datasets tags: - Datasets - /datagateway-api/datasets/findone: + /datasets/findone: get: description: Retrieves the first Dataset objects that satisfies the filters. parameters: @@ -7075,7 +7075,7 @@ paths: summary: Get single Dataset tags: - Datasets - /datagateway-api/facilities: + /facilities: get: description: Retrieves a list of Facility objects parameters: @@ -7169,7 +7169,7 @@ paths: summary: Create new Facilities tags: - Facilities - /datagateway-api/facilities/{id_}: + /facilities/{id_}: delete: description: Updates Facility with the specified ID with details provided in the request body @@ -7256,7 +7256,7 @@ paths: summary: Update Facilities by id tags: - Facilities - /datagateway-api/facilities/count: + /facilities/count: get: description: Return the count of the Facility objects that would be retrieved given the filters provided @@ -7282,7 +7282,7 @@ paths: summary: Count Facilities tags: - Facilities - /datagateway-api/facilities/findone: + /facilities/findone: get: description: Retrieves the first Facility objects that satisfies the filters. parameters: @@ -7310,7 +7310,7 @@ paths: summary: Get single Facility tags: - Facilities - /datagateway-api/facilitycycles: + /facilitycycles: get: description: Retrieves a list of FacilityCycle objects parameters: @@ -7404,7 +7404,7 @@ paths: summary: Create new FacilityCycles tags: - FacilityCycles - /datagateway-api/facilitycycles/{id_}: + /facilitycycles/{id_}: delete: description: Updates FacilityCycle with the specified ID with details provided in the request body @@ -7491,7 +7491,7 @@ paths: summary: Update FacilityCycles by id tags: - FacilityCycles - /datagateway-api/facilitycycles/count: + /facilitycycles/count: get: description: Return the count of the FacilityCycle objects that would be retrieved given the filters provided @@ -7517,7 +7517,7 @@ paths: summary: Count FacilityCycles tags: - FacilityCycles - /datagateway-api/facilitycycles/findone: + /facilitycycles/findone: get: description: Retrieves the first FacilityCycle objects that satisfies the filters. parameters: @@ -7545,7 +7545,7 @@ paths: summary: Get single FacilityCycle tags: - FacilityCycles - /datagateway-api/fundingreferences: + /fundingreferences: get: description: Retrieves a list of FundingReference objects parameters: @@ -7639,7 +7639,7 @@ paths: summary: Create new FundingReferences tags: - FundingReferences - /datagateway-api/fundingreferences/{id_}: + /fundingreferences/{id_}: delete: description: Updates FundingReference with the specified ID with details provided in the request body @@ -7726,7 +7726,7 @@ paths: summary: Update FundingReferences by id tags: - FundingReferences - /datagateway-api/fundingreferences/count: + /fundingreferences/count: get: description: Return the count of the FundingReference objects that would be retrieved given the filters provided @@ -7752,7 +7752,7 @@ paths: summary: Count FundingReferences tags: - FundingReferences - /datagateway-api/fundingreferences/findone: + /fundingreferences/findone: get: description: Retrieves the first FundingReference objects that satisfies the filters. @@ -7781,7 +7781,7 @@ paths: summary: Get single FundingReference tags: - FundingReferences - /datagateway-api/groupings: + /groupings: get: description: Retrieves a list of Grouping objects parameters: @@ -7875,7 +7875,7 @@ paths: summary: Create new Groupings tags: - Groupings - /datagateway-api/groupings/{id_}: + /groupings/{id_}: delete: description: Updates Grouping with the specified ID with details provided in the request body @@ -7962,7 +7962,7 @@ paths: summary: Update Groupings by id tags: - Groupings - /datagateway-api/groupings/count: + /groupings/count: get: description: Return the count of the Grouping objects that would be retrieved given the filters provided @@ -7988,7 +7988,7 @@ paths: summary: Count Groupings tags: - Groupings - /datagateway-api/groupings/findone: + /groupings/findone: get: description: Retrieves the first Grouping objects that satisfies the filters. parameters: @@ -8016,7 +8016,7 @@ paths: summary: Get single Grouping tags: - Groupings - /datagateway-api/instrumentscientists: + /instrumentscientists: get: description: Retrieves a list of InstrumentScientist objects parameters: @@ -8110,7 +8110,7 @@ paths: summary: Create new InstrumentScientists tags: - InstrumentScientists - /datagateway-api/instrumentscientists/{id_}: + /instrumentscientists/{id_}: delete: description: Updates InstrumentScientist with the specified ID with details provided in the request body @@ -8197,7 +8197,7 @@ paths: summary: Update InstrumentScientists by id tags: - InstrumentScientists - /datagateway-api/instrumentscientists/count: + /instrumentscientists/count: get: description: Return the count of the InstrumentScientist objects that would be retrieved given the filters provided @@ -8223,7 +8223,7 @@ paths: summary: Count InstrumentScientists tags: - InstrumentScientists - /datagateway-api/instrumentscientists/findone: + /instrumentscientists/findone: get: description: Retrieves the first InstrumentScientist objects that satisfies the filters. @@ -8252,7 +8252,7 @@ paths: summary: Get single InstrumentScientist tags: - InstrumentScientists - /datagateway-api/instruments: + /instruments: get: description: Retrieves a list of Instrument objects parameters: @@ -8346,7 +8346,7 @@ paths: summary: Create new Instruments tags: - Instruments - /datagateway-api/instruments/{id_}: + /instruments/{id_}: delete: description: Updates Instrument with the specified ID with details provided in the request body @@ -8433,7 +8433,7 @@ paths: summary: Update Instruments by id tags: - Instruments - /datagateway-api/instruments/count: + /instruments/count: get: description: Return the count of the Instrument objects that would be retrieved given the filters provided @@ -8459,7 +8459,7 @@ paths: summary: Count Instruments tags: - Instruments - /datagateway-api/instruments/findone: + /instruments/findone: get: description: Retrieves the first Instrument objects that satisfies the filters. parameters: @@ -8487,7 +8487,7 @@ paths: summary: Get single Instrument tags: - Instruments - /datagateway-api/investigationfacilitycycles: + /investigationfacilitycycles: get: description: Retrieves a list of InvestigationFacilityCycle objects parameters: @@ -8582,7 +8582,7 @@ paths: summary: Create new InvestigationFacilityCycles tags: - InvestigationFacilityCycles - /datagateway-api/investigationfacilitycycles/{id_}: + /investigationfacilitycycles/{id_}: delete: description: Updates InvestigationFacilityCycle with the specified ID with details provided in the request body @@ -8669,7 +8669,7 @@ paths: summary: Update InvestigationFacilityCycles by id tags: - InvestigationFacilityCycles - /datagateway-api/investigationfacilitycycles/count: + /investigationfacilitycycles/count: get: description: Return the count of the InvestigationFacilityCycle objects that would be retrieved given the filters provided @@ -8695,7 +8695,7 @@ paths: summary: Count InvestigationFacilityCycles tags: - InvestigationFacilityCycles - /datagateway-api/investigationfacilitycycles/findone: + /investigationfacilitycycles/findone: get: description: Retrieves the first InvestigationFacilityCycle objects that satisfies the filters. @@ -8725,7 +8725,7 @@ paths: summary: Get single InvestigationFacilityCycle tags: - InvestigationFacilityCycles - /datagateway-api/investigationfundings: + /investigationfundings: get: description: Retrieves a list of InvestigationFunding objects parameters: @@ -8819,7 +8819,7 @@ paths: summary: Create new InvestigationFundings tags: - InvestigationFundings - /datagateway-api/investigationfundings/{id_}: + /investigationfundings/{id_}: delete: description: Updates InvestigationFunding with the specified ID with details provided in the request body @@ -8906,7 +8906,7 @@ paths: summary: Update InvestigationFundings by id tags: - InvestigationFundings - /datagateway-api/investigationfundings/count: + /investigationfundings/count: get: description: Return the count of the InvestigationFunding objects that would be retrieved given the filters provided @@ -8932,7 +8932,7 @@ paths: summary: Count InvestigationFundings tags: - InvestigationFundings - /datagateway-api/investigationfundings/findone: + /investigationfundings/findone: get: description: Retrieves the first InvestigationFunding objects that satisfies the filters. @@ -8962,7 +8962,7 @@ paths: summary: Get single InvestigationFunding tags: - InvestigationFundings - /datagateway-api/investigationgroups: + /investigationgroups: get: description: Retrieves a list of InvestigationGroup objects parameters: @@ -9056,7 +9056,7 @@ paths: summary: Create new InvestigationGroups tags: - InvestigationGroups - /datagateway-api/investigationgroups/{id_}: + /investigationgroups/{id_}: delete: description: Updates InvestigationGroup with the specified ID with details provided in the request body @@ -9143,7 +9143,7 @@ paths: summary: Update InvestigationGroups by id tags: - InvestigationGroups - /datagateway-api/investigationgroups/count: + /investigationgroups/count: get: description: Return the count of the InvestigationGroup objects that would be retrieved given the filters provided @@ -9169,7 +9169,7 @@ paths: summary: Count InvestigationGroups tags: - InvestigationGroups - /datagateway-api/investigationgroups/findone: + /investigationgroups/findone: get: description: Retrieves the first InvestigationGroup objects that satisfies the filters. @@ -9198,7 +9198,7 @@ paths: summary: Get single InvestigationGroup tags: - InvestigationGroups - /datagateway-api/investigationinstruments: + /investigationinstruments: get: description: Retrieves a list of InvestigationInstrument objects parameters: @@ -9293,7 +9293,7 @@ paths: summary: Create new InvestigationInstruments tags: - InvestigationInstruments - /datagateway-api/investigationinstruments/{id_}: + /investigationinstruments/{id_}: delete: description: Updates InvestigationInstrument with the specified ID with details provided in the request body @@ -9380,7 +9380,7 @@ paths: summary: Update InvestigationInstruments by id tags: - InvestigationInstruments - /datagateway-api/investigationinstruments/count: + /investigationinstruments/count: get: description: Return the count of the InvestigationInstrument objects that would be retrieved given the filters provided @@ -9406,7 +9406,7 @@ paths: summary: Count InvestigationInstruments tags: - InvestigationInstruments - /datagateway-api/investigationinstruments/findone: + /investigationinstruments/findone: get: description: Retrieves the first InvestigationInstrument objects that satisfies the filters. @@ -9436,7 +9436,7 @@ paths: summary: Get single InvestigationInstrument tags: - InvestigationInstruments - /datagateway-api/investigationparameters: + /investigationparameters: get: description: Retrieves a list of InvestigationParameter objects parameters: @@ -9530,7 +9530,7 @@ paths: summary: Create new InvestigationParameters tags: - InvestigationParameters - /datagateway-api/investigationparameters/{id_}: + /investigationparameters/{id_}: delete: description: Updates InvestigationParameter with the specified ID with details provided in the request body @@ -9617,7 +9617,7 @@ paths: summary: Update InvestigationParameters by id tags: - InvestigationParameters - /datagateway-api/investigationparameters/count: + /investigationparameters/count: get: description: Return the count of the InvestigationParameter objects that would be retrieved given the filters provided @@ -9643,7 +9643,7 @@ paths: summary: Count InvestigationParameters tags: - InvestigationParameters - /datagateway-api/investigationparameters/findone: + /investigationparameters/findone: get: description: Retrieves the first InvestigationParameter objects that satisfies the filters. @@ -9673,7 +9673,7 @@ paths: summary: Get single InvestigationParameter tags: - InvestigationParameters - /datagateway-api/investigationtypes: + /investigationtypes: get: description: Retrieves a list of InvestigationType objects parameters: @@ -9767,7 +9767,7 @@ paths: summary: Create new InvestigationTypes tags: - InvestigationTypes - /datagateway-api/investigationtypes/{id_}: + /investigationtypes/{id_}: delete: description: Updates InvestigationType with the specified ID with details provided in the request body @@ -9854,7 +9854,7 @@ paths: summary: Update InvestigationTypes by id tags: - InvestigationTypes - /datagateway-api/investigationtypes/count: + /investigationtypes/count: get: description: Return the count of the InvestigationType objects that would be retrieved given the filters provided @@ -9880,7 +9880,7 @@ paths: summary: Count InvestigationTypes tags: - InvestigationTypes - /datagateway-api/investigationtypes/findone: + /investigationtypes/findone: get: description: Retrieves the first InvestigationType objects that satisfies the filters. @@ -9909,7 +9909,7 @@ paths: summary: Get single InvestigationType tags: - InvestigationTypes - /datagateway-api/investigationusers: + /investigationusers: get: description: Retrieves a list of InvestigationUser objects parameters: @@ -10003,7 +10003,7 @@ paths: summary: Create new InvestigationUsers tags: - InvestigationUsers - /datagateway-api/investigationusers/{id_}: + /investigationusers/{id_}: delete: description: Updates InvestigationUser with the specified ID with details provided in the request body @@ -10090,7 +10090,7 @@ paths: summary: Update InvestigationUsers by id tags: - InvestigationUsers - /datagateway-api/investigationusers/count: + /investigationusers/count: get: description: Return the count of the InvestigationUser objects that would be retrieved given the filters provided @@ -10116,7 +10116,7 @@ paths: summary: Count InvestigationUsers tags: - InvestigationUsers - /datagateway-api/investigationusers/findone: + /investigationusers/findone: get: description: Retrieves the first InvestigationUser objects that satisfies the filters. @@ -10145,7 +10145,7 @@ paths: summary: Get single InvestigationUser tags: - InvestigationUsers - /datagateway-api/investigations: + /investigations: get: description: Retrieves a list of Investigation objects parameters: @@ -10239,7 +10239,7 @@ paths: summary: Create new Investigations tags: - Investigations - /datagateway-api/investigations/{id_}: + /investigations/{id_}: delete: description: Updates Investigation with the specified ID with details provided in the request body @@ -10326,7 +10326,7 @@ paths: summary: Update Investigations by id tags: - Investigations - /datagateway-api/investigations/count: + /investigations/count: get: description: Return the count of the Investigation objects that would be retrieved given the filters provided @@ -10352,7 +10352,7 @@ paths: summary: Count Investigations tags: - Investigations - /datagateway-api/investigations/findone: + /investigations/findone: get: description: Retrieves the first Investigation objects that satisfies the filters. parameters: @@ -10380,7 +10380,7 @@ paths: summary: Get single Investigation tags: - Investigations - /datagateway-api/jobs: + /jobs: get: description: Retrieves a list of Job objects parameters: @@ -10473,7 +10473,7 @@ paths: summary: Create new Jobs tags: - Jobs - /datagateway-api/jobs/{id_}: + /jobs/{id_}: delete: description: Updates Job with the specified ID with details provided in the request body @@ -10560,7 +10560,7 @@ paths: summary: Update Jobs by id tags: - Jobs - /datagateway-api/jobs/count: + /jobs/count: get: description: Return the count of the Job objects that would be retrieved given the filters provided @@ -10586,7 +10586,7 @@ paths: summary: Count Jobs tags: - Jobs - /datagateway-api/jobs/findone: + /jobs/findone: get: description: Retrieves the first Job objects that satisfies the filters. parameters: @@ -10614,7 +10614,7 @@ paths: summary: Get single Job tags: - Jobs - /datagateway-api/keywords: + /keywords: get: description: Retrieves a list of Keyword objects parameters: @@ -10708,7 +10708,7 @@ paths: summary: Create new Keywords tags: - Keywords - /datagateway-api/keywords/{id_}: + /keywords/{id_}: delete: description: Updates Keyword with the specified ID with details provided in the request body @@ -10795,7 +10795,7 @@ paths: summary: Update Keywords by id tags: - Keywords - /datagateway-api/keywords/count: + /keywords/count: get: description: Return the count of the Keyword objects that would be retrieved given the filters provided @@ -10821,7 +10821,7 @@ paths: summary: Count Keywords tags: - Keywords - /datagateway-api/keywords/findone: + /keywords/findone: get: description: Retrieves the first Keyword objects that satisfies the filters. parameters: @@ -10849,7 +10849,7 @@ paths: summary: Get single Keyword tags: - Keywords - /datagateway-api/parametertypes: + /parametertypes: get: description: Retrieves a list of ParameterType objects parameters: @@ -10943,7 +10943,7 @@ paths: summary: Create new ParameterTypes tags: - ParameterTypes - /datagateway-api/parametertypes/{id_}: + /parametertypes/{id_}: delete: description: Updates ParameterType with the specified ID with details provided in the request body @@ -11030,7 +11030,7 @@ paths: summary: Update ParameterTypes by id tags: - ParameterTypes - /datagateway-api/parametertypes/count: + /parametertypes/count: get: description: Return the count of the ParameterType objects that would be retrieved given the filters provided @@ -11056,7 +11056,7 @@ paths: summary: Count ParameterTypes tags: - ParameterTypes - /datagateway-api/parametertypes/findone: + /parametertypes/findone: get: description: Retrieves the first ParameterType objects that satisfies the filters. parameters: @@ -11084,7 +11084,7 @@ paths: summary: Get single ParameterType tags: - ParameterTypes - /datagateway-api/permissiblestringvalues: + /permissiblestringvalues: get: description: Retrieves a list of PermissibleStringValue objects parameters: @@ -11178,7 +11178,7 @@ paths: summary: Create new PermissibleStringValues tags: - PermissibleStringValues - /datagateway-api/permissiblestringvalues/{id_}: + /permissiblestringvalues/{id_}: delete: description: Updates PermissibleStringValue with the specified ID with details provided in the request body @@ -11265,7 +11265,7 @@ paths: summary: Update PermissibleStringValues by id tags: - PermissibleStringValues - /datagateway-api/permissiblestringvalues/count: + /permissiblestringvalues/count: get: description: Return the count of the PermissibleStringValue objects that would be retrieved given the filters provided @@ -11291,7 +11291,7 @@ paths: summary: Count PermissibleStringValues tags: - PermissibleStringValues - /datagateway-api/permissiblestringvalues/findone: + /permissiblestringvalues/findone: get: description: Retrieves the first PermissibleStringValue objects that satisfies the filters. @@ -11321,7 +11321,7 @@ paths: summary: Get single PermissibleStringValue tags: - PermissibleStringValues - /datagateway-api/publicsteps: + /publicsteps: get: description: Retrieves a list of PublicStep objects parameters: @@ -11415,7 +11415,7 @@ paths: summary: Create new PublicSteps tags: - PublicSteps - /datagateway-api/publicsteps/{id_}: + /publicsteps/{id_}: delete: description: Updates PublicStep with the specified ID with details provided in the request body @@ -11502,7 +11502,7 @@ paths: summary: Update PublicSteps by id tags: - PublicSteps - /datagateway-api/publicsteps/count: + /publicsteps/count: get: description: Return the count of the PublicStep objects that would be retrieved given the filters provided @@ -11528,7 +11528,7 @@ paths: summary: Count PublicSteps tags: - PublicSteps - /datagateway-api/publicsteps/findone: + /publicsteps/findone: get: description: Retrieves the first PublicStep objects that satisfies the filters. parameters: @@ -11556,7 +11556,7 @@ paths: summary: Get single PublicStep tags: - PublicSteps - /datagateway-api/publications: + /publications: get: description: Retrieves a list of Publication objects parameters: @@ -11650,7 +11650,7 @@ paths: summary: Create new Publications tags: - Publications - /datagateway-api/publications/{id_}: + /publications/{id_}: delete: description: Updates Publication with the specified ID with details provided in the request body @@ -11737,7 +11737,7 @@ paths: summary: Update Publications by id tags: - Publications - /datagateway-api/publications/count: + /publications/count: get: description: Return the count of the Publication objects that would be retrieved given the filters provided @@ -11763,7 +11763,7 @@ paths: summary: Count Publications tags: - Publications - /datagateway-api/publications/findone: + /publications/findone: get: description: Retrieves the first Publication objects that satisfies the filters. parameters: @@ -11791,7 +11791,7 @@ paths: summary: Get single Publication tags: - Publications - /datagateway-api/relateddatafiles: + /relateddatafiles: get: description: Retrieves a list of RelatedDatafile objects parameters: @@ -11885,7 +11885,7 @@ paths: summary: Create new RelatedDatafiles tags: - RelatedDatafiles - /datagateway-api/relateddatafiles/{id_}: + /relateddatafiles/{id_}: delete: description: Updates RelatedDatafile with the specified ID with details provided in the request body @@ -11972,7 +11972,7 @@ paths: summary: Update RelatedDatafiles by id tags: - RelatedDatafiles - /datagateway-api/relateddatafiles/count: + /relateddatafiles/count: get: description: Return the count of the RelatedDatafile objects that would be retrieved given the filters provided @@ -11998,7 +11998,7 @@ paths: summary: Count RelatedDatafiles tags: - RelatedDatafiles - /datagateway-api/relateddatafiles/findone: + /relateddatafiles/findone: get: description: Retrieves the first RelatedDatafile objects that satisfies the filters. @@ -12027,7 +12027,7 @@ paths: summary: Get single RelatedDatafile tags: - RelatedDatafiles - /datagateway-api/relateditems: + /relateditems: get: description: Retrieves a list of RelatedItem objects parameters: @@ -12121,7 +12121,7 @@ paths: summary: Create new RelatedItems tags: - RelatedItems - /datagateway-api/relateditems/{id_}: + /relateditems/{id_}: delete: description: Updates RelatedItem with the specified ID with details provided in the request body @@ -12208,7 +12208,7 @@ paths: summary: Update RelatedItems by id tags: - RelatedItems - /datagateway-api/relateditems/count: + /relateditems/count: get: description: Return the count of the RelatedItem objects that would be retrieved given the filters provided @@ -12234,7 +12234,7 @@ paths: summary: Count RelatedItems tags: - RelatedItems - /datagateway-api/relateditems/findone: + /relateditems/findone: get: description: Retrieves the first RelatedItem objects that satisfies the filters. parameters: @@ -12262,7 +12262,7 @@ paths: summary: Get single RelatedItem tags: - RelatedItems - /datagateway-api/rules: + /rules: get: description: Retrieves a list of Rule objects parameters: @@ -12355,7 +12355,7 @@ paths: summary: Create new Rules tags: - Rules - /datagateway-api/rules/{id_}: + /rules/{id_}: delete: description: Updates Rule with the specified ID with details provided in the request body @@ -12442,7 +12442,7 @@ paths: summary: Update Rules by id tags: - Rules - /datagateway-api/rules/count: + /rules/count: get: description: Return the count of the Rule objects that would be retrieved given the filters provided @@ -12468,7 +12468,7 @@ paths: summary: Count Rules tags: - Rules - /datagateway-api/rules/findone: + /rules/findone: get: description: Retrieves the first Rule objects that satisfies the filters. parameters: @@ -12496,7 +12496,7 @@ paths: summary: Get single Rule tags: - Rules - /datagateway-api/sampleparameters: + /sampleparameters: get: description: Retrieves a list of SampleParameter objects parameters: @@ -12590,7 +12590,7 @@ paths: summary: Create new SampleParameters tags: - SampleParameters - /datagateway-api/sampleparameters/{id_}: + /sampleparameters/{id_}: delete: description: Updates SampleParameter with the specified ID with details provided in the request body @@ -12677,7 +12677,7 @@ paths: summary: Update SampleParameters by id tags: - SampleParameters - /datagateway-api/sampleparameters/count: + /sampleparameters/count: get: description: Return the count of the SampleParameter objects that would be retrieved given the filters provided @@ -12703,7 +12703,7 @@ paths: summary: Count SampleParameters tags: - SampleParameters - /datagateway-api/sampleparameters/findone: + /sampleparameters/findone: get: description: Retrieves the first SampleParameter objects that satisfies the filters. @@ -12732,7 +12732,7 @@ paths: summary: Get single SampleParameter tags: - SampleParameters - /datagateway-api/sampletypes: + /sampletypes: get: description: Retrieves a list of SampleType objects parameters: @@ -12826,7 +12826,7 @@ paths: summary: Create new SampleTypes tags: - SampleTypes - /datagateway-api/sampletypes/{id_}: + /sampletypes/{id_}: delete: description: Updates SampleType with the specified ID with details provided in the request body @@ -12913,7 +12913,7 @@ paths: summary: Update SampleTypes by id tags: - SampleTypes - /datagateway-api/sampletypes/count: + /sampletypes/count: get: description: Return the count of the SampleType objects that would be retrieved given the filters provided @@ -12939,7 +12939,7 @@ paths: summary: Count SampleTypes tags: - SampleTypes - /datagateway-api/sampletypes/findone: + /sampletypes/findone: get: description: Retrieves the first SampleType objects that satisfies the filters. parameters: @@ -12967,7 +12967,7 @@ paths: summary: Get single SampleType tags: - SampleTypes - /datagateway-api/samples: + /samples: get: description: Retrieves a list of Sample objects parameters: @@ -13060,7 +13060,7 @@ paths: summary: Create new Samples tags: - Samples - /datagateway-api/samples/{id_}: + /samples/{id_}: delete: description: Updates Sample with the specified ID with details provided in the request body @@ -13147,7 +13147,7 @@ paths: summary: Update Samples by id tags: - Samples - /datagateway-api/samples/count: + /samples/count: get: description: Return the count of the Sample objects that would be retrieved given the filters provided @@ -13173,7 +13173,7 @@ paths: summary: Count Samples tags: - Samples - /datagateway-api/samples/findone: + /samples/findone: get: description: Retrieves the first Sample objects that satisfies the filters. parameters: @@ -13201,7 +13201,7 @@ paths: summary: Get single Sample tags: - Samples - /datagateway-api/shifts: + /shifts: get: description: Retrieves a list of Shift objects parameters: @@ -13294,7 +13294,7 @@ paths: summary: Create new Shifts tags: - Shifts - /datagateway-api/shifts/{id_}: + /shifts/{id_}: delete: description: Updates Shift with the specified ID with details provided in the request body @@ -13381,7 +13381,7 @@ paths: summary: Update Shifts by id tags: - Shifts - /datagateway-api/shifts/count: + /shifts/count: get: description: Return the count of the Shift objects that would be retrieved given the filters provided @@ -13407,7 +13407,7 @@ paths: summary: Count Shifts tags: - Shifts - /datagateway-api/shifts/findone: + /shifts/findone: get: description: Retrieves the first Shift objects that satisfies the filters. parameters: @@ -13435,7 +13435,7 @@ paths: summary: Get single Shift tags: - Shifts - /datagateway-api/studies: + /studies: get: description: Retrieves a list of Study objects parameters: @@ -13528,7 +13528,7 @@ paths: summary: Create new Studies tags: - Studies - /datagateway-api/studies/{id_}: + /studies/{id_}: delete: description: Updates Study with the specified ID with details provided in the request body @@ -13615,7 +13615,7 @@ paths: summary: Update Studies by id tags: - Studies - /datagateway-api/studies/count: + /studies/count: get: description: Return the count of the Study objects that would be retrieved given the filters provided @@ -13641,7 +13641,7 @@ paths: summary: Count Studies tags: - Studies - /datagateway-api/studies/findone: + /studies/findone: get: description: Retrieves the first Study objects that satisfies the filters. parameters: @@ -13669,7 +13669,7 @@ paths: summary: Get single Study tags: - Studies - /datagateway-api/studyinvestigations: + /studyinvestigations: get: description: Retrieves a list of StudyInvestigation objects parameters: @@ -13763,7 +13763,7 @@ paths: summary: Create new StudyInvestigations tags: - StudyInvestigations - /datagateway-api/studyinvestigations/{id_}: + /studyinvestigations/{id_}: delete: description: Updates StudyInvestigation with the specified ID with details provided in the request body @@ -13850,7 +13850,7 @@ paths: summary: Update StudyInvestigations by id tags: - StudyInvestigations - /datagateway-api/studyinvestigations/count: + /studyinvestigations/count: get: description: Return the count of the StudyInvestigation objects that would be retrieved given the filters provided @@ -13876,7 +13876,7 @@ paths: summary: Count StudyInvestigations tags: - StudyInvestigations - /datagateway-api/studyinvestigations/findone: + /studyinvestigations/findone: get: description: Retrieves the first StudyInvestigation objects that satisfies the filters. @@ -13905,7 +13905,7 @@ paths: summary: Get single StudyInvestigation tags: - StudyInvestigations - /datagateway-api/techniques: + /techniques: get: description: Retrieves a list of Technique objects parameters: @@ -13999,7 +13999,7 @@ paths: summary: Create new Techniques tags: - Techniques - /datagateway-api/techniques/{id_}: + /techniques/{id_}: delete: description: Updates Technique with the specified ID with details provided in the request body @@ -14086,7 +14086,7 @@ paths: summary: Update Techniques by id tags: - Techniques - /datagateway-api/techniques/count: + /techniques/count: get: description: Return the count of the Technique objects that would be retrieved given the filters provided @@ -14112,7 +14112,7 @@ paths: summary: Count Techniques tags: - Techniques - /datagateway-api/techniques/findone: + /techniques/findone: get: description: Retrieves the first Technique objects that satisfies the filters. parameters: @@ -14140,7 +14140,7 @@ paths: summary: Get single Technique tags: - Techniques - /datagateway-api/usergroups: + /usergroups: get: description: Retrieves a list of UserGroup objects parameters: @@ -14234,7 +14234,7 @@ paths: summary: Create new UserGroups tags: - UserGroups - /datagateway-api/usergroups/{id_}: + /usergroups/{id_}: delete: description: Updates UserGroup with the specified ID with details provided in the request body @@ -14321,7 +14321,7 @@ paths: summary: Update UserGroups by id tags: - UserGroups - /datagateway-api/usergroups/count: + /usergroups/count: get: description: Return the count of the UserGroup objects that would be retrieved given the filters provided @@ -14347,7 +14347,7 @@ paths: summary: Count UserGroups tags: - UserGroups - /datagateway-api/usergroups/findone: + /usergroups/findone: get: description: Retrieves the first UserGroup objects that satisfies the filters. parameters: @@ -14375,7 +14375,7 @@ paths: summary: Get single UserGroup tags: - UserGroups - /datagateway-api/users: + /users: get: description: Retrieves a list of User objects parameters: @@ -14468,7 +14468,7 @@ paths: summary: Create new Users tags: - Users - /datagateway-api/users/{id_}: + /users/{id_}: delete: description: Updates User with the specified ID with details provided in the request body @@ -14555,7 +14555,7 @@ paths: summary: Update Users by id tags: - Users - /datagateway-api/users/count: + /users/count: get: description: Return the count of the User objects that would be retrieved given the filters provided @@ -14581,7 +14581,7 @@ paths: summary: Count Users tags: - Users - /datagateway-api/users/findone: + /users/findone: get: description: Retrieves the first User objects that satisfies the filters. parameters: @@ -14609,7 +14609,7 @@ paths: summary: Get single User tags: - Users - /datagateway-api/sessions: + /sessions: delete: description: Deletes a users sessionID when they logout responses: @@ -14709,7 +14709,7 @@ paths: summary: Refresh session tags: - Sessions - /datagateway-api/ping: + /ping: get: description: Pings the API's connection method to check responsiveness responses: @@ -14720,7 +14720,7 @@ paths: description: OK message example: DataGateway API OK type: string - description: Success - the API is responsive on the backend configured + description: Success - the API is responsive on the python ICAT server '500': description: Pinging the API's connection method has gone wrong summary: Ping API connection method diff --git a/test/conftest.py b/test/conftest.py index b82e9d2a..ef577839 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -27,11 +27,9 @@ def test_config_data(): return { "datagateway_api": { "extension": "/datagateway-api", - "backend": "db", "client_cache_size": 5, "client_pool_init_size": 2, "client_pool_max_size": 5, - "db_url": "mysql+pymysql://icatdbuser:icatdbuserpw@localhost:3306/icatdb", "icat_url": "https://localhost:8181", "icat_check_cert": False, }, diff --git a/test/integration/conftest.py b/test/integration/conftest.py index a8acf1c6..0f0cd56b 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -11,11 +11,7 @@ create_app_infrastructure, ) from datagateway_api.src.common.config import APIConfig, Config -from datagateway_api.src.datagateway_api.database.helpers import ( - delete_row_by_id, - insert_row_into_table, -) -from datagateway_api.src.datagateway_api.database.models import SESSION +from datagateway_api.src.datagateway_api.icat.models import SESSION @pytest.fixture(scope="package") @@ -48,7 +44,6 @@ def flask_test_app_db(): """ db_app = Flask(__name__) db_app.config["TESTING"] = True - db_app.config["TEST_BACKEND"] = "db" api, spec = create_app_infrastructure(db_app) create_api_endpoints(db_app, api, spec) @@ -64,11 +59,11 @@ def valid_db_credentials_header(): session.expireDateTime = datetime.now() + timedelta(hours=1) session.username = "Test User" - insert_row_into_table(SESSION, session) + # insert_row_into_table(SESSION, session) yield {"Authorization": f"Bearer {session.id}"} - delete_row_by_id(SESSION, "Test") + # delete_row_by_id(SESSION, "Test") @pytest.fixture() diff --git a/test/integration/datagateway_api/db/conftest.py b/test/integration/datagateway_api/db/conftest.py deleted file mode 100644 index 124801c9..00000000 --- a/test/integration/datagateway_api/db/conftest.py +++ /dev/null @@ -1,156 +0,0 @@ -from datetime import datetime -import uuid - -import pytest - -from datagateway_api.src.common.config import Config -from datagateway_api.src.common.constants import Constants -from datagateway_api.src.common.exceptions import MissingRecordError -from datagateway_api.src.datagateway_api.database.helpers import ( - delete_row_by_id, - insert_row_into_table, -) -from datagateway_api.src.datagateway_api.database.models import ( - INSTRUMENT, - INVESTIGATION, - INVESTIGATIONINSTRUMENT, -) -from test.integration.datagateway_api.db.endpoints.test_create_db import ( - TestDBCreateData, -) - - -def set_meta_attributes(entity): - db_meta_attributes = { - "createTime": Constants.TEST_MOD_CREATE_DATETIME, - "modTime": Constants.TEST_MOD_CREATE_DATETIME, - "createId": "test create id", - "modId": "test mod id", - } - - for attr, value in db_meta_attributes.items(): - setattr(entity, attr, value) - - -def create_investigation_db_data(num_entities=1): - test_data = [] - - for i in range(num_entities): - investigation = INVESTIGATION() - investigation.name = f"Test Data for DataGateway API Testing (DB) {i}" - investigation.title = f"Title for DataGateway API Testing (DB) {i}" - investigation.startDate = datetime( - year=2020, month=1, day=4, hour=1, minute=1, second=1, - ) - investigation.endDate = datetime( - year=2020, month=1, day=8, hour=1, minute=1, second=1, - ) - investigation.visitId = str(uuid.uuid1()) - investigation.facilityID = 1 - investigation.typeID = 1 - investigation.fileSize = 1073741824 - investigation.fileCount = 3 - - set_meta_attributes(investigation) - - insert_row_into_table(INVESTIGATION, investigation) - - test_data.append(investigation) - - if len(test_data) == 1: - return test_data[0] - else: - return test_data - - -@pytest.fixture() -def single_investigation_test_data_db(): - investigation = create_investigation_db_data() - - yield investigation - try: - delete_row_by_id(INVESTIGATION, investigation.id) - except MissingRecordError as e: - # This should occur on DELETE endpoints, normal behaviour for those tests - print(e) - - -@pytest.fixture() -def multiple_investigation_test_data_db(): - investigations = create_investigation_db_data(num_entities=5) - - yield investigations - - for investigation in investigations: - delete_row_by_id(INVESTIGATION, investigation.id) - - -@pytest.fixture() -def related_distinct_data_db(): - investigation = create_investigation_db_data() - - instrument = INSTRUMENT() - instrument.name = "Test Instrument for DataGateway API Endpoint Testing (DB)" - instrument.facilityID = 1 - set_meta_attributes(instrument) - insert_row_into_table(INSTRUMENT, instrument) - - investigation_instrument = INVESTIGATIONINSTRUMENT() - investigation_instrument.investigationID = investigation.id - investigation_instrument.instrumentID = instrument.id - set_meta_attributes(investigation_instrument) - - insert_row_into_table(INVESTIGATIONINSTRUMENT, investigation_instrument) - - yield (instrument.id, investigation) - - delete_row_by_id(INVESTIGATIONINSTRUMENT, investigation_instrument.id) - delete_row_by_id(INVESTIGATION, investigation.id) - delete_row_by_id(INSTRUMENT, instrument.id) - - -@pytest.fixture() -def final_instrument_id(flask_test_app_db, valid_db_credentials_header): - final_instrument_result = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/instruments/findone" - '?order="id DESC"', - headers=valid_db_credentials_header, - ) - return final_instrument_result.json["id"] - - -@pytest.fixture() -def final_facilitycycle_id(flask_test_app_db, valid_db_credentials_header): - final_facilitycycle_result = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/facilitycycles/findone" - '?order="id DESC"', - headers=valid_db_credentials_header, - ) - return final_facilitycycle_result.json["id"] - - -@pytest.fixture() -def remove_test_created_investigation_data( - flask_test_app_db, valid_db_credentials_header, -): - yield - - created_test_data = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations?where=" - '{"name":{"like":' - f'"{TestDBCreateData.investigation_name_prefix}"' - "}}", - headers=valid_db_credentials_header, - ) - - investigation_ids = [] - - for investigation in created_test_data.json: - investigation_ids.append(investigation["id"]) - - for investigation_id in investigation_ids: - flask_test_app_db.delete( - f"{Config.config.datagateway_api.extension}/investigations" - f"/{investigation_id}", - headers=valid_db_credentials_header, - ) diff --git a/test/integration/datagateway_api/db/endpoints/conftest.py b/test/integration/datagateway_api/db/endpoints/conftest.py deleted file mode 100644 index ee58e0a0..00000000 --- a/test/integration/datagateway_api/db/endpoints/conftest.py +++ /dev/null @@ -1,51 +0,0 @@ -from icat.query import Query -import pytest - - -@pytest.fixture() -def icat_query(icat_client): - return Query(icat_client, "Investigation") - - -@pytest.fixture() -def bad_credentials_header(): - return {"Authorization": "Bearer Invalid"} - - -@pytest.fixture() -def invalid_credentials_header(): - return {"Authorization": "Test"} - - -@pytest.fixture() -def test_config_data(): - return { - "datagateway_api": { - "extension": "/datagateway-api", - "backend": "db", - "client_cache_size": 5, - "client_pool_init_size": 2, - "client_pool_max_size": 5, - "db_url": "mysql+pymysql://icatdbuser:icatdbuserpw@localhost:3306/icatdb", - "icat_url": "https://localhost:8181", - "icat_check_cert": False, - }, - "search_api": { - "extension": "/search-api", - "icat_url": "https://localhost.testdomain:8181", - "icat_check_cert": True, - "mechanism": "anon", - "username": "", - "password": "", - }, - "flask_reloader": False, - "log_level": "WARN", - "log_location": "/home/runner/work/datagateway-api/datagateway-api/logs.log", - "debug_mode": False, - "generate_swagger": False, - "host": "127.0.0.1", - "port": "5000", - "test_user_credentials": {"username": "root", "password": "pw"}, - "test_mechanism": "simple", - "url_prefix": "", - } diff --git a/test/integration/datagateway_api/db/endpoints/test_count_with_filters_db.py b/test/integration/datagateway_api/db/endpoints/test_count_with_filters_db.py deleted file mode 100644 index 6005a360..00000000 --- a/test/integration/datagateway_api/db/endpoints/test_count_with_filters_db.py +++ /dev/null @@ -1,29 +0,0 @@ -import pytest - -from datagateway_api.src.common.config import Config - - -class TestDBCountWithFilters: - @pytest.mark.usefixtures("single_investigation_test_data_db") - def test_valid_count_with_filters( - self, flask_test_app_db, valid_db_credentials_header, - ): - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations/count?where=" - '{"title": {"like": "Title for DataGateway API Testing (DB)"}}', - headers=valid_db_credentials_header, - ) - - assert test_response.json == 1 - - def test_valid_no_results_count_with_filters( - self, flask_test_app_db, valid_db_credentials_header, - ): - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations/count?where=" - '{"title": {"like": "This filter should cause a404 for testing ' - 'purposes..."}}', - headers=valid_db_credentials_header, - ) - - assert test_response.json == 0 diff --git a/test/integration/datagateway_api/db/endpoints/test_create_db.py b/test/integration/datagateway_api/db/endpoints/test_create_db.py deleted file mode 100644 index 6258f020..00000000 --- a/test/integration/datagateway_api/db/endpoints/test_create_db.py +++ /dev/null @@ -1,114 +0,0 @@ -import pytest - -from datagateway_api.src.common.config import Config - - -def prepare_db_data_for_assertion(response_json): - response_json.pop("createId") - response_json.pop("createTime") - response_json.pop("id") - response_json.pop("modId") - response_json.pop("modTime") - response_json.pop("visitId") - - if response_json["releaseDate"] is None: - return response_json - - response_json["endDate"] = response_json["endDate"][:-6] - response_json["startDate"] = response_json["startDate"][:-6] - - if response_json["releaseDate"] is None: - return response_json - else: - response_json["releaseDate"] = response_json["releaseDate"][:-6] - - return response_json - - -class TestDBCreateData: - investigation_name_prefix = "DB Test Data for API Testing, Data Creation" - - @pytest.mark.usefixtures("remove_test_created_investigation_data") - def test_valid_create_data(self, flask_test_app_db, valid_db_credentials_header): - create_investigations_json = [ - { - "name": f"{self.investigation_name_prefix} {i}", - "title": "Test data for the DB Backend on DataGateway API", - "summary": "DB Test data for DataGateway API testing", - "releaseDate": "2020-03-03 08:00:08", - "startDate": "2020-02-02 09:00:09", - "endDate": "2020-02-03 10:00:10", - "visitId": "Data Creation Visit DB", - "doi": "DataGateway API DB Test DOI", - "facilityID": 1, - "typeID": 1, - "fileCount": 1, - "fileSize": 6, - } - for i in range(2) - ] - - test_response = flask_test_app_db.post( - f"{Config.config.datagateway_api.extension}/investigations", - headers=valid_db_credentials_header, - json=create_investigations_json, - ) - - response_json = test_response.json - - for investigation_request in response_json: - prepare_db_data_for_assertion(investigation_request) - - for investigation in create_investigations_json: - investigation.pop("visitId") - assert create_investigations_json == response_json - - @pytest.mark.usefixtures("remove_test_created_investigation_data") - def test_valid_boundary_create_data( - self, flask_test_app_db, valid_db_credentials_header, - ): - """Create a single investigation, as opposed to multiple""" - - create_investigation_json = { - "name": f"{self.investigation_name_prefix} 0", - "title": "Test data for the DB Backend on the API", - "summary": "Test data for DataGateway API testing", - "releaseDate": "2020-03-03 08:00:08", - "startDate": "2020-02-02 09:00:09", - "endDate": "2020-02-03 10:00:10", - "visitId": "Data Creation Visit", - "doi": "DataGateway API Test DOI", - "facilityID": 1, - "typeID": 1, - "fileCount": 3, - "fileSize": 2, - } - - test_response = flask_test_app_db.post( - f"{Config.config.datagateway_api.extension}/investigations", - headers=valid_db_credentials_header, - json=create_investigation_json, - ) - - response_json = prepare_db_data_for_assertion(test_response.json) - - create_investigation_json.pop("visitId") - - assert create_investigation_json == response_json - - def test_invalid_create_data( - self, flask_test_app_db, valid_db_credentials_header, - ): - """An investigation requires a minimum of: name, visitId, facility, type""" - - invalid_request_body = { - "title": "Test Title for DataGateway API Backend testing", - } - - test_response = flask_test_app_db.post( - f"{Config.config.datagateway_api.extension}/investigations", - headers=valid_db_credentials_header, - json=invalid_request_body, - ) - - assert test_response.status_code == 400 diff --git a/test/integration/datagateway_api/db/endpoints/test_delete_by_id_db.py b/test/integration/datagateway_api/db/endpoints/test_delete_by_id_db.py deleted file mode 100644 index 759775c6..00000000 --- a/test/integration/datagateway_api/db/endpoints/test_delete_by_id_db.py +++ /dev/null @@ -1,40 +0,0 @@ -from datagateway_api.src.common.config import Config - - -class TestDeleteById: - def test_valid_delete_with_id( - self, - flask_test_app_db, - valid_db_credentials_header, - single_investigation_test_data_db, - ): - single_investigation_test_data = single_investigation_test_data_db.to_dict() - - test_response = flask_test_app_db.delete( - f"{Config.config.datagateway_api.extension}/investigations" - f'/{single_investigation_test_data["id"]}', - headers=valid_db_credentials_header, - ) - - assert test_response.status_code == 204 - - def test_invalid_delete_with_id( - self, flask_test_app_db, valid_db_credentials_header, - ): - """Request with a non-existent ID""" - - final_investigation_result = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations" - '/findone?order="id DESC"', - headers=valid_db_credentials_header, - ) - test_data_id = final_investigation_result.json["id"] - - # Adding 100 onto the ID to the most recent result should ensure a 404 - test_response = flask_test_app_db.delete( - f"{Config.config.datagateway_api.extension}/investigations" - f"/{test_data_id + 100}", - headers=valid_db_credentials_header, - ) - - assert test_response.status_code == 404 diff --git a/test/integration/datagateway_api/db/endpoints/test_findone_db.py b/test/integration/datagateway_api/db/endpoints/test_findone_db.py deleted file mode 100644 index 167b2a63..00000000 --- a/test/integration/datagateway_api/db/endpoints/test_findone_db.py +++ /dev/null @@ -1,29 +0,0 @@ -from datagateway_api.src.common.config import Config - - -class TestDBFindone: - def test_valid_findone_with_filters( - self, - flask_test_app_db, - valid_db_credentials_header, - single_investigation_test_data_db, - ): - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations/findone?where=" - '{"title": {"like": "Title for DataGateway API Testing (DB)"}}', - headers=valid_db_credentials_header, - ) - - assert test_response.json == single_investigation_test_data_db.to_dict() - - def test_valid_no_results_findone_with_filters( - self, flask_test_app_db, valid_db_credentials_header, - ): - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations/findone?where=" - '{"title": {"eq": "This filter should cause a404 for testing ' - 'purposes..."}}', - headers=valid_db_credentials_header, - ) - - assert test_response.status_code == 404 diff --git a/test/integration/datagateway_api/db/endpoints/test_get_by_id_db.py b/test/integration/datagateway_api/db/endpoints/test_get_by_id_db.py deleted file mode 100644 index edd12244..00000000 --- a/test/integration/datagateway_api/db/endpoints/test_get_by_id_db.py +++ /dev/null @@ -1,43 +0,0 @@ -from datagateway_api.src.common.config import Config - - -class TestDBGetByID: - def test_valid_get_with_id( - self, - flask_test_app_db, - valid_db_credentials_header, - single_investigation_test_data_db, - ): - # Need to identify the ID given to the test data - investigation_data = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations?where=" - '{"title": {"like": "Title for DataGateway API Testing (DB)"}}', - headers=valid_db_credentials_header, - ) - test_data_id = investigation_data.json[0]["id"] - - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations/{test_data_id}", - headers=valid_db_credentials_header, - ) - - assert test_response.json == single_investigation_test_data_db.to_dict() - - def test_invalid_get_with_id( - self, flask_test_app_db, valid_db_credentials_header, - ): - final_investigation_result = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations/findone?order=" - '"id DESC"', - headers=valid_db_credentials_header, - ) - test_data_id = final_investigation_result.json["id"] - - # Adding 100 onto the ID to the most recent result should ensure a 404 - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations" - f"/{test_data_id + 100}", - headers=valid_db_credentials_header, - ) - - assert test_response.status_code == 404 diff --git a/test/integration/datagateway_api/db/endpoints/test_get_with_filters.py b/test/integration/datagateway_api/db/endpoints/test_get_with_filters.py deleted file mode 100644 index 3a0bb257..00000000 --- a/test/integration/datagateway_api/db/endpoints/test_get_with_filters.py +++ /dev/null @@ -1,167 +0,0 @@ -import pytest - -from datagateway_api.src.common.config import Config -from datagateway_api.src.common.constants import Constants -from datagateway_api.src.common.date_handler import DateHandler - - -class TestDBGetWithFilters: - def test_valid_get_with_filters( - self, - flask_test_app_db, - valid_db_credentials_header, - single_investigation_test_data_db, - ): - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations?where=" - '{"title": {"like": "Title for DataGateway API Testing (DB)"}}', - headers=valid_db_credentials_header, - ) - - assert test_response.json == [single_investigation_test_data_db.to_dict()] - - def test_valid_no_results_get_with_filters( - self, flask_test_app_db, valid_db_credentials_header, - ): - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations?where=" - '{"title": {"eq": "This filter should cause a 404 fortesting ' - 'purposes..."}}', - headers=valid_db_credentials_header, - ) - - assert test_response.json == [] - - @pytest.mark.usefixtures("multiple_investigation_test_data_db") - def test_valid_get_with_filters_multiple_distinct( - self, flask_test_app_db, valid_db_credentials_header, - ): - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations?where=" - '{"title": {"like": "Title for DataGateway API Testing (DB)"}}' - '&distinct="title"', - headers=valid_db_credentials_header, - ) - - expected = [ - {"title": f"Title for DataGateway API Testing (DB) {i}"} for i in range(5) - ] - - assert test_response.json == expected - - @pytest.mark.parametrize( - "distinct_param, expected_response", - [ - pytest.param( - '"title"', - [{"title": "Title for DataGateway API Testing (DB) 0"}], - id="Single unrelated distinct field", - ), - pytest.param( - '"investigationInstruments.createTime"', - [ - { - "investigationInstruments": { - "createTime": DateHandler.datetime_object_to_str( - Constants.TEST_MOD_CREATE_DATETIME, - ), - }, - }, - ], - id="Single related distinct field", - ), - pytest.param( - '["createTime", "investigationInstruments.createTime"]', - [ - { - "createTime": DateHandler.datetime_object_to_str( - Constants.TEST_MOD_CREATE_DATETIME, - ), - "investigationInstruments": { - "createTime": DateHandler.datetime_object_to_str( - Constants.TEST_MOD_CREATE_DATETIME, - ), - }, - }, - ], - id="Single related distinct field with unrelated field", - ), - pytest.param( - '["investigationInstruments.createTime", "facility.id"]', - [ - { - "facility": {"id": 1}, - "investigationInstruments": { - "createTime": DateHandler.datetime_object_to_str( - Constants.TEST_MOD_CREATE_DATETIME, - ), - }, - }, - ], - id="Multiple related distinct fields", - ), - pytest.param( - '["createTime", "investigationInstruments.createTime", "facility.id"]', - [ - { - "createTime": DateHandler.datetime_object_to_str( - Constants.TEST_MOD_CREATE_DATETIME, - ), - "facility": {"id": 1}, - "investigationInstruments": { - "createTime": DateHandler.datetime_object_to_str( - Constants.TEST_MOD_CREATE_DATETIME, - ), - }, - }, - ], - id="Multiple related distinct fields with unrelated field", - ), - ], - ) - @pytest.mark.usefixtures("related_distinct_data_db") - def test_valid_get_with_filters_related_distinct( - self, - flask_test_app_db, - valid_db_credentials_header, - distinct_param, - expected_response, - ): - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations?where=" - '{"title": {"like": "Title for DataGateway API Testing (DB)"}}' - f"&distinct={distinct_param}", - headers=valid_db_credentials_header, - ) - - print(test_response.json) - - assert test_response.json == expected_response - - def test_limit_skip_merge_get_with_filters( - self, - flask_test_app_db, - valid_db_credentials_header, - multiple_investigation_test_data_db, - ): - skip_value = 1 - limit_value = 2 - - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/investigations?where=" - '{"title": {"like": "Title for DataGateway API Testing (DB)"}}' - f'&skip={skip_value}&limit={limit_value}&order="id ASC"', - headers=valid_db_credentials_header, - ) - - # Copy required to ensure data is deleted at the end of the test - investigation_test_data_copy = multiple_investigation_test_data_db.copy() - filtered_investigation_data = [] - filter_count = 0 - while filter_count < limit_value: - filtered_investigation_data.append( - investigation_test_data_copy.pop(skip_value).to_dict(), - ) - filter_count += 1 - - assert test_response.json == filtered_investigation_data diff --git a/test/integration/datagateway_api/db/endpoints/test_ping_db.py b/test/integration/datagateway_api/db/endpoints/test_ping_db.py deleted file mode 100644 index aa8dfa30..00000000 --- a/test/integration/datagateway_api/db/endpoints/test_ping_db.py +++ /dev/null @@ -1,27 +0,0 @@ -from unittest.mock import patch - -import pytest -from sqlalchemy.exc import SQLAlchemyError - -from datagateway_api.src.common.config import Config -from datagateway_api.src.common.constants import Constants -from datagateway_api.src.common.exceptions import DatabaseError -from datagateway_api.src.datagateway_api.backends import create_backend - - -class TestICATPing: - def test_valid_ping(self, flask_test_app_db): - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/ping", - ) - - assert test_response.json == Constants.PING_OK_RESPONSE - - def test_invalid_ping(self): - with patch( - "sqlalchemy.engine.reflection.Inspector.get_table_names", - side_effect=SQLAlchemyError("Mocked Exception"), - ): - with pytest.raises(DatabaseError): - backend = create_backend("db") - backend.ping() diff --git a/test/integration/datagateway_api/db/endpoints/test_update_by_id_db.py b/test/integration/datagateway_api/db/endpoints/test_update_by_id_db.py deleted file mode 100644 index facb5554..00000000 --- a/test/integration/datagateway_api/db/endpoints/test_update_by_id_db.py +++ /dev/null @@ -1,66 +0,0 @@ -from datagateway_api.src.common.config import Config - - -class TestUpdateByID: - def test_valid_update_with_id( - self, - flask_test_app_db, - valid_db_credentials_header, - single_investigation_test_data_db, - ): - update_data_json = { - "doi": "DB Test Data Identifier", - "summary": "DB Test Summary", - "startDate": "2019-01-04 01:01:01", - } - - single_investigation_test_data = single_investigation_test_data_db.to_dict() - - single_investigation_test_data.update(update_data_json) - test_response = flask_test_app_db.patch( - f"{Config.config.datagateway_api.extension}/investigations" - f"/{single_investigation_test_data['id']}", - headers=valid_db_credentials_header, - json=update_data_json, - ) - - response_json = test_response.json - - # The DB returns times with timezone indicators, - # but does not accept them being created. - # This strips the timezone indicators out so that the results can be compared. - response_json["startDate"] = response_json["startDate"][:-6] - - assert response_json == single_investigation_test_data - - def test_invalid_update_with_id( - self, - flask_test_app_db, - valid_db_credentials_header, - single_investigation_test_data_db, - ): - """This test will attempt to put the DB in an invalid state""" - - invalid_update_json = { - "doi": "_" * 300, - } - - single_investigation_test_data = single_investigation_test_data_db.to_dict() - - test_response = flask_test_app_db.patch( - f"{Config.config.datagateway_api.extension}/investigations" - f"/{single_investigation_test_data['id']}", - headers=valid_db_credentials_header, - json=invalid_update_json, - ) - - print( - "If this test is failing " - "you may need to set sql_mode to " - "STRICT_ALL_TABLES", - ) - - # If this test is failing - # you may need to set sql_mode to - # STRICT_ALL_TABLES - assert test_response.status_code == 400 diff --git a/test/integration/datagateway_api/db/endpoints/test_update_multiple_db.py b/test/integration/datagateway_api/db/endpoints/test_update_multiple_db.py deleted file mode 100644 index dead98a2..00000000 --- a/test/integration/datagateway_api/db/endpoints/test_update_multiple_db.py +++ /dev/null @@ -1,85 +0,0 @@ -from datagateway_api.src.common.config import Config - - -class TestUpdateMultipleEntities: - def test_valid_multiple_update_data( - self, - flask_test_app_db, - valid_db_credentials_header, - multiple_investigation_test_data_db, - ): - expected_doi = "DB Test Data Identifier" - expected_summary = "DB Test summary" - - update_data_list = [] - test_data_list = [] - - for investigation_object in multiple_investigation_test_data_db: - investigation = investigation_object.to_dict() - investigation["doi"] = expected_doi - investigation["summary"] = expected_summary - - update_entity = { - "id": investigation["id"], - "doi": expected_doi, - "summary": expected_summary, - } - update_data_list.append(update_entity) - test_data_list.append(investigation) - - test_response = flask_test_app_db.patch( - f"{Config.config.datagateway_api.extension}/investigations", - headers=valid_db_credentials_header, - json=update_data_list, - ) - - assert test_response.json == test_data_list - - def test_valid_boundary_update_data( - self, - flask_test_app_db, - valid_db_credentials_header, - single_investigation_test_data_db, - ): - """ Request body is a dictionary, not a list of dictionaries""" - - expected_doi = "Test Data Identifier" - expected_summary = "Test Summary" - single_investigation_test_data = single_investigation_test_data_db.to_dict() - - update_data_json = { - "id": single_investigation_test_data["id"], - "doi": expected_doi, - "summary": expected_summary, - } - single_investigation_test_data["doi"] = expected_doi - single_investigation_test_data["summary"] = expected_summary - - test_response = flask_test_app_db.patch( - f"{Config.config.datagateway_api.extension}/investigations", - headers=valid_db_credentials_header, - json=update_data_json, - ) - - assert test_response.json == [single_investigation_test_data] - - def test_invalid_missing_update_data( - self, - flask_test_app_db, - valid_db_credentials_header, - single_investigation_test_data_db, - ): - """There should be an ID in the request body to know which entity to update""" - - update_data_json = { - "doi": "Test Data Identifier", - "summary": "Test Summary", - } - - test_response = flask_test_app_db.patch( - f"{Config.config.datagateway_api.extension}/investigations", - headers=valid_db_credentials_header, - json=update_data_json, - ) - - assert test_response.status_code == 400 diff --git a/test/integration/datagateway_api/db/test_database_filter_utilities.py b/test/integration/datagateway_api/db/test_database_filter_utilities.py deleted file mode 100644 index d0ce50a2..00000000 --- a/test/integration/datagateway_api/db/test_database_filter_utilities.py +++ /dev/null @@ -1,349 +0,0 @@ -import pytest - -from datagateway_api.src.common.exceptions import FilterError -from datagateway_api.src.common.helpers import get_entity_object_from_name -from datagateway_api.src.datagateway_api.database.filters import ( - DatabaseFilterUtilities, - DatabaseWhereFilter, -) -from datagateway_api.src.datagateway_api.database.helpers import ReadQuery -from test.integration.datagateway_api.db.endpoints.test_create_db import ( - prepare_db_data_for_assertion, -) - - -class TestDatabaseFilterUtilities: - @pytest.mark.parametrize( - "input_field, expected_fields", - [ - pytest.param("name", ("name", None, None), id="Unrelated field"), - pytest.param( - "facility.daysUntilRelease", - ("facility", "daysUntilRelease", None), - id="Related field matching ICAT schema name", - ), - pytest.param( - "FACILITY.daysUntilRelease", - ("FACILITY", "daysUntilRelease", None), - id="Related field matching database format (uppercase)", - ), - pytest.param( - "user.investigationUsers.role", - ("user", "investigationUsers", "role"), - id="Related related field (2 levels deep)", - ), - ], - ) - def test_valid_extract_filter_fields(self, input_field, expected_fields): - test_utility = DatabaseFilterUtilities() - test_utility.extract_filter_fields(input_field) - - assert test_utility.field == expected_fields[0] - assert test_utility.related_field == expected_fields[1] - assert test_utility.related_related_field == expected_fields[2] - - def test_invalid_extract_filter_fields(self): - test_utility = DatabaseFilterUtilities() - - with pytest.raises(ValueError): - test_utility.extract_filter_fields( - "user.investigationUsers.investigation.summary", - ) - - @pytest.mark.parametrize( - "input_field", - [ - pytest.param("name", id="No related fields"), - pytest.param("facility.daysUntilRelease", id="Related field"), - pytest.param( - "investigationUsers.user.fullName", id="Related related field", - ), - ], - ) - def test_valid_add_query_join( - self, flask_test_app_db, input_field, - ): - table = get_entity_object_from_name("Investigation") - - test_utility = DatabaseFilterUtilities() - test_utility.extract_filter_fields(input_field) - - expected_query = ReadQuery(table) - if test_utility.related_related_field: - expected_table = get_entity_object_from_name(test_utility.related_field) - - included_table = get_entity_object_from_name(test_utility.field) - expected_query.base_query = expected_query.base_query.join( - included_table, - ).join(expected_table) - elif test_utility.related_field: - expected_table = get_entity_object_from_name(test_utility.field) - - expected_query = ReadQuery(table) - expected_query.base_query = expected_query.base_query.join(expected_table) - else: - expected_table = table - - with ReadQuery(table) as test_query: - test_utility.add_query_join(test_query) - - # Check the JOIN has been applied - assert str(test_query.base_query) == str(expected_query.base_query) - - @pytest.mark.parametrize( - "input_field", - [ - pytest.param("name", id="No related fields"), - pytest.param("facility.daysUntilRelease", id="Related field"), - pytest.param( - "investigationUsers.user.fullName", id="Related related field", - ), - ], - ) - def test_valid_get_entity_model_for_filter(self, input_field): - table = get_entity_object_from_name("Investigation") - - test_utility = DatabaseFilterUtilities() - test_utility.extract_filter_fields(input_field) - - if test_utility.related_related_field: - expected_table = get_entity_object_from_name(test_utility.related_field) - elif test_utility.related_field: - expected_table = get_entity_object_from_name(test_utility.field) - else: - expected_table = table - - with ReadQuery(table) as test_query: - output_field = test_utility.get_entity_model_for_filter(test_query) - - # Check the output is correct - field_name_to_fetch = input_field.split(".")[-1] - assert output_field == getattr(expected_table, field_name_to_fetch) - - def test_valid_get_field(self, flask_test_app_db): - table = get_entity_object_from_name("Investigation") - - test_utility = DatabaseFilterUtilities() - field = test_utility._get_field(table, "name") - - assert field == table.name - - def test_invalid_get_field(self, flask_test_app_db): - table = get_entity_object_from_name("Investigation") - - test_utility = DatabaseFilterUtilities() - with pytest.raises(FilterError): - test_utility._get_field(table, "unknown") - - @pytest.mark.parametrize( - "operation, value, expected_output", - [ - pytest.param( - "eq", - "Title for DataGateway API Testing (DB) 0", - { - "doi": None, - "endDate": "2020-01-08 01:01:01+00:00", - "name": "Test Data for DataGateway API Testing (DB) 0", - "releaseDate": None, - "startDate": "2020-01-04 01:01:01+00:00", - "summary": None, - "title": "Title for DataGateway API Testing (DB) 0", - "facilityID": 1, - "typeID": 1, - "fileSize": 1073741824, - "fileCount": 3, - }, - id="equal", - ), - pytest.param( - "ne", - "Title for DataGateway API Testing (DB) 0", - { - "doi": "0-417-77631-4", - "endDate": "2000-07-09 00:00:00", - "name": "INVESTIGATION 1", - "releaseDate": "2000-07-05 00:00:00", - "startDate": "2000-04-03 00:00:00", - "summary": "Throw hope parent. Receive entire soon." - " War top air agent must voice high describe.\nMonth " - "shake voice. Do discuss despite least face again study." - " Two beyond picture rich fast sea time.", - "title": "Analysis reflect work or hour color maybe." - "\nMuch team discussion message weight.", - "facilityID": 1, - "typeID": 3, - "fileCount": 30, - "fileSize": 3118779841, - }, - id="not equal (ne)", - ), - pytest.param( - "like", - "Title for DataGateway API Testing (DB) 0", - { - "doi": None, - "endDate": "2020-01-08 01:01:01+00:00", - "name": "Test Data for DataGateway API Testing (DB) 0", - "releaseDate": None, - "startDate": "2020-01-04 01:01:01+00:00", - "summary": None, - "title": "Title for DataGateway API Testing (DB) 0", - "facilityID": 1, - "typeID": 1, - "fileSize": 1073741824, - "fileCount": 3, - }, - id="like", - ), - pytest.param( - "nlike", - "Title for DataGateway API Testing (DB) 0", - { - "doi": "0-417-77631-4", - "endDate": "2000-07-09 00:00:00", - "name": "INVESTIGATION 1", - "releaseDate": "2000-07-05 00:00:00", - "startDate": "2000-04-03 00:00:00", - "summary": "Throw hope parent. Receive entire soon. " - "War top air agent must voice high describe.\nMonth " - "shake voice. Do discuss despite least face again study. " - "Two beyond picture rich fast sea time.", - "title": "Analysis reflect work or hour color maybe." - "\nMuch team discussion message weight.", - "facilityID": 1, - "typeID": 3, - "fileSize": 3118779841, - "fileCount": 30, - }, - id="not like", - ), - pytest.param( - "lt", - "Title for DataGateway API Testing (DB) 0", - { - "doi": "0-417-77631-4", - "endDate": "2000-07-09 00:00:00", - "name": "INVESTIGATION 1", - "releaseDate": "2000-07-05 00:00:00", - "startDate": "2000-04-03 00:00:00", - "summary": "Throw hope parent. Receive entire soon. " - "War top air agent must voice high describe.\n" - "Month shake voice. " - "Do discuss despite least face again study. " - "Two beyond picture rich fast sea time.", - "title": "Analysis reflect work or hour color maybe." - "\nMuch team discussion message weight.", - "facilityID": 1, - "typeID": 3, - "fileSize": 3118779841, - "fileCount": 30, - }, - id="less than", - ), - pytest.param( - "lte", - "Title for DataGateway API Testing (DB) 0", - { - "doi": "0-417-77631-4", - "endDate": "2000-07-09 00:00:00", - "name": "INVESTIGATION 1", - "releaseDate": "2000-07-05 00:00:00", - "startDate": "2000-04-03 00:00:00", - "summary": "Throw hope parent. Receive entire soon. " - "War top air agent must voice high describe.\n" - "Month shake voice. " - "Do discuss despite least face again study. " - "Two beyond picture rich fast sea time.", - "title": "Analysis reflect work or hour color maybe." - "\nMuch team discussion message weight.", - "facilityID": 1, - "typeID": 3, - "fileSize": 3118779841, - "fileCount": 30, - }, - id="less than or equal", - ), - pytest.param( - "gt", - "Title for DataGateway API Testing (DB) 0", - { - "doi": "0-9996467-0-2", - "endDate": "2007-07-09 00:00:00", - "name": "INVESTIGATION 29", - "releaseDate": "2007-07-05 00:00:00", - "startDate": "2007-04-03 00:00:00", - "summary": "City plant especially ever eight. Wife street" - " under. Life character drive down. Bag sport benefit also" - " price.\nIncrease spring box successful travel.", - "title": "Usually water six learn bring white development " - "political. Meeting those voice hand.", - "facilityID": 1, - "typeID": 1, - "fileSize": 3700075351, - "fileCount": 30, - }, - id="greater than", - ), - pytest.param( - "gte", - "Title for DataGateway API Testing (DB) 0", - { - "doi": "0-9996467-0-2", - "endDate": "2007-07-09 00:00:00", - "name": "INVESTIGATION 29", - "releaseDate": "2007-07-05 00:00:00", - "startDate": "2007-04-03 00:00:00", - "summary": "City plant especially ever eight. " - "Wife street under. Life character drive down. Bag sport" - " benefit also price.\nIncrease spring box successful" - " travel.", - "title": "Usually water six learn bring white development " - "political. Meeting those voice hand.", - "facilityID": 1, - "typeID": 1, - "fileSize": 3700075351, - "fileCount": 30, - }, - id="greater than or equal", - ), - pytest.param( - "in", - ["Title for DataGateway API Testing (DB) 0"], - { - "doi": None, - "endDate": "2020-01-08 01:01:01+00:00", - "name": "Test Data for DataGateway API Testing (DB) 0", - "releaseDate": None, - "startDate": "2020-01-04 01:01:01+00:00", - "summary": None, - "title": "Title for DataGateway API Testing (DB) 0", - "facilityID": 1, - "typeID": 1, - "fileSize": 1073741824, - "fileCount": 3, - }, - id="in", - ), - ], - ) - def test_valid_where_operation( - self, - flask_test_app_db, - operation, - value, - expected_output, - single_investigation_test_data_db, - ): - test_utility = DatabaseWhereFilter("title", value, operation) - table = get_entity_object_from_name("Investigation") - - test_query = ReadQuery(table) - - test_utility.apply_filter(test_query) - - response_json = prepare_db_data_for_assertion( - test_query.base_query.first().to_dict(), - ) - - assert response_json == expected_output diff --git a/test/integration/datagateway_api/db/test_entity_helper.py b/test/integration/datagateway_api/db/test_entity_helper.py deleted file mode 100644 index adae61cf..00000000 --- a/test/integration/datagateway_api/db/test_entity_helper.py +++ /dev/null @@ -1,202 +0,0 @@ -import pytest - -from datagateway_api.src.common.constants import Constants -from datagateway_api.src.datagateway_api.database.models import ( - DATAFILE, - DATAFILEFORMAT, - DATASET, - INVESTIGATION, -) - - -@pytest.fixture() -def dataset_entity(): - dataset = DATASET() - investigation = INVESTIGATION() - dataset.INVESTIGATION = investigation - - return dataset - - -@pytest.fixture() -def datafile_entity(dataset_entity): - datafileformat = DATAFILEFORMAT() - datafile = DATAFILE() - datafile.id = 1 - datafile.location = "test location" - datafile.DATASET = dataset_entity - datafile.DATAFILEFORMAT = datafileformat - datafile.name = "test name" - datafile.modTime = Constants.TEST_MOD_CREATE_DATETIME - datafile.createTime = Constants.TEST_MOD_CREATE_DATETIME - datafile.checksum = "test checksum" - datafile.fileSize = 64 - datafile.datafileModTime = Constants.TEST_MOD_CREATE_DATETIME - datafile.datafileCreateTime = Constants.TEST_MOD_CREATE_DATETIME - datafile.datasetID = 1 - datafile.doi = "test doi" - datafile.description = "test description" - datafile.createId = "test create id" - datafile.modId = "test mod id" - datafile.datafileFormatID = 1 - - return datafile - - -class TestEntityHelper: - def test_valid_to_dict(self, datafile_entity): - expected_dict = { - "id": 1, - "location": "test location", - "name": "test name", - "modTime": str(Constants.TEST_MOD_CREATE_DATETIME), - "checksum": "test checksum", - "fileSize": 64, - "datafileModTime": str(Constants.TEST_MOD_CREATE_DATETIME), - "datafileCreateTime": str(Constants.TEST_MOD_CREATE_DATETIME), - "datasetID": 1, - "doi": "test doi", - "description": "test description", - "createId": "test create id", - "modId": "test mod id", - "datafileFormatID": 1, - "createTime": str(Constants.TEST_MOD_CREATE_DATETIME), - } - - test_data = datafile_entity.to_dict() - - assert expected_dict == test_data - - @pytest.mark.parametrize( - "expected_dict, entity_names", - [ - pytest.param( - { - "id": 1, - "location": "test location", - "name": "test name", - "modTime": str(Constants.TEST_MOD_CREATE_DATETIME), - "checksum": "test checksum", - "fileSize": 64, - "datafileModTime": str(Constants.TEST_MOD_CREATE_DATETIME), - "datafileCreateTime": str(Constants.TEST_MOD_CREATE_DATETIME), - "doi": "test doi", - "description": "test description", - "createId": "test create id", - "modId": "test mod id", - "datafileFormatID": 1, - "createTime": str(Constants.TEST_MOD_CREATE_DATETIME), - "datasetID": 1, - "dataset": { - "id": None, - "createTime": None, - "modTime": None, - "createId": None, - "modId": None, - "investigationID": None, - "complete": None, - "description": None, - "doi": None, - "endDate": None, - "location": None, - "name": None, - "startDate": None, - "sampleID": None, - "typeID": None, - }, - }, - "dataset", - id="Dataset", - ), - pytest.param( - { - "id": 1, - "location": "test location", - "name": "test name", - "modTime": str(Constants.TEST_MOD_CREATE_DATETIME), - "checksum": "test checksum", - "fileSize": 64, - "datafileModTime": str(Constants.TEST_MOD_CREATE_DATETIME), - "datafileCreateTime": str(Constants.TEST_MOD_CREATE_DATETIME), - "doi": "test doi", - "description": "test description", - "createId": "test create id", - "modId": "test mod id", - "datafileFormatID": 1, - "createTime": str(Constants.TEST_MOD_CREATE_DATETIME), - "datasetID": 1, - "dataset": { - "id": None, - "createTime": None, - "modTime": None, - "createId": None, - "modId": None, - "complete": None, - "description": None, - "doi": None, - "endDate": None, - "location": None, - "name": None, - "startDate": None, - "sampleID": None, - "typeID": None, - "investigationID": None, - "investigation": { - "id": None, - "createId": None, - "createTime": None, - "fileCount": None, - "fileSize": None, - "doi": None, - "endDate": None, - "modId": None, - "modTime": None, - "name": None, - "releaseDate": None, - "startDate": None, - "summary": None, - "title": None, - "visitId": None, - "facilityID": None, - "typeID": None, - }, - }, - }, - {"dataset": "investigation"}, - id="Dataset including investigation", - ), - ], - ) - def test_valid_to_nested_dict(self, datafile_entity, expected_dict, entity_names): - test_data = datafile_entity.to_nested_dict(entity_names) - - assert expected_dict == test_data - - def test_valid_get_related_entity(self, dataset_entity, datafile_entity): - assert dataset_entity == datafile_entity.get_related_entity("DATASET") - - def test_valid_update_from_dict(self, datafile_entity): - datafile = DATAFILE() - test_dict_data = { - "id": 1, - "location": "test location", - "name": "test name", - "modTime": str(Constants.TEST_MOD_CREATE_DATETIME), - "checksum": "test checksum", - "fileSize": 64, - "datafileModTime": str(Constants.TEST_MOD_CREATE_DATETIME), - "datafileCreateTime": str(Constants.TEST_MOD_CREATE_DATETIME), - "datasetID": 1, - "doi": "test doi", - "description": "test description", - "createId": "test create id", - "modId": "test mod id", - "datafileFormatID": 1, - "createTime": str(Constants.TEST_MOD_CREATE_DATETIME), - } - - datafile.update_from_dict(test_dict_data) - - expected_datafile_dict = datafile_entity.to_dict() - - assert test_dict_data == expected_datafile_dict diff --git a/test/integration/datagateway_api/db/test_requires_session_id.py b/test/integration/datagateway_api/db/test_requires_session_id.py deleted file mode 100644 index 392d5e96..00000000 --- a/test/integration/datagateway_api/db/test_requires_session_id.py +++ /dev/null @@ -1,47 +0,0 @@ -from datagateway_api.src.common.config import Config - - -class TestRequiresSessionID: - """ - This class tests the session decorator used for the database backend. The equivalent - decorator for the Python ICAT backend is tested in `test_session_handling.py` - """ - - def test_login(self, flask_test_app_db): - test_response = flask_test_app_db.post( - f"{Config.config.datagateway_api.extension}/sessions", - json={"username": "user", "password": "password", "mechanism": "simple"}, - ) - - assert test_response.status_code == 201 - - def test_invalid_missing_credentials(self, flask_test_app_db): - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/datafiles", - ) - - assert test_response.status_code == 401 - - def test_invalid_credentials(self, flask_test_app_db, invalid_credentials_header): - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/datafiles", - headers=invalid_credentials_header, - ) - - assert test_response.status_code == 403 - - def test_bad_credentials(self, flask_test_app_db, bad_credentials_header): - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/datafiles", - headers=bad_credentials_header, - ) - - assert test_response.status_code == 403 - - def test_valid_credentials(self, flask_test_app_db, valid_db_credentials_header): - test_response = flask_test_app_db.get( - f"{Config.config.datagateway_api.extension}/datafiles?limit=0", - headers=valid_db_credentials_header, - ) - - assert test_response.status_code == 200 diff --git a/test/integration/datagateway_api/icat/endpoints/test_ping_icat.py b/test/integration/datagateway_api/icat/endpoints/test_ping_icat.py index faf0b42a..e9073468 100644 --- a/test/integration/datagateway_api/icat/endpoints/test_ping_icat.py +++ b/test/integration/datagateway_api/icat/endpoints/test_ping_icat.py @@ -6,8 +6,8 @@ from datagateway_api.src.common.config import Config from datagateway_api.src.common.constants import Constants from datagateway_api.src.common.exceptions import PythonICATError -from datagateway_api.src.datagateway_api.backends import create_backend from datagateway_api.src.datagateway_api.icat.icat_client_pool import create_client_pool +from datagateway_api.src.datagateway_api.icat.python_icat import PythonICAT class TestICATPing: @@ -24,6 +24,6 @@ def test_invalid_ping(self): side_effect=ICATError("Mocked Exception"), ): with pytest.raises(PythonICATError): - backend = create_backend("python_icat") + python_icat = PythonICAT() client_pool = create_client_pool() - backend.ping(client_pool=client_pool) + python_icat.ping(client_pool=client_pool) diff --git a/test/integration/datagateway_api/icat/test_session_handling.py b/test/integration/datagateway_api/icat/test_session_handling.py index 20155faf..1a89a886 100644 --- a/test/integration/datagateway_api/icat/test_session_handling.py +++ b/test/integration/datagateway_api/icat/test_session_handling.py @@ -5,12 +5,13 @@ from icat.client import Client import pytest + from datagateway_api.src.common.config import Config from datagateway_api.src.common.date_handler import DateHandler from datagateway_api.src.common.exceptions import AuthenticationError -from datagateway_api.src.datagateway_api.backends import create_backend from datagateway_api.src.datagateway_api.icat.filters import PythonICATWhereFilter from datagateway_api.src.datagateway_api.icat.icat_client_pool import create_client_pool +from datagateway_api.src.datagateway_api.icat.python_icat import PythonICAT class TestSessionHandling: @@ -141,11 +142,13 @@ def test_invalid_login( assert login_response.status_code == expected_response_code def test_expired_session(self): - test_backend = create_backend("python_icat") + test_python_icat = PythonICAT() client_pool = create_client_pool() with patch("icat.client.Client.getRemainingMinutes", return_value=-1): with pytest.raises(AuthenticationError): - test_backend.get_session_details("session id", client_pool=client_pool) + test_python_icat.get_session_details( + "session id", client_pool=client_pool, + ) def test_valid_logout(self, flask_test_app_icat): client = Client( diff --git a/test/integration/datagateway_api/test_backends.py b/test/integration/datagateway_api/test_backends.py deleted file mode 100644 index 26d597b4..00000000 --- a/test/integration/datagateway_api/test_backends.py +++ /dev/null @@ -1,56 +0,0 @@ -import pytest - -from datagateway_api.src.datagateway_api.backend import Backend -from datagateway_api.src.datagateway_api.backends import create_backend -from datagateway_api.src.datagateway_api.database.backend import DatabaseBackend -from datagateway_api.src.datagateway_api.icat.backend import PythonICATBackend - - -class TestBackends: - @pytest.mark.parametrize( - "backend_name, backend_type", - [ - pytest.param("db", DatabaseBackend, id="Database Backend"), - pytest.param("python_icat", PythonICATBackend, id="Python ICAT Backend"), - ], - ) - def test_valid_backend_creation(self, backend_name, backend_type): - test_backend = create_backend(backend_name) - - assert type(test_backend) == backend_type - - def test_invalid_backend_creation(self): - with pytest.raises(SystemExit): - create_backend("invalid_backend_name") - - def test_abstract_class(self): - """ - Test the `Backend` abstract class has all required abstract methods for the API - """ - Backend.__abstractmethods__ = set() - - class DummyBackend(Backend): - pass - - d = DummyBackend() - - credentials = "credentials" - session_id = "session_id" - entity_type = "entity_type" - filters = "filters" - data = "data" - id_ = "id_" - - assert d.ping() is None - assert d.login(credentials) is None - assert d.get_session_details(session_id) is None - assert d.refresh(session_id) is None - assert d.logout(session_id) is None - assert d.get_with_filters(session_id, entity_type, filters) is None - assert d.create(session_id, entity_type, data) is None - assert d.update(session_id, entity_type, data) is None - assert d.get_one_with_filters(session_id, entity_type, filters) is None - assert d.count_with_filters(session_id, entity_type, filters) is None - assert d.get_with_id(session_id, entity_type, id_) is None - assert d.delete_with_id(session_id, entity_type, id_) is None - assert d.update_with_id(session_id, entity_type, id_, data) is None diff --git a/test/integration/datagateway_api/test_query_filter_factory.py b/test/integration/datagateway_api/test_query_filter_factory.py index e3b0119f..b469834f 100644 --- a/test/integration/datagateway_api/test_query_filter_factory.py +++ b/test/integration/datagateway_api/test_query_filter_factory.py @@ -1,12 +1,12 @@ import pytest -from datagateway_api.src.datagateway_api.database.filters import ( - DatabaseDistinctFieldFilter, - DatabaseIncludeFilter, - DatabaseLimitFilter, - DatabaseOrderFilter, - DatabaseSkipFilter, - DatabaseWhereFilter, +from datagateway_api.src.datagateway_api.icat.filters import ( + PythonICATDistinctFieldFilter, + PythonICATIncludeFilter, + PythonICATLimitFilter, + PythonICATOrderFilter, + PythonICATSkipFilter, + PythonICATWhereFilter, ) from datagateway_api.src.datagateway_api.query_filter_factory import ( DataGatewayAPIQueryFilterFactory, @@ -19,7 +19,7 @@ def test_valid_distinct_filter(self): test_filter = DataGatewayAPIQueryFilterFactory.get_query_filter( {"distinct": "TEST"}, ) - assert isinstance(test_filter[0], DatabaseDistinctFieldFilter) + assert isinstance(test_filter[0], PythonICATDistinctFieldFilter) assert len(test_filter) == 1 @pytest.mark.usefixtures("flask_test_app_db") @@ -36,13 +36,13 @@ def test_valid_distinct_filter(self): ) def test_valid_include_filter(self, filter_input): test_filter = DataGatewayAPIQueryFilterFactory.get_query_filter(filter_input) - assert isinstance(test_filter[0], DatabaseIncludeFilter) + assert isinstance(test_filter[0], PythonICATIncludeFilter) assert len(test_filter) == 1 @pytest.mark.usefixtures("flask_test_app_db") def test_valid_limit_filter(self): test_filter = DataGatewayAPIQueryFilterFactory.get_query_filter({"limit": 10}) - assert isinstance(test_filter[0], DatabaseLimitFilter) + assert isinstance(test_filter[0], PythonICATLimitFilter) assert len(test_filter) == 1 @pytest.mark.usefixtures("flask_test_app_db") @@ -50,13 +50,13 @@ def test_valid_order_filter(self): test_filter = DataGatewayAPIQueryFilterFactory.get_query_filter( {"order": "id DESC"}, ) - assert isinstance(test_filter[0], DatabaseOrderFilter) + assert isinstance(test_filter[0], PythonICATOrderFilter) assert len(test_filter) == 1 @pytest.mark.usefixtures("flask_test_app_db") def test_valid_skip_filter(self): test_filter = DataGatewayAPIQueryFilterFactory.get_query_filter({"skip": 10}) - assert isinstance(test_filter[0], DatabaseSkipFilter) + assert isinstance(test_filter[0], PythonICATSkipFilter) assert len(test_filter) == 1 @pytest.mark.usefixtures("flask_test_app_db") @@ -75,5 +75,5 @@ def test_valid_skip_filter(self): ) def test_valid_where_filter(self, filter_input): test_filter = DataGatewayAPIQueryFilterFactory.get_query_filter(filter_input) - assert isinstance(test_filter[0], DatabaseWhereFilter) + assert isinstance(test_filter[0], PythonICATWhereFilter) assert len(test_filter) == 1 diff --git a/test/integration/test_config.py b/test/integration/test_config.py deleted file mode 100644 index 122c2f8a..00000000 --- a/test/integration/test_config.py +++ /dev/null @@ -1,9 +0,0 @@ -class TestAPIConfig: - def test_load_with_valid_config_data(self, test_config): - backend_type = test_config.datagateway_api.backend - assert backend_type == "db" - - def test_set_backend_type(self, test_config): - test_config.datagateway_api.set_backend_type("backend_name_changed") - - assert test_config.datagateway_api.backend == "backend_name_changed" diff --git a/test/integration/test_get_filters_from_query.py b/test/integration/test_get_filters_from_query.py index 02f98d47..1c43198e 100644 --- a/test/integration/test_get_filters_from_query.py +++ b/test/integration/test_get_filters_from_query.py @@ -1,14 +1,4 @@ -import pytest - -from datagateway_api.src.common.exceptions import FilterError from datagateway_api.src.common.helpers import get_filters_from_query_string -from datagateway_api.src.datagateway_api.database.filters import ( - DatabaseDistinctFieldFilter, - DatabaseIncludeFilter, - DatabaseLimitFilter, - DatabaseOrderFilter, - DatabaseSkipFilter, -) class TestGetFiltersFromQueryString: @@ -18,27 +8,6 @@ def test_valid_no_filters(self, flask_test_app_db): assert [] == get_filters_from_query_string("datagateway_api") - def test_invalid_filter(self, flask_test_app_db): - with flask_test_app_db: - flask_test_app_db.get('/?test="test"') - - with pytest.raises(FilterError): - get_filters_from_query_string("datagateway_api") - - @pytest.mark.parametrize( - "filter_input, filter_type", - [ - pytest.param( - 'distinct="id"', DatabaseDistinctFieldFilter, id="DB distinct filter", - ), - pytest.param( - 'include="TEST"', DatabaseIncludeFilter, id="DB include filter", - ), - pytest.param("limit=10", DatabaseLimitFilter, id="DB limit filter"), - pytest.param('order="id DESC"', DatabaseOrderFilter, id="DB order filter"), - pytest.param("skip=10", DatabaseSkipFilter, id="DB skip filter"), - ], - ) def test_valid_filter(self, flask_test_app_db, filter_input, filter_type): with flask_test_app_db: flask_test_app_db.get(f"/?{filter_input}") diff --git a/test/unit/test_config.py b/test/unit/test_config.py index 771ddfe9..46932274 100644 --- a/test/unit/test_config.py +++ b/test/unit/test_config.py @@ -18,18 +18,9 @@ def test_load_with_missing_mandatory_config_data(self, test_config_data): with pytest.raises(SystemExit): APIConfig.load("test/path") - def test_load_with_datagateway_api_db_backend_and_missing_db_config_data( - self, test_config_data, - ): - del test_config_data["datagateway_api"]["db_url"] - with patch("builtins.open", mock_open(read_data=json.dumps(test_config_data))): - with pytest.raises(SystemExit): - APIConfig.load("test/path") - def test_load_with_datagateway_api_icat_backend_and_missing_icat_config_data( self, test_config_data, ): - test_config_data["datagateway_api"]["backend"] = "python_icat" del test_config_data["datagateway_api"]["icat_url"] with patch("builtins.open", mock_open(read_data=json.dumps(test_config_data))): with pytest.raises(SystemExit): diff --git a/test/unit/test_get_entity_object.py b/test/unit/test_get_entity_object.py index e84bc0b0..c13f560b 100644 --- a/test/unit/test_get_entity_object.py +++ b/test/unit/test_get_entity_object.py @@ -2,7 +2,7 @@ from datagateway_api.src.common.exceptions import ApiError from datagateway_api.src.common.helpers import get_entity_object_from_name -from datagateway_api.src.datagateway_api.database.models import ( +from datagateway_api.src.datagateway_api.icat.models import ( FACILITY, INVESTIGATION, JOB, diff --git a/test/unit/test_query_filter.py b/test/unit/test_query_filter.py index d874f706..792c9584 100644 --- a/test/unit/test_query_filter.py +++ b/test/unit/test_query_filter.py @@ -1,11 +1,4 @@ -import pytest - -from datagateway_api.src.common.config import Config -from datagateway_api.src.common.exceptions import ApiError from datagateway_api.src.common.filters import QueryFilter -from datagateway_api.src.datagateway_api.query_filter_factory import ( - DataGatewayAPIQueryFilterFactory, -) class TestQueryFilter: @@ -23,8 +16,3 @@ class DummyQueryFilter(QueryFilter): assert qf.precedence is None assert qf.apply_filter(apply_filter) is None - - def test_invalid_query_filter_getter(self): - Config.config.datagateway_api.backend = "invalid_backend" - with pytest.raises(ApiError): - DataGatewayAPIQueryFilterFactory.get_query_filter({"order": "id DESC"}) From cf349cfacc6fa593360bd4111eeac33759ff8914 Mon Sep 17 00:00:00 2001 From: Joshua Kitenge Date: Mon, 2 Dec 2024 12:09:25 +0000 Subject: [PATCH 2/5] fix intergration tests #489 --- .../test_get_filters_from_query.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/integration/test_get_filters_from_query.py b/test/integration/test_get_filters_from_query.py index 1c43198e..2af849e1 100644 --- a/test/integration/test_get_filters_from_query.py +++ b/test/integration/test_get_filters_from_query.py @@ -1,4 +1,13 @@ +import pytest + from datagateway_api.src.common.helpers import get_filters_from_query_string +from datagateway_api.src.datagateway_api.icat.filters import ( + PythonICATDistinctFieldFilter, + PythonICATIncludeFilter, + PythonICATLimitFilter, + PythonICATOrderFilter, + PythonICATSkipFilter, +) class TestGetFiltersFromQueryString: @@ -8,6 +17,22 @@ def test_valid_no_filters(self, flask_test_app_db): assert [] == get_filters_from_query_string("datagateway_api") + @pytest.mark.parametrize( + "filter_input, filter_type", + [ + pytest.param( + 'distinct="id"', PythonICATDistinctFieldFilter, id="DB distinct filter", + ), + pytest.param( + 'include="TEST"', PythonICATIncludeFilter, id="DB include filter", + ), + pytest.param("limit=10", PythonICATLimitFilter, id="DB limit filter"), + pytest.param( + 'order="id DESC"', PythonICATOrderFilter, id="DB order filter", + ), + pytest.param("skip=10", PythonICATSkipFilter, id="DB skip filter"), + ], + ) def test_valid_filter(self, flask_test_app_db, filter_input, filter_type): with flask_test_app_db: flask_test_app_db.get(f"/?{filter_input}") From 2939010d56c3ad0644bc666d4b6fcad3c23229a0 Mon Sep 17 00:00:00 2001 From: Joshua Kitenge Date: Mon, 2 Dec 2024 12:56:23 +0000 Subject: [PATCH 3/5] set up icat stack before pip install test #489 --- .github/workflows/ci-build.yml | 59 +++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index db726b0c..e871b3d4 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -382,13 +382,67 @@ jobs: # Checkout DataGateway API and setup Python - name: Check out repo uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v3.5.3 + # Setup Java & Python - name: Setup Python uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: ${{ matrix.python-version }} architecture: x64 + - name: Setup Java + uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0 + with: + distribution: 'temurin' + java-version: 8 + + # ICAT Ansible clone and install dependencies + - name: Checkout icat-ansible + if: success() + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + with: + repository: icatproject-contrib/icat-ansible + ref: master + path: icat-ansible + - name: Install Ansible + run: pip install -r icat-ansible/requirements.txt + # Prep for running the playbook + - name: Create hosts file + run: echo -e "[icatdb_minimal_hosts]\nlocalhost ansible_connection=local" > icat-ansible/hosts + - name: Prepare vault pass + run: echo -e "icattravispw" > icat-ansible/vault_pass.txt + - name: Move vault to directory it'll get detected by Ansible + run: mv icat-ansible/vault.yml icat-ansible/group_vars/all + - name: Replace default payara user with Actions user + run: | + sed -i -e "s/^payara_user: \"glassfish\"/payara_user: \"runner\"/" icat-ansible/group_vars/all/vars.yml + + # Force hostname to localhost - bug fix for previous ICAT Ansible issues on Actions + - name: Change hostname to localhost + run: sudo hostname -b localhost + + # Remove existing MySQL installation so it doesn't interfere with GitHub Actions + - name: Remove existing mysql + run: | + sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld + sudo apt-get remove --purge "mysql*" + sudo rm -rf /var/lib/mysql* /etc/mysql + + # Create local instance of ICAT + - name: Run ICAT Ansible Playbook + run: | + ansible-playbook icat-ansible/icatdb_minimal_hosts.yml -i icat-ansible/hosts --vault-password-file icat-ansible/vault_pass.txt -vv + + # rootUserNames needs editing as anon/anon is used in search API and required to pass endpoint tests + - name: Add anon user to rootUserNames + run: | + awk -F" =" '/rootUserNames/{$2="= simple/root anon/anon";print;next}1' /home/runner/install/icat.server/run.properties > /home/runner/install/icat.server/run.properties.tmp + - name: Apply rootUserNames change + run: | + mv -f /home/runner/install/icat.server/run.properties.tmp /home/runner/install/icat.server/run.properties + - name: Reinstall ICAT Server + run: | + cd /home/runner/install/icat.server/ && ./setup -vv install - # Create virtual environment and install DataGateway API + # Create virtual environment and install DataGateway API - name: Create and activate virtual environment run: | python3 -m venv dg-api-venv @@ -407,9 +461,6 @@ jobs: '.datagateway_api.extension="/datagateway_api"' datagateway_api/config.yaml.example`" > datagateway_api/config.yaml.example - name: Create config.yaml run: cp datagateway_api/config.yaml.example datagateway_api/config.yaml - # These sections are removed so the API doesn't try to (and fail) to connect to an ICAT stack on startup - - name: Remove DataGateway API and Search API sections from config - run: yq -i 'del(.datagateway_api, .search_api)' datagateway_api/config.yaml - name: Create search_api_mapping.json run: cp datagateway_api/search_api_mapping.json.example datagateway_api/search_api_mapping.json From 27f8ff94edbb7dbff3b7d6a12dff93ee662f7e8d Mon Sep 17 00:00:00 2001 From: Joshua Kitenge Date: Mon, 2 Dec 2024 14:47:33 +0000 Subject: [PATCH 4/5] ignore vulnerability for werkzeug and cryptography #489 --- noxfile.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/noxfile.py b/noxfile.py index fd2214b5..df199993 100644 --- a/noxfile.py +++ b/noxfile.py @@ -187,6 +187,12 @@ def safety(session): "72731", "--ignore", "72981", + "--ignore", + "73711", + "--ignore", + "73969", + "--ignore", + "73889", ) try: From 34d2603d4ab705316a3cc5965c683e07d995ba7f Mon Sep 17 00:00:00 2001 From: Joshua Kitenge Date: Mon, 27 Jan 2025 12:45:13 +0000 Subject: [PATCH 5/5] remove remaining remnants of the backends #489 --- README.md | 119 ++++-------------- datagateway_api/src/common/helpers.py | 17 +-- noxfile.py | 2 + test/integration/conftest.py | 3 +- .../datagateway_api/icat/conftest.py | 5 +- .../endpoints/test_count_with_filters_icat.py | 4 +- .../icat/endpoints/test_create_icat.py | 6 +- .../icat/endpoints/test_findone_icat.py | 2 +- .../icat/endpoints/test_get_by_id_icat.py | 2 +- .../endpoints/test_get_with_filters_icat.py | 8 +- .../datagateway_api/icat/test_query.py | 60 +++------ .../icat/test_session_handling.py | 3 +- test/integration/test_filter_order_handler.py | 2 +- test/unit/test_config.py | 2 +- 14 files changed, 63 insertions(+), 172 deletions(-) diff --git a/README.md b/README.md index 5665a951..21264dd5 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,7 @@ # DataGateway API -This is a Flask-based API that fetches data from an ICAT instance, and has two sets of -endpoints, for two different use cases. The first is for -[DataGateway](https://github.com/ral-facilities/datagateway) which has two methods of -interfacing with an ICAT stack, using a -[Python-based ICAT wrapper library](https://github.com/icatproject/python-icat) or using -[sqlalchemy](https://www.sqlalchemy.org/) to communicate directly with an ICAT database. +This is a Flask-based API that fetches data from an ICAT instance, and has two sets of endpoints, for two different use cases. The first is for [DataGateway](https://github.com/ral-facilities/datagateway) which interfaces with an ICAT stack using a [Python-based ICAT wrapper library](https://github.com/icatproject/python-icat). The other use case is for the [PaNOSC Search API](https://github.com/panosc-eu/search-api/), required to be @@ -186,11 +181,9 @@ Currently, the following Nox sessions have been created: dependencies (pulled directly from Poetry) for any known vulnerabilities. This session gives the output in a full ASCII style report. - `unit_tests` - this uses [pytest](https://docs.pytest.org/en/stable/) to execute the - automated tests in `test/unit`, tests for the database and ICAT backends, and non-backend - specific tests. More details about the tests themselves [here](#running-tests). + automated tests in `test/unit`, tests for Python ICAT, and non Python ICAT specific tests. More details about the tests themselves [here](#running-tests). - `integration_tests` - this uses [pytest](https://docs.pytest.org/en/stable/) to execute the - automated tests in `test/unit`, tests for the database and ICAT backends, and non-backend - specific tests. Requires an ICAT backend. More details about the tests themselves [here](#running-tests). + automated tests in `test/unit`, tests for Python ICAT, and non Python ICAT specific tests. Requires ICAT. More details about the tests themselves [here](#running-tests). Each Nox session builds an environment using the repo's dependencies (defined using Poetry) using `install_with_constraints()`. This stores the dependencies in a @@ -283,12 +276,9 @@ e.g. `http://localhost:5000/datagateway-api/sessions`. ## DataGateway API -Depending on the backend you want to use (either `db` or `python_icat`, more details -about backends [here](#datagateway-api-backends)) the connection URL for the backend needs to be set. -These are set in `config.yaml` (an example file is provided in the base directory of -this repository). While both `db_url` and `icat_url` should have values assigned to them -(for best practice), `db_url` will only be used for the database backend, and `icat_url` -will only be used for the Python ICAT backend. Copy `config.yaml.example` to +The `icat_url` should have a value assigned to it. +This is set in `config.yaml` (an example file is provided in the base directory of +this repository). Copy `config.yaml.example` to `config.yaml` and set the values as needed. If you need to create an instance of ICAT, there are a number of markdown-formatted tutorials that can be found on the [icat.manual](https://github.com/icatproject/icat.manual/tree/master/tutorials) @@ -361,7 +351,7 @@ The Flask app can be configured so that code changes are monitored and the serve reload itself when a change is detected. This setting can be toggled using `flask_reloader` in `config.yaml`. This is useful for development purposes. It should be noted that when this setting is enabled, the API will go through the startup process -twice. In the case of the ICAT backend, this could dramatically increase startup time if +twice. In the case of the Python ICAT, this could dramatically increase startup time if the API is configured with a large initial client pool size. If you get the following error when starting the API, changes need to be made to your @@ -393,7 +383,7 @@ docker build -t datagateway_api_image . To start a container on port `8000` from the image that you just built, run: ```bash -docker run -p 8000:8000 --name datagateway_api_container datagateway_api_image +docker run -p 8000:8000 --name datagateway_api_container datagateway_api_image ``` If you want to pass values for the environment variables then instead run: @@ -417,13 +407,7 @@ specification to visualise and allow users to easily interact with the API witho building their own requests. It's great for gaining an understanding in what endpoints are available and what inputs the requests can receive, all from an interactive interface. - -For DataGateway API, this specification is built with the Database Backend in mind -(e.g. attribute names on example outputs are capitalised), however the Swagger interface -can also be used with the Python ICAT Backend. More details on how the API's OpenAPI -specification is built can be found [here](#generating-the-openapi-specification). An -issue has been [created](https://github.com/ral-facilities/datagateway-api/issues/347) -for the Swagger interface to be up to date when using the Python ICAT backend. +For DataGateway API, the Swagger interface is designed to be compatible with the Python ICAT. More details on how the API's OpenAPI specification is built can be found [here](#generating-the-openapi-specification). An issue has been [created](https://github.com/ral-facilities/datagateway-api/issues/347) to ensure the Swagger interface remains up to date with the Python ICAT. # Running Tests @@ -434,10 +418,9 @@ require an ICAT stack. In order to cover all the code you will need to run both To run the unit test use `nox -s unit_tests`, and to run the integration tests use `nox -s integration_tests` The repository contains a variety of tests, to test the functionality of the API works as intended, for convenience and quicker action runs these are additionally split into the unit and integration tests. -The tests are split into 3 main sections: non-backend specific (testing features such as the date handler), ICAT backend -tests (containing tests for backend specific components, including tests for the -different types of endpoints) and Database Backend tests (like the ICAT backend tests, -but covering only the most used aspects of the API). +The tests are split into 2 main sections: non Python ICAT specific (testing features such as the date handler) and python ICAT +tests (containing tests for the specific components, including tests for the +different types of endpoints). The configuration file (`config.yaml`) contains two options that will be used during the testing of the API. Set `test_user_credentials` and `test_mechanism` appropriately for your test environment, using `config.yaml.example` as a reference. The tests require a @@ -490,7 +473,7 @@ poetry run pytest test/integration/datagateway_api/icat/test_query.py::TestICATQ The project consists of 5 main packages: -- `datagateway_api.src.datagateway_api` - code for DataGateway API, for both database and Python ICAT backends +- `datagateway_api.src.datagateway_api` - code for DataGateway API, for Python ICAT - `datagateway_api.src.search_api` - Search API specific code e.g. `NestedWhereFilters` for the OR functionality for WHERE clauses - `datagateway_api.src.common` - code that is shared between DataGateway API and the search API - `datagateway_api.src.resources` - contains the API resources and their HTTP method definitions (e.g. GET, POST) @@ -513,9 +496,8 @@ The logic for each endpoint is within `/src/resources` - they're split into enti non_entities. The entities package contains `entity_map` which -maps entity names to their field name used in backend-specific code. The Database -Backend uses this for its mapped classes (explained below) and the Python ICAT Backend -uses this for interacting with ICAT objects within Python ICAT. In most instances, the +maps entity names to their field name. +this is used for interacting with ICAT objects within Python ICAT. In most instances, the dictionary found in `entity_map.py` is simply mapping the plural entity name (used to build the entity endpoints) to the singular version. The `entity_endpoint` module contains the function that is used to generate endpoints at start up. Finally, @@ -539,16 +521,13 @@ handler can be used to convert dates between string and datetime objects (using agreed in `datagateway_api.src.common.constants`) and uses a parser from `dateutil` to detect if an input contains a date. This is useful for determining if a JSON value given in a request body is a date, at which point it can be converted to a datetime object, -ready for storing in ICAT. The handler is currently only used in the Python ICAT -Backend, however this is non-backend specific class. +ready for storing in ICAT. The handler is currently only used in the Python ICAT. ## Exceptions & Flask Error Handling Exceptions custom to DataGateway API are defined in `datagateway_api.src.common.exceptions`. Each exception has a status code and a default -message (which can be changed when raising the exception in code). None of them are -backend specific, however some are only used in a single backend because their meaning -becomes irrelevant anywhere else. +message (which can be changed when raising the exception in code). When the API is setup in `main.py`, a custom API object is created (inheriting flask_restful's `Api` object) so `handle_error()` can be overridden. A previous @@ -565,64 +544,18 @@ status code in `exceptions.py`) in production mode. This is explained in a Filters available for use in the API are defined in `datagateway_api.src.common.filters`. These filters are all based from `QueryFilter`, an asbtract class to define any filter for the API. Precedence is used to prioritise in which order filters should be applied, -but is only needed for the Database Backend. +but is only needed for the Search API. Filtering logic is located in `datagateway_api.src.common.helpers`. `get_filters_from_query_string()` uses the request query parameters to form filters to -be used within the API. A `QueryFilterFactory` is used to build filters for the correct -backend and the static method within this class is called in +be used within the API. A `QueryFilterFactory` is used to build filters for the Python ICAT and the static method within this class is called in `get_filters_from_query_string()`. -## DataGateway API Backends - -As described at the top of this file, there are currently two ways that DataGateway API -creates/fetches/updates/deletes data from ICAT. The intention is each backend allows a -different method to communicate with ICAT, but results in a very similarly behaving -DataGateway API. - -### Abstract Backend Class - -The abstract class can be found in `datagateway_api.src.datagateway_api.backend` and -contains all the abstract methods that should be found in a class which implements -`Backend`. The typical architecture across both backends is that the implemented -functions call a helper function to process the request and the result of that is -returned to the user. - -Each backend module contains the following files which offer similar functionality, -implemented in their own ways: - -- `backend.py` - Implemented version of `datagateway_api.src.datagateway_api.backend` -- `filters.py` - Inherited versions of each filter defined in - `datagateway_api.src.common.filters` -- `helpers.py` - Helper functions that are called in `backend.py` - -### Creating a Backend - -A function inside `datagateway_api.src.datagateway_api.backends` creates an instance of a -backend using input to that function to decide which backend to create. This function is -called in `main.py` which uses the backend type set in `config.yaml`, or a config value -in the Flask app if it's set (this config option is only used in the tests however). The -backend object is then parsed into the endpoint classes so the correct backend can be -used. - -## Database Backend - -The Database Backend uses [SQLAlchemy](https://www.sqlalchemy.org/) to interface -directly with the database for an instance of ICAT. This backend favours speed over -thoroughness, allowing no control over which users can access a particular piece of -data. - -### Mapped Classes -The classes mapped from the database (as described [above](#endpoints)) are stored in -`/common/database/models.py`. Each model was automatically generated using sqlacodegen. -A class `EntityHelper` is defined so that each model may inherit two methods `to_dict()` -and `update_from_dict(dictionary)`, both used for returning entities and updating them, -in a form easily converted to JSON. -## Python ICAT Backend +## Python ICAT -Sometimes referred to as the ICAT Backend, this uses +This uses [python-icat](https://python-icat.readthedocs.io/en/stable/) to interact with ICAT data. The Python-based API wrapper allows ICAT Server to be accessed using the SOAP interface. Python ICAT allows control over which users can access a particular piece of data, with @@ -848,8 +781,6 @@ can be changed by using the arg flags `-s` or `--seed` for the seed, and `-y` or `python -m util.icat_db_generator -s 4 -y 10` Would set the seed to 4 and generate 10 years of data. -This uses code from the API's Database Backend, so a suitable `db_url` should be -configured in `config.yaml`. When used on a machine that doesn't use UTC timezone, you may find there are a mix of timezones when querying the API. This issue was found on SciGateway Preprod when using @@ -869,12 +800,8 @@ containing over 300 requests, with each type of endpoint for every entity as wel table and session endpoints. The exported collection is in v2.1 format and is currently the recommended export version for Postman. -This collection is mainly based around the Python ICAT Backend (request bodies for -creating and updating data uses camelCase attribute names as accepted by that backend) -but can easily be adapted for using the Database Backend if needed (changing attribute -names to uppercase for example). The collection also contains a login request specially -for the Database Backend, as logging in using that backend is slightly different to -logging in via the Python ICAT Backend. +This collection is mainly based around the Python ICAT (request bodies for +creating and updating data uses camelCase attribute names as accepted by that python_icat). The repo's collection can be easily imported into your Postman installation by opening Postman and selecting File > Import... and choosing the Postman collection from your diff --git a/datagateway_api/src/common/helpers.py b/datagateway_api/src/common/helpers.py index dd9c3616..255b39c8 100644 --- a/datagateway_api/src/common/helpers.py +++ b/datagateway_api/src/common/helpers.py @@ -3,7 +3,6 @@ import json import logging -from dateutil.tz.tz import tzlocal from flask import request from flask_restful import reqparse import requests @@ -132,7 +131,7 @@ def get_filters_from_query_string(api_type, entity_name=None): def get_entity_object_from_name(entity_name): """ From an entity name, this function gets a Python version of that entity for the - database backend + Python ICAT :param entity_name: Name of the entity to fetch a version from this model :type entity_name: :class:`str` @@ -173,17 +172,15 @@ def map_distinct_attributes_to_results(distinct_attributes, query_result): When selecting multiple (but not all) attributes in a database query, the results are returned in a list and not mapped to an entity object. This means the 'normal' - functions used to process data ready for output (`entity_to_dict()` for the ICAT - backend) cannot be used, as the structure of the query result is different. + functions used to process data ready for output (`entity_to_dict()` for Python ICAT + ) cannot be used, as the structure of the query result is different. :param distinct_attributes: List of distinct attributes from the distinct filter of the incoming request :type distinct_attributes: :class:`list` - :param query_result: Results fetched from a database query (backend independent due - to the data structure of this parameter) + :param query_result: Results fetched from a database query :type query_result: :class:`tuple` or :class:`list` when a single attribute is - given from ICAT backend, or :class:`sqlalchemy.engine.row.Row` when used on the - DB backend + given from Python ICAT :return: Dictionary of attribute names paired with the results, ready to be returned to the user """ @@ -193,10 +190,6 @@ def map_distinct_attributes_to_results(distinct_attributes, query_result): split_attr_name = attr_name.split(".") if isinstance(data, datetime): - # Workaround for when this function is used on DB backend, where usually - # `_make_serializable()` would fix tzinfo - if data.tzinfo is None: - data = data.replace(tzinfo=tzlocal()) data = DateHandler.datetime_object_to_str(data) # Attribute name is from the 'origin' entity (i.e. not a related entity) diff --git a/noxfile.py b/noxfile.py index df199993..38493db0 100644 --- a/noxfile.py +++ b/noxfile.py @@ -136,6 +136,8 @@ def safety(session): "--ignore", "64227", "--ignore", + "74735", + "--ignore", "65212", "--ignore", "64484", diff --git a/test/integration/conftest.py b/test/integration/conftest.py index 0f0cd56b..ef21ed85 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -6,6 +6,7 @@ from icat.client import Client import pytest + from datagateway_api.src.api_start_utils import ( create_api_endpoints, create_app_infrastructure, @@ -40,7 +41,7 @@ def flask_test_app(): def flask_test_app_db(): """ This is in the common conftest file because this test app is also used in - non-backend specific tests + non Python ICAT specific tests """ db_app = Flask(__name__) db_app.config["TESTING"] = True diff --git a/test/integration/datagateway_api/icat/conftest.py b/test/integration/datagateway_api/icat/conftest.py index 8130d433..77df5207 100644 --- a/test/integration/datagateway_api/icat/conftest.py +++ b/test/integration/datagateway_api/icat/conftest.py @@ -30,9 +30,7 @@ def create_investigation_test_data(client, num_entities=1): for i in range(num_entities): investigation = client.new("investigation") investigation.name = f"Test Data for DataGateway API Testing {i}" - investigation.title = ( - f"Test data for the Python ICAT Backend on DataGateway API {i}" - ) + investigation.title = f"Test data for Python ICAT on DataGateway API {i}" investigation.startDate = datetime( year=2020, month=1, day=4, hour=1, minute=1, second=1, tzinfo=tzlocal(), ) @@ -86,7 +84,6 @@ def multiple_investigation_test_data(icat_client): def flask_test_app_icat(flask_test_app): icat_app = Flask(__name__) icat_app.config["TESTING"] = True - icat_app.config["TEST_BACKEND"] = "python_icat" api, spec = create_app_infrastructure(icat_app) create_api_endpoints(icat_app, api, spec) diff --git a/test/integration/datagateway_api/icat/endpoints/test_count_with_filters_icat.py b/test/integration/datagateway_api/icat/endpoints/test_count_with_filters_icat.py index 1fb23509..5b6769a8 100644 --- a/test/integration/datagateway_api/icat/endpoints/test_count_with_filters_icat.py +++ b/test/integration/datagateway_api/icat/endpoints/test_count_with_filters_icat.py @@ -8,13 +8,13 @@ class TestICATCountWithFilters: "query_params, expected_result", [ pytest.param( - '?where={"title": {"like": "Test data for the Python ICAT Backend on' + '?where={"title": {"like": "Test data for Python ICAT on' ' DataGateway API"}}', 5, id="Filter on test data", ), pytest.param( - '?where={"title": {"like": "Test data for the Python ICAT Backend on' + '?where={"title": {"like": "Test data for Python ICAT on' ' DataGateway API"}}&distinct=["startDate"]', 1, id="Distinct test data", diff --git a/test/integration/datagateway_api/icat/endpoints/test_create_icat.py b/test/integration/datagateway_api/icat/endpoints/test_create_icat.py index 3442759c..ab287d78 100644 --- a/test/integration/datagateway_api/icat/endpoints/test_create_icat.py +++ b/test/integration/datagateway_api/icat/endpoints/test_create_icat.py @@ -16,7 +16,7 @@ def test_valid_create_data( create_investigations_json = [ { "name": f"{self.investigation_name_prefix} {i}", - "title": "Test data for the Python ICAT Backend on DataGateway API", + "title": "Test data for Python ICAT on DataGateway API", "summary": "Test data for DataGateway API testing", "releaseDate": "2020-03-03 08:00:08+00:00", "startDate": "2020-02-02 09:00:09+00:00", @@ -55,7 +55,7 @@ def test_valid_boundary_create_data( create_investigation_json = { "name": f"{self.investigation_name_prefix} 0", - "title": "Test data for the Python ICAT Backend on the API", + "title": "Test data for Python ICAT on the API", "summary": "Test data for DataGateway API testing", "releaseDate": "2020-03-03 08:00:08+00:00", "startDate": "2020-02-02 09:00:09+00:00", @@ -89,7 +89,7 @@ def test_invalid_create_data( """An investigation requires a minimum of: name, visitId, facility, type""" invalid_request_body = { - "title": "Test Title for DataGateway API Backend testing", + "title": "Test Title for DataGateway API Python ICAT testing", } test_response = flask_test_app_icat.post( diff --git a/test/integration/datagateway_api/icat/endpoints/test_findone_icat.py b/test/integration/datagateway_api/icat/endpoints/test_findone_icat.py index 70097c88..da76d6b9 100644 --- a/test/integration/datagateway_api/icat/endpoints/test_findone_icat.py +++ b/test/integration/datagateway_api/icat/endpoints/test_findone_icat.py @@ -13,7 +13,7 @@ def test_valid_findone_with_filters( ): test_response = flask_test_app_icat.get( f"{Config.config.datagateway_api.extension}/investigations/findone?where=" - '{"title": {"like": "Test data for the Python ICAT Backend on ' + '{"title": {"like": "Test data for Python ICAT on ' 'DataGateway API"}}', headers=valid_icat_credentials_header, ) diff --git a/test/integration/datagateway_api/icat/endpoints/test_get_by_id_icat.py b/test/integration/datagateway_api/icat/endpoints/test_get_by_id_icat.py index cbe16a3b..5b69065c 100644 --- a/test/integration/datagateway_api/icat/endpoints/test_get_by_id_icat.py +++ b/test/integration/datagateway_api/icat/endpoints/test_get_by_id_icat.py @@ -14,7 +14,7 @@ def test_valid_get_with_id( # Need to identify the ID given to the test data investigation_data = flask_test_app_icat.get( f"{Config.config.datagateway_api.extension}/investigations?where=" - '{"title": {"like": "Test data for the Python ICAT Backend on ' + '{"title": {"like": "Test data for Python ICAT on ' 'DataGateway API"}}', headers=valid_icat_credentials_header, ) diff --git a/test/integration/datagateway_api/icat/endpoints/test_get_with_filters_icat.py b/test/integration/datagateway_api/icat/endpoints/test_get_with_filters_icat.py index a868ec1d..72c53c65 100644 --- a/test/integration/datagateway_api/icat/endpoints/test_get_with_filters_icat.py +++ b/test/integration/datagateway_api/icat/endpoints/test_get_with_filters_icat.py @@ -15,7 +15,7 @@ def test_valid_get_with_filters( ): test_response = flask_test_app_icat.get( f"{Config.config.datagateway_api.extension}/investigations?where=" - '{"title": {"like": "Test data for the Python ICAT Backend on ' + '{"title": {"like": "Test data for Python ICAT on ' 'DataGateway API"}}', headers=valid_icat_credentials_header, ) @@ -41,13 +41,13 @@ def test_valid_get_with_filters_distinct( ): test_response = flask_test_app_icat.get( f"{Config.config.datagateway_api.extension}/investigations?where=" - '{"title": {"like": "Test data for the Python ICAT Backend on ' + '{"title": {"like": "Test data for Python ICAT on ' 'DataGateway API"}}&distinct="title"', headers=valid_icat_credentials_header, ) expected = [ - {"title": f"Test data for the Python ICAT Backend on DataGateway API {i}"} + {"title": f"Test data for Python ICAT on DataGateway API {i}"} for i in range(5) ] @@ -65,7 +65,7 @@ def test_limit_skip_merge_get_with_filters( test_response = flask_test_app_icat.get( f"{Config.config.datagateway_api.extension}/investigations?where=" - '{"title": {"like": "Test data for the Python ICAT Backend on ' + '{"title": {"like": "Test data for Python ICAT on ' 'DataGateway API"}}' f'&skip={skip_value}&limit={limit_value}&order="id ASC"', headers=valid_icat_credentials_header, diff --git a/test/integration/datagateway_api/icat/test_query.py b/test/integration/datagateway_api/icat/test_query.py index dac81ef1..5647d488 100644 --- a/test/integration/datagateway_api/icat/test_query.py +++ b/test/integration/datagateway_api/icat/test_query.py @@ -12,7 +12,9 @@ from datagateway_api.src.datagateway_api.icat.query import ICATQuery -def prepare_icat_data_for_assertion(data, remove_id=False, remove_visit_id=False): +def prepare_icat_data_for_assertion( + data, remove_id=False, remove_visit_id=False, +): """ Remove meta attributes from ICAT data. Meta attributes contain data about data creation/modification, and should be removed to ensure correct assertion values @@ -125,10 +127,7 @@ def test_invalid_query_creation(self, icat_client): ", manual_count, return_json_format_flag, expected_query_result", [ pytest.param( - { - "title": "like '%Test data for the Python ICAT Backend on" - " DataGateway API%'", - }, + {"title": "like '%Test data for Python ICAT on" " DataGateway API%'"}, None, None, None, @@ -144,17 +143,13 @@ def test_invalid_query_creation(self, icat_client): "releaseDate": None, "startDate": "2020-01-04 01:01:01+00:00", "summary": None, - "title": "Test data for the Python ICAT Backend on DataGateway" - " API 0", + "title": "Test data for Python ICAT on DataGateway" " API 0", }, ], id="Ordinary query", ), pytest.param( - { - "title": "like '%Test data for the Python ICAT Backend on" - " DataGateway API%'", - }, + {"title": "like '%Test data for Python ICAT on" " DataGateway API%'"}, None, ["facility"], None, @@ -168,8 +163,7 @@ def test_invalid_query_creation(self, icat_client): "releaseDate": None, "startDate": "2020-01-04 01:01:01+00:00", "summary": None, - "title": "Test data for the Python ICAT Backend on DataGateway" - " API 0", + "title": "Test data for Python ICAT on DataGateway" " API 0", "fileCount": 3, "fileSize": 1073741824, "facility": { @@ -185,10 +179,7 @@ def test_invalid_query_creation(self, icat_client): id="Query with included entity", ), pytest.param( - { - "title": "like '%Test data for the Python ICAT Backend on" - " DataGateway API%'", - }, + {"title": "like '%Test data for Python ICAT on" " DataGateway API%'"}, "COUNT", None, None, @@ -198,10 +189,7 @@ def test_invalid_query_creation(self, icat_client): id="Count query", ), pytest.param( - { - "title": "like '%Test data for the Python ICAT Backend on" - " DataGateway API%'", - }, + {"title": "like '%Test data for Python ICAT on" " DataGateway API%'"}, None, None, None, @@ -217,35 +205,23 @@ def test_invalid_query_creation(self, icat_client): "releaseDate": None, "startDate": "2020-01-04 01:01:01+00:00", "summary": None, - "title": "Test data for the Python ICAT Backend on DataGateway" - " API 0", + "title": "Test data for Python ICAT on DataGateway" " API 0", }, ], id="Data returned as entity objects", ), pytest.param( - { - "title": "like '%Test data for the Python ICAT Backend on" - " DataGateway API%'", - }, + {"title": "like '%Test data for Python ICAT on" " DataGateway API%'"}, "DISTINCT", None, "title", False, True, - [ - { - "title": "Test data for the Python ICAT Backend on DataGateway" - " API 0", - }, - ], + [{"title": "Test data for Python ICAT on DataGateway" " API 0"}], id="Single distinct field", ), pytest.param( - { - "title": "like '%Test data for the Python ICAT Backend on" - " DataGateway API%'", - }, + {"title": "like '%Test data for Python ICAT on" " DataGateway API%'"}, "DISTINCT", None, ["title", "name"], @@ -253,18 +229,14 @@ def test_invalid_query_creation(self, icat_client): True, [ { - "title": "Test data for the Python ICAT Backend on DataGateway" - " API 0", + "title": "Test data for Python ICAT on DataGateway" " API 0", "name": "Test Data for DataGateway API Testing 0", }, ], id="Multiple distinct fields", ), pytest.param( - { - "title": "like '%Test data for the Python ICAT Backend on" - " DataGateway API%'", - }, + {"title": "like '%Test data for Python ICAT on" " DataGateway API%'"}, "DISTINCT", None, ["title", "name"], @@ -335,7 +307,7 @@ def test_json_format_execution_output( ): test_query = ICATQuery(icat_client, "Investigation") test_data_filter = PythonICATWhereFilter( - "title", "Test data for the Python ICAT Backend on DataGateway API", "like", + "title", "Test data for Python ICAT on DataGateway API", "like", ) test_data_filter.apply_filter(test_query.query) query_data = test_query.execute_query(icat_client, True) diff --git a/test/integration/datagateway_api/icat/test_session_handling.py b/test/integration/datagateway_api/icat/test_session_handling.py index 1a89a886..10cd2b56 100644 --- a/test/integration/datagateway_api/icat/test_session_handling.py +++ b/test/integration/datagateway_api/icat/test_session_handling.py @@ -5,7 +5,6 @@ from icat.client import Client import pytest - from datagateway_api.src.common.config import Config from datagateway_api.src.common.date_handler import DateHandler from datagateway_api.src.common.exceptions import AuthenticationError @@ -109,7 +108,7 @@ def test_valid_login( icat_client.sessionId = login_response.json["sessionID"] icat_query.setAggregate("COUNT") title_filter = PythonICATWhereFilter( - "title", "Test data for the Python ICAT Backend on DataGateway API", "like", + "title", "Test data for Python ICAT on DataGateway API", "like", ) title_filter.apply_filter(icat_query) diff --git a/test/integration/test_filter_order_handler.py b/test/integration/test_filter_order_handler.py index c02c588d..19d13a3b 100644 --- a/test/integration/test_filter_order_handler.py +++ b/test/integration/test_filter_order_handler.py @@ -12,7 +12,7 @@ class TestFilterOrderHandler: """ `merge_python_icat_limit_skip_filters` and`clear_python_icat_order_filters()` are - tested while testing the ICAT backend filters, so tests of these functions won't be + tested while testing the Python ICAT filters, so tests of these functions won't be found here """ diff --git a/test/unit/test_config.py b/test/unit/test_config.py index 46932274..fcc8c635 100644 --- a/test/unit/test_config.py +++ b/test/unit/test_config.py @@ -18,7 +18,7 @@ def test_load_with_missing_mandatory_config_data(self, test_config_data): with pytest.raises(SystemExit): APIConfig.load("test/path") - def test_load_with_datagateway_api_icat_backend_and_missing_icat_config_data( + def test_load_with_datagateway_api_python_icat_and_missing_icat_config_data( self, test_config_data, ): del test_config_data["datagateway_api"]["icat_url"]