|
4 | 4 |
|
5 | 5 | # pylint: disable=protected-access,too-many-lines |
6 | 6 | import time |
| 7 | +import collections |
7 | 8 | import types |
8 | 9 | from functools import partial |
9 | 10 | from inspect import Parameter, signature |
10 | 11 | from os import PathLike |
11 | 12 | from pathlib import Path |
12 | 13 | from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union, cast |
| 14 | +import hashlib |
13 | 15 |
|
14 | 16 | from azure.ai.ml._restclient.v2021_10_01_dataplanepreview import ( |
15 | 17 | AzureMachineLearningWorkspaces as ServiceClient102021Dataplane, |
16 | 18 | ) |
17 | | -from azure.ai.ml._restclient.v2024_01_01_preview import AzureMachineLearningWorkspaces as ServiceClient012024 |
18 | | -from azure.ai.ml._restclient.v2024_01_01_preview.models import ComponentVersion, ListViewType |
| 19 | +from azure.ai.ml._restclient.v2024_01_01_preview import ( |
| 20 | + AzureMachineLearningWorkspaces as ServiceClient012024, |
| 21 | +) |
| 22 | +from azure.ai.ml._restclient.v2024_01_01_preview.models import ( |
| 23 | + ComponentVersion, |
| 24 | + ListViewType, |
| 25 | +) |
19 | 26 | from azure.ai.ml._scope_dependent_operations import ( |
20 | 27 | OperationConfig, |
21 | 28 | OperationsContainer, |
22 | 29 | OperationScope, |
23 | 30 | _ScopeDependentOperations, |
24 | 31 | ) |
25 | | -from azure.ai.ml._telemetry import ActivityType, monitor_with_activity, monitor_with_telemetry_mixin |
| 32 | +from azure.ai.ml._telemetry import ( |
| 33 | + ActivityType, |
| 34 | + monitor_with_activity, |
| 35 | + monitor_with_telemetry_mixin, |
| 36 | +) |
26 | 37 | from azure.ai.ml._utils._asset_utils import ( |
27 | 38 | _archive_or_restore, |
28 | 39 | _create_or_update_autoincrement, |
| 40 | + _get_file_hash, |
29 | 41 | _get_latest, |
30 | 42 | _get_next_version_from_container, |
31 | 43 | _resolve_label_to_asset, |
| 44 | + get_ignore_file, |
| 45 | + get_upload_files_from_folder, |
| 46 | + IgnoreFile, |
| 47 | + delete_two_catalog_files, |
| 48 | + create_catalog_files, |
32 | 49 | ) |
33 | 50 | from azure.ai.ml._utils._azureml_polling import AzureMLPolling |
34 | 51 | from azure.ai.ml._utils._endpoint_utils import polling_wait |
|
42 | 59 | LROConfigurations, |
43 | 60 | ) |
44 | 61 | from azure.ai.ml.entities import Component, ValidationResult |
45 | | -from azure.ai.ml.exceptions import ComponentException, ErrorCategory, ErrorTarget, ValidationException |
| 62 | +from azure.ai.ml.exceptions import ( |
| 63 | + ComponentException, |
| 64 | + ErrorCategory, |
| 65 | + ErrorTarget, |
| 66 | + ValidationException, |
| 67 | +) |
46 | 68 | from azure.core.exceptions import HttpResponseError, ResourceNotFoundError |
47 | 69 |
|
48 | 70 | from .._utils._cache_utils import CachedNodeResolver |
@@ -282,7 +304,8 @@ def _localize_code(self, component: Component, base_dir: Path) -> None: |
282 | 304 |
|
283 | 305 | target_code_value = "./code" |
284 | 306 | self._code_operations.download( |
285 | | - **extract_name_and_version(code), download_path=base_dir.joinpath(target_code_value) |
| 307 | + **extract_name_and_version(code), |
| 308 | + download_path=base_dir.joinpath(target_code_value), |
286 | 309 | ) |
287 | 310 |
|
288 | 311 | setattr(component, component._get_code_field_name(), target_code_value) |
@@ -311,7 +334,13 @@ def _localize_environment(self, component: Component, base_dir: Path) -> None: |
311 | 334 |
|
312 | 335 | @experimental |
313 | 336 | @monitor_with_telemetry_mixin(ops_logger, "Component.Download", ActivityType.PUBLICAPI) |
314 | | - def download(self, name: str, download_path: Union[PathLike, str] = ".", *, version: Optional[str] = None) -> None: |
| 337 | + def download( |
| 338 | + self, |
| 339 | + name: str, |
| 340 | + download_path: Union[PathLike, str] = ".", |
| 341 | + *, |
| 342 | + version: Optional[str] = None, |
| 343 | + ) -> None: |
315 | 344 | """Download the specified component and its dependencies to local. Local component can be used to create |
316 | 345 | the component in another workspace or for offline development. |
317 | 346 |
|
@@ -491,7 +520,11 @@ def _reset_version_if_no_change(self, component: Component, current_name: str, c |
491 | 520 | return current_version, rest_component_resource |
492 | 521 |
|
493 | 522 | def _create_or_update_component_version( |
494 | | - self, component: Component, name: str, version: Optional[str], rest_component_resource: Any |
| 523 | + self, |
| 524 | + component: Component, |
| 525 | + name: str, |
| 526 | + version: Optional[str], |
| 527 | + rest_component_resource: Any, |
495 | 528 | ) -> Any: |
496 | 529 | try: |
497 | 530 | if self._registry_name: |
@@ -652,6 +685,28 @@ def create_or_update( |
652 | 685 | ) |
653 | 686 | return component |
654 | 687 |
|
| 688 | + @experimental |
| 689 | + def prepare_for_sign(self, component: Component): |
| 690 | + ignore_file = IgnoreFile() |
| 691 | + |
| 692 | + if isinstance(component, ComponentCodeMixin): |
| 693 | + with component._build_code() as code: |
| 694 | + delete_two_catalog_files(code.path) |
| 695 | + ignore_file = get_ignore_file(code.path) if code._ignore_file is None else ignore_file |
| 696 | + file_list = get_upload_files_from_folder(code.path, ignore_file=ignore_file) |
| 697 | + json_stub = {} |
| 698 | + json_stub["HashAlgorithm"] = "SHA256" |
| 699 | + json_stub["CatalogItems"] = {} # type: ignore |
| 700 | + |
| 701 | + for file_path, file_name in sorted(file_list, key=lambda x: str(x[1]).lower()): |
| 702 | + file_hash = _get_file_hash(file_path, hashlib.sha256()).hexdigest().upper() |
| 703 | + json_stub["CatalogItems"][file_name] = file_hash # type: ignore |
| 704 | + |
| 705 | + json_stub["CatalogItems"] = collections.OrderedDict( # type: ignore |
| 706 | + sorted(json_stub["CatalogItems"].items()) # type: ignore |
| 707 | + ) |
| 708 | + create_catalog_files(code.path, json_stub) |
| 709 | + |
655 | 710 | @monitor_with_telemetry_mixin(ops_logger, "Component.Archive", ActivityType.PUBLICAPI) |
656 | 711 | def archive( |
657 | 712 | self, |
@@ -860,7 +915,9 @@ def _resolve_binding_on_supported_fields_for_node(cls, node: BaseNode) -> None: |
860 | 915 | :param node: The node |
861 | 916 | :type node: BaseNode |
862 | 917 | """ |
863 | | - from azure.ai.ml.entities._job.pipeline._attr_dict import try_get_non_arbitrary_attr |
| 918 | + from azure.ai.ml.entities._job.pipeline._attr_dict import ( |
| 919 | + try_get_non_arbitrary_attr, |
| 920 | + ) |
864 | 921 | from azure.ai.ml.entities._job.pipeline._io import PipelineInput |
865 | 922 |
|
866 | 923 | # compute binding to pipeline input is supported on node. |
@@ -968,7 +1025,9 @@ def _try_resolve_compute_for_node(cls, node: BaseNode, _: str, resolver: _AssetR |
968 | 1025 |
|
969 | 1026 | @classmethod |
970 | 1027 | def _divide_nodes_to_resolve_into_layers( |
971 | | - cls, component: PipelineComponent, extra_operations: List[Callable[[BaseNode, str], Any]] |
| 1028 | + cls, |
| 1029 | + component: PipelineComponent, |
| 1030 | + extra_operations: List[Callable[[BaseNode, str], Any]], |
972 | 1031 | ) -> List: |
973 | 1032 | """Traverse the pipeline component and divide nodes to resolve into layers. Note that all leaf nodes will be |
974 | 1033 | put in the last layer. |
@@ -1029,7 +1088,8 @@ def _divide_nodes_to_resolve_into_layers( |
1029 | 1088 | def _get_workspace_key(self) -> str: |
1030 | 1089 | try: |
1031 | 1090 | workspace_rest = self._workspace_operations._operation.get( |
1032 | | - resource_group_name=self._resource_group_name, workspace_name=self._workspace_name |
| 1091 | + resource_group_name=self._resource_group_name, |
| 1092 | + workspace_name=self._workspace_name, |
1033 | 1093 | ) |
1034 | 1094 | return str(workspace_rest.workspace_id) |
1035 | 1095 | except HttpResponseError: |
@@ -1099,7 +1159,10 @@ def _resolve_dependencies_for_pipeline_component_jobs( |
1099 | 1159 | extra_operations=[ |
1100 | 1160 | # no need to do this as we now keep the original component name for anonymous components |
1101 | 1161 | # self._set_default_display_name_for_anonymous_component_in_node, |
1102 | | - partial(self._try_resolve_node_level_task_for_parallel_node, resolver=resolver), |
| 1162 | + partial( |
| 1163 | + self._try_resolve_node_level_task_for_parallel_node, |
| 1164 | + resolver=resolver, |
| 1165 | + ), |
1103 | 1166 | partial(self._try_resolve_environment_for_component, resolver=resolver), |
1104 | 1167 | partial(self._try_resolve_compute_for_node, resolver=resolver), |
1105 | 1168 | # should we resolve code here after we do extra operations concurrently? |
|
0 commit comments