From 7b6d05a10a2e57148656999d850f6f86a8eefa58 Mon Sep 17 00:00:00 2001 From: Alessandro Pomponio Date: Tue, 28 Jul 2026 09:51:54 +0100 Subject: [PATCH] fix(core): enforce correct kind on core resource models Signed-off-by: Alessandro Pomponio --- ado/core/actuatorconfiguration/resource.py | 6 ++++-- ado/core/datacontainer/resource.py | 4 ++-- ado/core/discoveryspace/resource.py | 6 ++++-- ado/core/operation/resource.py | 6 ++++-- ado/core/resources.py | 3 +-- ado/core/samplestore/resource.py | 6 ++++-- tests/core/test_actuatorconfigs.py | 11 +++++++++++ tests/core/test_datacontainer.py | 14 ++++++++++++++ tests/core/test_operation.py | 10 ++++++++++ tests/core/test_space.py | 11 +++++++++++ 10 files changed, 65 insertions(+), 12 deletions(-) diff --git a/ado/core/actuatorconfiguration/resource.py b/ado/core/actuatorconfiguration/resource.py index e1001660e..9d31d7345 100644 --- a/ado/core/actuatorconfiguration/resource.py +++ b/ado/core/actuatorconfiguration/resource.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: MIT import uuid -from typing import Annotated, Any +from typing import Annotated, Any, Literal import pydantic @@ -33,7 +33,9 @@ def _identifier_from_data(data: dict[str, Any]) -> str: return f"{data['kind'].value}-{data['config'].actuatorIdentifier}-{str(uuid.uuid4())[:8]}" version: str = "v1" - kind: CoreResourceKinds = CoreResourceKinds.ACTUATORCONFIGURATION + kind: Annotated[ + Literal[CoreResourceKinds.ACTUATORCONFIGURATION], pydantic.Field() + ] = CoreResourceKinds.ACTUATORCONFIGURATION config: ActuatorConfiguration identifier: Annotated[ Defaultable[str], diff --git a/ado/core/datacontainer/resource.py b/ado/core/datacontainer/resource.py index 8073c1c63..37504c1dd 100644 --- a/ado/core/datacontainer/resource.py +++ b/ado/core/datacontainer/resource.py @@ -3,7 +3,7 @@ import typing import uuid -from typing import Annotated +from typing import Annotated, Literal import pydantic @@ -188,7 +188,7 @@ def _identifier_from_data(data: dict[str, typing.Any]) -> str: return f"{data['kind'].value}-{str(uuid.uuid4())[:8]}" version: Annotated[str, pydantic.Field()] = "v1" - kind: Annotated[CoreResourceKinds, pydantic.Field()] = ( + kind: Annotated[Literal[CoreResourceKinds.DATACONTAINER], pydantic.Field()] = ( CoreResourceKinds.DATACONTAINER ) config: Annotated[DataContainer, pydantic.Field(description="A collection of data")] diff --git a/ado/core/discoveryspace/resource.py b/ado/core/discoveryspace/resource.py index 5f0c346f8..90c862c80 100644 --- a/ado/core/discoveryspace/resource.py +++ b/ado/core/discoveryspace/resource.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: MIT import typing import uuid -from typing import Annotated +from typing import Annotated, Literal import pydantic import rich.box @@ -44,7 +44,9 @@ class DiscoverySpaceProvenanceInfo(ProvenanceInfo): class DiscoverySpaceResource(ADOResource): version: str = "v2" - kind: CoreResourceKinds = CoreResourceKinds.DISCOVERYSPACE + kind: Annotated[Literal[CoreResourceKinds.DISCOVERYSPACE], pydantic.Field()] = ( + CoreResourceKinds.DISCOVERYSPACE + ) config: DiscoverySpaceConfiguration identifier: Annotated[ diff --git a/ado/core/operation/resource.py b/ado/core/operation/resource.py index dfb6a3893..ffbebd076 100644 --- a/ado/core/operation/resource.py +++ b/ado/core/operation/resource.py @@ -4,7 +4,7 @@ import enum import typing import uuid -from typing import Annotated +from typing import Annotated, Literal import pydantic @@ -81,7 +81,9 @@ class OperationProvenanceInfo(ProvenanceInfo): class OperationResource(ADOResource): version: Annotated[str, pydantic.Field()] = "v1" - kind: Annotated[CoreResourceKinds, pydantic.Field()] = CoreResourceKinds.OPERATION + kind: Annotated[Literal[CoreResourceKinds.OPERATION], pydantic.Field()] = ( + CoreResourceKinds.OPERATION + ) operationType: Annotated[ DiscoveryOperationEnum, pydantic.Field(description="The type of this operation") ] diff --git a/ado/core/resources.py b/ado/core/resources.py index 2f2117ebc..bed9fafbe 100644 --- a/ado/core/resources.py +++ b/ado/core/resources.py @@ -16,10 +16,9 @@ from ado.utilities.pydantic import Defaultable -class CoreResourceKinds(enum.Enum): +class CoreResourceKinds(str, enum.Enum): OPERATION = "operation" DISCOVERYSPACE = "discoveryspace" - # ACTUATOR = "actuator" AP - REMOVING IT AS REPLACED BY ACTUATORCONFIGURATION ACTUATORCONFIGURATION = "actuatorconfiguration" SAMPLESTORE = "samplestore" DATACONTAINER = "datacontainer" diff --git a/ado/core/samplestore/resource.py b/ado/core/samplestore/resource.py index 0c599a9fd..dc0b1f58f 100644 --- a/ado/core/samplestore/resource.py +++ b/ado/core/samplestore/resource.py @@ -1,6 +1,6 @@ # Copyright IBM Corporation 2025, 2026 # SPDX-License-Identifier: MIT -from typing import Annotated +from typing import Annotated, Literal import pydantic @@ -32,7 +32,9 @@ def _generate_sample_store_identifier() -> str: return identifier version: str = "v2" - kind: CoreResourceKinds = CoreResourceKinds.SAMPLESTORE + kind: Annotated[Literal[CoreResourceKinds.SAMPLESTORE], pydantic.Field()] = ( + CoreResourceKinds.SAMPLESTORE + ) config: SampleStoreConfiguration identifier: Annotated[ Defaultable[str], diff --git a/tests/core/test_actuatorconfigs.py b/tests/core/test_actuatorconfigs.py index 530054caf..b9ec30e97 100644 --- a/tests/core/test_actuatorconfigs.py +++ b/tests/core/test_actuatorconfigs.py @@ -4,6 +4,7 @@ import pathlib import re +import pydantic import pytest import yaml @@ -176,3 +177,13 @@ def test_ml_multi_cloud_operation_base_get( operation_configuration.get_actuatorconfigurations( project_context=valid_ado_project_context ) + + +def test_actuator_configuration_resource_wrong_kind_raises_validation_error( + ml_multi_cloud_correct_actuatorconfiguration: ActuatorConfigurationResource, +) -> None: + """ActuatorConfigurationResource rejects a kind value other than ACTUATORCONFIGURATION.""" + data = ml_multi_cloud_correct_actuatorconfiguration.model_dump() + data["kind"] = CoreResourceKinds.OPERATION + with pytest.raises(pydantic.ValidationError): + ActuatorConfigurationResource.model_validate(data) diff --git a/tests/core/test_datacontainer.py b/tests/core/test_datacontainer.py index 913fb5c52..7c0c5b97c 100644 --- a/tests/core/test_datacontainer.py +++ b/tests/core/test_datacontainer.py @@ -1,10 +1,14 @@ # Copyright IBM Corporation 2025, 2026 # SPDX-License-Identifier: MIT +import pydantic +import pytest + import ado.core import ado.utilities.location from ado.core import DataContainerResource from ado.core.datacontainer.resource import DataContainer, TabularData +from ado.core.resources import CoreResourceKinds from ado.utilities.location import SQLStoreConfiguration @@ -73,3 +77,13 @@ def test_datacontainer_rich_print( assert hasattr(data_container_resource, "__rich__") Console().print(data_container_resource) + + +def test_datacontainer_resource_wrong_kind_raises_validation_error( + data_container_resource: DataContainerResource, +) -> None: + """DataContainerResource rejects a kind value other than DATACONTAINER.""" + data = data_container_resource.model_dump() + data["kind"] = CoreResourceKinds.OPERATION + with pytest.raises(pydantic.ValidationError): + DataContainerResource.model_validate(data) diff --git a/tests/core/test_operation.py b/tests/core/test_operation.py index 7903af344..a8c04ced4 100644 --- a/tests/core/test_operation.py +++ b/tests/core/test_operation.py @@ -241,3 +241,13 @@ def test_script_operation_resource_identifier() -> None: assert operation.operationType == DiscoveryOperationEnum.CHARACTERIZE assert operation.operatorIdentifier == "script-inline-script-0.1.0" assert operation.identifier.startswith("operation-script-inline-script-0.1.0-") + + +def test_operation_resource_wrong_kind_raises_validation_error( + operation_resource: OperationResource, +) -> None: + """OperationResource rejects a kind value other than OPERATION.""" + data = operation_resource.model_dump() + data["kind"] = CoreResourceKinds.DISCOVERYSPACE + with pytest.raises(pydantic.ValidationError): + OperationResource.model_validate(data) diff --git a/tests/core/test_space.py b/tests/core/test_space.py index 3d4880ff8..eda9562ff 100644 --- a/tests/core/test_space.py +++ b/tests/core/test_space.py @@ -6,6 +6,7 @@ import re from collections.abc import Callable +import pydantic import pytest import yaml @@ -540,3 +541,13 @@ def test_operation_context_failure_lifecycle(pfas_space: DiscoverySpace) -> None assert lifecycle_statuses[0].event == OperationResourceEventEnum.STARTED assert lifecycle_statuses[-1].event == OperationResourceEventEnum.FINISHED assert lifecycle_statuses[-1].exit_state == OperationExitStateEnum.FAIL + + +def test_discovery_space_resource_wrong_kind_raises_validation_error( + discovery_space_resource: DiscoverySpaceResource, +) -> None: + """DiscoverySpaceResource rejects a kind value other than DISCOVERYSPACE.""" + data = discovery_space_resource.model_dump() + data["kind"] = CoreResourceKinds.OPERATION + with pytest.raises(pydantic.ValidationError): + DiscoverySpaceResource.model_validate(data)