azureml-registry-tools 0.1.0a2__py3-none-any.whl → 0.1.0a3__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.
@@ -1,16 +1,14 @@
1
1
  # ---------------------------------------------------------
2
2
  # Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  # ---------------------------------------------------------
4
- from azure.identity import DefaultAzureCredential
5
- from .base_rest_client import BaseRestClient
6
- import time
4
+ from .base_rest_client import BaseAzureRestClient
7
5
  from json.decoder import JSONDecodeError
8
6
 
9
7
  DEFAULT_API_VERSION = "2025-04-01" # Default API version for Azure Resource Manager
10
8
 
11
9
 
12
- class ArmClient(BaseRestClient):
13
- """Simple Azure Resource Manager (ARM) client leveraging BaseRestClient for GET, PATCH, PUT, and DELETE operations.
10
+ class ArmClient(BaseAzureRestClient):
11
+ """Simple Azure Resource Manager (ARM) client leveraging BaseAzureRestClient for GET, PATCH, PUT, and DELETE operations.
14
12
 
15
13
  Handles authentication via Bearer token (pass as api_key) and supports standard ARM resource operations.
16
14
  """
@@ -25,27 +23,8 @@ class ArmClient(BaseRestClient):
25
23
  backoff_factor (int): Backoff factor for retries.
26
24
  """
27
25
  base_url = "https://management.azure.com"
28
- self._credential = None
29
- self._token_expires_on = None
30
- if api_key is None:
31
- # Use DefaultAzureCredential for authentication if no API key is provided
32
- self._credential = DefaultAzureCredential()
33
- token = self._credential.get_token("https://management.azure.com/.default")
34
- api_key = token.token
35
- self._token_expires_on = token.expires_on
36
26
  super().__init__(base_url, api_key=api_key, max_retries=max_retries, backoff_factor=backoff_factor)
37
27
 
38
- def _refresh_api_key_if_needed(self) -> None:
39
- """Refresh the API key if using DefaultAzureCredential and the token is close to expiration."""
40
- if self._credential is not None:
41
- now = int(time.time())
42
- # Refresh if less than 10 minutes (600 seconds) left
43
- if not self._token_expires_on or self._token_expires_on - now < 600:
44
- token = self._credential.get_token("https://management.azure.com/.default")
45
- self.api_key = token.token
46
- self.session.headers.update({'Authorization': f'Bearer {self.api_key}'})
47
- self._token_expires_on = token.expires_on
48
-
49
28
  def get_resource(self, resource_id, api_version=DEFAULT_API_VERSION, **kwargs) -> object:
50
29
  """
51
30
  Get an ARM resource by its resource ID.
@@ -4,6 +4,11 @@
4
4
  import requests
5
5
  import time
6
6
  import logging
7
+ from azure.identity import DefaultAzureCredential
8
+ from diskcache import Cache
9
+ import tempfile
10
+
11
+ ENABLE_CACHE = True # Set to False to disable diskcache usage for API key
7
12
 
8
13
 
9
14
  class BaseRestClient:
@@ -136,3 +141,61 @@ class BaseRestClient:
136
141
  requests.Response: The HTTP response object.
137
142
  """
138
143
  return self._request_with_retry('DELETE', url, **kwargs)
144
+
145
+
146
+ class BaseAzureRestClient(BaseRestClient):
147
+ """Base Azure REST client that handles Azure authentication and token refresh, with diskcache for API key."""
148
+
149
+ if ENABLE_CACHE:
150
+ _cache_dir = tempfile.gettempdir() + '/.azureml_registry_token_cache'
151
+ _cache = Cache(_cache_dir)
152
+ else:
153
+ _cache = None
154
+
155
+ def __init__(self, base_url: str, api_key: str = None, max_retries: int = 5, backoff_factor: int = 1):
156
+ """
157
+ Initialize the BaseAzureRestClient.
158
+
159
+ Args:
160
+ base_url (str): The base URL for the REST API.
161
+ api_key (str, optional): Bearer token for authentication. If None, uses DefaultAzureCredential.
162
+ max_retries (int): Maximum number of retries for failed requests.
163
+ backoff_factor (int): Backoff factor for retry delays.
164
+ """
165
+ self._credential = None
166
+ self._token_expires_on = None
167
+ cache_key = f"azureml_api_key_{base_url}"
168
+ cache_expiry_key = f"azureml_api_key_expiry_{base_url}"
169
+ cached_token = None
170
+ cached_expiry = None
171
+ now = int(time.time())
172
+ if ENABLE_CACHE and self._cache is not None:
173
+ cached_token = self._cache.get(cache_key)
174
+ cached_expiry = self._cache.get(cache_expiry_key)
175
+ if api_key is None and cached_token and cached_expiry and cached_expiry > now:
176
+ api_key = cached_token
177
+ self._token_expires_on = cached_expiry
178
+ super().__init__(base_url, api_key=api_key, max_retries=max_retries, backoff_factor=backoff_factor)
179
+ # Only after super().__init__ is self.session available
180
+ if api_key is None:
181
+ self._credential = DefaultAzureCredential()
182
+ self._refresh_api_key_if_needed()
183
+ # Ensure self.api_key is set for future use
184
+ api_key = self.api_key
185
+
186
+ def _refresh_api_key_if_needed(self) -> None:
187
+ if self._credential is None:
188
+ return
189
+ now = int(time.time())
190
+ if not getattr(self, '_token_expires_on', None) or self._token_expires_on - now < 600:
191
+ token = self._credential.get_token("https://management.azure.com/.default")
192
+ self.api_key = token.token
193
+ self.session.headers.update({'Authorization': f'Bearer {self.api_key}'})
194
+ self._token_expires_on = token.expires_on
195
+ # Store in diskcache if enabled
196
+ if ENABLE_CACHE and self._cache is not None:
197
+ base_url = getattr(self, 'base_url', 'default')
198
+ cache_key = f"azureml_api_key_{base_url}"
199
+ cache_expiry_key = f"azureml_api_key_expiry_{base_url}"
200
+ self._cache.set(cache_key, self.api_key, expire=(self._token_expires_on - now))
201
+ self._cache.set(cache_expiry_key, self._token_expires_on, expire=(self._token_expires_on - now))
@@ -1,18 +1,16 @@
1
1
  # ---------------------------------------------------------
2
2
  # Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  # ---------------------------------------------------------
4
- from azure.identity import DefaultAzureCredential
5
- from .base_rest_client import BaseRestClient
6
- import time
4
+ from .base_rest_client import BaseAzureRestClient
7
5
 
8
6
 
9
- class RegistryManagementClient(BaseRestClient):
7
+ class RegistryManagementClient(BaseAzureRestClient):
10
8
  """Python client for RegistrySyndicationManifestController (excluding S2S APIs).
11
9
 
12
10
  Handles authentication, token refresh, and provides methods for manifest and registry management.
13
11
  """
14
12
 
15
- def __init__(self, registry_name: str, primary_region: str = "eastus2euap", api_key: str = None, max_retries: int = 5, backoff_factor: int = 1) -> None:
13
+ def __init__(self, registry_name: str, primary_region: str = None, api_key: str = None, max_retries: int = 5, backoff_factor: int = 1) -> None:
16
14
  """
17
15
  Initialize the RegistryManagementClient.
18
16
 
@@ -23,29 +21,26 @@ class RegistryManagementClient(BaseRestClient):
23
21
  max_retries (int): Maximum number of retries for failed requests.
24
22
  backoff_factor (int): Backoff factor for retry delays.
25
23
  """
24
+ if primary_region is None:
25
+ # Resolve the primary region if not provided
26
+ primary_region = self.resolve_registry_primary_region(registry_name)
26
27
  base_url = f"https://{primary_region}.api.azureml.ms"
27
- self._credential = None
28
- self._token_expires_on = None
29
- if api_key is None:
30
- # Use DefaultAzureCredential for authentication if no API key is provided
31
- self._credential = DefaultAzureCredential()
32
- token = self._credential.get_token("https://management.azure.com/.default")
33
- api_key = token.token
34
- self._token_expires_on = token.expires_on
35
28
  super().__init__(base_url, api_key=api_key, max_retries=max_retries, backoff_factor=backoff_factor)
36
29
  self.registry_name = registry_name
37
30
 
38
- def _refresh_api_key_if_needed(self) -> None:
39
- """Refresh the API key if using DefaultAzureCredential and the token is close to expiration."""
40
- # Only refresh if using DefaultAzureCredential
41
- if self._credential is not None:
42
- now = int(time.time())
43
- # Refresh if less than 10 minutes (600 seconds) left
44
- if not self._token_expires_on or self._token_expires_on - now < 600:
45
- token = self._credential.get_token("https://management.azure.com/.default")
46
- self.api_key = token.token
47
- self.session.headers.update({'Authorization': f'Bearer {self.api_key}'})
48
- self._token_expires_on = token.expires_on
31
+ @staticmethod
32
+ def resolve_registry_primary_region(registry_name: str) -> str:
33
+ """
34
+ Resolve the primary region for the given registry name.
35
+
36
+ Args:
37
+ registry_name (str): The name of the AzureML registry.
38
+
39
+ Returns:
40
+ str: The primary region for the registry.
41
+ """
42
+ discovery = RegistryManagementClient(registry_name, primary_region="eastus").discovery()
43
+ return discovery.get('primaryRegion', 'eastus')
49
44
 
50
45
  def create_or_update_manifest(self, manifest_dto: dict) -> dict:
51
46
  """
@@ -110,5 +105,15 @@ class RegistryManagementClient(BaseRestClient):
110
105
  """
111
106
  self._refresh_api_key_if_needed()
112
107
  url = f"{self.base_url}/registrymanagement/v1.0/registries/{self.registry_name}/discovery"
113
- response = self.get(url)
114
- return response.json()
108
+ try:
109
+ response = self.get(url)
110
+ return response.json()
111
+ except Exception as ex:
112
+ # Special handling for HTTP errors with status_code attribute
113
+ if hasattr(ex, 'response') and ex.response is not None:
114
+ status_code = ex.response.status_code
115
+ if status_code == 403:
116
+ raise RuntimeError(f"Received 403 Forbidden. This may indicate that the registry '{self.registry_name}' does not exist or you do not have access.")
117
+ else:
118
+ raise RuntimeError(f"Failed to get discovery information: {status_code} {ex.response.text}")
119
+ raise RuntimeError(f"Error occurred while trying to get discovery information: {ex}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: azureml-registry-tools
3
- Version: 0.1.0a2
3
+ Version: 0.1.0a3
4
4
  Summary: AzureML Registry tools and CLI
5
5
  Author: Microsoft Corp
6
6
  License: https://aka.ms/azureml-sdk-license
@@ -8,6 +8,7 @@ Requires-Python: >=3.9,<3.14
8
8
  License-File: LICENSE.txt
9
9
  Requires-Dist: azure-identity<2.0
10
10
  Requires-Dist: ruamel-yaml<0.19,>=0.17.21
11
+ Requires-Dist: diskcache~=5.6
11
12
  Requires-Dist: azure-ai-ml<2.0
12
13
  Requires-Dist: azureml-assets<2.0
13
14
  Dynamic: author
@@ -4,9 +4,9 @@ azureml/registry/_cli/__init__.py,sha256=dJ020ynrregpUaNGu8xVLLmX3cC9DAjaBMP1qnl
4
4
  azureml/registry/_cli/registry_syndication_cli.py,sha256=87hxSu4iwFOdwfukSGqc0fjJEpSpQhDRlthiCGCDwQ8,10003
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
- azureml/registry/_rest_client/arm_client.py,sha256=dgm9nDnmGI1NIZaopXCIXmViM8sSAipZETfuOWnQRa0,5638
8
- azureml/registry/_rest_client/base_rest_client.py,sha256=cXFYvYgSYSveEeY511VIlDpmXLOY9Jat_E-Zs1ehMyA,5164
9
- azureml/registry/_rest_client/registry_management_client.py,sha256=JGBohCVpL8XR4rNuScOKt7tNQnjw203pqlTSLKVjYgM,4833
7
+ azureml/registry/_rest_client/arm_client.py,sha256=KVSj0V1bN-vCUV3fBbIdZDpWTLKYT6qdWIZtDjx2la4,4516
8
+ azureml/registry/_rest_client/base_rest_client.py,sha256=JcmhckfR-ZdUtEh0EeQIKfe63Zjowa1fLzmVeWpeLno,8173
9
+ azureml/registry/_rest_client/registry_management_client.py,sha256=P2aijKHdHvsSZ507-06Rq_n9pNJ_xci1oQKDGBJjm-I,4986
10
10
  azureml/registry/mgmt/__init__.py,sha256=LMhqcEC8ItmmpKZljElGXH-6olHlT3SLl0dJU01OvuM,226
11
11
  azureml/registry/mgmt/create_manifest.py,sha256=N9wRmjAKO09A3utN_lCUsM_Ufpj7PL0SJz-XHPHWuyM,9528
12
12
  azureml/registry/mgmt/syndication_manifest.py,sha256=ma1_vUEHHtkRcgiFMQanlXZajvKodynylxfblTHuleE,6195
@@ -15,9 +15,9 @@ azureml/registry/tools/config.py,sha256=tjPaoBsWtPXBL8Ww1hcJtsr2SuIjPKt79dR8iovc
15
15
  azureml/registry/tools/create_or_update_assets.py,sha256=NexSY22bpN2eHPqZZ21eDIMerwq3SzVk_4fBV5_GTBY,14787
16
16
  azureml/registry/tools/registry_utils.py,sha256=zgYlCiOONtQJ4yZ9wg8tKVoE8dh6rrjB8hYBGhpV9-0,1403
17
17
  azureml/registry/tools/repo2registry_config.py,sha256=eXp_tU8Jyi30g8xGf7wbpLgKEPpieohBANKxMSLzq7s,4873
18
- azureml_registry_tools-0.1.0a2.dist-info/licenses/LICENSE.txt,sha256=n20rxwp7_NGrrShv9Qvcs90sjI1l3Pkt3m-5OPCWzgs,845
19
- azureml_registry_tools-0.1.0a2.dist-info/METADATA,sha256=SJpiD2SsYXqJi_DUSLk-mquFZgbnb54Sd_mrP6H-GBI,491
20
- azureml_registry_tools-0.1.0a2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
- azureml_registry_tools-0.1.0a2.dist-info/entry_points.txt,sha256=iRUkAeQidMnO6RQzpLqMUBTcyYtNzAfSin9WnSdVGLw,147
22
- azureml_registry_tools-0.1.0a2.dist-info/top_level.txt,sha256=ZOeEa0TAXo6i5wOjwBoqfIGEuxOcKuscGgNSpizqREY,8
23
- azureml_registry_tools-0.1.0a2.dist-info/RECORD,,
18
+ azureml_registry_tools-0.1.0a3.dist-info/licenses/LICENSE.txt,sha256=n20rxwp7_NGrrShv9Qvcs90sjI1l3Pkt3m-5OPCWzgs,845
19
+ azureml_registry_tools-0.1.0a3.dist-info/METADATA,sha256=0UQUDwCZO5K7-EnWwtUFqrSHJeXX2DU14PUwuoA2df0,521
20
+ azureml_registry_tools-0.1.0a3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
+ azureml_registry_tools-0.1.0a3.dist-info/entry_points.txt,sha256=iRUkAeQidMnO6RQzpLqMUBTcyYtNzAfSin9WnSdVGLw,147
22
+ azureml_registry_tools-0.1.0a3.dist-info/top_level.txt,sha256=ZOeEa0TAXo6i5wOjwBoqfIGEuxOcKuscGgNSpizqREY,8
23
+ azureml_registry_tools-0.1.0a3.dist-info/RECORD,,