From c1e69ce2ef4d959f2b658dd0e5856b1ea7d16ad7 Mon Sep 17 00:00:00 2001 From: Stephen Rosen Date: Mon, 5 Feb 2024 16:33:24 -0600 Subject: [PATCH 1/2] Add support for 'filter_endpoint_use' Add this flag to EP manager task list. Check that it is not supplied without `filter_endpoint`, and raise a usage error if it is. --- ...240205_163424_sirosen_add_filter_ep_use.rst | 5 +++++ src/globus_sdk/services/transfer/client.py | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 changelog.d/20240205_163424_sirosen_add_filter_ep_use.rst diff --git a/changelog.d/20240205_163424_sirosen_add_filter_ep_use.rst b/changelog.d/20240205_163424_sirosen_add_filter_ep_use.rst new file mode 100644 index 000000000..6d00658a0 --- /dev/null +++ b/changelog.d/20240205_163424_sirosen_add_filter_ep_use.rst @@ -0,0 +1,5 @@ +Added +~~~~~ + +- ``TransferClient.endpoint_manager_task_list()`` now supports + ``filter_endpoint_use`` as a parameter. (:pr:`NUMBER`) diff --git a/src/globus_sdk/services/transfer/client.py b/src/globus_sdk/services/transfer/client.py index ecf4c8f34..c0ee9236b 100644 --- a/src/globus_sdk/services/transfer/client.py +++ b/src/globus_sdk/services/transfer/client.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import sys import time import typing as t @@ -13,6 +14,11 @@ from .response import ActivationRequirementsResponse, IterableTransferResponse from .transport import TransferRequestsTransport +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + log = logging.getLogger(__name__) TransferFilterDict = t.Dict[str, t.Union[str, t.List[str]]] @@ -1963,6 +1969,7 @@ def endpoint_manager_task_list( filter_task_id: None | UUIDLike | t.Iterable[UUIDLike] = None, filter_owner_id: UUIDLike | None = None, filter_endpoint: UUIDLike | None = None, + filter_endpoint_use: Literal["source", "destination"] | None = None, filter_is_paused: bool | None = None, filter_completion_time: None | str | tuple[DateLike, DateLike] = None, filter_min_faults: int | None = None, @@ -1997,6 +2004,9 @@ def endpoint_manager_task_list( :param filter_endpoint: Single endpoint id. Return only tasks with a matching source or destination endpoint or matching source or destination host endpoint. + :param filter_endpoint_use: In combination with ``filter_endpoint``, filter to + tasks where the endpoint or collection was used specifically as the source or + destination of the transfer. :param filter_is_paused: Return only tasks with the specified ``is_paused`` value. Requires that ``filter_status`` is also passed and contains a subset of ``"ACTIVE"`` and ``"INACTIVE"``. Completed tasks always have @@ -2072,6 +2082,12 @@ def endpoint_manager_task_list( :ref: transfer/advanced_collection_management/#get_tasks """ # noqa: E501 log.info("TransferClient.endpoint_manager_task_list(...)") + if filter_endpoint is None and filter_endpoint_use is not None: + raise exc.GlobusSDKUsageError( + "`filter_endpoint_use` is only valid when `filter_endpoint` is " + "also supplied." + ) + if query_params is None: query_params = {} if filter_status is not None: @@ -2082,6 +2098,8 @@ def endpoint_manager_task_list( query_params["filter_owner_id"] = filter_owner_id if filter_endpoint is not None: query_params["filter_endpoint"] = filter_endpoint + if filter_endpoint_use is not None: + query_params["filter_endpoint_use"] = filter_endpoint_use if filter_is_paused is not None: query_params["filter_is_paused"] = filter_is_paused if filter_completion_time is not None: From 6c6a717635e0fe98c68110655ed51b79f0029a66 Mon Sep 17 00:00:00 2001 From: Stephen Rosen Date: Tue, 6 Feb 2024 01:26:48 -0600 Subject: [PATCH 2/2] Add tests for filter_endpoint_use - Add test data for endpoint_manager_task_list - Convert existing tests to use that data (minimal changes) - Add a new test which uses the relevant test data - Add a new test which exercises the usage error on bad `filter_endpoint_use` usage --- .../transfer/endpoint_manager_task_list.py | 105 ++++++++++++++++++ .../endpoint_manager/test_task_list.py | 44 ++++++-- 2 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 src/globus_sdk/_testing/data/transfer/endpoint_manager_task_list.py diff --git a/src/globus_sdk/_testing/data/transfer/endpoint_manager_task_list.py b/src/globus_sdk/_testing/data/transfer/endpoint_manager_task_list.py new file mode 100644 index 000000000..1bad1ccc4 --- /dev/null +++ b/src/globus_sdk/_testing/data/transfer/endpoint_manager_task_list.py @@ -0,0 +1,105 @@ +import uuid + +from globus_sdk._testing.models import RegisteredResponse, ResponseSet + +from ._common import ENDPOINT_ID as SRC_ENDPOINT_ID +from ._common import TASK_ID + +DEST_ENDPOINT_ID = str(uuid.uuid4()) +OWNER_ID = str(uuid.uuid4()) + + +RESPONSES = ResponseSet( + default=RegisteredResponse( + service="transfer", + method="GET", + path="/endpoint_manager/task_list", + metadata={ + "task_id": TASK_ID, + "source": SRC_ENDPOINT_ID, + "destination": DEST_ENDPOINT_ID, + "owner_id": OWNER_ID, + }, + json={ + "DATA": [ + { + "DATA_TYPE": "task", + "bytes_checksummed": 0, + "bytes_transferred": 14, + "canceled_by_admin": None, + "canceled_by_admin_message": None, + "command": "API 0.10", + "completion_time": "2024-02-06T06:59:02+00:00", + "deadline": "2024-02-07T06:58:59+00:00", + "delete_destination_extra": False, + "destination_base_path": None, + "destination_endpoint": f"pliny_the_elder#{DEST_ENDPOINT_ID}", + "destination_endpoint_display_name": "Ercolano", + "destination_endpoint_id": DEST_ENDPOINT_ID, + "destination_host_endpoint": None, + "destination_host_endpoint_display_name": None, + "destination_host_endpoint_id": None, + "destination_host_path": None, + "destination_local_user": "pliny", + "destination_local_user_status": "OK", + "destination_mapped_collection_display_name": None, + "destination_mapped_collection_id": None, + "directories": 2, + "effective_bytes_per_second": 5, + "encrypt_data": False, + "fail_on_quota_errors": False, + "fatal_error": None, + "faults": 0, + "files": 3, + "files_skipped": 0, + "files_transferred": 3, + "filter_rules": None, + "history_deleted": False, + "is_ok": None, + "is_paused": False, + "label": None, + "nice_status": None, + "nice_status_details": None, + "nice_status_expires_in": None, + "nice_status_short_description": None, + "owner_id": OWNER_ID, + "owner_string": "pliny-the-elder@globus.org", + "preserve_timestamp": False, + "recursive_symlinks": "ignore", + "request_time": "2024-02-06T06:58:59+00:00", + "skip_source_errors": False, + "source_base_path": None, + "source_endpoint": f"pliny#{SRC_ENDPOINT_ID}", + "source_endpoint_display_name": "Pompeii", + "source_endpoint_id": SRC_ENDPOINT_ID, + "source_host_endpoint": None, + "source_host_endpoint_display_name": None, + "source_host_endpoint_id": None, + "source_host_path": None, + "source_local_user": None, + "source_local_user_status": "NO_PERMISSION", + "source_mapped_collection_display_name": None, + "source_mapped_collection_id": None, + "status": "SUCCEEDED", + "subtasks_canceled": 0, + "subtasks_expired": 0, + "subtasks_failed": 0, + "subtasks_pending": 0, + "subtasks_retrying": 0, + "subtasks_skipped_errors": 0, + "subtasks_succeeded": 6, + "subtasks_total": 6, + "symlinks": 0, + "sync_level": None, + "task_id": TASK_ID, + "type": "TRANSFER", + "username": "pliny", + "verify_checksum": True, + } + ], + "DATA_TYPE": "task_list", + "has_next_page": False, + "last_key": "complete,2024-02-06T06:59:02.291996", + }, + ), +) diff --git a/tests/functional/services/transfer/endpoint_manager/test_task_list.py b/tests/functional/services/transfer/endpoint_manager/test_task_list.py index eb45c68a4..abce649a3 100644 --- a/tests/functional/services/transfer/endpoint_manager/test_task_list.py +++ b/tests/functional/services/transfer/endpoint_manager/test_task_list.py @@ -3,8 +3,8 @@ import pytest -from globus_sdk._testing import get_last_request -from tests.common import register_api_route +import globus_sdk +from globus_sdk._testing import get_last_request, load_response ZERO_ID = uuid.UUID(int=0) @@ -13,12 +13,6 @@ def get_last_params(): return get_last_request().params -# stub in empty data, this can be explicitly replaced if a test wants specific data -@pytest.fixture(autouse=True) -def empty_response(): - register_api_route("transfer", "/endpoint_manager/task_list", json={"DATA": []}) - - @pytest.mark.parametrize( "paramname, paramvalue", [ @@ -38,6 +32,7 @@ def empty_response(): ], ) def test_strsafe_params(client, paramname, paramvalue): + load_response(client.endpoint_manager_task_list) paramstr = str(paramvalue) client.endpoint_manager_task_list(**{paramname: paramvalue}) params = get_last_params() @@ -46,6 +41,7 @@ def test_strsafe_params(client, paramname, paramvalue): def test_filter_status_list(client): + load_response(client.endpoint_manager_task_list) client.endpoint_manager_task_list(filter_status=["ACTIVE", "INACTIVE"]) params = get_last_params() assert "filter_status" in params @@ -53,6 +49,7 @@ def test_filter_status_list(client): def test_filter_task_id_list(client): + load_response(client.endpoint_manager_task_list) # mixed list of str and UUID client.endpoint_manager_task_list(filter_task_id=["foo", ZERO_ID, "bar"]) params = get_last_params() @@ -65,6 +62,8 @@ def _fromisoformat(datestr): # for py3.6, datetime.fromisoformat was added in p def test_filter_completion_time_datetime_tuple(client): + load_response(client.endpoint_manager_task_list) + dt1 = _fromisoformat("2020-08-25T00:00:00") dt2 = _fromisoformat("2021-08-25T16:05:28") @@ -82,3 +81,32 @@ def test_filter_completion_time_datetime_tuple(client): params = get_last_params() assert "filter_completion_time" in params assert params["filter_completion_time"] == ",2020-08-25T00:00:00" + + +@pytest.mark.parametrize("ep_use", ("source", "destination")) +def test_filter_by_endpoint_use(client, ep_use): + meta = load_response(client.endpoint_manager_task_list).metadata + if ep_use == "source": + ep_id = meta["source"] + else: + ep_id = meta["destination"] + + client.endpoint_manager_task_list(filter_endpoint=ep_id, filter_endpoint_use=ep_use) + params = get_last_params() + + assert "filter_endpoint" in params + assert params["filter_endpoint"] == str(ep_id) + assert "filter_endpoint_use" in params + assert params["filter_endpoint_use"] == ep_use + + +@pytest.mark.parametrize("ep_use", ("source", "destination")) +def test_usage_error_on_filter_endpoint_use_without_endpoint(client, ep_use): + with pytest.raises( + globus_sdk.GlobusSDKUsageError, + match=( + "`filter_endpoint_use` is only valid when `filter_endpoint` is " + r"also supplied\." + ), + ): + client.endpoint_manager_task_list(filter_endpoint_use=ep_use)