azureml-registry-tools 0.1.0a10__py3-none-any.whl → 0.1.0a12__py3-none-any.whl

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.
@@ -6,13 +6,17 @@ import json
6
6
  import sys
7
7
  from pathlib import Path
8
8
  from uuid import UUID
9
- from azureml.registry._rest_client.registry_management_client import RegistryManagementClient
9
+
10
10
  from azureml.registry._rest_client.arm_client import ArmClient
11
- from azureml.registry.mgmt.asset_management import asset_validate, asset_deploy
11
+ from azureml.registry._rest_client.registry_management_client import RegistryManagementClient
12
+ from azureml.registry.mgmt.asset_management import asset_deploy, asset_validate
12
13
  from azureml.registry.mgmt.create_asset_template import asset_template
13
14
  from azureml.registry.mgmt.create_manifest import generate_syndication_manifest
15
+ from azureml.registry.mgmt.model_management import (
16
+ model_delete, model_get, model_list, parse_asset_id
17
+ )
14
18
  from azureml.registry.mgmt.registry_config import create_registry_config
15
- from azureml.registry.mgmt.syndication_manifest import SyndicationManifest, ResyncAssetsInManifestDto
19
+ from azureml.registry.mgmt.syndication_manifest import ResyncAssetsInManifestDto, SyndicationManifest
16
20
 
17
21
 
18
22
  def syndication_manifest_show(registry_name: str) -> dict:
@@ -195,6 +199,36 @@ def main() -> None:
195
199
  # Create asset template files
196
200
  registry-mgmt asset template --folder ./folder-path
197
201
 
202
+ # List models in registry (auto-discover registry details: subscription/resource-group/primary-region)
203
+ registry-mgmt model list --registry-name myreg
204
+
205
+ # List models with explicit subscription, resource group, and primary region
206
+ registry-mgmt model list --registry-name myreg --subscription sub123 --resource-group rg123 --primary-region eastus
207
+
208
+ # Get a specific model by name and version (auto-discover registry details: subscription/resource-group/primary-region)
209
+ registry-mgmt model get --registry-name myreg --name mymodel --version 1
210
+
211
+ # Get a specific model with explicit subscription, resource group, and primary region
212
+ registry-mgmt model get --registry-name myreg --subscription sub123 --resource-group rg123 --primary-region eastus --name mymodel --version 1
213
+
214
+ # Get a model by asset ID (auto-discover registry details: subscription/resource-group/primary-region)
215
+ registry-mgmt model get --asset-id azureml://registries/myreg/models/mymodel/versions/1
216
+
217
+ # Delete a specific model version (auto-discover registry details: subscription/resource-group/primary-region)
218
+ registry-mgmt model delete --registry-name myreg --name mymodel --version 1
219
+
220
+ # Delete a specific model version with explicit subscription, resource group, and primary region
221
+ registry-mgmt model delete --registry-name myreg --subscription sub123 --resource-group rg123 --primary-region eastus --name mymodel --version 1
222
+
223
+ # Delete a model by asset ID (auto-discover registry details: subscription/resource-group/primary-region)
224
+ registry-mgmt model delete --asset-id azureml://registries/myreg/models/mymodel/versions/1
225
+
226
+ # Delete all versions of a model (auto-discover registry details: subscription/resource-group/primary-region)
227
+ registry-mgmt model delete --registry-name myreg --name mymodel --force
228
+
229
+ # Delete all versions of a model with explicit subscription, resource group, and primary region
230
+ registry-mgmt model delete --registry-name myreg --subscription sub123 --resource-group rg123 --primary-region eastus --name mymodel --force
231
+
198
232
  # Show registry discovery info
199
233
  registry-mgmt show --registry-name myreg
200
234
 
@@ -216,6 +250,52 @@ def main() -> None:
216
250
  }
217
251
  }
218
252
 
253
+ subscription_arg = {
254
+ "args": ("--subscription",),
255
+ "kwargs": {
256
+ "type": str,
257
+ "required": True,
258
+ "help": "Registry subscription ID."
259
+ }
260
+ }
261
+
262
+ resource_group_arg = {
263
+ "args": ("-g", "--resource-group"),
264
+ "kwargs": {
265
+ "type": str,
266
+ "required": True,
267
+ "help": "Registry resource group."
268
+ }
269
+ }
270
+
271
+ # Model-specific arguments (optional for registry auto-discovery)
272
+ model_subscription_arg = {
273
+ "args": ("--subscription",),
274
+ "kwargs": {
275
+ "type": str,
276
+ "required": False,
277
+ "help": "Registry subscription ID. If not provided, auto-resolves from registry discovery."
278
+ }
279
+ }
280
+
281
+ model_resource_group_arg = {
282
+ "args": ("-g", "--resource-group"),
283
+ "kwargs": {
284
+ "type": str,
285
+ "required": False,
286
+ "help": "Registry resource group. If not provided, auto-resolves from registry discovery."
287
+ }
288
+ }
289
+
290
+ model_primary_region_arg = {
291
+ "args": ("--primary-region",),
292
+ "kwargs": {
293
+ "type": str,
294
+ "required": False,
295
+ "help": "Registry primary region. If not provided, auto-resolves from registry discovery."
296
+ }
297
+ }
298
+
219
299
  dry_run_arg = {
220
300
  "args": ("--dry-run",),
221
301
  "kwargs": {
@@ -279,9 +359,7 @@ def main() -> None:
279
359
 
280
360
  # asset config command
281
361
  asset_config_parser = asset_subparsers.add_parser("config", help="Create registry configuration file.")
282
- asset_config_parser.add_argument("--registry-name", type=str, required=True, help="AzureML Registry name.")
283
- asset_config_parser.add_argument("--subscription", type=str, required=True, help="Registry subscription ID.")
284
- asset_config_parser.add_argument("-g", "--resource-group", type=str, required=True, help="Registry resource group.")
362
+ _add_common_args(asset_config_parser, [registry_name_arg, subscription_arg, resource_group_arg])
285
363
  asset_config_parser.add_argument("--tenant-id", type=str, required=True, help="Registry Tenant ID.")
286
364
  asset_config_parser.add_argument("-c", "--config-file", type=validate_registry_cfg, help="Registry config file path to write to (default: registry-mgmt.cfg).")
287
365
  asset_config_parser.add_argument("--storage-name", type=str, help="Storage account name for storage overrides.")
@@ -293,6 +371,42 @@ def main() -> None:
293
371
  _add_common_args(asset_template_parser, [dry_run_arg])
294
372
  asset_template_parser.add_argument("--folder", type=Path, required=True, help="Path to the folder where asset template files will be created.")
295
373
 
374
+ # model root command
375
+ model_parser = subparsers.add_parser("model", help="Model registry operations")
376
+ model_subparsers = model_parser.add_subparsers(dest="model_subcommand", required=True)
377
+
378
+ # model list command
379
+ model_list_parser = model_subparsers.add_parser("list", help="List models in the registry.")
380
+ _add_common_args(model_list_parser, [registry_name_arg, model_subscription_arg, model_resource_group_arg, model_primary_region_arg])
381
+ model_list_parser.add_argument("--name", type=str, help="Filter by model name.")
382
+ model_list_parser.add_argument("--tags", type=str, help="Comma separated string of tags key or tags key=value.")
383
+ model_list_parser.add_argument("--version", type=str, help="Filter by model version.")
384
+ model_list_parser.add_argument("--framework", type=str, help="Filter by framework.")
385
+ model_list_parser.add_argument("--description", type=str, help="Filter by description.")
386
+ model_list_parser.add_argument("--properties", type=str, help="Comma separated string of properties key and/or properties key=value.")
387
+ model_list_parser.add_argument("--run-id", type=str, help="Filter by runId which created the model.")
388
+ model_list_parser.add_argument("--dataset-id", type=str, help="Filter by datasetId associated with the model.")
389
+ model_list_parser.add_argument("--order-by", type=str, help="How the models are ordered in the response.")
390
+ model_list_parser.add_argument("--skip-token", type=str, help="The continuation token to retrieve the next page.")
391
+ model_list_parser.add_argument("--list-view-type", type=str, default="ActiveOnly", help="View type filter (default: ActiveOnly).")
392
+
393
+ # model get command
394
+ model_get_parser = model_subparsers.add_parser("get", help="Get a model by name and version, or by asset ID.")
395
+ _add_common_args(model_get_parser, [registry_name_arg, model_subscription_arg, model_resource_group_arg, model_primary_region_arg])
396
+ model_get_parser.add_argument("--asset-id", type=str, help="Model asset ID like azureml://registries/myRegistry/models/myModel/versions/1. When using asset-id, registry details are auto-discovered.")
397
+ model_get_parser.add_argument("--name", type=str, help="The name of the model.")
398
+ model_get_parser.add_argument("--version", type=str, help="The version of the model.")
399
+ model_get_parser.add_argument("--include-deployment-settings", action="store_true", help="Whether to include deployment settings.")
400
+ model_get_parser.add_argument("--workspace-id", type=str, help="Workspace ID GUID for deployment environment association.")
401
+
402
+ # model delete command
403
+ model_delete_parser = model_subparsers.add_parser("delete", help="Delete a model by name and version, or by asset ID.")
404
+ _add_common_args(model_delete_parser, [registry_name_arg, model_subscription_arg, model_resource_group_arg, model_primary_region_arg, dry_run_arg])
405
+ model_delete_parser.add_argument("--asset-id", type=str, help="Model asset ID like azureml://registries/myRegistry/models/myModel/versions/1. When using asset-id, registry details are auto-discovered.")
406
+ model_delete_parser.add_argument("--name", type=str, help="The name of the model to delete.")
407
+ model_delete_parser.add_argument("--version", type=str, help="The version to delete. If not specified, deletes all versions.")
408
+ model_delete_parser.add_argument("--force", action="store_true", help="Skip confirmation prompt.")
409
+
296
410
  # show root command
297
411
  show_parser = subparsers.add_parser("show", help="Show syndication info.")
298
412
  _add_common_args(show_parser, [registry_name_arg, dry_run_arg])
@@ -356,6 +470,98 @@ def main() -> None:
356
470
  elif args.asset_subcommand == "template":
357
471
  # Create asset template files
358
472
  asset_template(args.folder, args.dry_run)
473
+ elif args.command == "model":
474
+ if args.model_subcommand == "list":
475
+ print(model_list(
476
+ registry_name=args.registry_name,
477
+ subscription_id=args.subscription,
478
+ resource_group_name=args.resource_group,
479
+ primary_region=args.primary_region,
480
+ name=args.name,
481
+ tags=args.tags,
482
+ version=args.version,
483
+ framework=args.framework,
484
+ description=args.description,
485
+ properties=args.properties,
486
+ run_id=args.run_id,
487
+ dataset_id=args.dataset_id,
488
+ order_by=args.order_by,
489
+ skip_token=args.skip_token,
490
+ list_view_type=args.list_view_type
491
+ ))
492
+ elif args.model_subcommand == "get":
493
+ # Validate that either asset_id is provided OR both name and version are provided
494
+ if args.asset_id:
495
+ if args.name or args.version:
496
+ print("Error: Cannot use --asset-id with --name or --version", file=sys.stderr)
497
+ sys.exit(1)
498
+ # Asset ID provided - auto-discover registry details
499
+ try:
500
+ registry_name, model_name, version = parse_asset_id(args.asset_id)
501
+ except Exception as e:
502
+ print(f"Error processing asset ID: {e}", file=sys.stderr)
503
+ sys.exit(1)
504
+ else:
505
+ # Name/version provided - validate both are present
506
+ if not args.name or not args.version:
507
+ print("Error: Both --name and --version are required when not using --asset-id", file=sys.stderr)
508
+ sys.exit(1)
509
+ registry_name = args.registry_name
510
+ model_name = args.name
511
+ version = args.version
512
+
513
+ # Validate required parameters are not None
514
+ if not registry_name or not model_name or not version:
515
+ print("Error: Registry name, model name, and version must be provided", file=sys.stderr)
516
+ sys.exit(1)
517
+
518
+ # Single call to model_get with args values directly
519
+ print(model_get(
520
+ registry_name=registry_name,
521
+ name=model_name,
522
+ version=version,
523
+ subscription_id=args.subscription,
524
+ resource_group_name=args.resource_group,
525
+ primary_region=args.primary_region,
526
+ include_deployment_settings=args.include_deployment_settings,
527
+ workspace_id=args.workspace_id
528
+ ))
529
+ elif args.model_subcommand == "delete":
530
+ # Validate that either asset_id is provided OR name is provided
531
+ if args.asset_id:
532
+ if args.name:
533
+ print("Error: Cannot use --asset-id with --name", file=sys.stderr)
534
+ sys.exit(1)
535
+ # Asset ID provided - auto-discover registry details
536
+ try:
537
+ registry_name, model_name, version = parse_asset_id(args.asset_id)
538
+ except Exception as e:
539
+ print(f"Error processing asset ID: {e}", file=sys.stderr)
540
+ sys.exit(1)
541
+ else:
542
+ if not args.name:
543
+ print("Error: --name is required when not using --asset-id", file=sys.stderr)
544
+ sys.exit(1)
545
+ registry_name = args.registry_name
546
+ model_name = args.name
547
+ version = args.version
548
+
549
+ # Validate required parameters are not None
550
+ if not registry_name or not model_name:
551
+ print("Error: Registry name and model name must be provided", file=sys.stderr)
552
+ sys.exit(1)
553
+
554
+ # Single call to model_delete with args values directly
555
+ model_delete(
556
+ registry_name=registry_name,
557
+ name=model_name,
558
+ version=version,
559
+ subscription_id=args.subscription,
560
+ resource_group_name=args.resource_group,
561
+ primary_region=args.primary_region,
562
+ dry_run=args.dry_run,
563
+ force=args.force
564
+ )
359
565
  elif args.command == "show":
360
566
  print(show_command(args.registry_name, args.as_arm_object))
361
567
  else:
@@ -0,0 +1,333 @@
1
+ # ---------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # ---------------------------------------------------------
4
+ from .base_rest_client import BaseAzureRestClient
5
+ from .registry_management_client import RegistryManagementClient
6
+ from json.decoder import JSONDecodeError
7
+ from typing import Optional, Dict, Any
8
+
9
+ DEFAULT_API_VERSION = "2025-04-01"
10
+
11
+
12
+ class RegistryModelClient(BaseAzureRestClient):
13
+ """Python client for RegistryModelController.
14
+
15
+ Provides key operations for AzureML model management: get, list and, delete models.
16
+ """
17
+
18
+ def __init__(self, registry_name: str, subscription_id: str = None, resource_group_name: str = None,
19
+ primary_region: str = None, api_key: str = None, max_retries: int = 5, backoff_factor: int = 1):
20
+ """
21
+ Initialize the RegistryModelClient.
22
+
23
+ Args:
24
+ registry_name (str): Name of the AzureML registry.
25
+ subscription_id (str, optional): Azure subscription ID. If None, auto-resolves from registry discovery.
26
+ resource_group_name (str, optional): Resource group name containing the registry. If None, auto-resolves from registry discovery.
27
+ primary_region (str, optional): Azure primary region for the registry. If None, auto-resolves from registry discovery.
28
+ api_key (str, optional): API key or bearer token. If None, uses DefaultAzureCredential.
29
+ max_retries (int): Maximum number of retries for requests.
30
+ backoff_factor (int): Backoff factor for retries.
31
+ """
32
+ # Auto-discover missing parameters using registry discovery
33
+ if subscription_id is None or resource_group_name is None or primary_region is None:
34
+ discovery = RegistryManagementClient(registry_name=registry_name).discovery()
35
+ discovered_subscription_id = discovery.get('subscriptionId')
36
+ discovered_resource_group = discovery.get('resourceGroup')
37
+ discovered_primary_region = discovery.get('primaryRegion')
38
+
39
+ # Validate that passed parameters match discovered ones
40
+ if subscription_id is not None and subscription_id != discovered_subscription_id:
41
+ raise ValueError(
42
+ f"Provided subscription_id '{subscription_id}' does not match "
43
+ f"discovered subscription_id '{discovered_subscription_id}' for registry '{registry_name}'"
44
+ )
45
+ if resource_group_name is not None and resource_group_name != discovered_resource_group:
46
+ raise ValueError(
47
+ f"Provided resource_group_name '{resource_group_name}' does not match "
48
+ f"discovered resource_group '{discovered_resource_group}' for registry '{registry_name}'"
49
+ )
50
+ if primary_region is not None and primary_region != discovered_primary_region:
51
+ raise ValueError(
52
+ f"Provided primary_region '{primary_region}' does not match "
53
+ f"discovered primary_region '{discovered_primary_region}' for registry '{registry_name}'"
54
+ )
55
+
56
+ # Use discovered values for any missing parameters
57
+ subscription_id = subscription_id or discovered_subscription_id
58
+ resource_group_name = resource_group_name or discovered_resource_group
59
+ primary_region = primary_region or discovered_primary_region
60
+
61
+ base_url = f"https://{primary_region}.api.azureml.ms"
62
+ super().__init__(base_url, api_key=api_key, max_retries=max_retries, backoff_factor=backoff_factor)
63
+
64
+ self.subscription_id = subscription_id
65
+ self.resource_group_name = resource_group_name
66
+ self.registry_name = registry_name
67
+
68
+ # Build the base path for model operations
69
+ self.base_path = (
70
+ f"/modelregistry/v1.0/subscriptions/{subscription_id}"
71
+ f"/resourceGroups/{resource_group_name}"
72
+ f"/providers/Microsoft.MachineLearningServices"
73
+ f"/registries/{registry_name}/models"
74
+ )
75
+
76
+ def list_models(self, name: str = None, tags: str = None, version: str = None,
77
+ framework: str = None, description: str = None, properties: str = None,
78
+ run_id: str = None, dataset_id: str = None, order_by: str = None,
79
+ skip_token: str = None, list_view_type: str = "ActiveOnly",
80
+ api_version: str = DEFAULT_API_VERSION, **kwargs) -> Dict[str, Any]:
81
+ """
82
+ Query the list of models in a registry.
83
+
84
+ Args:
85
+ name (str, optional): The model name.
86
+ tags (str, optional): Comma separated string of tags key or tags key=value.
87
+ version (str, optional): The model version.
88
+ framework (str, optional): The framework.
89
+ description (str, optional): The model description.
90
+ properties (str, optional): Comma separated string of properties key and/or properties key=value.
91
+ run_id (str, optional): The runId which created the model.
92
+ dataset_id (str, optional): The datasetId associated with the model.
93
+ order_by (str, optional): How the models are ordered in the response.
94
+ skip_token (str, optional): The continuation token to retrieve the next page.
95
+ list_view_type (str): View type filter (default: ActiveOnly).
96
+ api_version (str): The API version to use.
97
+ **kwargs: Additional arguments for the request.
98
+
99
+ Returns:
100
+ Dict[str, Any]: Paged response containing list of models.
101
+ """
102
+ self._refresh_api_key_if_needed()
103
+
104
+ # Build query parameters
105
+ params = {"api-version": api_version, "listViewType": list_view_type}
106
+ if name:
107
+ params["name"] = name
108
+ if tags:
109
+ params["tags"] = tags
110
+ if version:
111
+ params["version"] = version
112
+ if framework:
113
+ params["framework"] = framework
114
+ if description:
115
+ params["description"] = description
116
+ if properties:
117
+ params["properties"] = properties
118
+ if run_id:
119
+ params["runId"] = run_id
120
+ if dataset_id:
121
+ params["datasetId"] = dataset_id
122
+ if order_by:
123
+ params["orderBy"] = order_by
124
+ if skip_token:
125
+ params["$skipToken"] = skip_token
126
+
127
+ url = f"{self.base_url}{self.base_path}"
128
+ response = self.get(url, params=params, **kwargs)
129
+
130
+ try:
131
+ return response.json()
132
+ except JSONDecodeError:
133
+ raise ValueError(f"Failed to decode JSON response from {url}: {response.text.strip()}")
134
+
135
+ # Method below is not currently used in the CLI, but kept for reference
136
+ def get_model_by_asset_id(self, asset_id_or_reference: str, include_deployment_settings: bool = False,
137
+ workspace_id: str = None, api_version: str = DEFAULT_API_VERSION, **kwargs) -> Dict[str, Any]:
138
+ """
139
+ Get a model from registry using assetId or reference.
140
+
141
+ Args:
142
+ asset_id_or_reference (str): Model assetId like azureml://registries/myRegistry/models/myModel/versions/1.
143
+ include_deployment_settings (bool): Whether to include deployment settings.
144
+ workspace_id (str, optional): Workspace ID GUID for deployment environment association.
145
+ api_version (str): The API version to use.
146
+ **kwargs: Additional arguments for the request.
147
+
148
+ Returns:
149
+ Dict[str, Any]: The model data.
150
+ """
151
+ self._refresh_api_key_if_needed()
152
+
153
+ # Use the alternate endpoint
154
+ url = f"{self.base_url}/modelregistry/v1.0/registry/models"
155
+
156
+ params = {
157
+ "assetIdOrReference": asset_id_or_reference,
158
+ "includeDeploymentSettings": str(include_deployment_settings).lower(),
159
+ "api-version": api_version
160
+ }
161
+ if workspace_id:
162
+ params["workspaceId"] = workspace_id
163
+
164
+ response = self.get(url, params=params, **kwargs)
165
+
166
+ try:
167
+ return response.json()
168
+ except JSONDecodeError:
169
+ raise ValueError(f"Failed to decode JSON response from {url}: {response.text.strip()}")
170
+
171
+ def get_model_by_name_and_version(self, name: str, version: str,
172
+ api_version: str = DEFAULT_API_VERSION, **kwargs) -> Dict[str, Any]:
173
+ """
174
+ Get a specific model by name and version from the registry.
175
+
176
+ Args:
177
+ name (str): The name of the model.
178
+ version (str): The version of the model.
179
+ api_version (str): The API version to use.
180
+ **kwargs: Additional arguments for the request.
181
+
182
+ Returns:
183
+ Dict[str, Any]: The model data.
184
+
185
+ Raises:
186
+ ValueError: If name or version is not provided.
187
+ """
188
+ if not name or not version:
189
+ raise ValueError("Both name and version must be provided")
190
+
191
+ self._refresh_api_key_if_needed()
192
+
193
+ # Use the MMS ID format that the server expects: {name}:{version}
194
+ model_id = f"{name}:{version}"
195
+ url = f"{self.base_url}{self.base_path}/{model_id}"
196
+
197
+ response = self.get(url, **kwargs)
198
+
199
+ try:
200
+ return response.json()
201
+ except JSONDecodeError:
202
+ raise ValueError(f"Failed to decode JSON response from {url}: {response.text.strip()}")
203
+
204
+ def delete_model(self, name: str, version: str = None,
205
+ api_version: str = DEFAULT_API_VERSION, **kwargs) -> Optional[Dict[str, Any]]:
206
+ """
207
+ Delete a model from the registry.
208
+
209
+ Args:
210
+ name (str): The name of the model to delete.
211
+ version (str, optional): The version to delete. If None, deletes all versions.
212
+ api_version (str): The API version to use.
213
+ **kwargs: Additional arguments for the request.
214
+
215
+ Returns:
216
+ Optional[Dict[str, Any]]: The deletion response, if any.
217
+
218
+ Raises:
219
+ ValueError: If name is not provided.
220
+ """
221
+ if not name:
222
+ raise ValueError("name must be provided")
223
+
224
+ self._refresh_api_key_if_needed()
225
+
226
+ if version:
227
+ # Delete a specific version using the MMS ID format: {name}:{version}
228
+ model_id = f"{name}:{version}"
229
+ url = f"{self.base_url}{self.base_path}/{model_id}"
230
+ response = self.delete(url, **kwargs)
231
+
232
+ try:
233
+ return response.json()
234
+ except JSONDecodeError:
235
+ return response.text or None
236
+ else:
237
+ # Delete all versions: first list all versions, then delete each one
238
+ list_response = self.list_models(name=name, **kwargs)
239
+
240
+ if "value" not in list_response:
241
+ return {"message": f"No models found with name '{name}'"}
242
+
243
+ models = list_response["value"]
244
+ if not models:
245
+ return {"message": f"No models found with name '{name}'"}
246
+
247
+ deleted_versions = []
248
+ errors = []
249
+
250
+ for model in models:
251
+ try:
252
+ model_version = model.get("properties", {}).get("version") or model.get("version")
253
+ if model_version:
254
+ # Delete this specific version
255
+ model_id = f"{name}:{model_version}"
256
+ url = f"{self.base_url}{self.base_path}/{model_id}"
257
+ response = self.delete(url, **kwargs)
258
+
259
+ if response.status_code in [200, 202, 204]:
260
+ deleted_versions.append(model_version)
261
+ else:
262
+ errors.append(f"Failed to delete version {model_version}: {response.status_code}")
263
+ else:
264
+ errors.append(f"Could not determine version for model: {model}")
265
+ except Exception as e:
266
+ errors.append(f"Error deleting model version: {str(e)}")
267
+
268
+ result = {
269
+ "message": f"Deleted {len(deleted_versions)} version(s) of model '{name}'",
270
+ "deleted_versions": deleted_versions
271
+ }
272
+
273
+ if errors:
274
+ result["errors"] = errors
275
+
276
+ return result
277
+
278
+ # Method below is not currently used in the CLI, but kept for reference
279
+ def get_model_for_non_azure_accounts(self, asset_id: str, api_version: str = DEFAULT_API_VERSION, **kwargs) -> Dict[str, Any]:
280
+ """
281
+ Get a model from registry for non-Azure accounts.
282
+
283
+ Args:
284
+ asset_id (str): Model assetId like azureml://registries/myRegistry/models/myModel/versions/1.
285
+ api_version (str): The API version to use.
286
+ **kwargs: Additional arguments for the request.
287
+
288
+ Returns:
289
+ Dict[str, Any]: The model data with SAS URL.
290
+ """
291
+ self._refresh_api_key_if_needed()
292
+
293
+ url = f"{self.base_url}/modelregistry/v1.0/registry/models/nonazureaccount"
294
+ params = {
295
+ "assetId": asset_id,
296
+ "api-version": api_version
297
+ }
298
+
299
+ response = self.get(url, params=params, **kwargs)
300
+
301
+ try:
302
+ return response.json()
303
+ except JSONDecodeError:
304
+ raise ValueError(f"Failed to decode JSON response from {url}: {response.text.strip()}")
305
+
306
+ @staticmethod
307
+ def parse_asset_id(asset_id: str) -> Dict[str, str]:
308
+ """
309
+ Parse an asset ID into its components.
310
+
311
+ Args:
312
+ asset_id (str): Model assetId like azureml://registries/myRegistry/models/myModel/versions/1.
313
+
314
+ Returns:
315
+ Dict[str, str]: Dictionary containing registry_name, name, and version.
316
+
317
+ Raises:
318
+ ValueError: If the asset ID format is invalid.
319
+ """
320
+ if not asset_id.startswith("azureml://registries/"):
321
+ raise ValueError("Asset ID must start with 'azureml://registries/'")
322
+
323
+ asset_id_parts = asset_id.split("/")
324
+ if len(asset_id_parts) < 8 or asset_id_parts[4] != "models" or asset_id_parts[6] != "versions":
325
+ raise ValueError(
326
+ "Invalid asset ID format. Expected: azureml://registries/myRegistry/models/myModel/versions/1"
327
+ )
328
+
329
+ return {
330
+ "registry_name": asset_id_parts[3],
331
+ "name": asset_id_parts[5],
332
+ "version": asset_id_parts[7]
333
+ }
@@ -179,7 +179,7 @@
179
179
  "pattern": "^(Publicly available sources|bookcorpus|wikipedia)(?:\\s*,\\s*(Publicly available sources|bookcorpus|wikipedia))*$"
180
180
  },
181
181
  "deploymentOptions": {
182
- "description": "Where data was sourced for training as a comma-separated string",
182
+ "description": "Which types of deployment are supported in the UI",
183
183
  "type": "string",
184
184
  "pattern": "^(ServerlessMaaS|UnifiedEndpointMaaS|MaaP|AOAI|Restricted|MonetizedMaaP)(?:\\s*,\\s*(ServerlessMaaS|UnifiedEndpointMaaS|MaaP|AOAI|Restricted|MonetizedMaaP))*$"
185
185
  },
@@ -360,7 +360,7 @@
360
360
  "task": {
361
361
  "description": "Tasks supported by the model as a comma-separated string",
362
362
  "type": "string",
363
- "pattern": "^(audio-analysis|audio-classification|audio-generation|automatic-speech-recognition|chat-completion|completions|content-filters|content-safety|conversational-ai|custom-extraction|data-generation|document-analysis|document-ingestion|document-translation|embeddings|face-detection|fill-mask|forecasting|image-analysis|image-classification|image-feature-extraction|image-text-to-text|image-to-image|image-to-text|intelligent-content-processing|intelligent-document-processing|optical-character-recognition|protein-sequence-generation|protein-structure-prediction|responses|responsible-ai|summarization|text-analysis|text-analytics|text-classification|text-generation|text-to-image|text-to-speech|time-series-forecasting|translation|speech-to-text|speech-translation|video-analysis|video-generation|video-text-to-text|visual-question-answering|zero-shot-classification|zero-shot-image-classification)(?:\\s*,\\s*(audio-analysis|audio-classification|audio-generation|automatic-speech-recognition|chat-completion|completions|content-filters|content-safety|conversational-ai|custom-extraction|data-generation|document-analysis|document-ingestion|document-translation|embeddings|face-detection|fill-mask|forecasting|image-analysis|image-classification|image-feature-extraction|image-text-to-text|image-to-image|image-to-text|intelligent-content-processing|intelligent-document-processing|optical-character-recognition|protein-sequence-generation|protein-structure-prediction|responses|responsible-ai|summarization|text-analysis|text-analytics|text-classification|text-generation|text-to-image|text-to-speech|time-series-forecasting|translation|speech-to-text|speech-translation|video-analysis|video-generation|video-text-to-text|visual-question-answering|zero-shot-classification|zero-shot-image-classification))*$"
363
+ "pattern": "^(audio-analysis|audio-classification|audio-generation|automatic-speech-recognition|chat-completion|completions|content-filters|content-safety|conversational-ai|custom-extraction|data-generation|document-analysis|document-ingestion|document-translation|embeddings|face-detection|fill-mask|forecasting|image-analysis|image-classification|image-feature-extraction|image-text-to-text|image-to-image|image-to-text|intelligent-content-processing|intelligent-document-processing|optical-character-recognition|protein-sequence-generation|protein-structure-prediction|responses|responsible-ai|retrosynthesis-prediction|summarization|text-analysis|text-analytics|text-classification|text-generation|text-to-image|text-to-speech|time-series-forecasting|translation|speech-to-text|speech-translation|video-analysis|video-generation|video-text-to-text|visual-question-answering|zero-shot-classification|zero-shot-image-classification)(?:\\s*,\\s*(audio-analysis|audio-classification|audio-generation|automatic-speech-recognition|chat-completion|completions|content-filters|content-safety|conversational-ai|custom-extraction|data-generation|document-analysis|document-ingestion|document-translation|embeddings|face-detection|fill-mask|forecasting|image-analysis|image-classification|image-feature-extraction|image-text-to-text|image-to-image|image-to-text|intelligent-content-processing|intelligent-document-processing|optical-character-recognition|protein-sequence-generation|protein-structure-prediction|responses|responsible-ai|summarization|text-analysis|text-analytics|text-classification|text-generation|text-to-image|text-to-speech|time-series-forecasting|translation|speech-to-text|speech-translation|video-analysis|video-generation|video-text-to-text|visual-question-answering|zero-shot-classification|zero-shot-image-classification))*$"
364
364
  },
365
365
  "textContextWindow": {
366
366
  "description": "Context window size",
@@ -418,7 +418,7 @@
418
418
  "featureFlags": {
419
419
  "description": "Comma separated list of feature flags for the models to gate usage",
420
420
  "type": "string",
421
- "pattern": "^(OpenAI\\.(?:EAGatingTier|EnterpriseTier|1PGatingTier|OneVetGatingTier|1000CUGatingTier|computer-use-preview|Preview\\.202409)|OpenAITest)(?:\\s*,\\s*(OpenAI\\.(?:EAGatingTier|EnterpriseTier|1PGatingTier|OneVetGatingTier|1000CUGatingTier|computer-use-preview|Preview\\.202409)|OpenAITest))*$"
421
+ "pattern": "^(OpenAI\\.(?:EAGatingTier|EnterpriseTier|1PGatingTier|OneVetGatingTier|1000CUGatingTier|computer-use-preview|Preview\\.202409)|OpenAITest|Microsoft\\.CognitiveServices/OpenAI\\.(?:EAGatingTier|EnterpriseTier|1PGatingTier|OneVetGatingTier|1000CUGatingTier))(?:\\s*,\\s*(OpenAI\\.(?:EAGatingTier|EnterpriseTier|1PGatingTier|OneVetGatingTier|1000CUGatingTier|computer-use-preview|Preview\\.202409)|OpenAITest|Microsoft\\.CognitiveServices/OpenAI\\.(?:EAGatingTier|EnterpriseTier|1PGatingTier|OneVetGatingTier|1000CUGatingTier)))*$"
422
422
  }
423
423
  }
424
424
  },
@@ -0,0 +1,169 @@
1
+ # ---------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # ---------------------------------------------------------
4
+ """Model management methods."""
5
+ import json
6
+ import sys
7
+ from typing import Tuple
8
+ from azureml.registry._rest_client.registry_model_client import RegistryModelClient
9
+
10
+
11
+ def parse_asset_id(asset_id: str) -> Tuple[str, str, str]:
12
+ """Parse an asset ID into its components.
13
+
14
+ Args:
15
+ asset_id (str): Model assetId like azureml://registries/myRegistry/models/myModel/versions/1.
16
+
17
+ Returns:
18
+ Tuple[str, str, str]: (registry_name, model_name, version)
19
+
20
+ Raises:
21
+ ValueError: If the asset ID format is invalid.
22
+ """
23
+ try:
24
+ parsed = RegistryModelClient.parse_asset_id(asset_id)
25
+ return parsed["registry_name"], parsed["name"], parsed["version"]
26
+ except ValueError as e:
27
+ raise ValueError(f"Invalid asset ID '{asset_id}': {e}")
28
+
29
+
30
+ def model_list(registry_name: str, subscription_id: str = None, resource_group_name: str = None,
31
+ primary_region: str = None, name: str = None, tags: str = None,
32
+ version: str = None, framework: str = None, description: str = None,
33
+ properties: str = None, run_id: str = None, dataset_id: str = None,
34
+ order_by: str = None, skip_token: str = None, list_view_type: str = "ActiveOnly") -> str:
35
+ """List models in the registry.
36
+
37
+ Args:
38
+ registry_name (str): Name of the Azure ML registry.
39
+ subscription_id (str, optional): Azure subscription ID. If None, auto-resolves from registry discovery.
40
+ resource_group_name (str, optional): Resource group name containing the registry. If None, auto-resolves from registry discovery.
41
+ primary_region (str, optional): Azure primary region for the registry. If None, auto-resolves from registry discovery.
42
+ name (str, optional): The object name.
43
+ tags (str, optional): Comma separated string of tags key or tags key=value.
44
+ version (str, optional): The object version.
45
+ framework (str, optional): The framework.
46
+ description (str, optional): The object description.
47
+ properties (str, optional): Comma separated string of properties key and/or properties key=value.
48
+ run_id (str, optional): The runId which created the model.
49
+ dataset_id (str, optional): The datasetId associated with the model.
50
+ order_by (str, optional): How the models are ordered in the response.
51
+ skip_token (str, optional): The continuation token to retrieve the next page.
52
+ list_view_type (str): View type filter (default: ActiveOnly).
53
+
54
+ Returns:
55
+ str: JSON response containing list of models.
56
+ """
57
+ try:
58
+ client = RegistryModelClient(
59
+ registry_name=registry_name,
60
+ subscription_id=subscription_id,
61
+ resource_group_name=resource_group_name,
62
+ primary_region=primary_region
63
+ )
64
+
65
+ result = client.list_models(
66
+ name=name, tags=tags, version=version, framework=framework,
67
+ description=description, properties=properties, run_id=run_id,
68
+ dataset_id=dataset_id, order_by=order_by, skip_token=skip_token,
69
+ list_view_type=list_view_type
70
+ )
71
+
72
+ return json.dumps(result, indent=2)
73
+
74
+ except Exception as e:
75
+ print(f"Error listing models: {e}", file=sys.stderr)
76
+ sys.exit(1)
77
+
78
+
79
+ def model_get(registry_name: str, name: str, version: str,
80
+ subscription_id: str = None, resource_group_name: str = None,
81
+ primary_region: str = None, include_deployment_settings: bool = False, workspace_id: str = None) -> str:
82
+ """Get a specific model by name and version.
83
+
84
+ Args:
85
+ registry_name (str): Name of the Azure ML registry.
86
+ name (str): The name of the model.
87
+ version (str): The version of the model.
88
+ subscription_id (str, optional): Azure subscription ID. If None, auto-resolves from registry discovery.
89
+ resource_group_name (str, optional): Resource group name containing the registry. If None, auto-resolves from registry discovery.
90
+ primary_region (str, optional): Azure primary region for the registry. If None, auto-resolves from registry discovery.
91
+ include_deployment_settings (bool): Whether to include deployment settings.
92
+ workspace_id (str, optional): Workspace ID GUID for deployment environment association.
93
+
94
+ Returns:
95
+ str: JSON response containing the model data.
96
+ """
97
+ try:
98
+ client = RegistryModelClient(
99
+ registry_name=registry_name,
100
+ subscription_id=subscription_id,
101
+ resource_group_name=resource_group_name,
102
+ primary_region=primary_region
103
+ )
104
+
105
+ result = client.get_model_by_name_and_version(
106
+ name=name,
107
+ version=version
108
+ )
109
+
110
+ return json.dumps(result, indent=2)
111
+
112
+ except Exception as e:
113
+ print(f"Error getting model {name} version {version}: {e}", file=sys.stderr)
114
+ sys.exit(1)
115
+
116
+
117
+ def model_delete(registry_name: str, name: str, version: str = None,
118
+ subscription_id: str = None, resource_group_name: str = None,
119
+ primary_region: str = None, dry_run: bool = False, force: bool = False) -> None:
120
+ """Delete a model from the registry.
121
+
122
+ Args:
123
+ registry_name (str): Name of the Azure ML registry.
124
+ name (str): The name of the model to delete.
125
+ version (str, optional): The version to delete. If None, deletes all versions.
126
+ subscription_id (str, optional): Azure subscription ID. If None, auto-resolves from registry discovery.
127
+ resource_group_name (str, optional): Resource group name containing the registry. If None, auto-resolves from registry discovery.
128
+ primary_region (str, optional): Azure primary region for the registry. If None, auto-resolves from registry discovery.
129
+ dry_run (bool): If True, do not perform any changes.
130
+ force (bool): If True, skip confirmation prompt.
131
+ """
132
+ try:
133
+ if not force:
134
+ if version:
135
+ confirm = input(f"Are you sure you want to delete model '{name}' version '{version}' from registry '{registry_name}'? [y/N]: ")
136
+ else:
137
+ confirm = input(f"Are you sure you want to delete ALL versions of model '{name}' from registry '{registry_name}'? [y/N]: ")
138
+ if confirm.lower() != "y":
139
+ print("Model deletion cancelled.")
140
+ return
141
+
142
+ if dry_run:
143
+ if version:
144
+ print(f"Dry run: Would delete model {name} version {version}")
145
+ else:
146
+ print(f"Dry run: Would delete all versions of model {name}")
147
+ return
148
+
149
+ client = RegistryModelClient(
150
+ registry_name=registry_name,
151
+ subscription_id=subscription_id,
152
+ resource_group_name=resource_group_name,
153
+ primary_region=primary_region
154
+ )
155
+
156
+ result = client.delete_model(
157
+ name=name,
158
+ version=version
159
+ )
160
+ print(result)
161
+
162
+ if version:
163
+ print(f"Successfully deleted model '{name}' version '{version}'")
164
+ else:
165
+ print(f"Successfully deleted all versions of model '{name}'")
166
+
167
+ except Exception as e:
168
+ print(f"Error deleting model {name}: {e}", file=sys.stderr)
169
+ sys.exit(1)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: azureml-registry-tools
3
- Version: 0.1.0a10
3
+ Version: 0.1.0a12
4
4
  Summary: AzureML Registry tools and CLI
5
5
  Author: Microsoft Corp
6
6
  License: https://aka.ms/azureml-sdk-license
@@ -1,18 +1,19 @@
1
1
  azureml/__init__.py,sha256=_gYz_vABVrp9EyOZEYn4Ya8XotJC2rZn-b9kFTEj1Dk,266
2
2
  azureml/registry/__init__.py,sha256=qjP86n1Yz6hdZb3az-wjfi7LNndsFjMQI6lXmKfXapM,266
3
3
  azureml/registry/_cli/__init__.py,sha256=dJ020ynrregpUaNGu8xVLLmX3cC9DAjaBMP1qnljLEE,208
4
- azureml/registry/_cli/registry_syndication_cli.py,sha256=6Lf2ydgSBKZ3S9AAsY7zLfr0RBX_TcRmKTUaZ9gX3fI,18031
4
+ azureml/registry/_cli/registry_syndication_cli.py,sha256=7HZ9dfv_KL4H-sRebz7xvNHgeN3MUCSJl-pOxZPFJeU,29000
5
5
  azureml/registry/_cli/repo2registry_cli.py,sha256=4CDv4tYWLTjVgdXIcoKA9iu_gOv_Z_xn05wSANkdmdg,5378
6
6
  azureml/registry/_rest_client/__init__.py,sha256=Eh0vB8AVlwkZlHLO4DCGHyyfRYYgeTeVjiUAX_WujG0,219
7
7
  azureml/registry/_rest_client/arm_client.py,sha256=KVSj0V1bN-vCUV3fBbIdZDpWTLKYT6qdWIZtDjx2la4,4516
8
8
  azureml/registry/_rest_client/base_rest_client.py,sha256=JcmhckfR-ZdUtEh0EeQIKfe63Zjowa1fLzmVeWpeLno,8173
9
9
  azureml/registry/_rest_client/registry_management_client.py,sha256=3-PBwj91lkG1yrlB-s7jaYj0w5j177oSHZHvcoO8icQ,4984
10
+ azureml/registry/_rest_client/registry_model_client.py,sha256=LqJGTuYQtBnHSeSbOl5KVbiO6vGBkKQ7HD5jc4fvV3k,14466
10
11
  azureml/registry/data/__init__.py,sha256=cW3X6HATz6XF-K_7uKdybTbJb9EZSecBN4J27NGdZmU,231
11
12
  azureml/registry/data/asset.yaml.template,sha256=WTgfuvKEBp-EVFSQ0JpU0h4z_ULJdULO9kHmB_9Fr1o,96
12
13
  azureml/registry/data/description.md.template,sha256=tIcjydGGScMP0PhJMrYjzdWMjd-8z5winufNjH2A3OE,135
13
14
  azureml/registry/data/evaluation.md.template,sha256=UZJti1kFu-VsRGVl_QA_GhUS8hU63Yo3Flih3_RMD14,328
14
15
  azureml/registry/data/model-variant.schema.json,sha256=AT4Dy6cCtp_SFUfSqYIqcER8AldpYm0QIEy1abY3QWE,1699
15
- azureml/registry/data/model.schema.json,sha256=sNzo7agu_49GdfSa08T61Hy87j-XY5IwLh6XU39QTAY,25814
16
+ azureml/registry/data/model.schema.json,sha256=2Ew3wt9QMzIBdLeIQXph0Fj7X8OqZJbzu_7-c4SEKZY,26062
16
17
  azureml/registry/data/model.yaml.template,sha256=h5uqAN22FLaWrbPxIb8yVKH9cGDBrIwooXYYfsKhxDw,245
17
18
  azureml/registry/data/notes.md.template,sha256=yFQ7qbrjlGDLZGuSoJUvQjctXsnCdoUd6txJuT-duCY,300
18
19
  azureml/registry/data/validate_model_schema.py,sha256=-13fTuX1iqO7OhZFv8NbBpPlBcs-plNYJVPu40Cgl5M,5236
@@ -22,6 +23,7 @@ azureml/registry/mgmt/asset_management.py,sha256=loe_RkzJYwFX0brG5OJfv4XpgHAvc28
22
23
  azureml/registry/mgmt/create_asset_template.py,sha256=ejwLuIsmzJOoUePoxbM-eGMg2E3QHfdX-nPMBzYUVMQ,3525
23
24
  azureml/registry/mgmt/create_manifest.py,sha256=N9wRmjAKO09A3utN_lCUsM_Ufpj7PL0SJz-XHPHWuyM,9528
24
25
  azureml/registry/mgmt/create_model_spec.py,sha256=1PdAcUf-LomvljoT8wKQihXMTLd7DoTgN0qDX4Lol1A,10473
26
+ azureml/registry/mgmt/model_management.py,sha256=STTr_uvdPKV2NaJ5UvS5aMi3yejVF6Hkj9DjofJLQik,7453
25
27
  azureml/registry/mgmt/registry_config.py,sha256=NhIkTCNlfbfJrVGw61AmBAIZQ5HU2cb4kXAI9j3Wwy4,7181
26
28
  azureml/registry/mgmt/syndication_manifest.py,sha256=8Sfd49QuCA5en5_mIOLE21kZVpnReUXowx_g0TVRgWg,9025
27
29
  azureml/registry/tools/__init__.py,sha256=IAuWWpGfZm__pAkBIxmpJz84QskpkxBr0yDk1TUSnkE,223
@@ -29,9 +31,9 @@ azureml/registry/tools/config.py,sha256=tjPaoBsWtPXBL8Ww1hcJtsr2SuIjPKt79dR8iovc
29
31
  azureml/registry/tools/create_or_update_assets.py,sha256=Q-_BV7KWn1huQn5JriKT_8xJNoQQ_HK5wCftrq9DepA,15988
30
32
  azureml/registry/tools/registry_utils.py,sha256=zgYlCiOONtQJ4yZ9wg8tKVoE8dh6rrjB8hYBGhpV9-0,1403
31
33
  azureml/registry/tools/repo2registry_config.py,sha256=eXp_tU8Jyi30g8xGf7wbpLgKEPpieohBANKxMSLzq7s,4873
32
- azureml_registry_tools-0.1.0a10.dist-info/licenses/LICENSE.txt,sha256=n20rxwp7_NGrrShv9Qvcs90sjI1l3Pkt3m-5OPCWzgs,845
33
- azureml_registry_tools-0.1.0a10.dist-info/METADATA,sha256=gsUEH4kr13hW31G86H4noWUs94Ho0XvCGEAxAr2riQ0,527
34
- azureml_registry_tools-0.1.0a10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
35
- azureml_registry_tools-0.1.0a10.dist-info/entry_points.txt,sha256=iRUkAeQidMnO6RQzpLqMUBTcyYtNzAfSin9WnSdVGLw,147
36
- azureml_registry_tools-0.1.0a10.dist-info/top_level.txt,sha256=ZOeEa0TAXo6i5wOjwBoqfIGEuxOcKuscGgNSpizqREY,8
37
- azureml_registry_tools-0.1.0a10.dist-info/RECORD,,
34
+ azureml_registry_tools-0.1.0a12.dist-info/licenses/LICENSE.txt,sha256=n20rxwp7_NGrrShv9Qvcs90sjI1l3Pkt3m-5OPCWzgs,845
35
+ azureml_registry_tools-0.1.0a12.dist-info/METADATA,sha256=Dgdf2cPZ_pYJThoZ2IpkbmiG5xeTm5VY_3B--34Rjhc,527
36
+ azureml_registry_tools-0.1.0a12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
+ azureml_registry_tools-0.1.0a12.dist-info/entry_points.txt,sha256=iRUkAeQidMnO6RQzpLqMUBTcyYtNzAfSin9WnSdVGLw,147
38
+ azureml_registry_tools-0.1.0a12.dist-info/top_level.txt,sha256=ZOeEa0TAXo6i5wOjwBoqfIGEuxOcKuscGgNSpizqREY,8
39
+ azureml_registry_tools-0.1.0a12.dist-info/RECORD,,