aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/azure/ai/ml/operations/_code_operations.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/azure/ai/ml/operations/_code_operations.py')
-rw-r--r--.venv/lib/python3.12/site-packages/azure/ai/ml/operations/_code_operations.py307
1 files changed, 307 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/azure/ai/ml/operations/_code_operations.py b/.venv/lib/python3.12/site-packages/azure/ai/ml/operations/_code_operations.py
new file mode 100644
index 00000000..7b0e64c3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/azure/ai/ml/operations/_code_operations.py
@@ -0,0 +1,307 @@
+# ---------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# ---------------------------------------------------------
+import re
+from os import PathLike
+from pathlib import Path
+from typing import Dict, Optional, Union
+
+from marshmallow.exceptions import ValidationError as SchemaValidationError
+
+from azure.ai.ml._artifacts._artifact_utilities import (
+ _check_and_upload_path,
+ _get_datastore_name,
+ _get_snapshot_path_info,
+ get_datastore_info,
+)
+from azure.ai.ml._artifacts._constants import (
+ ASSET_PATH_ERROR,
+ CHANGED_ASSET_PATH_MSG,
+ CHANGED_ASSET_PATH_MSG_NO_PERSONAL_DATA,
+)
+from azure.ai.ml._exception_helper import log_and_raise_error
+from azure.ai.ml._restclient.v2021_10_01_dataplanepreview import (
+ AzureMachineLearningWorkspaces as ServiceClient102021Dataplane,
+)
+from azure.ai.ml._restclient.v2022_10_01_preview import AzureMachineLearningWorkspaces as ServiceClient102022
+from azure.ai.ml._restclient.v2023_04_01 import AzureMachineLearningWorkspaces as ServiceClient042023
+from azure.ai.ml._scope_dependent_operations import OperationConfig, OperationScope, _ScopeDependentOperations
+from azure.ai.ml._telemetry import ActivityType, monitor_with_activity
+from azure.ai.ml._utils._asset_utils import (
+ _get_existing_asset_name_and_version,
+ get_content_hash_version,
+ get_storage_info_for_non_registry_asset,
+)
+from azure.ai.ml._utils._logger_utils import OpsLogger
+from azure.ai.ml._utils._registry_utils import get_asset_body_for_registry_storage, get_sas_uri_for_registry_asset
+from azure.ai.ml._utils._storage_utils import get_storage_client
+from azure.ai.ml.entities._assets import Code
+from azure.ai.ml.exceptions import (
+ AssetPathException,
+ ErrorCategory,
+ ErrorTarget,
+ ValidationErrorType,
+ ValidationException,
+)
+from azure.ai.ml.operations._datastore_operations import DatastoreOperations
+from azure.core.exceptions import HttpResponseError
+
+# pylint: disable=protected-access
+
+
+ops_logger = OpsLogger(__name__)
+module_logger = ops_logger.module_logger
+
+
+class CodeOperations(_ScopeDependentOperations):
+ """Represents a client for performing operations on code assets.
+
+ You should not instantiate this class directly. Instead, you should create MLClient and use this client via the
+ property MLClient.code
+
+ :param operation_scope: Scope variables for the operations classes of an MLClient object.
+ :type operation_scope: ~azure.ai.ml._scope_dependent_operations.OperationScope
+ :param operation_config: Common configuration for operations classes of an MLClient object.
+ :type operation_config: ~azure.ai.ml._scope_dependent_operations.OperationConfig
+ :param service_client: Service client to allow end users to operate on Azure Machine Learning Workspace resources.
+ :type service_client: typing.Union[
+ ~azure.ai.ml._restclient.v2022_10_01_preview._azure_machine_learning_workspaces.AzureMachineLearningWorkspaces,
+ ~azure.ai.ml._restclient.v2021_10_01_dataplanepreview._azure_machine_learning_workspaces.
+ AzureMachineLearningWorkspaces,
+ ~azure.ai.ml._restclient.v2023_04_01._azure_machine_learning_workspaces.AzureMachineLearningWorkspaces]
+ :param datastore_operations: Represents a client for performing operations on Datastores.
+ :type datastore_operations: ~azure.ai.ml.operations._datastore_operations.DatastoreOperations
+ """
+
+ def __init__(
+ self,
+ operation_scope: OperationScope,
+ operation_config: OperationConfig,
+ service_client: Union[ServiceClient102022, ServiceClient102021Dataplane, ServiceClient042023],
+ datastore_operations: DatastoreOperations,
+ **kwargs: Dict,
+ ):
+ super(CodeOperations, self).__init__(operation_scope, operation_config)
+ ops_logger.update_filter()
+ self._service_client = service_client
+ self._version_operation = service_client.code_versions
+ self._container_operation = service_client.code_containers
+ self._datastore_operation = datastore_operations
+ self._init_kwargs = kwargs
+
+ @monitor_with_activity(ops_logger, "Code.CreateOrUpdate", ActivityType.PUBLICAPI)
+ def create_or_update(self, code: Code) -> Code:
+ """Returns created or updated code asset.
+
+ If not already in storage, asset will be uploaded to the workspace's default datastore.
+
+ :param code: Code asset object.
+ :type code: Code
+ :raises ~azure.ai.ml.exceptions.AssetPathException: Raised when the Code artifact path is
+ already linked to another asset
+ :raises ~azure.ai.ml.exceptions.ValidationException: Raised if Code cannot be successfully validated.
+ Details will be provided in the error message.
+ :raises ~azure.ai.ml.exceptions.EmptyDirectoryError: Raised if local path provided points to an empty directory.
+ :return: Code asset object.
+ :rtype: ~azure.ai.ml.entities.Code
+
+ .. admonition:: Example:
+
+ .. literalinclude:: ../samples/ml_samples_misc.py
+ :start-after: [START code_operations_create_or_update]
+ :end-before: [END code_operations_create_or_update]
+ :language: python
+ :dedent: 8
+ :caption: Create code asset example.
+ """
+ try:
+ name = code.name
+ version = code.version
+ sas_uri = None
+ blob_uri = None
+
+ if self._registry_name:
+ sas_uri = get_sas_uri_for_registry_asset(
+ service_client=self._service_client,
+ name=name,
+ version=version,
+ resource_group=self._resource_group_name,
+ registry=self._registry_name,
+ body=get_asset_body_for_registry_storage(self._registry_name, "codes", name, version),
+ )
+ else:
+ snapshot_path_info = _get_snapshot_path_info(code)
+ if snapshot_path_info:
+ _, _, asset_hash = snapshot_path_info
+ existing_assets = list(
+ self._version_operation.list(
+ resource_group_name=self._resource_group_name,
+ workspace_name=self._workspace_name,
+ name=name,
+ hash=asset_hash,
+ hash_version=str(get_content_hash_version()),
+ )
+ )
+
+ if len(existing_assets) > 0:
+ existing_asset = existing_assets[0]
+ name, version = _get_existing_asset_name_and_version(existing_asset)
+ return self.get(name=name, version=version)
+ sas_info = get_storage_info_for_non_registry_asset(
+ service_client=self._service_client,
+ workspace_name=self._workspace_name,
+ name=name,
+ version=version,
+ resource_group=self._resource_group_name,
+ )
+ sas_uri = sas_info["sas_uri"]
+ blob_uri = sas_info["blob_uri"]
+
+ code, _ = _check_and_upload_path(
+ artifact=code,
+ asset_operations=self,
+ sas_uri=sas_uri,
+ artifact_type=ErrorTarget.CODE,
+ show_progress=self._show_progress,
+ blob_uri=blob_uri,
+ )
+
+ # For anonymous code, if the code already exists in storage, we reuse the name,
+ # version stored in the storage metadata so the same anonymous code won't be created again.
+ if code._is_anonymous:
+ name = code.name
+ version = code.version
+
+ code_version_resource = code._to_rest_object()
+
+ result = (
+ self._version_operation.begin_create_or_update(
+ name=name,
+ version=version,
+ registry_name=self._registry_name,
+ resource_group_name=self._operation_scope.resource_group_name,
+ body=code_version_resource,
+ **self._init_kwargs,
+ ).result()
+ if self._registry_name
+ else self._version_operation.create_or_update(
+ name=name,
+ version=version,
+ workspace_name=self._workspace_name,
+ resource_group_name=self._operation_scope.resource_group_name,
+ body=code_version_resource,
+ **self._init_kwargs,
+ )
+ )
+
+ if not result:
+ return self.get(name=name, version=version)
+ return Code._from_rest_object(result)
+ except Exception as ex:
+ if isinstance(ex, (ValidationException, SchemaValidationError)):
+ log_and_raise_error(ex)
+ elif isinstance(ex, HttpResponseError):
+ # service side raises an exception if we attempt to update an existing asset's asset path
+ if str(ex) == ASSET_PATH_ERROR:
+ raise AssetPathException(
+ message=CHANGED_ASSET_PATH_MSG,
+ target=ErrorTarget.CODE,
+ no_personal_data_message=CHANGED_ASSET_PATH_MSG_NO_PERSONAL_DATA,
+ error_category=ErrorCategory.USER_ERROR,
+ ) from ex
+ raise ex
+
+ @monitor_with_activity(ops_logger, "Code.Get", ActivityType.PUBLICAPI)
+ def get(self, name: str, version: str) -> Code:
+ """Returns information about the specified code asset.
+
+ :param name: Name of the code asset.
+ :type name: str
+ :param version: Version of the code asset.
+ :type version: str
+ :raises ~azure.ai.ml.exceptions.ValidationException: Raised if Code cannot be successfully validated.
+ Details will be provided in the error message.
+ :return: Code asset object.
+ :rtype: ~azure.ai.ml.entities.Code
+
+ .. admonition:: Example:
+
+ .. literalinclude:: ../samples/ml_samples_misc.py
+ :start-after: [START code_operations_get]
+ :end-before: [END code_operations_get]
+ :language: python
+ :dedent: 8
+ :caption: Get code asset example.
+ """
+ return self._get(name=name, version=version)
+
+ # this is a public API but CodeOperations is hidden, so it may only monitor internal calls
+ @monitor_with_activity(ops_logger, "Code.Download", ActivityType.PUBLICAPI)
+ def download(self, name: str, version: str, download_path: Union[PathLike, str]) -> None:
+ """Download content of a code.
+
+ :param str name: Name of the code.
+ :param str version: Version of the code.
+ :param Union[PathLike, str] download_path: Local path as download destination,
+ defaults to current working directory of the current user. Contents will be overwritten.
+ :raise: ResourceNotFoundError if can't find a code matching provided name.
+ """
+ output_dir = Path(download_path)
+ if output_dir.is_dir():
+ # an OSError will be raised if the directory is not empty
+ output_dir.rmdir()
+ output_dir.mkdir(parents=True)
+
+ code = self._get(name=name, version=version)
+
+ # TODO: how should we maintain this regex?
+ m = re.match(
+ r"https://(?P<account_name>.+)\.blob\.core\.windows\.net"
+ r"(:[0-9]+)?/(?P<container_name>.+)/(?P<blob_name>.*)",
+ str(code.path),
+ )
+ if not m:
+ raise ValueError(f"Invalid code path: {code.path}")
+
+ datastore_info = get_datastore_info(
+ self._datastore_operation,
+ # always use WORKSPACE_BLOB_STORE
+ name=_get_datastore_name(),
+ container_name=m.group("container_name"),
+ )
+ storage_client = get_storage_client(**datastore_info)
+ storage_client.download(
+ starts_with=m.group("blob_name"),
+ destination=output_dir.as_posix(),
+ )
+ if not output_dir.is_dir() or not any(output_dir.iterdir()):
+ raise RuntimeError(f"Failed to download code to {output_dir}")
+
+ def _get(self, name: str, version: Optional[str] = None) -> Code:
+ if not version:
+ msg = "Code asset version must be specified as part of name parameter, in format 'name:version'."
+ raise ValidationException(
+ message=msg,
+ target=ErrorTarget.CODE,
+ no_personal_data_message=msg,
+ error_category=ErrorCategory.USER_ERROR,
+ error_type=ValidationErrorType.INVALID_VALUE,
+ )
+ code_version_resource = (
+ self._version_operation.get(
+ name=name,
+ version=version,
+ resource_group_name=self._operation_scope.resource_group_name,
+ registry_name=self._registry_name,
+ **self._init_kwargs,
+ )
+ if self._registry_name
+ else self._version_operation.get(
+ name=name,
+ version=version,
+ resource_group_name=self._operation_scope.resource_group_name,
+ workspace_name=self._workspace_name,
+ **self._init_kwargs,
+ )
+ )
+ return Code._from_rest_object(code_version_resource)