Skip to content

Commit

Permalink
make registry delete return an lro poller (#26983)
Browse files Browse the repository at this point in the history
* make registry delete reutrn lro poller

* cleanup

* change operation name to begin_delete

* sleep long enough to dodge ev issues in testing

* move CL line to breaking changes

* further refine delete swagger, edit docstrings

* review comments

* add reliability notice
  • Loading branch information
MilesHolland authored Oct 26, 2022
1 parent 52a0357 commit 9357d9d
Show file tree
Hide file tree
Showing 9 changed files with 523 additions and 681 deletions.
1 change: 1 addition & 0 deletions sdk/ml/azure-ai-ml/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- MLClient.from_config can now find the default config.json on Compute Instance when running sample notebooks.
- Registries now assign managed tags to match registry's tags.
- Adjust registry experimental tags and imports to avoid warning printouts for unrelated operations.
- Make registry delete operation return an LROPoller, and change name to begin_delete.
- Prevent registering an already existing environment that references conda file.

### Other Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from ... import models as _models
from ..._vendor import _convert_request
from ...operations._registries_operations import build_create_or_update_request_initial, build_delete_request, build_get_request, build_list_by_subscription_request, build_list_request, build_update_request
from ...operations._registries_operations import build_create_or_update_request_initial, build_delete_request_initial, build_get_request, build_list_by_subscription_request, build_list_request, build_update_request
T = TypeVar('T')
ClsType = Optional[Callable[[PipelineResponse[HttpRequest, AsyncHttpResponse], T, Dict[str, Any]], Any]]

Expand Down Expand Up @@ -214,26 +214,12 @@ async def get_next(next_link=None):
)
list.metadata = {'url': "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.MachineLearningServices/registries"} # type: ignore

@distributed_trace_async
async def delete( # pylint: disable=inconsistent-return-statements
async def _delete_initial( # pylint: disable=inconsistent-return-statements
self,
resource_group_name: str,
registry_name: str,
**kwargs: Any
) -> None:
"""Delete registry.
Delete registry.
:param resource_group_name: The name of the resource group. The name is case insensitive.
:type resource_group_name: str
:param registry_name: Name of registry. This is case-insensitive.
:type registry_name: str
:keyword callable cls: A custom type or function that will be passed the direct response
:return: None, or the result of cls(response)
:rtype: None
:raises: ~azure.core.exceptions.HttpResponseError
"""
cls = kwargs.pop('cls', None) # type: ClsType[None]
error_map = {
401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError
Expand All @@ -243,12 +229,12 @@ async def delete( # pylint: disable=inconsistent-return-statements
api_version = kwargs.pop('api_version', "2022-10-01-preview") # type: str


request = build_delete_request(
request = build_delete_request_initial(
subscription_id=self._config.subscription_id,
resource_group_name=resource_group_name,
registry_name=registry_name,
api_version=api_version,
template_url=self.delete.metadata['url'],
template_url=self._delete_initial.metadata['url'],
)
request = _convert_request(request)
request.url = self._client.format_url(request.url)
Expand All @@ -262,14 +248,84 @@ async def delete( # pylint: disable=inconsistent-return-statements

if response.status_code not in [200, 202, 204]:
map_error(status_code=response.status_code, response=response, error_map=error_map)
error = self._deserialize.failsafe_deserialize(_models.ErrorResponse, pipeline_response)
raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat)
raise HttpResponseError(response=response, error_format=ARMErrorFormat)

response_headers = {}
if response.status_code == 202:
response_headers['x-ms-async-operation-timeout']=self._deserialize('duration', response.headers.get('x-ms-async-operation-timeout'))
response_headers['Location']=self._deserialize('str', response.headers.get('Location'))
response_headers['Retry-After']=self._deserialize('int', response.headers.get('Retry-After'))


if cls:
return cls(pipeline_response, None, {})
return cls(pipeline_response, None, response_headers)

_delete_initial.metadata = {'url': "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.MachineLearningServices/registries/{registryName}"} # type: ignore


delete.metadata = {'url': "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.MachineLearningServices/registries/{registryName}"} # type: ignore
@distributed_trace_async
async def begin_delete( # pylint: disable=inconsistent-return-statements
self,
resource_group_name: str,
registry_name: str,
**kwargs: Any
) -> AsyncLROPoller[None]:
"""Delete registry.
Delete registry.
:param resource_group_name: The name of the resource group. The name is case insensitive.
:type resource_group_name: str
:param registry_name: Name of registry. This is case-insensitive.
:type registry_name: str
:keyword callable cls: A custom type or function that will be passed the direct response
:keyword str continuation_token: A continuation token to restart a poller from a saved state.
:keyword polling: By default, your polling method will be AsyncARMPolling. Pass in False for
this operation to not poll, or pass in your own initialized polling object for a personal
polling strategy.
:paramtype polling: bool or ~azure.core.polling.AsyncPollingMethod
:keyword int polling_interval: Default waiting time between two polls for LRO operations if no
Retry-After header is present.
:return: An instance of AsyncLROPoller that returns either None or the result of cls(response)
:rtype: ~azure.core.polling.AsyncLROPoller[None]
:raises: ~azure.core.exceptions.HttpResponseError
"""
api_version = kwargs.pop('api_version', "2022-10-01-preview") # type: str
polling = kwargs.pop('polling', True) # type: Union[bool, AsyncPollingMethod]
cls = kwargs.pop('cls', None) # type: ClsType[None]
lro_delay = kwargs.pop(
'polling_interval',
self._config.polling_interval
)
cont_token = kwargs.pop('continuation_token', None) # type: Optional[str]
if cont_token is None:
raw_result = await self._delete_initial(
resource_group_name=resource_group_name,
registry_name=registry_name,
api_version=api_version,
cls=lambda x,y,z: x,
**kwargs
)
kwargs.pop('error_map', None)

def get_long_running_output(pipeline_response):
if cls:
return cls(pipeline_response, None, {})


if polling is True: polling_method = AsyncARMPolling(lro_delay, **kwargs)
elif polling is False: polling_method = AsyncNoPolling()
else: polling_method = polling
if cont_token:
return AsyncLROPoller.from_continuation_token(
polling_method=polling_method,
continuation_token=cont_token,
client=self._client,
deserialization_callback=get_long_running_output
)
return AsyncLROPoller(self._client, raw_result, get_long_running_output, polling_method)

begin_delete.metadata = {'url': "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.MachineLearningServices/registries/{registryName}"} # type: ignore

@distributed_trace_async
async def get(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def build_list_request(
)


def build_delete_request(
def build_delete_request_initial(
subscription_id, # type: str
resource_group_name, # type: str
registry_name, # type: str
Expand Down Expand Up @@ -452,27 +452,13 @@ def get_next(next_link=None):
)
list.metadata = {'url': "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.MachineLearningServices/registries"} # type: ignore

@distributed_trace
def delete( # pylint: disable=inconsistent-return-statements
def _delete_initial( # pylint: disable=inconsistent-return-statements
self,
resource_group_name, # type: str
registry_name, # type: str
**kwargs # type: Any
):
# type: (...) -> None
"""Delete registry.
Delete registry.
:param resource_group_name: The name of the resource group. The name is case insensitive.
:type resource_group_name: str
:param registry_name: Name of registry. This is case-insensitive.
:type registry_name: str
:keyword callable cls: A custom type or function that will be passed the direct response
:return: None, or the result of cls(response)
:rtype: None
:raises: ~azure.core.exceptions.HttpResponseError
"""
cls = kwargs.pop('cls', None) # type: ClsType[None]
error_map = {
401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError
Expand All @@ -482,12 +468,12 @@ def delete( # pylint: disable=inconsistent-return-statements
api_version = kwargs.pop('api_version', "2022-10-01-preview") # type: str


request = build_delete_request(
request = build_delete_request_initial(
subscription_id=self._config.subscription_id,
resource_group_name=resource_group_name,
registry_name=registry_name,
api_version=api_version,
template_url=self.delete.metadata['url'],
template_url=self._delete_initial.metadata['url'],
)
request = _convert_request(request)
request.url = self._client.format_url(request.url)
Expand All @@ -501,14 +487,85 @@ def delete( # pylint: disable=inconsistent-return-statements

if response.status_code not in [200, 202, 204]:
map_error(status_code=response.status_code, response=response, error_map=error_map)
error = self._deserialize.failsafe_deserialize(_models.ErrorResponse, pipeline_response)
raise HttpResponseError(response=response, model=error, error_format=ARMErrorFormat)
raise HttpResponseError(response=response, error_format=ARMErrorFormat)

response_headers = {}
if response.status_code == 202:
response_headers['x-ms-async-operation-timeout']=self._deserialize('duration', response.headers.get('x-ms-async-operation-timeout'))
response_headers['Location']=self._deserialize('str', response.headers.get('Location'))
response_headers['Retry-After']=self._deserialize('int', response.headers.get('Retry-After'))


if cls:
return cls(pipeline_response, None, {})
return cls(pipeline_response, None, response_headers)

_delete_initial.metadata = {'url': "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.MachineLearningServices/registries/{registryName}"} # type: ignore


@distributed_trace
def begin_delete( # pylint: disable=inconsistent-return-statements
self,
resource_group_name, # type: str
registry_name, # type: str
**kwargs # type: Any
):
# type: (...) -> LROPoller[None]
"""Delete registry.
Delete registry.
:param resource_group_name: The name of the resource group. The name is case insensitive.
:type resource_group_name: str
:param registry_name: Name of registry. This is case-insensitive.
:type registry_name: str
:keyword callable cls: A custom type or function that will be passed the direct response
:keyword str continuation_token: A continuation token to restart a poller from a saved state.
:keyword polling: By default, your polling method will be ARMPolling. Pass in False for this
operation to not poll, or pass in your own initialized polling object for a personal polling
strategy.
:paramtype polling: bool or ~azure.core.polling.PollingMethod
:keyword int polling_interval: Default waiting time between two polls for LRO operations if no
Retry-After header is present.
:return: An instance of LROPoller that returns either None or the result of cls(response)
:rtype: ~azure.core.polling.LROPoller[None]
:raises: ~azure.core.exceptions.HttpResponseError
"""
api_version = kwargs.pop('api_version', "2022-10-01-preview") # type: str
polling = kwargs.pop('polling', True) # type: Union[bool, PollingMethod]
cls = kwargs.pop('cls', None) # type: ClsType[None]
lro_delay = kwargs.pop(
'polling_interval',
self._config.polling_interval
)
cont_token = kwargs.pop('continuation_token', None) # type: Optional[str]
if cont_token is None:
raw_result = self._delete_initial(
resource_group_name=resource_group_name,
registry_name=registry_name,
api_version=api_version,
cls=lambda x,y,z: x,
**kwargs
)
kwargs.pop('error_map', None)

delete.metadata = {'url': "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.MachineLearningServices/registries/{registryName}"} # type: ignore
def get_long_running_output(pipeline_response):
if cls:
return cls(pipeline_response, None, {})


if polling is True: polling_method = ARMPolling(lro_delay, **kwargs)
elif polling is False: polling_method = NoPolling()
else: polling_method = polling
if cont_token:
return LROPoller.from_continuation_token(
polling_method=polling_method,
continuation_token=cont_token,
client=self._client,
deserialization_callback=get_long_running_output
)
return LROPoller(self._client, raw_result, get_long_running_output, polling_method)

begin_delete.metadata = {'url': "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.MachineLearningServices/registries/{registryName}"} # type: ignore

@distributed_trace
def get(
Expand Down
21 changes: 15 additions & 6 deletions sdk/ml/azure-ai-ml/azure/ai/ml/operations/_registry_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,16 @@ def begin_create(
registry: Registry,
**kwargs: Dict,
) -> LROPoller[Registry]:
"""Create a new Azure Machine Learning Registry.
"""Create a new Azure Machine Learning Registry,
or try to update if it already exists.
Returns the registry if already exists.
Note: Due to service limitations we have to sleep for
an additional 30~45 seconds AFTER the LRO Poller concludes
before the registry will be consistently deleted from the
perspective of subsequent operations.
If a deletion is required for subsequent operations to
work properly, callers should implement that logic until the
service has been fixed to return a reliable LRO.
:param registry: Registry definition.
:type registry: Registry
Expand All @@ -140,16 +147,18 @@ def begin_create(
return poller


@monitor_with_activity(logger, "Registry.Delete", ActivityType.PUBLICAPI)
@monitor_with_activity(logger, "Registry.BeginDelete", ActivityType.PUBLICAPI)
@experimental
def delete(self, *, name: str, **kwargs: Dict) -> None:
"""Delete a registry. Returns nothing on a successful operation.
def begin_delete(self, *, name: str, **kwargs: Dict) -> LROPoller[Registry]:
"""Delete a registry if it exists. Returns nothing on a successful operation.
:param name: Name of the registry
:type name: str
:return: A poller to track the operation status.
:rtype: LROPoller
"""
resource_group = kwargs.get("resource_group") or self._resource_group_name
return self._operation.delete(
return self._operation.begin_delete(
resource_group_name=resource_group,
registry_name=name,
**self._init_kwargs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"RegistryManagement"
],
"summary": "Delete registry.",
"x-ms-long-running-operation": true,
"operationId": "Registries_Delete",
"produces": [
"application/json"
Expand Down Expand Up @@ -142,9 +143,27 @@
},
"200": {
"description": "Success"
},
},
"202": {
"description": "Success"
"description": "Accepted",
"headers": {
"x-ms-async-operation-timeout": {
"description": "Timeout for the client to use when polling the asynchronous operation.",
"type": "string",
"format": "duration"
},
"Location": {
"description": "URI to poll for asynchronous operation result.",
"type": "string"
},
"Retry-After": {
"description": "Duration the client should wait between requests, in seconds.",
"type": "integer",
"format": "int32",
"maximum": 600,
"minimum": 10
}
}
},
"204": {
"description": "No Content"
Expand Down
Loading

0 comments on commit 9357d9d

Please sign in to comment.