Skip to content

Commit 7a94f45

Browse files
faymarieMarie-Luise Klaus
andauthored
fix: do not validate name if overwrite set to true (#269)
* do not validate name if overwrite set to true * allow import of PipelineOutputType * remove name from integration test --------- Co-authored-by: Marie-Luise Klaus <marieluiseklauscode@gmail.com>
1 parent d370b98 commit 7a94f45

6 files changed

Lines changed: 182 additions & 11 deletions

File tree

deepset_cloud_sdk/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
PipelineConfig,
1515
PipelineInputs,
1616
PipelineOutputs,
17+
PipelineOutputType,
1718
)
1819
from deepset_cloud_sdk.workflows.pipeline_client.pipeline_service import (
1920
DeepsetValidationError,
@@ -37,4 +38,5 @@
3738
"IndexInputs",
3839
"IndexOutputs",
3940
"DeepsetValidationError",
41+
"PipelineOutputType",
4042
]

deepset_cloud_sdk/workflows/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
PipelineConfig,
99
PipelineInputs,
1010
PipelineOutputs,
11+
PipelineOutputType,
1112
)
1213
from deepset_cloud_sdk.workflows.pipeline_client.pipeline_client import PipelineClient
1314
from deepset_cloud_sdk.workflows.pipeline_client.pipeline_service import (
@@ -26,4 +27,5 @@
2627
"PipelineConfig",
2728
"PipelineClient",
2829
"DeepsetValidationError",
30+
"PipelineOutputType",
2931
]

deepset_cloud_sdk/workflows/pipeline_client/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
PipelineConfig,
99
PipelineInputs,
1010
PipelineOutputs,
11+
PipelineOutputType,
1112
)
1213
from deepset_cloud_sdk.workflows.pipeline_client.pipeline_client import PipelineClient
1314
from deepset_cloud_sdk.workflows.pipeline_client.pipeline_service import (
@@ -26,4 +27,5 @@
2627
"PipelineOutputs",
2728
"IndexConfig",
2829
"PipelineConfig",
30+
"PipelineOutputType",
2931
]

deepset_cloud_sdk/workflows/pipeline_client/pipeline_service.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,9 @@ async def _validate_pipeline_yaml(self, config: IndexConfig | PipelineConfig, pi
148148
"""
149149
try:
150150
if isinstance(config, IndexConfig):
151-
await self._validate_index(config.name, pipeline_yaml)
151+
await self._validate_index(config.name, pipeline_yaml, config)
152152
return
153-
await self._validate_pipeline(config.name, pipeline_yaml)
153+
await self._validate_pipeline(config.name, pipeline_yaml, config)
154154
except DeepsetValidationError as err:
155155
if config.strict_validation:
156156
# Re-raise the error to fail the import
@@ -161,37 +161,51 @@ async def _validate_pipeline_yaml(self, config: IndexConfig | PipelineConfig, pi
161161
for error_detail in err.errors:
162162
logger.warning(f"Validation error [{error_detail.code}]: {error_detail.message}")
163163

164-
async def _validate_index(self, name: str, indexing_yaml: str) -> None:
164+
async def _validate_index(self, name: str, indexing_yaml: str, config: IndexConfig) -> None:
165165
"""Validate an index configuration.
166166
167167
:param name: Name of the index.
168168
:param indexing_yaml: YAML configuration for the index.
169+
:param config: Index configuration containing overwrite flag.
169170
:raises DeepsetValidationError: If validation fails.
170171
"""
171172
logger.debug(f"Validating index {name}.")
173+
174+
# exclude name if overwrite is True, else we get an error that the name is already in use
175+
json_payload = {"indexing_yaml": indexing_yaml}
176+
if not config.overwrite:
177+
json_payload["name"] = name
178+
172179
response = await self._api.post(
173180
workspace_name=self._workspace_name,
174181
endpoint="pipeline_validations",
175-
json={"name": name, "indexing_yaml": indexing_yaml},
182+
json=json_payload,
176183
)
177184

178185
if response.status_code != HTTPStatus.NO_CONTENT:
179186
self._handle_validation_error(response)
180187

181188
logger.debug(f"Index validation successful for {name}.")
182189

183-
async def _validate_pipeline(self, name: str, query_yaml: str) -> None:
190+
async def _validate_pipeline(self, name: str, query_yaml: str, config: PipelineConfig) -> None:
184191
"""Validate a pipeline configuration.
185192
186193
:param name: Name of the pipeline.
187194
:param query_yaml: YAML configuration for the pipeline.
195+
:param config: Pipeline configuration containing overwrite flag.
188196
:raises DeepsetValidationError: If validation fails.
189197
"""
190198
logger.debug(f"Validating pipeline {name}.")
199+
200+
# exclude name if overwrite is True, else we get an error that the name is already in use
201+
json_payload = {"query_yaml": query_yaml}
202+
if not config.overwrite:
203+
json_payload["name"] = name
204+
191205
response = await self._api.post(
192206
workspace_name=self._workspace_name,
193207
endpoint="pipeline_validations",
194-
json={"name": name, "query_yaml": query_yaml},
208+
json=json_payload,
195209
)
196210

197211
if response.status_code != HTTPStatus.NO_CONTENT:

tests/integration/workflows/test_integration_pipeline_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ def test_import_index_with_overwrite_fallback_to_create(
312312
validation_request = validation_route.calls[0].request
313313
assert validation_request.headers["Authorization"] == "Bearer test-api-key"
314314
validation_body = json.loads(validation_request.content)
315-
assert validation_body["name"] == "test-index-fallback"
315+
assert "indexing_yaml" in validation_body
316316

317317
# Check PATCH attempt
318318
overwrite_request = overwrite_route.calls[0].request
@@ -505,7 +505,7 @@ async def test_import_pipeline_with_overwrite_fallback_to_create_async(
505505
validation_request = validation_route.calls[0].request
506506
assert validation_request.headers["Authorization"] == "Bearer test-api-key"
507507
validation_body = json.loads(validation_request.content)
508-
assert validation_body["name"] == "test-pipeline-fallback"
508+
assert "query_yaml" in validation_body
509509

510510
# Check PUT attempt
511511
overwrite_request = overwrite_route.calls[0].request

tests/unit/workflows/pipeline_client/test_pipeline_service.py

Lines changed: 154 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,8 @@ async def test_import_index_with_overwrite_fallback_to_create(
568568
validation_call = mock_api.post.call_args_list[0]
569569
assert validation_call.kwargs["endpoint"] == "pipeline_validations"
570570
assert "indexing_yaml" in validation_call.kwargs["json"]
571-
assert validation_call.kwargs["json"]["name"] == "test_index_fallback"
571+
# When overwrite=True, name should be excluded from validation payload
572+
assert "name" not in validation_call.kwargs["json"]
572573

573574
# Check PATCH attempt
574575
patch_call = mock_api.patch.call_args_list[0]
@@ -609,7 +610,8 @@ async def test_import_pipeline_with_overwrite_true(
609610
validation_call = mock_api.post.call_args_list[0]
610611
assert validation_call.kwargs["endpoint"] == "pipeline_validations"
611612
assert "query_yaml" in validation_call.kwargs["json"]
612-
assert validation_call.kwargs["json"]["name"] == "test_pipeline_overwrite"
613+
# When overwrite=True, name should be excluded from validation payload
614+
assert "name" not in validation_call.kwargs["json"]
613615

614616
# Check overwrite call
615617
overwrite_call = mock_api.put.call_args_list[0]
@@ -655,7 +657,8 @@ async def test_import_pipeline_with_overwrite_fallback_to_create(
655657
validation_call = mock_api.post.call_args_list[0]
656658
assert validation_call.kwargs["endpoint"] == "pipeline_validations"
657659
assert "query_yaml" in validation_call.kwargs["json"]
658-
assert validation_call.kwargs["json"]["name"] == "test_pipeline_fallback"
660+
# When overwrite=True, name should be excluded from validation payload
661+
assert "name" not in validation_call.kwargs["json"]
659662

660663
# Check PUT attempt
661664
put_call = mock_api.put.call_args_list[0]
@@ -1339,3 +1342,151 @@ async def test_multiple_validation_errors_logged_individually(
13391342
)
13401343
assert error_log is not None, f"Expected to find log for error code {error_code}"
13411344
assert error_message in error_log.get("event", ""), f"Expected error message '{error_message}' in log"
1345+
1346+
@pytest.mark.asyncio
1347+
async def test_validate_index_excludes_name_when_overwrite_true(
1348+
self,
1349+
pipeline_service: PipelineService,
1350+
test_pipeline: Pipeline,
1351+
mock_api: AsyncMock,
1352+
) -> None:
1353+
"""Test that index validation excludes name from JSON payload when overwrite=True."""
1354+
# Mock successful validation response
1355+
validation_response = Mock(spec=Response)
1356+
validation_response.status_code = HTTPStatus.NO_CONTENT.value
1357+
1358+
# Mock successful import response
1359+
import_response = Mock(spec=Response)
1360+
import_response.status_code = HTTPStatus.OK.value
1361+
1362+
mock_api.post.side_effect = [validation_response, import_response]
1363+
mock_api.patch.return_value = import_response
1364+
1365+
config = IndexConfig(
1366+
name="test_index",
1367+
inputs=IndexInputs(files=["file_type_router.sources"]),
1368+
strict_validation=False,
1369+
overwrite=True,
1370+
)
1371+
1372+
await pipeline_service.import_async(test_pipeline, config)
1373+
1374+
# Check validation call
1375+
validation_call = mock_api.post.call_args_list[0]
1376+
assert validation_call.kwargs["endpoint"] == "pipeline_validations"
1377+
1378+
# When overwrite=True, name should be excluded from validation payload
1379+
validation_json = validation_call.kwargs["json"]
1380+
assert "name" not in validation_json
1381+
assert "indexing_yaml" in validation_json
1382+
1383+
@pytest.mark.asyncio
1384+
async def test_validate_index_includes_name_when_overwrite_false(
1385+
self,
1386+
pipeline_service: PipelineService,
1387+
test_pipeline: Pipeline,
1388+
mock_api: AsyncMock,
1389+
) -> None:
1390+
"""Test that index validation includes name in JSON payload when overwrite=False."""
1391+
# Mock successful validation response
1392+
validation_response = Mock(spec=Response)
1393+
validation_response.status_code = HTTPStatus.NO_CONTENT.value
1394+
1395+
# Mock successful import response
1396+
import_response = Mock(spec=Response)
1397+
import_response.status_code = HTTPStatus.OK.value
1398+
1399+
mock_api.post.side_effect = [validation_response, import_response]
1400+
1401+
config = IndexConfig(
1402+
name="test_index",
1403+
inputs=IndexInputs(files=["file_type_router.sources"]),
1404+
strict_validation=False,
1405+
overwrite=False,
1406+
)
1407+
1408+
await pipeline_service.import_async(test_pipeline, config)
1409+
1410+
# Check validation call
1411+
validation_call = mock_api.post.call_args_list[0]
1412+
assert validation_call.kwargs["endpoint"] == "pipeline_validations"
1413+
1414+
# When overwrite=False, name should be included in validation payload
1415+
validation_json = validation_call.kwargs["json"]
1416+
assert validation_json["name"] == "test_index"
1417+
assert "indexing_yaml" in validation_json
1418+
1419+
@pytest.mark.asyncio
1420+
async def test_validate_pipeline_excludes_name_when_overwrite_true(
1421+
self,
1422+
pipeline_service: PipelineService,
1423+
test_pipeline: Pipeline,
1424+
mock_api: AsyncMock,
1425+
) -> None:
1426+
"""Test that pipeline validation excludes name from JSON payload when overwrite=True."""
1427+
# Mock successful validation response
1428+
validation_response = Mock(spec=Response)
1429+
validation_response.status_code = HTTPStatus.NO_CONTENT.value
1430+
1431+
# Mock successful import response
1432+
import_response = Mock(spec=Response)
1433+
import_response.status_code = HTTPStatus.OK.value
1434+
1435+
mock_api.post.side_effect = [validation_response, import_response]
1436+
mock_api.put.return_value = import_response
1437+
1438+
config = PipelineConfig(
1439+
name="test_pipeline",
1440+
inputs=PipelineInputs(query=["prompt_builder.query"]),
1441+
outputs=PipelineOutputs(answers="prompt_builder.prompt"),
1442+
strict_validation=False,
1443+
overwrite=True,
1444+
)
1445+
1446+
await pipeline_service.import_async(test_pipeline, config)
1447+
1448+
# Check validation call
1449+
validation_call = mock_api.post.call_args_list[0]
1450+
assert validation_call.kwargs["endpoint"] == "pipeline_validations"
1451+
1452+
# When overwrite=True, name should be excluded from validation payload
1453+
validation_json = validation_call.kwargs["json"]
1454+
assert "name" not in validation_json
1455+
assert "query_yaml" in validation_json
1456+
1457+
@pytest.mark.asyncio
1458+
async def test_validate_pipeline_includes_name_when_overwrite_false(
1459+
self,
1460+
pipeline_service: PipelineService,
1461+
test_pipeline: Pipeline,
1462+
mock_api: AsyncMock,
1463+
) -> None:
1464+
"""Test that pipeline validation includes name in JSON payload when overwrite=False."""
1465+
# Mock successful validation response
1466+
validation_response = Mock(spec=Response)
1467+
validation_response.status_code = HTTPStatus.NO_CONTENT.value
1468+
1469+
# Mock successful import response
1470+
import_response = Mock(spec=Response)
1471+
import_response.status_code = HTTPStatus.OK.value
1472+
1473+
mock_api.post.side_effect = [validation_response, import_response]
1474+
1475+
config = PipelineConfig(
1476+
name="test_pipeline",
1477+
inputs=PipelineInputs(query=["prompt_builder.query"]),
1478+
outputs=PipelineOutputs(answers="prompt_builder.prompt"),
1479+
strict_validation=False,
1480+
overwrite=False,
1481+
)
1482+
1483+
await pipeline_service.import_async(test_pipeline, config)
1484+
1485+
# Check validation call
1486+
validation_call = mock_api.post.call_args_list[0]
1487+
assert validation_call.kwargs["endpoint"] == "pipeline_validations"
1488+
1489+
# When overwrite=False, name should be included in validation payload
1490+
validation_json = validation_call.kwargs["json"]
1491+
assert validation_json["name"] == "test_pipeline"
1492+
assert "query_yaml" in validation_json

0 commit comments

Comments
 (0)