azure-storage-blob 12.22.0__py3-none-any.whl → 12.23.0__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.
Files changed (32) hide show
  1. azure/storage/blob/_container_client.py +6 -0
  2. azure/storage/blob/_container_client_helpers.py +7 -2
  3. azure/storage/blob/_generated/_azure_blob_storage.py +2 -1
  4. azure/storage/blob/_generated/_serialization.py +2 -0
  5. azure/storage/blob/_generated/aio/_azure_blob_storage.py +2 -1
  6. azure/storage/blob/_generated/aio/operations/_append_blob_operations.py +1 -7
  7. azure/storage/blob/_generated/aio/operations/_blob_operations.py +21 -47
  8. azure/storage/blob/_generated/aio/operations/_block_blob_operations.py +2 -10
  9. azure/storage/blob/_generated/aio/operations/_container_operations.py +13 -26
  10. azure/storage/blob/_generated/aio/operations/_page_blob_operations.py +3 -14
  11. azure/storage/blob/_generated/aio/operations/_service_operations.py +14 -17
  12. azure/storage/blob/_generated/operations/_append_blob_operations.py +1 -7
  13. azure/storage/blob/_generated/operations/_blob_operations.py +21 -47
  14. azure/storage/blob/_generated/operations/_block_blob_operations.py +2 -10
  15. azure/storage/blob/_generated/operations/_container_operations.py +13 -26
  16. azure/storage/blob/_generated/operations/_page_blob_operations.py +3 -14
  17. azure/storage/blob/_generated/operations/_service_operations.py +14 -17
  18. azure/storage/blob/_serialize.py +1 -0
  19. azure/storage/blob/_shared/base_client.py +2 -0
  20. azure/storage/blob/_shared/policies.py +8 -9
  21. azure/storage/blob/_shared/policies_async.py +18 -5
  22. azure/storage/blob/_shared/shared_access_signature.py +20 -2
  23. azure/storage/blob/_shared_access_signature.py +42 -1
  24. azure/storage/blob/_version.py +1 -1
  25. azure/storage/blob/aio/_container_client_async.py +6 -0
  26. azure/storage/blob/aio/_download_async.py +94 -71
  27. {azure_storage_blob-12.22.0.dist-info → azure_storage_blob-12.23.0.dist-info}/METADATA +3 -3
  28. {azure_storage_blob-12.22.0.dist-info → azure_storage_blob-12.23.0.dist-info}/RECORD +31 -32
  29. {azure_storage_blob-12.22.0.dist-info → azure_storage_blob-12.23.0.dist-info}/WHEEL +1 -1
  30. azure/storage/blob/_generated/_vendor.py +0 -16
  31. {azure_storage_blob-12.22.0.dist-info → azure_storage_blob-12.23.0.dist-info}/LICENSE +0 -0
  32. {azure_storage_blob-12.22.0.dist-info → azure_storage_blob-12.23.0.dist-info}/top_level.txt +0 -0
@@ -15,17 +15,17 @@ from azure.core.exceptions import (
15
15
  ResourceExistsError,
16
16
  ResourceNotFoundError,
17
17
  ResourceNotModifiedError,
18
+ StreamClosedError,
19
+ StreamConsumedError,
18
20
  map_error,
19
21
  )
20
22
  from azure.core.pipeline import PipelineResponse
21
- from azure.core.pipeline.transport import HttpResponse
22
- from azure.core.rest import HttpRequest
23
+ from azure.core.rest import HttpRequest, HttpResponse
23
24
  from azure.core.tracing.decorator import distributed_trace
24
25
  from azure.core.utils import case_insensitive_dict
25
26
 
26
27
  from .. import models as _models
27
28
  from .._serialization import Serializer
28
- from .._vendor import _convert_request
29
29
 
30
30
  if sys.version_info >= (3, 9):
31
31
  from collections.abc import MutableMapping
@@ -427,7 +427,6 @@ class ServiceOperations:
427
427
  headers=_headers,
428
428
  params=_params,
429
429
  )
430
- _request = _convert_request(_request)
431
430
  _request.url = self._client.format_url(_request.url)
432
431
 
433
432
  _stream = False
@@ -497,7 +496,6 @@ class ServiceOperations:
497
496
  headers=_headers,
498
497
  params=_params,
499
498
  )
500
- _request = _convert_request(_request)
501
499
  _request.url = self._client.format_url(_request.url)
502
500
 
503
501
  _stream = False
@@ -519,7 +517,7 @@ class ServiceOperations:
519
517
  response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
520
518
  response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
521
519
 
522
- deserialized = self._deserialize("StorageServiceProperties", pipeline_response)
520
+ deserialized = self._deserialize("StorageServiceProperties", pipeline_response.http_response)
523
521
 
524
522
  if cls:
525
523
  return cls(pipeline_response, deserialized, response_headers) # type: ignore
@@ -572,7 +570,6 @@ class ServiceOperations:
572
570
  headers=_headers,
573
571
  params=_params,
574
572
  )
575
- _request = _convert_request(_request)
576
573
  _request.url = self._client.format_url(_request.url)
577
574
 
578
575
  _stream = False
@@ -595,7 +592,7 @@ class ServiceOperations:
595
592
  response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
596
593
  response_headers["Date"] = self._deserialize("rfc-1123", response.headers.get("Date"))
597
594
 
598
- deserialized = self._deserialize("StorageServiceStats", pipeline_response)
595
+ deserialized = self._deserialize("StorageServiceStats", pipeline_response.http_response)
599
596
 
600
597
  if cls:
601
598
  return cls(pipeline_response, deserialized, response_headers) # type: ignore
@@ -676,7 +673,6 @@ class ServiceOperations:
676
673
  headers=_headers,
677
674
  params=_params,
678
675
  )
679
- _request = _convert_request(_request)
680
676
  _request.url = self._client.format_url(_request.url)
681
677
 
682
678
  _stream = False
@@ -698,7 +694,7 @@ class ServiceOperations:
698
694
  response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
699
695
  response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
700
696
 
701
- deserialized = self._deserialize("ListContainersSegmentResponse", pipeline_response)
697
+ deserialized = self._deserialize("ListContainersSegmentResponse", pipeline_response.http_response)
702
698
 
703
699
  if cls:
704
700
  return cls(pipeline_response, deserialized, response_headers) # type: ignore
@@ -761,7 +757,6 @@ class ServiceOperations:
761
757
  headers=_headers,
762
758
  params=_params,
763
759
  )
764
- _request = _convert_request(_request)
765
760
  _request.url = self._client.format_url(_request.url)
766
761
 
767
762
  _stream = False
@@ -784,7 +779,7 @@ class ServiceOperations:
784
779
  response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
785
780
  response_headers["Date"] = self._deserialize("rfc-1123", response.headers.get("Date"))
786
781
 
787
- deserialized = self._deserialize("UserDelegationKey", pipeline_response)
782
+ deserialized = self._deserialize("UserDelegationKey", pipeline_response.http_response)
788
783
 
789
784
  if cls:
790
785
  return cls(pipeline_response, deserialized, response_headers) # type: ignore
@@ -835,7 +830,6 @@ class ServiceOperations:
835
830
  headers=_headers,
836
831
  params=_params,
837
832
  )
838
- _request = _convert_request(_request)
839
833
  _request.url = self._client.format_url(_request.url)
840
834
 
841
835
  _stream = False
@@ -923,9 +917,9 @@ class ServiceOperations:
923
917
  headers=_headers,
924
918
  params=_params,
925
919
  )
926
- _request = _convert_request(_request)
927
920
  _request.url = self._client.format_url(_request.url)
928
921
 
922
+ _decompress = kwargs.pop("decompress", True)
929
923
  _stream = True
930
924
  pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access
931
925
  _request, stream=_stream, **kwargs
@@ -934,6 +928,10 @@ class ServiceOperations:
934
928
  response = pipeline_response.http_response
935
929
 
936
930
  if response.status_code not in [200]:
931
+ try:
932
+ response.read() # Load the body in memory and close the socket
933
+ except (StreamConsumedError, StreamClosedError):
934
+ pass
937
935
  map_error(status_code=response.status_code, response=response, error_map=error_map)
938
936
  error = self._deserialize.failsafe_deserialize(_models.StorageError, pipeline_response)
939
937
  raise HttpResponseError(response=response, model=error)
@@ -943,7 +941,7 @@ class ServiceOperations:
943
941
  response_headers["x-ms-request-id"] = self._deserialize("str", response.headers.get("x-ms-request-id"))
944
942
  response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
945
943
 
946
- deserialized = response.stream_download(self._client._pipeline)
944
+ deserialized = response.stream_download(self._client._pipeline, decompress=_decompress)
947
945
 
948
946
  if cls:
949
947
  return cls(pipeline_response, deserialized, response_headers) # type: ignore
@@ -1025,7 +1023,6 @@ class ServiceOperations:
1025
1023
  headers=_headers,
1026
1024
  params=_params,
1027
1025
  )
1028
- _request = _convert_request(_request)
1029
1026
  _request.url = self._client.format_url(_request.url)
1030
1027
 
1031
1028
  _stream = False
@@ -1048,7 +1045,7 @@ class ServiceOperations:
1048
1045
  response_headers["x-ms-version"] = self._deserialize("str", response.headers.get("x-ms-version"))
1049
1046
  response_headers["Date"] = self._deserialize("rfc-1123", response.headers.get("Date"))
1050
1047
 
1051
- deserialized = self._deserialize("FilterBlobSegment", pipeline_response)
1048
+ deserialized = self._deserialize("FilterBlobSegment", pipeline_response.http_response)
1052
1049
 
1053
1050
  if cls:
1054
1051
  return cls(pipeline_response, deserialized, response_headers) # type: ignore
@@ -56,6 +56,7 @@ _SUPPORTED_API_VERSIONS = [
56
56
  '2023-11-03',
57
57
  '2024-05-04',
58
58
  '2024-08-04',
59
+ '2024-11-04',
59
60
  ]
60
61
 
61
62
 
@@ -76,6 +76,7 @@ class StorageAccountHostsMixin(object): # pylint: disable=too-many-instance-att
76
76
  self._location_mode = kwargs.get("_location_mode", LocationMode.PRIMARY)
77
77
  self._hosts = kwargs.get("_hosts")
78
78
  self.scheme = parsed_url.scheme
79
+ self._is_localhost = False
79
80
 
80
81
  if service not in ["blob", "queue", "file-share", "dfs"]:
81
82
  raise ValueError(f"Invalid service: {service}")
@@ -85,6 +86,7 @@ class StorageAccountHostsMixin(object): # pylint: disable=too-many-instance-att
85
86
  self.account_name = account[0] if len(account) > 1 else None
86
87
  if not self.account_name and parsed_url.netloc.startswith("localhost") \
87
88
  or parsed_url.netloc.startswith("127.0.0.1"):
89
+ self._is_localhost = True
88
90
  self.account_name = parsed_url.path.strip("/")
89
91
 
90
92
  self.credential = _format_shared_key_credential(self.account_name, credential)
@@ -35,11 +35,6 @@ from .authentication import AzureSigningError, StorageHttpChallenge
35
35
  from .constants import DEFAULT_OAUTH_SCOPE
36
36
  from .models import LocationMode
37
37
 
38
- try:
39
- _unicode_type = unicode # type: ignore
40
- except NameError:
41
- _unicode_type = str
42
-
43
38
  if TYPE_CHECKING:
44
39
  from azure.core.credentials import TokenCredential
45
40
  from azure.core.pipeline.transport import ( # pylint: disable=non-abstract-transport-import
@@ -52,7 +47,7 @@ _LOGGER = logging.getLogger(__name__)
52
47
 
53
48
 
54
49
  def encode_base64(data):
55
- if isinstance(data, _unicode_type):
50
+ if isinstance(data, str):
56
51
  data = data.encode('utf-8')
57
52
  encoded = base64.b64encode(data)
58
53
  return encoded.decode('utf-8')
@@ -95,10 +90,14 @@ def is_retry(response, mode): # pylint: disable=too-many-return-statements
95
90
  if status in [501, 505]:
96
91
  return False
97
92
  return True
93
+ return False
94
+
95
+
96
+ def is_checksum_retry(response):
98
97
  # retry if invalid content md5
99
98
  if response.context.get('validate_content', False) and response.http_response.headers.get('content-md5'):
100
99
  computed_md5 = response.http_request.headers.get('content-md5', None) or \
101
- encode_base64(StorageContentValidation.get_content_md5(response.http_response.body()))
100
+ encode_base64(StorageContentValidation.get_content_md5(response.http_response.body()))
102
101
  if response.http_response.headers['content-md5'] != computed_md5:
103
102
  return True
104
103
  return False
@@ -301,7 +300,7 @@ class StorageResponseHook(HTTPPolicy):
301
300
 
302
301
  response = self.next.send(request)
303
302
 
304
- will_retry = is_retry(response, request.context.options.get('mode'))
303
+ will_retry = is_retry(response, request.context.options.get('mode')) or is_checksum_retry(response)
305
304
  # Auth error could come from Bearer challenge, in which case this request will be made again
306
305
  is_auth_error = response.http_response.status_code == 401
307
306
  should_update_counts = not (will_retry or is_auth_error)
@@ -527,7 +526,7 @@ class StorageRetryPolicy(HTTPPolicy):
527
526
  while retries_remaining:
528
527
  try:
529
528
  response = self.next.send(request)
530
- if is_retry(response, retry_settings['mode']):
529
+ if is_retry(response, retry_settings['mode']) or is_checksum_retry(response):
531
530
  retries_remaining = self.increment(
532
531
  retry_settings,
533
532
  request=request.http_request,
@@ -10,12 +10,12 @@ import logging
10
10
  import random
11
11
  from typing import Any, Dict, TYPE_CHECKING
12
12
 
13
- from azure.core.exceptions import AzureError
13
+ from azure.core.exceptions import AzureError, StreamClosedError, StreamConsumedError
14
14
  from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy, AsyncHTTPPolicy
15
15
 
16
16
  from .authentication import AzureSigningError, StorageHttpChallenge
17
17
  from .constants import DEFAULT_OAUTH_SCOPE
18
- from .policies import is_retry, StorageRetryPolicy
18
+ from .policies import encode_base64, is_retry, StorageContentValidation, StorageRetryPolicy
19
19
 
20
20
  if TYPE_CHECKING:
21
21
  from azure.core.credentials_async import AsyncTokenCredential
@@ -42,6 +42,20 @@ async def retry_hook(settings, **kwargs):
42
42
  **kwargs)
43
43
 
44
44
 
45
+ async def is_checksum_retry(response):
46
+ # retry if invalid content md5
47
+ if response.context.get('validate_content', False) and response.http_response.headers.get('content-md5'):
48
+ try:
49
+ await response.http_response.read() # Load the body in memory and close the socket
50
+ except (StreamClosedError, StreamConsumedError):
51
+ pass
52
+ computed_md5 = response.http_request.headers.get('content-md5', None) or \
53
+ encode_base64(StorageContentValidation.get_content_md5(response.http_response.content))
54
+ if response.http_response.headers['content-md5'] != computed_md5:
55
+ return True
56
+ return False
57
+
58
+
45
59
  class AsyncStorageResponseHook(AsyncHTTPPolicy):
46
60
 
47
61
  def __init__(self, **kwargs): # pylint: disable=unused-argument
@@ -64,9 +78,8 @@ class AsyncStorageResponseHook(AsyncHTTPPolicy):
64
78
  request.context.options.pop('raw_response_hook', self._response_callback)
65
79
 
66
80
  response = await self.next.send(request)
67
- await response.http_response.load_body()
81
+ will_retry = is_retry(response, request.context.options.get('mode')) or await is_checksum_retry(response)
68
82
 
69
- will_retry = is_retry(response, request.context.options.get('mode'))
70
83
  # Auth error could come from Bearer challenge, in which case this request will be made again
71
84
  is_auth_error = response.http_response.status_code == 401
72
85
  should_update_counts = not (will_retry or is_auth_error)
@@ -112,7 +125,7 @@ class AsyncStorageRetryPolicy(StorageRetryPolicy):
112
125
  while retries_remaining:
113
126
  try:
114
127
  response = await self.next.send(request)
115
- if is_retry(response, retry_settings['mode']):
128
+ if is_retry(response, retry_settings['mode']) or await is_checksum_retry(response):
116
129
  retries_remaining = self.increment(
117
130
  retry_settings,
118
131
  request=request.http_request,
@@ -107,8 +107,17 @@ class SharedAccessSignature(object):
107
107
  self.account_key = account_key
108
108
  self.x_ms_version = x_ms_version
109
109
 
110
- def generate_account(self, services, resource_types, permission, expiry, start=None,
111
- ip=None, protocol=None, **kwargs) -> str:
110
+ def generate_account(
111
+ self, services,
112
+ resource_types,
113
+ permission,
114
+ expiry,
115
+ start=None,
116
+ ip=None,
117
+ protocol=None,
118
+ sts_hook=None,
119
+ **kwargs
120
+ ) -> str:
112
121
  '''
113
122
  Generates a shared access signature for the account.
114
123
  Use the returned signature with the sas_token parameter of the service
@@ -152,6 +161,10 @@ class SharedAccessSignature(object):
152
161
  :keyword str encryption_scope:
153
162
  Optional. If specified, this is the encryption scope to use when sending requests
154
163
  authorized with this SAS URI.
164
+ :param sts_hook:
165
+ For debugging purposes only. If provided, the hook is called with the string to sign
166
+ that was used to generate the SAS.
167
+ :type sts_hook: Optional[Callable[[str], None]]
155
168
  :returns: The generated SAS token for the account.
156
169
  :rtype: str
157
170
  '''
@@ -161,12 +174,16 @@ class SharedAccessSignature(object):
161
174
  sas.add_encryption_scope(**kwargs)
162
175
  sas.add_account_signature(self.account_name, self.account_key)
163
176
 
177
+ if sts_hook is not None:
178
+ sts_hook(sas.string_to_sign)
179
+
164
180
  return sas.get_token()
165
181
 
166
182
 
167
183
  class _SharedAccessHelper(object):
168
184
  def __init__(self):
169
185
  self.query_dict = {}
186
+ self.string_to_sign = ""
170
187
 
171
188
  def _add_query(self, name, val):
172
189
  if val:
@@ -229,6 +246,7 @@ class _SharedAccessHelper(object):
229
246
 
230
247
  self._add_query(QueryStringConstants.SIGNED_SIGNATURE,
231
248
  sign_string(account_key, string_to_sign))
249
+ self.string_to_sign = string_to_sign
232
250
 
233
251
  def get_token(self) -> str:
234
252
  return '&'.join([f'{n}={url_quote(v)}' for n, v in self.query_dict.items() if v is not None])
@@ -5,7 +5,10 @@
5
5
  # --------------------------------------------------------------------------
6
6
  # pylint: disable=docstring-keyword-should-match-keyword-only
7
7
 
8
- from typing import Union, Optional, Any, TYPE_CHECKING
8
+ from typing import (
9
+ Any, Callable, Optional, Union,
10
+ TYPE_CHECKING
11
+ )
9
12
  from urllib.parse import parse_qs
10
13
 
11
14
  from ._shared import sign_string, url_quote
@@ -64,6 +67,7 @@ class BlobSharedAccessSignature(SharedAccessSignature):
64
67
  content_encoding: Optional[str] = None,
65
68
  content_language: Optional[str] = None,
66
69
  content_type: Optional[str] = None,
70
+ sts_hook: Optional[Callable[[str], None]] = None,
67
71
  **kwargs: Any
68
72
  ) -> str:
69
73
  '''
@@ -132,6 +136,10 @@ class BlobSharedAccessSignature(SharedAccessSignature):
132
136
  :param str content_type:
133
137
  Response header value for Content-Type when resource is accessed
134
138
  using this shared access signature.
139
+ :param sts_hook:
140
+ For debugging purposes only. If provided, the hook is called with the string to sign
141
+ that was used to generate the SAS.
142
+ :type sts_hook: Optional[Callable[[str], None]]
135
143
  :return: A Shared Access Signature (sas) token.
136
144
  :rtype: str
137
145
  '''
@@ -155,6 +163,9 @@ class BlobSharedAccessSignature(SharedAccessSignature):
155
163
  sas.add_resource_signature(self.account_name, self.account_key, resource_path,
156
164
  user_delegation_key=self.user_delegation_key)
157
165
 
166
+ if sts_hook is not None:
167
+ sts_hook(sas.string_to_sign)
168
+
158
169
  return sas.get_token()
159
170
 
160
171
  def generate_container(
@@ -170,6 +181,7 @@ class BlobSharedAccessSignature(SharedAccessSignature):
170
181
  content_encoding: Optional[str] = None,
171
182
  content_language: Optional[str] = None,
172
183
  content_type: Optional[str] = None,
184
+ sts_hook: Optional[Callable[[str], None]] = None,
173
185
  **kwargs: Any
174
186
  ) -> str:
175
187
  '''
@@ -228,6 +240,10 @@ class BlobSharedAccessSignature(SharedAccessSignature):
228
240
  :param str content_type:
229
241
  Response header value for Content-Type when resource is accessed
230
242
  using this shared access signature.
243
+ :param sts_hook:
244
+ For debugging purposes only. If provided, the hook is called with the string to sign
245
+ that was used to generate the SAS.
246
+ :type sts_hook: Optional[Callable[[str], None]]
231
247
  :return: A Shared Access Signature (sas) token.
232
248
  :rtype: str
233
249
  '''
@@ -242,6 +258,10 @@ class BlobSharedAccessSignature(SharedAccessSignature):
242
258
  sas.add_info_for_hns_account(**kwargs)
243
259
  sas.add_resource_signature(self.account_name, self.account_key, container_name,
244
260
  user_delegation_key=self.user_delegation_key)
261
+
262
+ if sts_hook is not None:
263
+ sts_hook(sas.string_to_sign)
264
+
245
265
  return sas.get_token()
246
266
 
247
267
 
@@ -316,6 +336,7 @@ class _BlobSharedAccessHelper(_SharedAccessHelper):
316
336
  self._add_query(QueryStringConstants.SIGNED_SIGNATURE,
317
337
  sign_string(account_key if user_delegation_key is None else user_delegation_key.value,
318
338
  string_to_sign))
339
+ self.string_to_sign = string_to_sign
319
340
 
320
341
  def get_token(self) -> str:
321
342
  # a conscious decision was made to exclude the timestamp in the generated token
@@ -335,6 +356,7 @@ def generate_account_sas(
335
356
  ip: Optional[str] = None,
336
357
  *,
337
358
  services: Union[Services, str] = Services(blob=True),
359
+ sts_hook: Optional[Callable[[str], None]] = None,
338
360
  **kwargs: Any
339
361
  ) -> str:
340
362
  """Generates a shared access signature for the blob service.
@@ -376,6 +398,10 @@ def generate_account_sas(
376
398
  Specifies the protocol permitted for a request made. The default value is https.
377
399
  :keyword str encryption_scope:
378
400
  Specifies the encryption scope for a request made so that all write operations will be service encrypted.
401
+ :keyword sts_hook:
402
+ For debugging purposes only. If provided, the hook is called with the string to sign
403
+ that was used to generate the SAS.
404
+ :paramtype sts_hook: Optional[Callable[[str], None]]
379
405
  :return: A Shared Access Signature (sas) token.
380
406
  :rtype: str
381
407
 
@@ -396,6 +422,7 @@ def generate_account_sas(
396
422
  expiry=expiry,
397
423
  start=start,
398
424
  ip=ip,
425
+ sts_hook=sts_hook,
399
426
  **kwargs
400
427
  )
401
428
 
@@ -410,6 +437,8 @@ def generate_container_sas(
410
437
  start: Optional[Union["datetime", str]] = None,
411
438
  policy_id: Optional[str] = None,
412
439
  ip: Optional[str] = None,
440
+ *,
441
+ sts_hook: Optional[Callable[[str], None]] = None,
413
442
  **kwargs: Any
414
443
  ) -> str:
415
444
  """Generates a shared access signature for a container.
@@ -483,6 +512,10 @@ def generate_container_sas(
483
512
  :keyword str correlation_id:
484
513
  The correlation id to correlate the storage audit logs with the audit logs used by the principal
485
514
  generating and distributing the SAS. This can only be used when generating a SAS with delegation key.
515
+ :keyword sts_hook:
516
+ For debugging purposes only. If provided, the hook is called with the string to sign
517
+ that was used to generate the SAS.
518
+ :paramtype sts_hook: Optional[Callable[[str], None]]
486
519
  :return: A Shared Access Signature (sas) token.
487
520
  :rtype: str
488
521
 
@@ -515,6 +548,7 @@ def generate_container_sas(
515
548
  start=start,
516
549
  policy_id=policy_id,
517
550
  ip=ip,
551
+ sts_hook=sts_hook,
518
552
  **kwargs
519
553
  )
520
554
 
@@ -531,6 +565,8 @@ def generate_blob_sas(
531
565
  start: Optional[Union["datetime", str]] = None,
532
566
  policy_id: Optional[str] = None,
533
567
  ip: Optional[str] = None,
568
+ *,
569
+ sts_hook: Optional[Callable[[str], None]] = None,
534
570
  **kwargs: Any
535
571
  ) -> str:
536
572
  """Generates a shared access signature for a blob.
@@ -616,6 +652,10 @@ def generate_blob_sas(
616
652
  :keyword str correlation_id:
617
653
  The correlation id to correlate the storage audit logs with the audit logs used by the principal
618
654
  generating and distributing the SAS. This can only be used when generating a SAS with delegation key.
655
+ :keyword sts_hook:
656
+ For debugging purposes only. If provided, the hook is called with the string to sign
657
+ that was used to generate the SAS.
658
+ :paramtype sts_hook: Optional[Callable[[str], None]]
619
659
  :return: A Shared Access Signature (sas) token.
620
660
  :rtype: str
621
661
  """
@@ -645,6 +685,7 @@ def generate_blob_sas(
645
685
  start=start,
646
686
  policy_id=policy_id,
647
687
  ip=ip,
688
+ sts_hook=sts_hook,
648
689
  **kwargs
649
690
  )
650
691
 
@@ -4,4 +4,4 @@
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
6
 
7
- VERSION = "12.22.0"
7
+ VERSION = "12.23.0"
@@ -1406,6 +1406,8 @@ class ContainerClient(AsyncStorageAccountHostsMixin, StorageAccountHostsMixin, S
1406
1406
  """
1407
1407
  if len(blobs) == 0:
1408
1408
  return AsyncList([])
1409
+ if self._is_localhost:
1410
+ kwargs['url_prepend'] = self.account_name
1409
1411
 
1410
1412
  reqs, options = _generate_delete_blobs_options(
1411
1413
  self._query_str,
@@ -1485,6 +1487,8 @@ class ContainerClient(AsyncStorageAccountHostsMixin, StorageAccountHostsMixin, S
1485
1487
  :return: An async iterator of responses, one for each blob in order
1486
1488
  :rtype: asynciterator[~azure.core.pipeline.transport.AsyncHttpResponse]
1487
1489
  """
1490
+ if self._is_localhost:
1491
+ kwargs['url_prepend'] = self.account_name
1488
1492
  reqs, options = _generate_set_tiers_options(
1489
1493
  self._query_str,
1490
1494
  self.container_name,
@@ -1544,6 +1548,8 @@ class ContainerClient(AsyncStorageAccountHostsMixin, StorageAccountHostsMixin, S
1544
1548
  :return: An async iterator of responses, one for each blob in order
1545
1549
  :rtype: asynciterator[~azure.core.pipeline.transport.AsyncHttpResponse]
1546
1550
  """
1551
+ if self._is_localhost:
1552
+ kwargs['url_prepend'] = self.account_name
1547
1553
  reqs, options = _generate_set_tiers_options(
1548
1554
  self._query_str,
1549
1555
  self.container_name,