azureml-registry-tools 0.1.0a3__tar.gz → 0.1.0a5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {azureml_registry_tools-0.1.0a3/azureml_registry_tools.egg-info → azureml_registry_tools-0.1.0a5}/PKG-INFO +1 -1
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/_cli/registry_syndication_cli.py +53 -1
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/_rest_client/registry_management_client.py +1 -1
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/mgmt/syndication_manifest.py +76 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/tools/create_or_update_assets.py +33 -15
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5/azureml_registry_tools.egg-info}/PKG-INFO +1 -1
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/setup.py +1 -1
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/LICENSE.txt +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/__init__.py +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/__init__.py +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/_cli/__init__.py +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/_cli/repo2registry_cli.py +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/_rest_client/__init__.py +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/_rest_client/arm_client.py +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/_rest_client/base_rest_client.py +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/mgmt/__init__.py +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/mgmt/create_manifest.py +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/tools/__init__.py +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/tools/config.py +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/tools/registry_utils.py +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/tools/repo2registry_config.py +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml_registry_tools.egg-info/SOURCES.txt +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml_registry_tools.egg-info/dependency_links.txt +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml_registry_tools.egg-info/entry_points.txt +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml_registry_tools.egg-info/requires.txt +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml_registry_tools.egg-info/top_level.txt +0 -0
- {azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/setup.cfg +0 -0
@@ -3,10 +3,12 @@
|
|
3
3
|
# ---------------------------------------------------------
|
4
4
|
import argparse
|
5
5
|
import json
|
6
|
+
import sys
|
7
|
+
from uuid import UUID
|
6
8
|
from azureml.registry._rest_client.registry_management_client import RegistryManagementClient
|
7
9
|
from azureml.registry._rest_client.arm_client import ArmClient
|
8
10
|
from azureml.registry.mgmt.create_manifest import generate_syndication_manifest
|
9
|
-
from azureml.registry.mgmt.syndication_manifest import SyndicationManifest
|
11
|
+
from azureml.registry.mgmt.syndication_manifest import SyndicationManifest, ResyncAssetsInManifestDto
|
10
12
|
|
11
13
|
|
12
14
|
def syndication_manifest_show(registry_name: str) -> dict:
|
@@ -56,6 +58,31 @@ def syndication_manifest_delete(registry_name: str, dry_run: bool) -> None:
|
|
56
58
|
RegistryManagementClient(registry_name=registry_name).delete_manifest()
|
57
59
|
|
58
60
|
|
61
|
+
def syndication_manifest_sync(registry_name: str, manifest_value: str, tenant_id: str, asset_ids: list, dry_run: bool) -> None:
|
62
|
+
"""Sync assets in the syndication manifest for the specified registry.
|
63
|
+
|
64
|
+
Args:
|
65
|
+
registry_name (str): Name of the AzureML registry.
|
66
|
+
manifest_value (str): Manifest value as a string (JSON).
|
67
|
+
tenant_id (str): Tenant ID to use for all assets.
|
68
|
+
asset_ids (list): List of asset IDs to sync.
|
69
|
+
dry_run (bool): If True, do not perform any changes.
|
70
|
+
"""
|
71
|
+
if manifest_value:
|
72
|
+
# for inline manifest value allow different casing of keys
|
73
|
+
sync_dto = ResyncAssetsInManifestDto.from_dict(json.loads(manifest_value), normalize_keys=True)
|
74
|
+
else:
|
75
|
+
# Create from tenant ID and asset IDs
|
76
|
+
sync_dto = ResyncAssetsInManifestDto.from_asset_list(UUID(tenant_id), asset_ids)
|
77
|
+
|
78
|
+
dto = sync_dto.to_dict()
|
79
|
+
if dry_run:
|
80
|
+
print(f"Dry run: Would sync assets for registry {registry_name} with data: {json.dumps(dto, indent=2)}")
|
81
|
+
else:
|
82
|
+
client = RegistryManagementClient(registry_name=registry_name)
|
83
|
+
client.sync_assets_in_manifest(dto)
|
84
|
+
|
85
|
+
|
59
86
|
def syndication_target_show(registry_name: str) -> object:
|
60
87
|
"""Show the current syndication target(s) for the specified registry and region.
|
61
88
|
|
@@ -129,6 +156,12 @@ def main() -> None:
|
|
129
156
|
# Delete the current manifest
|
130
157
|
registry-mgmt syndication manifest delete --registry-name myreg
|
131
158
|
|
159
|
+
# Sync assets from a JSON value
|
160
|
+
registry-mgmt syndication manifest sync --registry-name myreg --value '{"AssetsToResync": [...]}'
|
161
|
+
|
162
|
+
# Sync assets from tenant ID and asset list
|
163
|
+
registry-mgmt syndication manifest sync --registry-name myreg --tenant-id "12345678-1234-1234-1234-123456789012" --asset-ids asset1 asset2 asset3
|
164
|
+
|
132
165
|
# Show the current target
|
133
166
|
registry-mgmt syndication target show --registry-name myreg
|
134
167
|
|
@@ -184,6 +217,13 @@ def main() -> None:
|
|
184
217
|
manifest_delete_parser = manifest_subparsers.add_parser("delete", help="Delete the current manifest.")
|
185
218
|
_add_common_args(manifest_delete_parser, [registry_name_arg, dry_run_arg])
|
186
219
|
|
220
|
+
manifest_sync_parser = manifest_subparsers.add_parser("sync", help="Sync assets in the manifest.")
|
221
|
+
_add_common_args(manifest_sync_parser, [registry_name_arg, dry_run_arg])
|
222
|
+
sync_group = manifest_sync_parser.add_mutually_exclusive_group(required=True)
|
223
|
+
sync_group.add_argument("-v", "--value", type=str, help="Manifest sync value as JSON string.")
|
224
|
+
sync_group.add_argument("-t", "--tenant-id", type=str, help="Tenant ID to use for all assets.")
|
225
|
+
manifest_sync_parser.add_argument("-a", "--asset-ids", type=str, nargs="+", help="List of asset IDs to sync (only valid with --tenant-id).")
|
226
|
+
|
187
227
|
# syndication target
|
188
228
|
target_parser = synd_subparsers.add_parser("target", help="Manage syndication target.")
|
189
229
|
target_subparsers = target_parser.add_subparsers(dest="target_subcommand", required=True)
|
@@ -215,6 +255,18 @@ def main() -> None:
|
|
215
255
|
syndication_manifest_delete(args.registry_name, args.dry_run)
|
216
256
|
else:
|
217
257
|
print("Manifest deletion cancelled.")
|
258
|
+
elif args.manifest_subcommand == "sync":
|
259
|
+
# Validate constraints for sync command
|
260
|
+
if args.tenant_id and not args.asset_ids:
|
261
|
+
print("Error: --asset-ids is required when using --tenant-id", file=sys.stderr)
|
262
|
+
sys.exit(1)
|
263
|
+
elif args.asset_ids and not args.tenant_id:
|
264
|
+
print("Error: --asset-ids can only be used with --tenant-id", file=sys.stderr)
|
265
|
+
sys.exit(1)
|
266
|
+
elif not args.value and not args.tenant_id:
|
267
|
+
print("Error: Either --value or --tenant-id must be specified", file=sys.stderr)
|
268
|
+
sys.exit(1)
|
269
|
+
syndication_manifest_sync(args.registry_name, args.value, args.tenant_id, args.asset_ids, args.dry_run)
|
218
270
|
elif args.synd_subcommand == "target":
|
219
271
|
if args.target_subcommand == "show":
|
220
272
|
print(syndication_target_show(args.registry_name))
|
@@ -57,7 +57,7 @@ class RegistryManagementClient(BaseAzureRestClient):
|
|
57
57
|
response = self.post(url, json=manifest_dto)
|
58
58
|
return response.json()
|
59
59
|
|
60
|
-
def
|
60
|
+
def sync_assets_in_manifest(self, resync_assets_dto: dict) -> dict:
|
61
61
|
"""
|
62
62
|
Resynchronize assets in the syndication manifest.
|
63
63
|
|
@@ -148,3 +148,79 @@ class SyndicationManifest:
|
|
148
148
|
tenant_id=tenant_id,
|
149
149
|
source_registries=source_registries
|
150
150
|
)
|
151
|
+
|
152
|
+
|
153
|
+
@dataclass
|
154
|
+
class AssetToResync:
|
155
|
+
"""Represents an asset to resync with its associated tenant ID."""
|
156
|
+
|
157
|
+
asset_id: str
|
158
|
+
tenant_id: UUID
|
159
|
+
|
160
|
+
def to_dict(self) -> dict:
|
161
|
+
"""Convert the AssetToResync instance to a dictionary."""
|
162
|
+
return {
|
163
|
+
"AssetId": self.asset_id,
|
164
|
+
"TenantId": str(self.tenant_id)
|
165
|
+
}
|
166
|
+
|
167
|
+
@staticmethod
|
168
|
+
def from_dict(d: dict, normalize_keys=False) -> "AssetToResync":
|
169
|
+
"""Create an AssetToResync instance from a dictionary."""
|
170
|
+
asset_id = _get_key(d, "AssetId", "asset_id", normalize_keys=normalize_keys)
|
171
|
+
if asset_id is None:
|
172
|
+
raise ValueError("Missing required field 'AssetId' in dictionary.")
|
173
|
+
|
174
|
+
tenant_id_str = _get_key(d, "TenantId", "tenant_id", normalize_keys=normalize_keys)
|
175
|
+
if tenant_id_str is None:
|
176
|
+
raise ValueError("Missing required field 'TenantId' in dictionary.")
|
177
|
+
|
178
|
+
try:
|
179
|
+
tenant_id = UUID(str(tenant_id_str))
|
180
|
+
except ValueError:
|
181
|
+
raise ValueError("TenantId must be a valid UUID string.")
|
182
|
+
|
183
|
+
return AssetToResync(
|
184
|
+
asset_id=str(asset_id),
|
185
|
+
tenant_id=tenant_id
|
186
|
+
)
|
187
|
+
|
188
|
+
|
189
|
+
@dataclass
|
190
|
+
class ResyncAssetsInManifestDto:
|
191
|
+
"""Represents a collection of assets to resync in a manifest."""
|
192
|
+
|
193
|
+
assets_to_resync: List[AssetToResync]
|
194
|
+
|
195
|
+
def to_dict(self) -> dict:
|
196
|
+
"""Convert the ResyncAssetsInManifestDto instance to a dictionary."""
|
197
|
+
return {
|
198
|
+
"AssetsToResync": [asset.to_dict() for asset in self.assets_to_resync]
|
199
|
+
}
|
200
|
+
|
201
|
+
@staticmethod
|
202
|
+
def from_dict(d: dict, normalize_keys=False) -> "ResyncAssetsInManifestDto":
|
203
|
+
"""Create a ResyncAssetsInManifestDto instance from a dictionary."""
|
204
|
+
assets_list = _get_key(d, "AssetsToResync", "assets_to_resync", normalize_keys=normalize_keys)
|
205
|
+
if not isinstance(assets_list, list):
|
206
|
+
raise ValueError("AssetsToResync must be a list.")
|
207
|
+
return ResyncAssetsInManifestDto(
|
208
|
+
assets_to_resync=[AssetToResync.from_dict(asset, normalize_keys=normalize_keys) for asset in assets_list]
|
209
|
+
)
|
210
|
+
|
211
|
+
@staticmethod
|
212
|
+
def from_asset_list(tenant_id: UUID, asset_ids: List[str]) -> "ResyncAssetsInManifestDto":
|
213
|
+
"""Create a ResyncAssetsInManifestDto instance from a tenant ID and list of asset ID strings.
|
214
|
+
|
215
|
+
Args:
|
216
|
+
tenant_id (UUID): The tenant ID to use for all assets.
|
217
|
+
asset_ids (List[str]): List of asset ID strings.
|
218
|
+
|
219
|
+
Returns:
|
220
|
+
ResyncAssetsInManifestDto: Instance with assets created from the provided IDs and tenant ID.
|
221
|
+
"""
|
222
|
+
assets_to_resync = [
|
223
|
+
AssetToResync(asset_id=asset_id, tenant_id=tenant_id)
|
224
|
+
for asset_id in asset_ids
|
225
|
+
]
|
226
|
+
return ResyncAssetsInManifestDto(assets_to_resync=assets_to_resync)
|
@@ -5,6 +5,7 @@
|
|
5
5
|
"""Create or update assets."""
|
6
6
|
|
7
7
|
import azure
|
8
|
+
import copy
|
8
9
|
import json
|
9
10
|
import os
|
10
11
|
import re
|
@@ -14,7 +15,8 @@ from git import InvalidGitRepositoryError, Repo
|
|
14
15
|
from pathlib import Path
|
15
16
|
from typing import List, Union
|
16
17
|
|
17
|
-
from azure.ai.ml import MLClient, load_data, load_model
|
18
|
+
from azure.ai.ml import MLClient, load_component, load_data, load_model
|
19
|
+
from azure.ai.ml.entities._component.component import Component
|
18
20
|
from azure.ai.ml.entities._assets._artifacts.data import Data
|
19
21
|
from azure.ai.ml.entities._assets._artifacts.model import Model
|
20
22
|
from azure.identity import DefaultAzureCredential
|
@@ -121,38 +123,52 @@ def find_assets(input_dir: Path,
|
|
121
123
|
|
122
124
|
|
123
125
|
def merge_yamls(existing_asset_file_name: str,
|
124
|
-
|
125
|
-
"""
|
126
|
+
updated_asset: AssetSpec) -> dict:
|
127
|
+
"""Preserve storage value from existing asset to updated asset.
|
126
128
|
|
127
129
|
Args:
|
128
130
|
existing_asset_file_name (str): Name of file containing details of asset currently in registry.
|
129
|
-
|
130
|
-
|
131
|
+
updated_asset (AssetSpec): AssetSpec containing updated asset info.
|
131
132
|
Returns:
|
132
133
|
dict: Merged asset data.
|
133
134
|
"""
|
134
|
-
with open(existing_asset_file_name, "r") as existing_asset_file, open(
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
merged_asset
|
135
|
+
with open(existing_asset_file_name, "r") as existing_asset_file, open(updated_asset.file_name_with_path, "r") as updated_asset_file:
|
136
|
+
existing_asset_dict = yaml.safe_load(existing_asset_file)
|
137
|
+
updated_asset_dict = yaml.safe_load(updated_asset_file)
|
138
|
+
|
139
|
+
merged_asset = copy.deepcopy(updated_asset_dict)
|
140
|
+
|
141
|
+
if updated_asset.type == AssetType.MODEL or updated_asset.type == AssetType.DATA:
|
142
|
+
storage_key = "path"
|
143
|
+
# "path" is always required for models, use the existing value
|
144
|
+
if existing_asset_dict.get(storage_key) != updated_asset_dict.get(storage_key):
|
145
|
+
print(f'"{storage_key}" is immutable, using existing value from asset in registry: {existing_asset_dict[storage_key]}')
|
146
|
+
merged_asset[storage_key] = existing_asset_dict[storage_key]
|
147
|
+
elif updated_asset.type == AssetType.COMPONENT:
|
148
|
+
storage_key = "code"
|
149
|
+
if existing_asset_dict.get(storage_key) is None and updated_asset_dict.get(storage_key) is not None:
|
150
|
+
print(f'"{storage_key}" is immutable and cannot be added. Please create a new version to include "{storage_key}"')
|
151
|
+
elif existing_asset_dict.get(storage_key) != updated_asset_dict.get(storage_key):
|
152
|
+
print(f'"{storage_key}" is immutable, using existing value from asset in registry: {existing_asset_dict[storage_key]}')
|
153
|
+
merged_asset[storage_key] = existing_asset_dict[storage_key]
|
139
154
|
|
140
155
|
return merged_asset
|
141
156
|
|
142
157
|
|
143
|
-
def merge_assets(existing_asset: Union[Data, Model],
|
158
|
+
def merge_assets(existing_asset: Union[Component, Data, Model],
|
144
159
|
asset: AssetSpec):
|
145
|
-
"""
|
160
|
+
"""Prepare asset for update by preserving storage value from existing asset.
|
146
161
|
|
147
162
|
Args:
|
148
|
-
existing_asset (Union[Data, Model]): Asset currently in registry.
|
163
|
+
existing_asset (Union[Component, Data, Model]): Asset currently in registry.
|
149
164
|
asset (AssetSpec): AssetSpec containing updated asset info.
|
150
165
|
"""
|
151
166
|
with tempfile.NamedTemporaryFile(suffix=".yaml", delete=False) as existing_asset_temp_file:
|
152
167
|
existing_asset_temp_file_name = existing_asset_temp_file.name
|
153
168
|
|
154
169
|
existing_asset.dump(existing_asset_temp_file_name)
|
155
|
-
|
170
|
+
|
171
|
+
merged_result = merge_yamls(existing_asset_temp_file_name, asset)
|
156
172
|
|
157
173
|
with tempfile.NamedTemporaryFile(suffix=".yaml", delete=False) as merged_asset_temp_file:
|
158
174
|
merged_asset_temp_file_name = merged_asset_temp_file.name
|
@@ -164,6 +180,8 @@ def merge_assets(existing_asset: Union[Data, Model],
|
|
164
180
|
merged_asset = load_model(merged_asset_temp_file_name)
|
165
181
|
elif asset.type == AssetType.DATA:
|
166
182
|
merged_asset = load_data(merged_asset_temp_file_name)
|
183
|
+
elif asset.type == AssetType.COMPONENT:
|
184
|
+
merged_asset = load_component(merged_asset_temp_file_name)
|
167
185
|
|
168
186
|
asset.set_asset_obj_to_create_or_update(merged_asset)
|
169
187
|
|
@@ -289,7 +307,7 @@ def create_or_update_assets(input_dir: Path,
|
|
289
307
|
raise
|
290
308
|
|
291
309
|
if existing_asset:
|
292
|
-
if asset.type
|
310
|
+
if asset.type in [AssetType.COMPONENT, AssetType.DATA, AssetType.MODEL]:
|
293
311
|
try:
|
294
312
|
merge_assets(existing_asset, asset)
|
295
313
|
except Exception as e:
|
File without changes
|
File without changes
|
{azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/__init__.py
RENAMED
File without changes
|
{azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/_cli/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/mgmt/__init__.py
RENAMED
File without changes
|
File without changes
|
{azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/tools/__init__.py
RENAMED
File without changes
|
{azureml_registry_tools-0.1.0a3 → azureml_registry_tools-0.1.0a5}/azureml/registry/tools/config.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|