Skip to content

Commit 41cb064

Browse files
authored
feat: add workspace resource API with CRUD operations (#129)
* feat: add workspace api module init file * feat: add workspace models * feat: add workspace resource protocol * feat: add workspace resource implementation * feat: add workspace protocol import * feat: add workspaces method to AsyncClientProtocol * feat: add workspace resource import * feat: add workspaces method to AsyncDeepsetClient * feat: add workspace protocol import to test conftest * feat: add workspaces method to BaseFakeClient * feat: add workspace unit test module init * feat: add comprehensive unit tests for workspace resource * feat: add integration tests for workspace resource * fix: tests, format, add integration
1 parent 765f63f commit 41cb064

11 files changed

Lines changed: 554 additions & 1 deletion

File tree

REPO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ You would need to perform the following steps:
8686
4. refer to `src/deepset_mcp/tools/pipeline.py` as a good example for tool implementations
8787
5. extract model or response serialization into reusable helper functions
8888
6. once you added a tool, import it in `src/deepset_mcp/tool_factory.py` and add it to the tool registry with the appropriate config
89-
7the docstring of the tool will serve as the prompt for the large language model calling the tool, make sure it has good instructions on when to use the tool, how to best use it, and what kind of answer to expect.
89+
7. the docstring of the tool will serve as the prompt for the large language model calling the tool, make sure it has good instructions on when to use the tool, how to best use it, and what kind of answer to expect.
9090

9191
#### Testing the tool
9292

src/deepset_mcp/api/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
TransportResponse,
1919
)
2020
from deepset_mcp.api.user.resource import UserResource
21+
from deepset_mcp.api.workspace.resource import WorkspaceResource
2122

2223
T = TypeVar("T")
2324

@@ -265,3 +266,7 @@ def users(self) -> UserResource:
265266
def secrets(self) -> SecretResource:
266267
"""Resource to interact with secrets."""
267268
return SecretResource(client=self)
269+
270+
def workspaces(self) -> WorkspaceResource:
271+
"""Resource to interact with workspaces."""
272+
return WorkspaceResource(client=self)

src/deepset_mcp/api/protocols.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from deepset_mcp.api.secrets.protocols import SecretResourceProtocol
1111
from deepset_mcp.api.transport import StreamingResponse, TransportResponse
1212
from deepset_mcp.api.user.protocols import UserResourceProtocol
13+
from deepset_mcp.api.workspace.protocols import WorkspaceResourceProtocol
1314

1415
T = TypeVar("T")
1516

@@ -110,3 +111,7 @@ def users(self) -> "UserResourceProtocol":
110111
def secrets(self) -> "SecretResourceProtocol":
111112
"""Access secrets."""
112113
...
114+
115+
def workspaces(self) -> "WorkspaceResourceProtocol":
116+
"""Access workspaces."""
117+
...
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"""Workspace API module."""
2+
3+
from .models import Workspace, WorkspaceList
4+
from .protocols import WorkspaceResourceProtocol
5+
from .resource import WorkspaceResource
6+
7+
__all__ = ["Workspace", "WorkspaceList", "WorkspaceResourceProtocol", "WorkspaceResource"]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Models for workspace API responses."""
2+
3+
from typing import Any
4+
from uuid import UUID
5+
6+
from pydantic import BaseModel
7+
8+
9+
class Workspace(BaseModel):
10+
"""Model representing a workspace on the deepset platform."""
11+
12+
name: str
13+
workspace_id: UUID
14+
languages: dict[str, Any]
15+
default_idle_timeout_in_seconds: int
16+
17+
18+
class WorkspaceList(BaseModel):
19+
"""Model representing a list of workspaces."""
20+
21+
data: list[Workspace]
22+
has_more: bool = False
23+
total: int
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""Protocols for workspace resources."""
2+
3+
from typing import Protocol
4+
5+
from deepset_mcp.api.shared_models import NoContentResponse
6+
from deepset_mcp.api.workspace.models import Workspace, WorkspaceList
7+
8+
9+
class WorkspaceResourceProtocol(Protocol):
10+
"""Protocol defining the interface for workspace resources."""
11+
12+
async def list(self) -> WorkspaceList:
13+
"""List all workspaces.
14+
15+
:returns: A WorkspaceList containing all workspaces.
16+
"""
17+
...
18+
19+
async def get(self, workspace_name: str) -> Workspace:
20+
"""Get a specific workspace by name.
21+
22+
:param workspace_name: Name of the workspace to fetch.
23+
:returns: A Workspace instance.
24+
"""
25+
...
26+
27+
async def create(self, name: str) -> NoContentResponse:
28+
"""Create a new workspace.
29+
30+
:param name: Name of the new workspace.
31+
:returns: NoContentResponse indicating successful creation.
32+
"""
33+
...
34+
35+
async def delete(self, workspace_name: str) -> NoContentResponse:
36+
"""Delete a workspace.
37+
38+
:param workspace_name: Name of the workspace to delete.
39+
:returns: NoContentResponse indicating successful deletion.
40+
"""
41+
...
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""Resource implementation for workspace API."""
2+
3+
import logging
4+
from typing import TYPE_CHECKING
5+
6+
from deepset_mcp.api.shared_models import NoContentResponse
7+
from deepset_mcp.api.transport import raise_for_status
8+
from deepset_mcp.api.workspace.models import Workspace, WorkspaceList
9+
from deepset_mcp.api.workspace.protocols import WorkspaceResourceProtocol
10+
11+
logger = logging.getLogger(__name__)
12+
13+
if TYPE_CHECKING:
14+
from deepset_mcp.api.protocols import AsyncClientProtocol
15+
16+
17+
class WorkspaceResource(WorkspaceResourceProtocol):
18+
"""Manages interactions with the deepset workspace API."""
19+
20+
def __init__(self, client: "AsyncClientProtocol") -> None:
21+
"""Initialize a WorkspaceResource instance.
22+
23+
:param client: The async client protocol instance.
24+
"""
25+
self._client = client
26+
27+
async def list(self) -> WorkspaceList:
28+
"""List all workspaces.
29+
30+
:returns: A WorkspaceList containing all workspaces.
31+
"""
32+
resp = await self._client.request(
33+
endpoint="v1/workspaces",
34+
method="GET",
35+
)
36+
37+
raise_for_status(resp)
38+
39+
if resp.json is not None and isinstance(resp.json, list):
40+
workspaces = [Workspace.model_validate(item) for item in resp.json]
41+
return WorkspaceList(
42+
data=workspaces,
43+
has_more=False,
44+
total=len(workspaces),
45+
)
46+
else:
47+
return WorkspaceList(data=[], has_more=False, total=0)
48+
49+
async def get(self, workspace_name: str) -> Workspace:
50+
"""Get a specific workspace by name.
51+
52+
:param workspace_name: Name of the workspace to fetch.
53+
:returns: A Workspace instance.
54+
"""
55+
resp = await self._client.request(
56+
endpoint=f"v1/workspaces/{workspace_name}",
57+
method="GET",
58+
)
59+
60+
raise_for_status(resp)
61+
62+
return Workspace.model_validate(resp.json)
63+
64+
async def create(self, name: str) -> NoContentResponse:
65+
"""Create a new workspace.
66+
67+
:param name: Name of the new workspace.
68+
:returns: NoContentResponse indicating successful creation.
69+
"""
70+
data = {"name": name}
71+
resp = await self._client.request(
72+
endpoint="v1/workspaces",
73+
method="POST",
74+
data=data,
75+
)
76+
77+
raise_for_status(resp)
78+
79+
return NoContentResponse(message="Workspace created successfully.")
80+
81+
async def delete(self, workspace_name: str) -> NoContentResponse:
82+
"""Delete a workspace.
83+
84+
:param workspace_name: Name of the workspace to delete.
85+
:returns: NoContentResponse indicating successful deletion.
86+
"""
87+
resp = await self._client.request(
88+
endpoint=f"v1/workspaces/{workspace_name}",
89+
method="DELETE",
90+
)
91+
92+
raise_for_status(resp)
93+
94+
return NoContentResponse(message="Workspace deleted successfully.")
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Integration tests for WorkspaceResource."""
2+
3+
import uuid
4+
5+
import pytest
6+
7+
from deepset_mcp.api.client import AsyncDeepsetClient
8+
from deepset_mcp.api.exceptions import ResourceNotFoundError
9+
from deepset_mcp.api.workspace.models import Workspace, WorkspaceList
10+
11+
pytestmark = pytest.mark.integration
12+
13+
14+
class TestWorkspaceResourceIntegration:
15+
"""Integration tests for WorkspaceResource."""
16+
17+
@pytest.mark.asyncio
18+
async def test_list_workspaces(self) -> None:
19+
"""Test listing workspaces."""
20+
async with AsyncDeepsetClient() as client:
21+
workspaces = await client.workspaces().list()
22+
assert isinstance(workspaces, WorkspaceList)
23+
assert isinstance(workspaces.data, list)
24+
assert workspaces.total >= 0
25+
26+
# If we have workspaces, verify their structure
27+
if workspaces.data:
28+
workspace = workspaces.data[0]
29+
assert isinstance(workspace, Workspace)
30+
assert isinstance(workspace.name, str)
31+
assert isinstance(workspace.workspace_id, uuid.UUID)
32+
assert isinstance(workspace.languages, dict)
33+
assert isinstance(workspace.default_idle_timeout_in_seconds, int)
34+
35+
@pytest.mark.asyncio
36+
async def test_get_workspace_not_found(self) -> None:
37+
"""Test getting a non-existent workspace."""
38+
async with AsyncDeepsetClient() as client:
39+
with pytest.raises(ResourceNotFoundError):
40+
await client.workspaces().get("definitely-does-not-exist-workspace")
41+
42+
@pytest.mark.asyncio
43+
async def test_create_get_and_delete_workspace(self) -> None:
44+
"""Tests creating, getting and deleting a workspace."""
45+
workspace_name = f"test-workspace-{uuid.uuid4()}"
46+
async with AsyncDeepsetClient() as client:
47+
# Create a new workspace
48+
create_response = await client.workspaces().create(workspace_name)
49+
assert create_response.success is True
50+
assert create_response.message == "Workspace created successfully."
51+
52+
# Get the workspace
53+
workspace = await client.workspaces().get(workspace_name)
54+
assert isinstance(workspace, Workspace)
55+
assert workspace.name == workspace_name
56+
57+
# Delete the workspace
58+
delete_response = await client.workspaces().delete(workspace_name)
59+
assert delete_response.success is True
60+
assert delete_response.message == "Workspace deleted successfully."
61+
62+
# Verify the workspace is deleted
63+
with pytest.raises(ResourceNotFoundError):
64+
await client.workspaces().get(workspace_name)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Unit tests for workspace API."""

0 commit comments

Comments
 (0)