Skip to content

Commit b3445ad

Browse files
authored
Env dedup (Azure#38590)
1 parent 70f7149 commit b3445ad

File tree

3 files changed

+157
-7
lines changed

3 files changed

+157
-7
lines changed

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,39 @@ def _get_next_version_from_container(
755755
return version
756756

757757

758+
def _get_next_latest_versions_from_container(
759+
name: str,
760+
container_operation: Any,
761+
resource_group_name: str,
762+
workspace_name: str,
763+
registry_name: str = None,
764+
**kwargs,
765+
) -> str:
766+
try:
767+
container = (
768+
container_operation.get(
769+
name=name,
770+
resource_group_name=resource_group_name,
771+
registry_name=registry_name,
772+
**kwargs,
773+
)
774+
if registry_name
775+
else container_operation.get(
776+
name=name,
777+
resource_group_name=resource_group_name,
778+
workspace_name=workspace_name,
779+
**kwargs,
780+
)
781+
)
782+
next_version = container.properties.next_version
783+
latest_version = container.properties.latest_version
784+
785+
except ResourceNotFoundError:
786+
next_version = "1"
787+
latest_version = "1"
788+
return next_version, latest_version
789+
790+
758791
def _get_latest_version_from_container(
759792
asset_name: str,
760793
container_operation: Any,

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from azure.ai.ml._utils._asset_utils import (
2727
_archive_or_restore,
2828
_get_latest,
29-
_get_next_version_from_container,
29+
_get_next_latest_versions_from_container,
3030
_resolve_label_to_asset,
3131
)
3232
from azure.ai.ml._utils._experimental import experimental
@@ -39,7 +39,7 @@
3939
from azure.ai.ml.constants._common import ARM_ID_PREFIX, ASSET_ID_FORMAT, AzureMLResourceType
4040
from azure.ai.ml.entities._assets import Environment, WorkspaceAssetReference
4141
from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, ValidationErrorType, ValidationException
42-
from azure.core.exceptions import ResourceNotFoundError
42+
from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError
4343

4444
ops_logger = OpsLogger(__name__)
4545
module_logger = ops_logger.module_logger
@@ -110,14 +110,18 @@ def create_or_update(self, environment: Environment) -> Environment: # type: ig
110110
"""
111111
try:
112112
if not environment.version and environment._auto_increment_version:
113-
environment.version = _get_next_version_from_container(
113+
114+
next_version, latest_version = _get_next_latest_versions_from_container(
114115
name=environment.name,
115116
container_operation=self._containers_operations,
116117
resource_group_name=self._operation_scope.resource_group_name,
117118
workspace_name=self._workspace_name,
118119
registry_name=self._registry_name,
119120
**self._kwargs,
120121
)
122+
# If user not passing the version, SDK will try to update the latest version
123+
return self._try_update_latest_version(next_version, latest_version, environment)
124+
121125
sas_uri = None
122126
if self._registry_name:
123127
if isinstance(environment, WorkspaceAssetReference):
@@ -204,6 +208,25 @@ def create_or_update(self, environment: Environment) -> Environment: # type: ig
204208
else:
205209
raise ex
206210

211+
def _try_update_latest_version(
212+
self, next_version: str, latest_version: str, environment: Environment
213+
) -> Environment:
214+
env = None
215+
if self._registry_name:
216+
environment.version = next_version
217+
env = self.create_or_update(environment)
218+
else:
219+
environment.version = latest_version
220+
try: # Try to update the latest version
221+
env = self.create_or_update(environment)
222+
except Exception as ex: # pylint: disable=W0718
223+
if isinstance(ex, ResourceExistsError):
224+
environment.version = next_version
225+
env = self.create_or_update(environment)
226+
else:
227+
raise ex
228+
return env
229+
207230
def _get(self, name: str, version: Optional[str] = None) -> EnvironmentVersion:
208231
if version:
209232
return (

sdk/ml/azure-ai-ml/tests/environment/unittests/test_environment_operations.py

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from typing import Iterable
2-
from unittest.mock import Mock, patch
2+
from unittest.mock import Mock, call, patch
33

44
import pytest
55

@@ -14,7 +14,7 @@
1414
from azure.ai.ml.constants._common import ARM_ID_PREFIX
1515
from azure.ai.ml.entities._assets import Environment
1616
from azure.ai.ml.operations import EnvironmentOperations
17-
from azure.core.exceptions import ResourceNotFoundError
17+
from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError
1818

1919

2020
@pytest.fixture
@@ -108,15 +108,106 @@ def test_create_autoincrement(
108108
with patch(
109109
"azure.ai.ml.operations._environment_operations.Environment._from_rest_object", return_value=None
110110
), patch(
111-
"azure.ai.ml.operations._environment_operations._get_next_version_from_container", return_value="version"
111+
"azure.ai.ml.operations._environment_operations._get_next_latest_versions_from_container",
112+
return_value=("version", "latest"),
112113
) as mock_nextver:
113114
mock_environment_operation.create_or_update(env)
114115
mock_nextver.assert_called_once()
115116

116117
mock_environment_operation._version_operations.create_or_update.assert_called_once_with(
117118
body=env._to_rest_object(),
118119
name=env.name,
119-
version=mock_nextver.return_value,
120+
version="latest",
121+
resource_group_name=mock_workspace_scope.resource_group_name,
122+
workspace_name=mock_workspace_scope.workspace_name,
123+
)
124+
125+
def test_create_when_latest_fail_with_resource_exist(
126+
self,
127+
mock_environment_operation: EnvironmentOperations,
128+
mock_workspace_scope: OperationScope,
129+
) -> None:
130+
env = load_environment(source="./tests/test_configs/environment/environment_no_version.yml")
131+
assert env._auto_increment_version
132+
env.version = None
133+
mock_environment_operation._version_operations.create_or_update(
134+
body=env._to_rest_object(),
135+
name=env.name,
136+
version="latest_version",
137+
resource_group_name=mock_workspace_scope.resource_group_name,
138+
workspace_name=mock_workspace_scope.workspace_name,
139+
).side_effect = ResourceExistsError()
140+
with patch(
141+
"azure.ai.ml.operations._environment_operations.Environment._from_rest_object", return_value=None
142+
), patch(
143+
"azure.ai.ml.operations._environment_operations._get_next_latest_versions_from_container",
144+
return_value=("next_version", "latest_version"),
145+
) as mock_nextver:
146+
147+
def side_effect(name, version, body, resource_group_name, workspace_name):
148+
if version == "latest_version":
149+
raise ResourceExistsError
150+
151+
mock_environment_operation._version_operations.create_or_update.side_effect = side_effect
152+
mock_environment_operation.create_or_update(env)
153+
mock_nextver.assert_called_once()
154+
155+
mock_environment_operation._version_operations.create_or_update.assert_has_calls(
156+
[
157+
call(
158+
body=env._to_rest_object(),
159+
name=env.name,
160+
version="latest_version",
161+
resource_group_name=mock_workspace_scope.resource_group_name,
162+
workspace_name=mock_workspace_scope.workspace_name,
163+
),
164+
call(
165+
body=env._to_rest_object(),
166+
name=env.name,
167+
version="next_version",
168+
resource_group_name=mock_workspace_scope.resource_group_name,
169+
workspace_name=mock_workspace_scope.workspace_name,
170+
),
171+
]
172+
)
173+
174+
def test_create_when_latest_fail_with_unknown(
175+
self,
176+
mock_environment_operation: EnvironmentOperations,
177+
mock_workspace_scope: OperationScope,
178+
) -> None:
179+
env = load_environment(source="./tests/test_configs/environment/environment_no_version.yml")
180+
assert env._auto_increment_version
181+
env.version = None
182+
mock_environment_operation._version_operations.create_or_update(
183+
body=env._to_rest_object(),
184+
name=env.name,
185+
version="latest_version",
186+
resource_group_name=mock_workspace_scope.resource_group_name,
187+
workspace_name=mock_workspace_scope.workspace_name,
188+
).side_effect = ResourceExistsError()
189+
with patch(
190+
"azure.ai.ml.operations._environment_operations.Environment._from_rest_object", return_value=None
191+
), patch(
192+
"azure.ai.ml.operations._environment_operations._get_next_latest_versions_from_container",
193+
return_value=("next_version", "latest_version"),
194+
) as mock_nextver:
195+
196+
def side_effect(name, version, body, resource_group_name, workspace_name):
197+
if version == "latest_version":
198+
raise Exception("Unknowm Error")
199+
200+
mock_environment_operation._version_operations.create_or_update.side_effect = side_effect
201+
try:
202+
mock_environment_operation.create_or_update(env)
203+
except Exception:
204+
pass
205+
mock_nextver.assert_called_once()
206+
207+
mock_environment_operation._version_operations.create_or_update.assert_called_with(
208+
body=env._to_rest_object(),
209+
name=env.name,
210+
version="latest_version",
120211
resource_group_name=mock_workspace_scope.resource_group_name,
121212
workspace_name=mock_workspace_scope.workspace_name,
122213
)
@@ -177,6 +268,9 @@ def test_restore_container(self, mock_environment_operation: EnvironmentOperatio
177268
resource_group_name=mock_environment_operation._resource_group_name,
178269
)
179270

271+
def side_effect(self, args):
272+
print(args)
273+
180274
# #Mock(azure.ai.ml._restclient.v2021_10_01_dataplanepreview.operations._environment_versions_operations, "get")
181275
# def test_promote_environment_from_workspace(
182276
# self,

0 commit comments

Comments
 (0)