Skip to content

Commit 31c2c66

Browse files
authored
feat: add create and update methods to IndexResource (#45)
* feat: add create and update methods to IndexResourceProtocol * feat: add create and update methods to IndexResource * test: add tests for create and update index methods * test: add fixtures for create and update failure cases * test: add tests for create and update failure cases * fix: lint and tests
1 parent 1aaebc8 commit 31c2c66

3 files changed

Lines changed: 175 additions & 1 deletion

File tree

src/deepset_mcp/api/indexes/resource.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,51 @@ async def get(self, index_name: str) -> Index:
4646
raise_for_status(response)
4747

4848
return Index.model_validate(response.json)
49+
50+
async def create(self, name: str, yaml_config: str, description: str | None = None) -> Index:
51+
"""Create a new index with the given name and configuration.
52+
53+
:param name: Name of the index
54+
:param yaml_config: YAML configuration for the index
55+
:param description: Optional description for the index
56+
:returns: Created index details
57+
"""
58+
data = {
59+
"name": name,
60+
"config_yaml": yaml_config,
61+
}
62+
if description is not None:
63+
data["description"] = description
64+
65+
response = await self._client.request(f"/api/v1/workspaces/{self._workspace}/indexes", method="POST", data=data)
66+
67+
raise_for_status(response)
68+
69+
return Index.model_validate(response.json)
70+
71+
async def update(
72+
self, index_name: str, updated_index_name: str | None = None, yaml_config: str | None = None
73+
) -> Index:
74+
"""Update name and/or configuration of an existing index.
75+
76+
:param index_name: Name of the index to update
77+
:param updated_index_name: Optional new name for the index
78+
:param yaml_config: Optional new YAML configuration
79+
:returns: Updated index details
80+
"""
81+
data = {}
82+
if updated_index_name is not None:
83+
data["name"] = updated_index_name
84+
if yaml_config is not None:
85+
data["config_yaml"] = yaml_config
86+
87+
if not data:
88+
raise ValueError("At least one of updated_index_name or yaml_config must be provided")
89+
90+
response = await self._client.request(
91+
f"/api/v1/workspaces/{self._workspace}/indexes/{index_name}", method="PATCH", data=data
92+
)
93+
94+
raise_for_status(response)
95+
96+
return Index.model_validate(response.json)

src/deepset_mcp/api/protocols.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,28 @@ async def get(self, index_name: str) -> Index:
104104
"""Fetch a single index by its name."""
105105
...
106106

107+
async def create(self, name: str, yaml_config: str, description: str | None = None) -> Index:
108+
"""Create a new index with the given name and configuration.
109+
110+
:param name: Name of the index
111+
:param yaml_config: YAML configuration for the index
112+
:param description: Optional description for the index
113+
:returns: Created index details
114+
"""
115+
...
116+
117+
async def update(
118+
self, index_name: str, updated_index_name: str | None = None, yaml_config: str | None = None
119+
) -> Index:
120+
"""Update name and/or configuration of an existing index.
121+
122+
:param index_name: Name of the index to update
123+
:param updated_index_name: Optional new name for the index
124+
:param yaml_config: Optional new YAML configuration
125+
:returns: Updated index details
126+
"""
127+
...
128+
107129

108130
class PipelineTemplateResourceProtocol(Protocol):
109131
"""Protocol defining the implementation for PipelineTemplateResource."""

test/unit/api/indexes/test_index_resource.py

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import pytest
55

6-
from deepset_mcp.api.exceptions import ResourceNotFoundError, UnexpectedAPIError
6+
from deepset_mcp.api.exceptions import BadRequestError, ResourceNotFoundError, UnexpectedAPIError
77
from deepset_mcp.api.indexes.models import Index, IndexList
88
from deepset_mcp.api.indexes.resource import IndexResource
99
from deepset_mcp.api.transport import TransportResponse
@@ -97,6 +97,36 @@ def fake_get_500_response(fake_client: BaseFakeClient, workspace: str) -> None:
9797
)
9898

9999

100+
@pytest.fixture()
101+
def fake_create_400_response(fake_client: BaseFakeClient, workspace: str) -> None:
102+
"""Configure fake client to return a 400 response for invalid create request."""
103+
fake_client.responses[f"/api/v1/workspaces/{workspace}/indexes"] = TransportResponse(
104+
status_code=400,
105+
json={"detail": "Invalid request parameters"},
106+
text=json.dumps({"detail": "Invalid request parameters"}),
107+
)
108+
109+
110+
@pytest.fixture()
111+
def fake_update_404_response(fake_client: BaseFakeClient, workspace: str) -> None:
112+
"""Configure fake client to return a 404 response for nonexistent index update."""
113+
fake_client.responses[f"/api/v1/workspaces/{workspace}/indexes/nonexistent-index"] = TransportResponse(
114+
status_code=404,
115+
json={"detail": "Index not found"},
116+
text=json.dumps({"detail": "Index not found"}),
117+
)
118+
119+
120+
@pytest.fixture()
121+
def fake_update_400_response(fake_client: BaseFakeClient, workspace: str) -> None:
122+
"""Configure fake client to return a 400 response for invalid update request."""
123+
fake_client.responses[f"/api/v1/workspaces/{workspace}/indexes/invalid-index"] = TransportResponse(
124+
status_code=400,
125+
json={"detail": "Invalid configuration format"},
126+
text=json.dumps({"detail": "Invalid configuration format"}),
127+
)
128+
129+
100130
class TestIndexResource:
101131
"""Test the IndexResource."""
102132

@@ -147,3 +177,77 @@ async def test_list_indexes_passes_params(
147177
# Check the last request's parameters
148178
last_request = fake_client.requests[-1]
149179
assert last_request["params"] == {"limit": 20, "page_number": 2}
180+
181+
async def test_create_index_successful(
182+
self, fake_client: BaseFakeClient, workspace: str, index_response: dict[str, Any]
183+
) -> None:
184+
"""Test creating a new index."""
185+
fake_client.responses[f"/api/v1/workspaces/{workspace}/indexes"] = TransportResponse(
186+
status_code=201, json=index_response, text=json.dumps(index_response)
187+
)
188+
189+
resource = IndexResource(fake_client, workspace)
190+
result = await resource.create(name="test-index", yaml_config="yaml: content", description="Test description")
191+
192+
assert isinstance(result, Index)
193+
assert result.name == "test-index"
194+
195+
# Verify request
196+
last_request = fake_client.requests[-1]
197+
assert last_request["method"] == "POST"
198+
assert last_request["data"] == {
199+
"name": "test-index",
200+
"config_yaml": "yaml: content",
201+
"description": "Test description",
202+
}
203+
204+
async def test_update_index_successful(
205+
self, fake_client: BaseFakeClient, workspace: str, index_response: dict[str, Any]
206+
) -> None:
207+
"""Test updating an existing index."""
208+
fake_client.responses[f"/api/v1/workspaces/{workspace}/indexes/test-index"] = TransportResponse(
209+
status_code=200, json=index_response, text=json.dumps(index_response)
210+
)
211+
212+
resource = IndexResource(fake_client, workspace)
213+
result = await resource.update(
214+
index_name="test-index", updated_index_name="new-name", yaml_config="new: config"
215+
)
216+
217+
assert isinstance(result, Index)
218+
assert result.name == "test-index"
219+
220+
# Verify request
221+
last_request = fake_client.requests[-1]
222+
assert last_request["method"] == "PATCH"
223+
assert last_request["data"] == {"name": "new-name", "config_yaml": "new: config"}
224+
225+
async def test_update_index_without_changes_fails(self, fake_client: BaseFakeClient, workspace: str) -> None:
226+
"""Test that updating an index without any changes raises ValueError."""
227+
resource = IndexResource(fake_client, workspace)
228+
with pytest.raises(ValueError, match="At least one of updated_index_name or yaml_config must be provided"):
229+
await resource.update(index_name="test-index")
230+
231+
async def test_create_index_invalid_request(
232+
self, fake_client: BaseFakeClient, workspace: str, fake_create_400_response: None
233+
) -> None:
234+
"""Test that creating an index with invalid parameters raises an error."""
235+
resource = IndexResource(fake_client, workspace)
236+
with pytest.raises(BadRequestError):
237+
await resource.create(name="invalid-index", yaml_config="invalid: yaml")
238+
239+
async def test_update_nonexistent_index(
240+
self, fake_client: BaseFakeClient, workspace: str, fake_update_404_response: None
241+
) -> None:
242+
"""Test that updating a nonexistent index raises ResourceNotFoundError."""
243+
resource = IndexResource(fake_client, workspace)
244+
with pytest.raises(ResourceNotFoundError):
245+
await resource.update(index_name="nonexistent-index", updated_index_name="new-name")
246+
247+
async def test_update_index_invalid_config(
248+
self, fake_client: BaseFakeClient, workspace: str, fake_update_400_response: None
249+
) -> None:
250+
"""Test that updating an index with invalid configuration raises an error."""
251+
resource = IndexResource(fake_client, workspace)
252+
with pytest.raises(BadRequestError):
253+
await resource.update(index_name="invalid-index", yaml_config="invalid: yaml")

0 commit comments

Comments
 (0)