Skip to content

Commit 35e02f3

Browse files
authored
feat: add pipeline template resource (#25)
* feat: add pipeline template package * feat: add pipeline template models * feat: add pipeline template resource implementation * feat: import PipelineTemplate model in protocols * feat: add pipeline_templates method to AsyncClientProtocol * feat: add PipelineTemplateResourceProtocol * feat: import PipelineTemplateResource in client * feat: add pipeline_templates method to AsyncDeepsetClient * test: add pipeline template tests package * test: add pipeline template resource tests * test: create conftest with fake client implementation * feat: add pipeline template tests directory * refactor: remove duplicate test directory * feat: add base test classes and helpers for pipeline template tests * feat: add pipeline template resource method tests * fix: minor issues; add integration tests
1 parent abbf326 commit 35e02f3

9 files changed

Lines changed: 406 additions & 1 deletion

File tree

src/deepset_mcp/api/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from deepset_mcp.api.haystack_service.resource import HaystackServiceResource
66
from deepset_mcp.api.pipeline.resource import PipelineResource
7+
from deepset_mcp.api.pipeline_template.resource import PipelineTemplateResource
78
from deepset_mcp.api.protocols import AsyncClientProtocol
89
from deepset_mcp.api.transport import AsyncTransport, TransportProtocol, TransportResponse
910

@@ -98,3 +99,7 @@ def pipelines(self, workspace: str) -> PipelineResource:
9899
def haystack_service(self) -> HaystackServiceResource:
99100
"""Resource to interact with the Haystack service API."""
100101
return HaystackServiceResource(client=self)
102+
103+
def pipeline_templates(self, workspace: str) -> PipelineTemplateResource:
104+
"""Resource to interact with pipeline templates in the specified workspace."""
105+
return PipelineTemplateResource(client=self, workspace=workspace)

src/deepset_mcp/api/pipeline_template/__init__.py

Whitespace-only changes.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from uuid import UUID
2+
3+
from pydantic import BaseModel, Field
4+
5+
6+
class PipelineTemplateTag(BaseModel):
7+
"""Model representing a tag on a pipeline template."""
8+
9+
name: str
10+
tag_id: UUID
11+
12+
13+
class PipelineTemplate(BaseModel):
14+
"""Model representing a pipeline template."""
15+
16+
author: str
17+
best_for: list[str]
18+
description: str
19+
template_name: str = Field(alias="pipeline_name")
20+
pipeline_template_id: UUID = Field(alias="pipeline_template_id")
21+
potential_applications: list[str] = Field(alias="potential_applications")
22+
yaml_config: str | None = Field(None, alias="query_yaml")
23+
tags: list[PipelineTemplateTag]
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from typing import Any
2+
3+
from deepset_mcp.api.exceptions import UnexpectedAPIError
4+
from deepset_mcp.api.pipeline_template.models import PipelineTemplate
5+
from deepset_mcp.api.protocols import AsyncClientProtocol
6+
from deepset_mcp.api.transport import raise_for_status
7+
8+
9+
class PipelineTemplateResource:
10+
"""Resource for interacting with pipeline templates in a workspace."""
11+
12+
def __init__(self, client: AsyncClientProtocol, workspace: str) -> None:
13+
"""Initialize the pipeline template resource.
14+
15+
Parameters
16+
----------
17+
client : AsyncClientProtocol
18+
Client to use for making API requests
19+
workspace : str
20+
Workspace to operate in
21+
"""
22+
self._client = client
23+
self._workspace = workspace
24+
25+
async def get_template(self, template_name: str) -> PipelineTemplate:
26+
"""Fetch a single pipeline template by its name.
27+
28+
Parameters
29+
----------
30+
template_name : str
31+
Name of the template to fetch
32+
33+
Returns
34+
-------
35+
PipelineTemplate
36+
The requested pipeline template
37+
"""
38+
response = await self._client.request(f"/v1/workspaces/{self._workspace}/pipeline_templates/{template_name}")
39+
raise_for_status(response)
40+
data = response.json
41+
42+
return PipelineTemplate.model_validate(data)
43+
44+
async def list_templates(self, limit: int = 100) -> list[PipelineTemplate]:
45+
"""List pipeline templates in the configured workspace.
46+
47+
Parameters
48+
----------
49+
limit : int, optional (default=100)
50+
Maximum number of templates to return
51+
52+
Returns
53+
-------
54+
list[PipelineTemplate]
55+
List of pipeline templates
56+
"""
57+
response = await self._client.request(
58+
f"/v1/workspaces/{self._workspace}/pipeline_templates?limit={limit}&page_number=1&field=created_at&order=DESC",
59+
method="GET",
60+
)
61+
62+
raise_for_status(response)
63+
64+
if response.json is None:
65+
raise UnexpectedAPIError(message="Unexpected API response, no templates returned.")
66+
67+
response_data: dict[str, Any] = response.json
68+
69+
return [PipelineTemplate.model_validate(template) for template in response_data["data"]]

src/deepset_mcp/api/protocols.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Any, Protocol, Self
33

44
from deepset_mcp.api.pipeline.models import DeepsetPipeline, NoContentResponse, PipelineValidationResult
5+
from deepset_mcp.api.pipeline_template.models import PipelineTemplate
56
from deepset_mcp.api.transport import TransportResponse
67

78

@@ -48,6 +49,22 @@ def haystack_service(self) -> "HaystackServiceProtocol":
4849
"""Access the Haystack service."""
4950
...
5051

52+
def pipeline_templates(self, workspace: str) -> "PipelineTemplateResourceProtocol":
53+
"""Access pipeline templates in the specified workspace."""
54+
...
55+
56+
57+
class PipelineTemplateResourceProtocol(Protocol):
58+
"""Protocol defining the implementation for PipelineTemplateResource."""
59+
60+
async def get_template(self, template_name: str) -> PipelineTemplate:
61+
"""Fetch a single pipeline template by its name."""
62+
...
63+
64+
async def list_templates(self, limit: int = 100) -> list[PipelineTemplate]:
65+
"""List pipeline templates in the configured workspace."""
66+
...
67+
5168

5269
class PipelineResourceProtocol(Protocol):
5370
"""Protocol defining the implementation for PipelineResource."""
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import pytest
2+
3+
from deepset_mcp.api.client import AsyncDeepsetClient
4+
from deepset_mcp.api.exceptions import ResourceNotFoundError
5+
from deepset_mcp.api.pipeline_template.models import PipelineTemplate
6+
from deepset_mcp.api.pipeline_template.resource import PipelineTemplateResource
7+
8+
pytestmark = pytest.mark.integration
9+
10+
11+
@pytest.fixture
12+
async def template_resource(
13+
client: AsyncDeepsetClient,
14+
test_workspace: str,
15+
) -> PipelineTemplateResource:
16+
"""Create a PipelineTemplateResource instance for testing."""
17+
return PipelineTemplateResource(client=client, workspace=test_workspace)
18+
19+
20+
@pytest.mark.asyncio
21+
async def test_get_template(
22+
template_resource: PipelineTemplateResource,
23+
) -> None:
24+
"""Test getting a single pipeline template by name.
25+
26+
First lists all templates, then gets the first one by name.
27+
"""
28+
# Get all templates to find an existing one
29+
templates = await template_resource.list_templates()
30+
31+
# Skip if no templates are available
32+
if not templates:
33+
pytest.skip("No templates available in the test environment")
34+
35+
# Get the first template's name
36+
template_name = templates[0].template_name
37+
38+
# Now get that specific template
39+
template = await template_resource.get_template(template_name=template_name)
40+
41+
# Verify the template was retrieved correctly
42+
assert template.template_name == template_name
43+
assert template.pipeline_template_id is not None
44+
assert isinstance(template.best_for, list)
45+
assert isinstance(template.potential_applications, list)
46+
assert isinstance(template.tags, list)
47+
48+
49+
@pytest.mark.asyncio
50+
async def test_get_nonexistent_template(
51+
template_resource: PipelineTemplateResource,
52+
) -> None:
53+
"""Test error handling when getting a non-existent template."""
54+
non_existent_name = "non-existent-template-xyz-123"
55+
56+
# Trying to get a non-existent template should raise an exception
57+
with pytest.raises(ResourceNotFoundError):
58+
await template_resource.get_template(template_name=non_existent_name)
59+
60+
61+
@pytest.mark.asyncio
62+
async def test_list_templates(
63+
template_resource: PipelineTemplateResource,
64+
) -> None:
65+
"""Test listing templates."""
66+
# Test listing templates with default limit
67+
templates = await template_resource.list_templates()
68+
69+
# Verify that the templates are returned as a list
70+
assert isinstance(templates, list)
71+
72+
# Skip further checks if no templates are available
73+
if not templates:
74+
pytest.skip("No templates available in the test environment")
75+
76+
# Verify the first template has the expected structure
77+
template = templates[0]
78+
assert isinstance(template, PipelineTemplate)
79+
assert template.template_name is not None
80+
assert template.author is not None
81+
assert template.description is not None
82+
assert template.pipeline_template_id is not None
83+
84+
85+
@pytest.mark.asyncio
86+
async def test_list_templates_with_limit(
87+
template_resource: PipelineTemplateResource,
88+
) -> None:
89+
"""Test listing templates with a specific limit."""
90+
# Test with a small limit
91+
limit = 1
92+
templates = await template_resource.list_templates(limit=limit)
93+
94+
# Verify that the number of templates is not more than the limit
95+
assert len(templates) <= limit

test/unit/api/pipeline_template/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)