Skip to content

Commit

Permalink
Add search index lifecycle methods: create_index, delete_index, reope…
Browse files Browse the repository at this point in the history
…n_index (#785)

* Add methods for index {create,delete,reopen}

These are a trio of Search API methods. Here they are introduced
without test data, tests, or anything beyond their basic call
structure and docs.

Because the docs reference fixture data which does not yet exist, the
docs are not fully valid yet.

This change includes a changelog for the new methods.

* Add test data for Search index lifecycle APIs

* Add simple tests for Search index lifecycle APIs

* Add failure tests to new Search methods

`{create,delete,reopen}_index` now each have an interesting failure
mode recorded in the test data and each one is tested to ensure that
the errors parse correctly.
  • Loading branch information
sirosen authored Jul 17, 2023
1 parent 0b44295 commit 3289e6f
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Added
~~~~~

- Add ``SearchClient`` methods for managing search index lifecycle:
``create_index``, ``delete_index``, and ``reopen_index`` (:pr:`NUMBER`)
55 changes: 55 additions & 0 deletions src/globus_sdk/_testing/data/search/create_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import uuid

from globus_sdk._testing.models import RegisteredResponse, ResponseSet

INDEX_ID = str(uuid.uuid4())


RESPONSES = ResponseSet(
default=RegisteredResponse(
service="search",
method="POST",
path="/v1/index",
json={
"@datatype": "GSearchIndex",
"@version": "2017-09-01",
"creation_date": "2021-04-05 15:05:18",
"display_name": "Awesome Index of Awesomeness",
"description": "An index so awesome that it simply cannot be described",
"id": INDEX_ID,
"is_trial": True,
"subscription_id": None,
"max_size_in_mb": 1,
"num_entries": 0,
"num_subjects": 0,
"size_in_mb": 0,
"status": "open",
},
metadata={"index_id": INDEX_ID},
),
trial_limit=RegisteredResponse(
service="search",
method="POST",
path="/v1/index",
status=409,
json={
"@datatype": "GError",
"request_id": "38186e960f3a64c9d530d48ba2271285",
"status": 409,
"error_data": {
"cause": (
"When creating an index, an 'owner' role is created "
"automatically. If this would exceed ownership limits, this error "
"is raised instead."
),
"constraint": (
"Cannot create more ownership roles on trial indices "
"than the limit (3)"
),
},
"@version": "2017-09-01",
"message": "Role limit exceeded",
"code": "Conflict.LimitExceeded",
},
),
)
37 changes: 37 additions & 0 deletions src/globus_sdk/_testing/data/search/delete_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import uuid

from globus_sdk._testing.models import RegisteredResponse, ResponseSet

INDEX_ID = str(uuid.uuid4())


RESPONSES = ResponseSet(
default=RegisteredResponse(
service="search",
method="DELETE",
path=f"/v1/index/{INDEX_ID}",
json={
"index_id": INDEX_ID,
"acknowledged": True,
},
metadata={"index_id": INDEX_ID},
),
delete_pending=RegisteredResponse(
service="search",
method="DELETE",
path=f"/v1/index/{INDEX_ID}",
status=409,
json={
"@datatype": "GError",
"request_id": "3430ce9a5f9d929ef7682e4c58363dee",
"status": 409,
"@version": "2017-09-01",
"message": (
"Index status (delete_pending) did not match required status "
"for this operation: open"
),
"code": "Conflict.IncompatibleIndexStatus",
},
metadata={"index_id": INDEX_ID},
),
)
37 changes: 37 additions & 0 deletions src/globus_sdk/_testing/data/search/reopen_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import uuid

from globus_sdk._testing.models import RegisteredResponse, ResponseSet

INDEX_ID = str(uuid.uuid4())


RESPONSES = ResponseSet(
default=RegisteredResponse(
service="search",
method="POST",
path=f"/v1/index/{INDEX_ID}/reopen",
json={
"index_id": INDEX_ID,
"acknowledged": True,
},
metadata={"index_id": INDEX_ID},
),
already_open=RegisteredResponse(
service="search",
method="POST",
path=f"/v1/index/{INDEX_ID}/reopen",
status=409,
json={
"code": "Conflict.IncompatibleIndexStatus",
"request_id": "e1ad6822156dea372027eee48c16e150",
"@datatype": "GError",
"message": (
"Index status (open) did not match required status for "
"this operation: delete_pending"
),
"@version": "2017-09-01",
"status": 409,
},
metadata={"index_id": INDEX_ID},
),
)
113 changes: 113 additions & 0 deletions src/globus_sdk/services/search/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,119 @@ class SearchClient(client.BaseClient):
# Index Management
#

def create_index(
self, display_name: str, description: str
) -> response.GlobusHTTPResponse:
"""
Create a new index.
:param display_name: the name of the index
:type display_name: str
:param description: a description of the index
:type description: str
New indices default to trial status. For subscribers with a subscription ID,
indices can be converted to non-trial by sending a request to support@globus.org
.. tab-set::
.. tab-item:: Example Usage
.. code-block:: python
sc = globus_sdk.SearchClient(...)
r = sc.create_index(
"History and Witchcraft",
"Searchable information about history and witchcraft",
)
print(f"index ID: {r['id']}")
.. tab-item:: Example Response Data
.. expandtestfixture:: search.create_index
.. tab-item:: API Info
``POST /v1/index``
.. extdoclink:: Index Create
:ref: search/reference/index_create/
"""
log.info(f"SearchClient.create_index({display_name!r}, ...)")
return self.post(
"/v1/index", data={"display_name": display_name, "description": description}
)

def delete_index(self, index_id: UUIDLike) -> response.GlobusHTTPResponse:
"""
Mark an index for deletion.
Globus Search does not immediately delete indices. Instead, this API sets the
index status to ``"delete-pending"``.
Search will move pending tasks on the index to the ``CANCELLED`` state and will
eventually delete the index.
If the index is a trial index, it will be deleted a few minutes after being
marked for deletion.
If the index is non-trial, it will be kept for 30 days and will be eligible for
use with the ``reopen`` API (see :meth:`~.reopen_index`) during that time.
:param index_id: the ID of the index
:type index_id: str or UUID
.. tab-set::
.. tab-item:: Example Usage
.. code-block:: python
sc = globus_sdk.SearchClient(...)
sc.delete_index(index_id)
.. tab-item:: Example Response Data
.. expandtestfixture:: search.delete_index
.. tab-item:: API Info
``DELETE /v1/index/<index_id>``
.. extdoclink:: Index Delete
:ref: search/reference/index_delete/
"""
log.info(f"SearchClient.delete_index({index_id!r}, ...)")
return self.delete(f"/v1/index/{index_id}")

def reopen_index(self, index_id: UUIDLike) -> response.GlobusHTTPResponse:
"""
Reopen an index that has been marked for deletion, cancelling the deletion.
:param index_id: the ID of the index
:type index_id: str or UUID
.. tab-set::
.. tab-item:: Example Usage
.. code-block:: python
sc = globus_sdk.SearchClient(...)
sc.reopen_index(index_id)
.. tab-item:: Example Response Data
.. expandtestfixture:: search.reopen_index
.. tab-item:: API Info
``POST /v1/index/<index_id>/reopen``
.. extdoclink:: Index Reopen
:ref: search/reference/index_reopen/
"""
log.info(f"SearchClient.reopen_index({index_id!r}, ...)")
return self.post(f"/v1/index/{index_id}/reopen")

def get_index(
self,
index_id: UUIDLike,
Expand Down
24 changes: 24 additions & 0 deletions tests/functional/services/search/test_create_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import pytest

import globus_sdk
from globus_sdk._testing import load_response


def test_create_index(client):
meta = load_response(client.create_index).metadata

res = client.create_index("Foo Title", "bar description")
assert res.http_status == 200
assert res["id"] == meta["index_id"]


def test_create_index_limit_exceeded(client):
load_response(client.create_index, case="trial_limit")

with pytest.raises(globus_sdk.SearchAPIError) as excinfo:
client.create_index("Foo Title", "bar description")

err = excinfo.value

assert err.http_status == 409
assert err.code == "Conflict.LimitExceeded"
24 changes: 24 additions & 0 deletions tests/functional/services/search/test_delete_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import pytest

import globus_sdk
from globus_sdk._testing import load_response


def test_delete_index(client):
meta = load_response(client.delete_index).metadata

res = client.delete_index(meta["index_id"])
assert res.http_status == 200
assert res["acknowledged"] is True


def test_delete_index_delete_already_pending(client):
meta = load_response(client.delete_index, case="delete_pending").metadata

with pytest.raises(globus_sdk.SearchAPIError) as excinfo:
client.delete_index(meta["index_id"])

err = excinfo.value

assert err.http_status == 409
assert err.code == "Conflict.IncompatibleIndexStatus"
24 changes: 24 additions & 0 deletions tests/functional/services/search/test_reopen_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import pytest

import globus_sdk
from globus_sdk._testing import load_response


def test_reopen_index(client):
meta = load_response(client.reopen_index).metadata

res = client.reopen_index(meta["index_id"])
assert res.http_status == 200
assert res["acknowledged"] is True


def test_reopen_index_already_open(client):
meta = load_response(client.reopen_index, case="already_open").metadata

with pytest.raises(globus_sdk.SearchAPIError) as excinfo:
client.reopen_index(meta["index_id"])

err = excinfo.value

assert err.http_status == 409
assert err.code == "Conflict.IncompatibleIndexStatus"

0 comments on commit 3289e6f

Please sign in to comment.