Skip to content

Commit 476a449

Browse files
Feature set and Feature store entity archive and restore (Azure#29356)
* Updating feature set and feature store entity operations to use get_entity * Remove archive and restore for feature set and feature store entity * fix lint * Remove tests * Adding archive and restore for version * Adding line
1 parent 4dd063f commit 476a449

File tree

7 files changed

+155
-80
lines changed

7 files changed

+155
-80
lines changed

sdk/ml/azure-ai-ml/azure/ai/ml/_utils/_feature_set_utils.py

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# ---------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# ---------------------------------------------------------
4+
5+
# pylint: disable=protected-access
6+
7+
from pathlib import Path
8+
from typing import TYPE_CHECKING, Dict, Optional, Union
9+
10+
11+
from azure.ai.ml._restclient.v2023_02_01_preview.operations import ( # pylint: disable = unused-import
12+
FeaturesetContainersOperations,
13+
FeaturesetVersionsOperations,
14+
FeaturestoreEntityContainersOperations,
15+
FeaturestoreEntityVersionsOperations,
16+
)
17+
from azure.ai.ml.entities._assets.asset import Asset
18+
from azure.ai.ml.exceptions import (
19+
ErrorCategory,
20+
ErrorTarget,
21+
ValidationErrorType,
22+
ValidationException,
23+
)
24+
from azure.core.exceptions import ResourceNotFoundError
25+
26+
from .utils import load_yaml
27+
28+
if TYPE_CHECKING:
29+
from azure.ai.ml.operations._feature_set_operations import _FeatureSetOperations
30+
from azure.ai.ml.operations._feature_store_entity_operations import _FeatureStoreEntityOperations
31+
32+
33+
def read_feature_set_metadata_contents(*, path: str) -> Dict:
34+
metadata_path = str(Path(path, "FeaturesetSpec.yaml"))
35+
return load_yaml(metadata_path)
36+
37+
38+
def _get_latest_version_from_container(
39+
asset_name: str,
40+
container_operation: Union[
41+
"FeaturesetContainersOperations",
42+
"FeaturestoreEntityContainersOperations",
43+
],
44+
resource_group_name: str,
45+
workspace_name: str,
46+
**kwargs,
47+
) -> str:
48+
try:
49+
container = container_operation.get_entity(
50+
name=asset_name,
51+
resource_group_name=resource_group_name,
52+
workspace_name=workspace_name,
53+
**kwargs,
54+
)
55+
version = container.properties.latest_version
56+
57+
except ResourceNotFoundError:
58+
message = f"Asset {asset_name} does not exist in feature store {workspace_name}."
59+
no_personal_data_message = "Asset {asset_name} does not exist in feature store {workspace_name}."
60+
raise ValidationException(
61+
message=message,
62+
no_personal_data_message=no_personal_data_message,
63+
target=ErrorTarget.ASSET,
64+
error_category=ErrorCategory.USER_ERROR,
65+
error_type=ValidationErrorType.RESOURCE_NOT_FOUND,
66+
)
67+
return version
68+
69+
70+
def _archive_or_restore(
71+
asset_operations: Union["_FeatureSetOperations", "_FeatureStoreEntityOperations"],
72+
version_operation: Union[
73+
"FeaturesetVersionsOperations",
74+
"FeaturestoreEntityVersionsOperations",
75+
],
76+
is_archived: bool,
77+
name: str,
78+
version: Optional[str] = None,
79+
label: Optional[str] = None,
80+
) -> None:
81+
resource_group_name = asset_operations._operation_scope._resource_group_name
82+
workspace_name = asset_operations._workspace_name
83+
if not version and not label:
84+
msg = "Must provide either version or label."
85+
raise ValidationException(
86+
message=msg,
87+
target=ErrorTarget.ASSET,
88+
no_personal_data_message=msg,
89+
error_category=ErrorCategory.USER_ERROR,
90+
error_type=ValidationErrorType.MISSING_FIELD,
91+
)
92+
93+
if version and label:
94+
msg = "Cannot specify both version and label."
95+
raise ValidationException(
96+
message=msg,
97+
no_personal_data_message=msg,
98+
target=ErrorTarget.ASSET,
99+
error_category=ErrorCategory.USER_ERROR,
100+
error_type=ValidationErrorType.RESOURCE_NOT_FOUND,
101+
)
102+
103+
if label:
104+
version = _resolve_label_to_asset(asset_operations, name, label).version
105+
106+
if version:
107+
version_resource = version_operation.get(
108+
name=name,
109+
version=version,
110+
resource_group_name=resource_group_name,
111+
workspace_name=workspace_name,
112+
)
113+
version_resource.properties.is_archived = is_archived
114+
version_resource.properties.stage = "Archived" if is_archived else "Development"
115+
version_operation.begin_create_or_update(
116+
name=name,
117+
version=version,
118+
resource_group_name=resource_group_name,
119+
workspace_name=workspace_name,
120+
body=version_resource,
121+
)
122+
123+
124+
def _resolve_label_to_asset(
125+
assetOperations: Union[
126+
"_FeatureSetOperations",
127+
"_FeatureStoreEntityOperations",
128+
],
129+
name: str,
130+
label: str,
131+
) -> Asset:
132+
"""Returns the asset referred to by the given label.
133+
134+
Throws if label does not refer to a version of the named asset
135+
"""
136+
137+
resolver = assetOperations._managed_label_resolver.get(label, None)
138+
if not resolver:
139+
msg = "Asset {} with version label {} does not exist in workspace."
140+
raise ValidationException(
141+
message=msg.format(name, label),
142+
no_personal_data_message=msg.format("[name]", "[label]"),
143+
target=ErrorTarget.ASSET,
144+
error_type=ValidationErrorType.RESOURCE_NOT_FOUND,
145+
)
146+
return resolver(name)

sdk/ml/azure-ai-ml/azure/ai/ml/entities/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
from ._feature_store_entity.feature_store_entity import _FeatureStoreEntity
145145
from ._feature_store_entity.data_column import _DataColumn
146146
from ._feature_store_entity.data_column_type import _DataColumnType
147+
from ._feature_set.feature import _Feature
147148
from ._feature_set.feature_set_specification import _FeatureSetSpecification
148149
from ._feature_set.materialization_compute_resource import _MaterializationComputeResource
149150
from ._feature_set.materialization_settings import _MaterializationSettings
@@ -287,6 +288,7 @@
287288
"AutoScaleSettings",
288289
"AutoPauseSettings",
289290
"WorkspaceModelReference",
291+
"_Feature",
290292
"_FeatureSet",
291293
"_ComputeRuntime",
292294
"_FeatureStoreSettings",

sdk/ml/azure-ai-ml/azure/ai/ml/operations/_feature_set_operations.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@
2727
from azure.ai.ml.operations._datastore_operations import DatastoreOperations
2828

2929
# from azure.ai.ml._telemetry import ActivityType, monitor_with_activity
30-
from azure.ai.ml._utils._asset_utils import (
30+
from azure.ai.ml._utils._feature_store_utils import (
3131
_archive_or_restore,
3232
_get_latest_version_from_container,
3333
_resolve_label_to_asset,
34+
read_feature_set_metadata_contents,
3435
)
35-
from azure.ai.ml._utils._feature_set_utils import read_feature_set_metadata_contents
3636
from azure.ai.ml._utils._logger_utils import OpsLogger
3737
from azure.ai.ml.entities._assets._artifacts.feature_set import _FeatureSet
3838
from azure.ai.ml.entities._feature_set.featureset_spec_metadata import FeaturesetSpecMetadata
@@ -365,7 +365,6 @@ def archive(
365365
_archive_or_restore(
366366
asset_operations=self,
367367
version_operation=self._operation,
368-
container_operation=self._container_operation,
369368
is_archived=True,
370369
name=name,
371370
version=version,
@@ -395,7 +394,6 @@ def restore(
395394
_archive_or_restore(
396395
asset_operations=self,
397396
version_operation=self._operation,
398-
container_operation=self._container_operation,
399397
is_archived=False,
400398
name=name,
401399
version=version,

sdk/ml/azure-ai-ml/azure/ai/ml/operations/_feature_store_entity_operations.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717

1818
# from azure.ai.ml._telemetry import ActivityType, monitor_with_activity
19-
from azure.ai.ml._utils._asset_utils import (
19+
from azure.ai.ml._utils._feature_store_utils import (
2020
_archive_or_restore,
2121
_get_latest_version_from_container,
2222
_resolve_label_to_asset,
@@ -184,7 +184,6 @@ def archive(
184184
_archive_or_restore(
185185
asset_operations=self,
186186
version_operation=self._operation,
187-
container_operation=self._container_operation,
188187
is_archived=True,
189188
name=name,
190189
version=version,
@@ -214,7 +213,6 @@ def restore(
214213
_archive_or_restore(
215214
asset_operations=self,
216215
version_operation=self._operation,
217-
container_operation=self._container_operation,
218216
is_archived=False,
219217
name=name,
220218
version=version,

sdk/ml/azure-ai-ml/tests/feature_set/unittests/test_feature_set_operations.py

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -137,52 +137,24 @@ def test_archive_version(self, mock_feature_set_operations: _FeatureSetOperation
137137
version = "1"
138138
mock_feature_set_operations._operation.get.return_value = featureset_version
139139
mock_feature_set_operations.archive(name=name, version=version)
140-
mock_feature_set_operations._operation.create_or_update.assert_called_once_with(
140+
mock_feature_set_operations._operation.begin_create_or_update.assert_called_once_with(
141141
name=name,
142142
version=version,
143143
workspace_name=mock_feature_set_operations._workspace_name,
144144
body=featureset_version,
145145
resource_group_name=mock_feature_set_operations._resource_group_name,
146146
)
147147

148-
def test_archive_container(self, mock_feature_set_operations: _FeatureSetOperations):
149-
name = "random_name"
150-
featureset_container = Mock(
151-
FeaturesetContainer(properties=Mock(FeaturesetContainerProperties(description="test")))
152-
)
153-
mock_feature_set_operations._container_operation.get.return_value = featureset_container
154-
mock_feature_set_operations.archive(name=name)
155-
mock_feature_set_operations._container_operation.create_or_update.assert_called_once_with(
156-
name=name,
157-
workspace_name=mock_feature_set_operations._workspace_name,
158-
body=featureset_container,
159-
resource_group_name=mock_feature_set_operations._resource_group_name,
160-
)
161-
162148
def test_restore_version(self, mock_feature_set_operations: _FeatureSetOperations):
163149
name = "random_name"
164150
featureset_version = Mock(FeaturesetVersion(properties=Mock(FeaturesetVersionProperties(entities=["test"]))))
165151
version = "1"
166152
mock_feature_set_operations._operation.get.return_value = featureset_version
167153
mock_feature_set_operations.restore(name=name, version=version)
168-
mock_feature_set_operations._operation.create_or_update.assert_called_once_with(
154+
mock_feature_set_operations._operation.begin_create_or_update.assert_called_once_with(
169155
name=name,
170156
version=version,
171157
workspace_name=mock_feature_set_operations._workspace_name,
172158
body=featureset_version,
173159
resource_group_name=mock_feature_set_operations._resource_group_name,
174160
)
175-
176-
def test_restore_container(self, mock_feature_set_operations: _FeatureSetOperations):
177-
name = "random_name"
178-
featureset_container = Mock(
179-
FeaturesetContainer(properties=Mock(FeaturesetContainerProperties(entities=["test"])))
180-
)
181-
mock_feature_set_operations._container_operation.get.return_value = featureset_container
182-
mock_feature_set_operations.restore(name=name)
183-
mock_feature_set_operations._container_operation.create_or_update.assert_called_once_with(
184-
name=name,
185-
workspace_name=mock_feature_set_operations._workspace_name,
186-
body=featureset_container,
187-
resource_group_name=mock_feature_set_operations._resource_group_name,
188-
)

sdk/ml/azure-ai-ml/tests/feature_store_entity/unittests/test_feature_store_entity_operations.py

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -75,28 +75,14 @@ def test_archive_version(self, mock_feature_store_entity_operations: _FeatureSto
7575
version = "1"
7676
mock_feature_store_entity_operations._operation.get.return_value = featureStoreEntity_version
7777
mock_feature_store_entity_operations.archive(name=name, version=version)
78-
mock_feature_store_entity_operations._operation.create_or_update.assert_called_once_with(
78+
mock_feature_store_entity_operations._operation.begin_create_or_update.assert_called_once_with(
7979
name=name,
8080
version=version,
8181
workspace_name=mock_feature_store_entity_operations._workspace_name,
8282
body=featureStoreEntity_version,
8383
resource_group_name=mock_feature_store_entity_operations._resource_group_name,
8484
)
8585

86-
def test_archive_container(self, mock_feature_store_entity_operations: _FeatureStoreEntityOperations):
87-
name = "random_name"
88-
featureStoreEntity_container = Mock(
89-
FeaturestoreEntityContainer(properties=Mock(FeaturestoreEntityContainerProperties(description="test")))
90-
)
91-
mock_feature_store_entity_operations._container_operation.get.return_value = featureStoreEntity_container
92-
mock_feature_store_entity_operations.archive(name=name)
93-
mock_feature_store_entity_operations._container_operation.create_or_update.assert_called_once_with(
94-
name=name,
95-
workspace_name=mock_feature_store_entity_operations._workspace_name,
96-
body=featureStoreEntity_container,
97-
resource_group_name=mock_feature_store_entity_operations._resource_group_name,
98-
)
99-
10086
def test_restore_version(self, mock_feature_store_entity_operations: _FeatureStoreEntityOperations):
10187
name = "random_name"
10288
featureStoreEntity_version = Mock(
@@ -105,24 +91,10 @@ def test_restore_version(self, mock_feature_store_entity_operations: _FeatureSto
10591
version = "1"
10692
mock_feature_store_entity_operations._operation.get.return_value = featureStoreEntity_version
10793
mock_feature_store_entity_operations.restore(name=name, version=version)
108-
mock_feature_store_entity_operations._operation.create_or_update.assert_called_once_with(
94+
mock_feature_store_entity_operations._operation.begin_create_or_update.assert_called_once_with(
10995
name=name,
11096
version=version,
11197
workspace_name=mock_feature_store_entity_operations._workspace_name,
11298
body=featureStoreEntity_version,
11399
resource_group_name=mock_feature_store_entity_operations._resource_group_name,
114100
)
115-
116-
def test_restore_container(self, mock_feature_store_entity_operations: _FeatureStoreEntityOperations):
117-
name = "random_name"
118-
featureStoreEntity_container = Mock(
119-
FeaturestoreEntityContainer(properties=Mock(FeaturestoreEntityContainerProperties()))
120-
)
121-
mock_feature_store_entity_operations._container_operation.get.return_value = featureStoreEntity_container
122-
mock_feature_store_entity_operations.restore(name=name)
123-
mock_feature_store_entity_operations._container_operation.create_or_update.assert_called_once_with(
124-
name=name,
125-
workspace_name=mock_feature_store_entity_operations._workspace_name,
126-
body=featureStoreEntity_container,
127-
resource_group_name=mock_feature_store_entity_operations._resource_group_name,
128-
)

0 commit comments

Comments
 (0)