azure-storage-blob 12.23.0b1__py3-none-any.whl → 12.24.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 (54) hide show
  1. azure/storage/blob/_blob_client.py +34 -10
  2. azure/storage/blob/_blob_client_helpers.py +7 -3
  3. azure/storage/blob/_blob_service_client.py +1 -1
  4. azure/storage/blob/_container_client.py +8 -2
  5. azure/storage/blob/_container_client_helpers.py +11 -6
  6. azure/storage/blob/_deserialize.py +2 -2
  7. azure/storage/blob/_encryption.py +15 -10
  8. azure/storage/blob/_generated/_azure_blob_storage.py +3 -2
  9. azure/storage/blob/_generated/_configuration.py +2 -2
  10. azure/storage/blob/_generated/_serialization.py +267 -150
  11. azure/storage/blob/_generated/aio/_azure_blob_storage.py +3 -2
  12. azure/storage/blob/_generated/aio/_configuration.py +2 -2
  13. azure/storage/blob/_generated/aio/operations/_append_blob_operations.py +23 -11
  14. azure/storage/blob/_generated/aio/operations/_blob_operations.py +137 -73
  15. azure/storage/blob/_generated/aio/operations/_block_blob_operations.py +42 -16
  16. azure/storage/blob/_generated/aio/operations/_container_operations.py +49 -44
  17. azure/storage/blob/_generated/aio/operations/_page_blob_operations.py +35 -23
  18. azure/storage/blob/_generated/aio/operations/_service_operations.py +30 -25
  19. azure/storage/blob/_generated/models/_azure_blob_storage_enums.py +1 -0
  20. azure/storage/blob/_generated/operations/_append_blob_operations.py +35 -15
  21. azure/storage/blob/_generated/operations/_blob_operations.py +187 -98
  22. azure/storage/blob/_generated/operations/_block_blob_operations.py +64 -22
  23. azure/storage/blob/_generated/operations/_container_operations.py +67 -62
  24. azure/storage/blob/_generated/operations/_page_blob_operations.py +52 -32
  25. azure/storage/blob/_generated/operations/_service_operations.py +38 -33
  26. azure/storage/blob/_list_blobs_helper.py +1 -1
  27. azure/storage/blob/_models.py +4 -3
  28. azure/storage/blob/_serialize.py +1 -0
  29. azure/storage/blob/_shared/avro/schema.py +1 -0
  30. azure/storage/blob/_shared/base_client.py +10 -8
  31. azure/storage/blob/_shared/base_client_async.py +5 -5
  32. azure/storage/blob/_shared/models.py +5 -2
  33. azure/storage/blob/_shared/policies.py +14 -16
  34. azure/storage/blob/_shared/policies_async.py +19 -6
  35. azure/storage/blob/_shared/request_handlers.py +2 -3
  36. azure/storage/blob/_shared/response_handlers.py +2 -2
  37. azure/storage/blob/_shared/uploads.py +4 -4
  38. azure/storage/blob/_shared/uploads_async.py +4 -4
  39. azure/storage/blob/_shared_access_signature.py +0 -1
  40. azure/storage/blob/_version.py +1 -1
  41. azure/storage/blob/aio/_blob_client_async.py +36 -13
  42. azure/storage/blob/aio/_blob_service_client_async.py +7 -3
  43. azure/storage/blob/aio/_container_client_async.py +10 -4
  44. azure/storage/blob/aio/_download_async.py +94 -71
  45. azure/storage/blob/aio/_lease_async.py +1 -1
  46. azure/storage/blob/aio/_list_blobs_helper.py +1 -2
  47. azure/storage/blob/aio/_models.py +1 -2
  48. {azure_storage_blob-12.23.0b1.dist-info → azure_storage_blob-12.24.0.dist-info}/METADATA +10 -10
  49. azure_storage_blob-12.24.0.dist-info/RECORD +84 -0
  50. {azure_storage_blob-12.23.0b1.dist-info → azure_storage_blob-12.24.0.dist-info}/WHEEL +1 -1
  51. azure/storage/blob/_generated/_vendor.py +0 -16
  52. azure_storage_blob-12.23.0b1.dist-info/RECORD +0 -85
  53. {azure_storage_blob-12.23.0b1.dist-info → azure_storage_blob-12.24.0.dist-info}/LICENSE +0 -0
  54. {azure_storage_blob-12.23.0b1.dist-info → azure_storage_blob-12.24.0.dist-info}/top_level.txt +0 -0
@@ -187,7 +187,7 @@ class BlobClient(StorageAccountHostsMixin, StorageEncryptionMixin): # pylint: d
187
187
  self._query_str, credential = self._format_query_string(sas_token, credential, snapshot=self.snapshot)
188
188
  super(BlobClient, self).__init__(parsed_url, service='blob', credential=credential, **kwargs)
189
189
  self._client = AzureBlobStorage(self.url, base_url=self.url, pipeline=self._pipeline)
190
- self._client._config.version = get_api_version(kwargs) # type: ignore [assignment] # pylint: disable=protected-access
190
+ self._client._config.version = get_api_version(kwargs) # type: ignore [assignment]
191
191
  self._configure_encryption(kwargs)
192
192
 
193
193
  def _format_url(self, hostname: str) -> str:
@@ -320,7 +320,12 @@ class BlobClient(StorageAccountHostsMixin, StorageEncryptionMixin): # pylint: d
320
320
  process_storage_error(error)
321
321
 
322
322
  @distributed_trace
323
- def upload_blob_from_url(self, source_url: str, **kwargs: Any) -> Dict[str, Any]:
323
+ def upload_blob_from_url(
324
+ self, source_url: str,
325
+ *,
326
+ metadata: Optional[Dict[str, str]] = None,
327
+ **kwargs: Any
328
+ ) -> Dict[str, Any]:
324
329
  """
325
330
  Creates a new Block Blob where the content of the blob is read from a given URL.
326
331
  The content of an existing blob is overwritten with the new blob.
@@ -337,6 +342,8 @@ class BlobClient(StorageAccountHostsMixin, StorageEncryptionMixin): # pylint: d
337
342
  https://myaccount.blob.core.windows.net/mycontainer/myblob?snapshot=<DateTime>
338
343
 
339
344
  https://otheraccount.blob.core.windows.net/mycontainer/myblob?sastoken
345
+ :keyword dict(str, str) metadata:
346
+ Name-value pairs associated with the blob as metadata.
340
347
  :keyword bool overwrite: Whether the blob to be uploaded should overwrite the current data.
341
348
  If True, upload_blob will overwrite the existing data. If set to False, the
342
349
  operation will fail with ResourceExistsError.
@@ -422,6 +429,7 @@ class BlobClient(StorageAccountHostsMixin, StorageEncryptionMixin): # pylint: d
422
429
  raise ValueError("Customer provided encryption key must be used over HTTPS.")
423
430
  options = _upload_blob_from_url_options(
424
431
  source_url=source_url,
432
+ metadata=metadata,
425
433
  **kwargs)
426
434
  try:
427
435
  return cast(Dict[str, Any], self._client.block_blob.put_blob_from_url(**options))
@@ -532,8 +540,9 @@ class BlobClient(StorageAccountHostsMixin, StorageEncryptionMixin): # pylint: d
532
540
  value specified in this header, the request will fail with
533
541
  MaxBlobSizeConditionNotMet error (HTTP status code 412 - Precondition Failed).
534
542
  :keyword int max_concurrency:
535
- Maximum number of parallel connections to use when the blob size exceeds
536
- 64MB.
543
+ Maximum number of parallel connections to use when transferring the blob in chunks.
544
+ This option does not affect the underlying connection pool, and may
545
+ require a separate configuration of the connection pool.
537
546
  :keyword ~azure.storage.blob.CustomerProvidedEncryptionKey cpk:
538
547
  Encrypts the data on the service-side with the given key.
539
548
  Use of customer-provided keys must be done over HTTPS.
@@ -687,7 +696,9 @@ class BlobClient(StorageAccountHostsMixin, StorageEncryptionMixin): # pylint: d
687
696
  As the encryption key itself is provided in the request,
688
697
  a secure connection must be established to transfer the key.
689
698
  :keyword int max_concurrency:
690
- The number of parallel connections with which to download.
699
+ Maximum number of parallel connections to use when transferring the blob in chunks.
700
+ This option does not affect the underlying connection pool, and may
701
+ require a separate configuration of the connection pool.
691
702
  :keyword Optional[str] encoding:
692
703
  Encoding to decode the downloaded bytes. Default is None, i.e. no decoding.
693
704
  :keyword progress_hook:
@@ -1220,6 +1231,9 @@ class BlobClient(StorageAccountHostsMixin, StorageEncryptionMixin): # pylint: d
1220
1231
  .. versionadded:: 12.10.0
1221
1232
  This was introduced in API version '2020-10-02'.
1222
1233
 
1234
+ :keyword str version_id:
1235
+ The version id parameter is an opaque DateTime
1236
+ value that, when present, specifies the version of the blob to check if it exists.
1223
1237
  :keyword int timeout:
1224
1238
  Sets the server-side timeout for the operation in seconds. For more details see
1225
1239
  https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
@@ -1230,9 +1244,11 @@ class BlobClient(StorageAccountHostsMixin, StorageEncryptionMixin): # pylint: d
1230
1244
  :rtype: Dict[str, str]
1231
1245
  """
1232
1246
 
1247
+ version_id = get_version_id(self.version_id, kwargs)
1233
1248
  kwargs['immutability_policy_expiry'] = immutability_policy.expiry_time
1234
1249
  kwargs['immutability_policy_mode'] = immutability_policy.policy_mode
1235
- return cast(Dict[str, str], self._client.blob.set_immutability_policy(cls=return_response_headers, **kwargs))
1250
+ return cast(Dict[str, str], self._client.blob.set_immutability_policy(
1251
+ cls=return_response_headers, version_id=version_id, **kwargs))
1236
1252
 
1237
1253
  @distributed_trace
1238
1254
  def delete_immutability_policy(self, **kwargs: Any) -> None:
@@ -1241,6 +1257,9 @@ class BlobClient(StorageAccountHostsMixin, StorageEncryptionMixin): # pylint: d
1241
1257
  .. versionadded:: 12.10.0
1242
1258
  This operation was introduced in API version '2020-10-02'.
1243
1259
 
1260
+ :keyword str version_id:
1261
+ The version id parameter is an opaque DateTime
1262
+ value that, when present, specifies the version of the blob to check if it exists.
1244
1263
  :keyword int timeout:
1245
1264
  Sets the server-side timeout for the operation in seconds. For more details see
1246
1265
  https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
@@ -1251,7 +1270,8 @@ class BlobClient(StorageAccountHostsMixin, StorageEncryptionMixin): # pylint: d
1251
1270
  :rtype: Dict[str, str]
1252
1271
  """
1253
1272
 
1254
- self._client.blob.delete_immutability_policy(**kwargs)
1273
+ version_id = get_version_id(self.version_id, kwargs)
1274
+ self._client.blob.delete_immutability_policy(version_id=version_id, **kwargs)
1255
1275
 
1256
1276
  @distributed_trace
1257
1277
  def set_legal_hold(self, legal_hold: bool, **kwargs: Any) -> Dict[str, Union[str, datetime, bool]]:
@@ -1262,6 +1282,9 @@ class BlobClient(StorageAccountHostsMixin, StorageEncryptionMixin): # pylint: d
1262
1282
 
1263
1283
  :param bool legal_hold:
1264
1284
  Specified if a legal hold should be set on the blob.
1285
+ :keyword str version_id:
1286
+ The version id parameter is an opaque DateTime
1287
+ value that, when present, specifies the version of the blob to check if it exists.
1265
1288
  :keyword int timeout:
1266
1289
  Sets the server-side timeout for the operation in seconds. For more details see
1267
1290
  https://learn.microsoft.com/rest/api/storageservices/setting-timeouts-for-blob-service-operations.
@@ -1272,8 +1295,9 @@ class BlobClient(StorageAccountHostsMixin, StorageEncryptionMixin): # pylint: d
1272
1295
  :rtype: Dict[str, Union[str, datetime, bool]]
1273
1296
  """
1274
1297
 
1275
- return cast(Dict[str, Union[str, datetime, bool]],
1276
- self._client.blob.set_legal_hold(legal_hold, cls=return_response_headers, **kwargs))
1298
+ version_id = get_version_id(self.version_id, kwargs)
1299
+ return cast(Dict[str, Union[str, datetime, bool]], self._client.blob.set_legal_hold(
1300
+ legal_hold, version_id=version_id, cls=return_response_headers, **kwargs))
1277
1301
 
1278
1302
  @distributed_trace
1279
1303
  def create_page_blob(
@@ -3281,7 +3305,7 @@ class BlobClient(StorageAccountHostsMixin, StorageEncryptionMixin): # pylint: d
3281
3305
  policies=self._pipeline._impl_policies # pylint: disable = protected-access
3282
3306
  )
3283
3307
  else:
3284
- _pipeline = self._pipeline # pylint: disable = protected-access
3308
+ _pipeline = self._pipeline
3285
3309
  return ContainerClient(
3286
3310
  f"{self.scheme}://{self.primary_hostname}", container_name=self.container_name,
3287
3311
  credential=self._raw_credential, api_version=self.api_version, _configuration=self._config,
@@ -188,7 +188,10 @@ def _upload_blob_options( # pylint:disable=too-many-statements
188
188
  raise ValueError(f"Unsupported BlobType: {blob_type}")
189
189
  return kwargs
190
190
 
191
- def _upload_blob_from_url_options(source_url: str, **kwargs: Any ) -> Dict[str, Any]:
191
+ def _upload_blob_from_url_options(source_url: str, **kwargs: Any) -> Dict[str, Any]:
192
+ metadata = kwargs.pop('metadata', None)
193
+ headers = kwargs.pop('headers', {})
194
+ headers.update(add_metadata_headers(metadata))
192
195
  source_url = _encode_source_url(source_url=source_url)
193
196
  tier = kwargs.pop('standard_blob_tier', None)
194
197
  overwrite = kwargs.pop('overwrite', False)
@@ -222,10 +225,11 @@ def _upload_blob_from_url_options(source_url: str, **kwargs: Any ) -> Dict[str,
222
225
  'tier': tier.value if tier else None,
223
226
  'source_modified_access_conditions': get_source_conditions(kwargs),
224
227
  'cpk_info': cpk_info,
225
- 'cpk_scope_info': get_cpk_scope_info(kwargs)
228
+ 'cpk_scope_info': get_cpk_scope_info(kwargs),
229
+ 'headers': headers,
226
230
  }
227
231
  options.update(kwargs)
228
- if not overwrite and not _any_conditions(**options): # pylint: disable=protected-access
232
+ if not overwrite and not _any_conditions(**options):
229
233
  options['modified_access_conditions'].if_none_match = '*'
230
234
  return options
231
235
 
@@ -128,7 +128,7 @@ class BlobServiceClient(StorageAccountHostsMixin, StorageEncryptionMixin):
128
128
  self._query_str, credential = self._format_query_string(sas_token, credential)
129
129
  super(BlobServiceClient, self).__init__(parsed_url, service='blob', credential=credential, **kwargs)
130
130
  self._client = AzureBlobStorage(self.url, base_url=self.url, pipeline=self._pipeline)
131
- self._client._config.version = get_api_version(kwargs) # type: ignore [assignment] # pylint: disable=protected-access
131
+ self._client._config.version = get_api_version(kwargs) # type: ignore [assignment]
132
132
  self._configure_encryption(kwargs)
133
133
 
134
134
  def _format_url(self, hostname):
@@ -624,7 +624,7 @@ class ContainerClient(StorageAccountHostsMixin, StorageEncryptionMixin): # py
624
624
  process_storage_error(error)
625
625
 
626
626
  @distributed_trace
627
- def _get_blob_service_client(self) -> "BlobServiceClient": # pylint: disable=client-method-missing-kwargs
627
+ def _get_blob_service_client(self) -> "BlobServiceClient":
628
628
  """Get a client to interact with the container's parent service account.
629
629
 
630
630
  Defaults to current container's credentials.
@@ -648,7 +648,7 @@ class ContainerClient(StorageAccountHostsMixin, StorageEncryptionMixin): # py
648
648
  policies=self._pipeline._impl_policies # pylint: disable = protected-access
649
649
  )
650
650
  else:
651
- _pipeline = self._pipeline # pylint: disable = protected-access
651
+ _pipeline = self._pipeline
652
652
  return BlobServiceClient(
653
653
  f"{self.scheme}://{self.primary_hostname}",
654
654
  credential=self._raw_credential, api_version=self.api_version, _configuration=self._config,
@@ -1412,6 +1412,8 @@ class ContainerClient(StorageAccountHostsMixin, StorageEncryptionMixin): # py
1412
1412
  """
1413
1413
  if len(blobs) == 0:
1414
1414
  return iter([])
1415
+ if self._is_localhost:
1416
+ kwargs['url_prepend'] = self.account_name
1415
1417
 
1416
1418
  reqs, options = _generate_delete_blobs_options(
1417
1419
  self._query_str,
@@ -1494,6 +1496,8 @@ class ContainerClient(StorageAccountHostsMixin, StorageEncryptionMixin): # py
1494
1496
  :return: An iterator of responses, one for each blob in order
1495
1497
  :rtype: Iterator[~azure.core.pipeline.transport.HttpResponse]
1496
1498
  """
1499
+ if self._is_localhost:
1500
+ kwargs['url_prepend'] = self.account_name
1497
1501
  reqs, options = _generate_set_tiers_options(
1498
1502
  self._query_str,
1499
1503
  self.container_name,
@@ -1553,6 +1557,8 @@ class ContainerClient(StorageAccountHostsMixin, StorageEncryptionMixin): # py
1553
1557
  :return: An iterator of responses, one for each blob in order
1554
1558
  :rtype: Iterator[~azure.core.pipeline.transport.HttpResponse]
1555
1559
  """
1560
+ if self._is_localhost:
1561
+ kwargs['url_prepend'] = self.account_name
1556
1562
  reqs, options = _generate_set_tiers_options(
1557
1563
  self._query_str,
1558
1564
  self.container_name,
@@ -120,6 +120,7 @@ def _generate_delete_blobs_options(
120
120
  if_modified_since = kwargs.pop('if_modified_since', None)
121
121
  if_unmodified_since = kwargs.pop('if_unmodified_since', None)
122
122
  if_tags_match_condition = kwargs.pop('if_tags_match_condition', None)
123
+ url_prepend = kwargs.pop('url_prepend', None)
123
124
  kwargs.update({'raise_on_any_failure': raise_on_any_failure,
124
125
  'sas': query_str.replace('?', '&'),
125
126
  'timeout': '&timeout=' + str(timeout) if timeout else "",
@@ -131,7 +132,7 @@ def _generate_delete_blobs_options(
131
132
  for blob in blobs:
132
133
  if not isinstance(blob, str):
133
134
  blob_name = blob.get('name')
134
- options = _generic_delete_blob_options( # pylint: disable=protected-access
135
+ options = _generic_delete_blob_options(
135
136
  snapshot=blob.get('snapshot'),
136
137
  version_id=blob.get('version_id'),
137
138
  delete_snapshots=delete_snapshots or blob.get('delete_snapshots'),
@@ -146,7 +147,7 @@ def _generate_delete_blobs_options(
146
147
  )
147
148
  else:
148
149
  blob_name = blob
149
- options = _generic_delete_blob_options( # pylint: disable=protected-access
150
+ options = _generic_delete_blob_options(
150
151
  delete_snapshots=delete_snapshots,
151
152
  if_modified_since=if_modified_since,
152
153
  if_unmodified_since=if_unmodified_since,
@@ -157,9 +158,11 @@ def _generate_delete_blobs_options(
157
158
 
158
159
  req = HttpRequest(
159
160
  "DELETE",
160
- f"/{quote(container_name)}/{quote(str(blob_name), safe='/~')}{query_str}",
161
+ (f"{'/' + quote(url_prepend) if url_prepend else ''}/"
162
+ f"{quote(container_name)}/{quote(str(blob_name), safe='/~')}{query_str}"),
161
163
  headers=header_parameters
162
164
  )
165
+
163
166
  req.format_parameters(query_parameters)
164
167
  reqs.append(req)
165
168
 
@@ -196,11 +199,11 @@ def _generate_set_tiers_subrequest_options(
196
199
  query_parameters['versionid'] = client._serialize.query("version_id", version_id, 'str') # pylint: disable=protected-access
197
200
  if timeout is not None:
198
201
  query_parameters['timeout'] = client._serialize.query("timeout", timeout, 'int', minimum=0) # pylint: disable=protected-access
199
- query_parameters['comp'] = client._serialize.query("comp", comp, 'str') # pylint: disable=protected-access, specify-parameter-names-in-call
202
+ query_parameters['comp'] = client._serialize.query("comp", comp, 'str') # pylint: disable=protected-access
200
203
 
201
204
  # Construct headers
202
205
  header_parameters = {}
203
- header_parameters['x-ms-access-tier'] = client._serialize.header("tier", tier, 'str') # pylint: disable=protected-access, specify-parameter-names-in-call
206
+ header_parameters['x-ms-access-tier'] = client._serialize.header("tier", tier, 'str') # pylint: disable=protected-access
204
207
  if rehydrate_priority is not None:
205
208
  header_parameters['x-ms-rehydrate-priority'] = client._serialize.header( # pylint: disable=protected-access
206
209
  "rehydrate_priority", rehydrate_priority, 'str')
@@ -223,6 +226,7 @@ def _generate_set_tiers_options(
223
226
  raise_on_any_failure = kwargs.pop('raise_on_any_failure', True)
224
227
  rehydrate_priority = kwargs.pop('rehydrate_priority', None)
225
228
  if_tags = kwargs.pop('if_tags_match_condition', None)
229
+ url_prepend = kwargs.pop('url_prepend', None)
226
230
  kwargs.update({'raise_on_any_failure': raise_on_any_failure,
227
231
  'sas': query_str.replace('?', '&'),
228
232
  'timeout': '&timeout=' + str(timeout) if timeout else "",
@@ -252,7 +256,8 @@ def _generate_set_tiers_options(
252
256
 
253
257
  req = HttpRequest(
254
258
  "PUT",
255
- f"/{quote(container_name)}/{quote(str(blob_name), safe='/~')}{query_str}",
259
+ (f"{'/' + quote(url_prepend) if url_prepend else ''}/"
260
+ f"{quote(container_name)}/{quote(str(blob_name), safe='/~')}{query_str}"),
256
261
  headers=header_parameters
257
262
  )
258
263
  req.format_parameters(query_parameters)
@@ -142,7 +142,7 @@ def service_properties_deserialize(generated: "StorageServiceProperties") -> Dic
142
142
  'hour_metrics': Metrics._from_generated(generated.hour_metrics), # pylint: disable=protected-access
143
143
  'minute_metrics': Metrics._from_generated(generated.minute_metrics), # pylint: disable=protected-access
144
144
  'cors': cors_list,
145
- 'target_version': generated.default_service_version, # pylint: disable=protected-access
145
+ 'target_version': generated.default_service_version,
146
146
  'delete_retention_policy': RetentionPolicy._from_generated(generated.delete_retention_policy), # pylint: disable=protected-access
147
147
  'static_website': StaticWebsite._from_generated(generated.static_website), # pylint: disable=protected-access
148
148
  }
@@ -181,7 +181,7 @@ def get_blob_properties_from_generated_code(generated: "BlobItemInternal") -> Bl
181
181
  blob.version_id = generated.version_id
182
182
  blob.is_current_version = generated.is_current_version
183
183
  blob.tag_count = generated.properties.tag_count
184
- blob.tags = parse_tags(generated.blob_tags) # pylint: disable=protected-access
184
+ blob.tags = parse_tags(generated.blob_tags)
185
185
  blob.object_replication_source_properties = deserialize_ors_policies(generated.object_replication_metadata)
186
186
  blob.last_accessed_on = generated.properties.last_accessed_on
187
187
  blob.immutability_policy = ImmutabilityPolicy._from_generated(generated) # pylint: disable=protected-access
@@ -40,6 +40,9 @@ if TYPE_CHECKING:
40
40
 
41
41
  _ENCRYPTION_PROTOCOL_V1 = '1.0'
42
42
  _ENCRYPTION_PROTOCOL_V2 = '2.0'
43
+ _ENCRYPTION_PROTOCOL_V2_1 = '2.1'
44
+ _VALID_ENCRYPTION_PROTOCOLS = [_ENCRYPTION_PROTOCOL_V1, _ENCRYPTION_PROTOCOL_V2, _ENCRYPTION_PROTOCOL_V2_1]
45
+ _ENCRYPTION_V2_PROTOCOLS = [_ENCRYPTION_PROTOCOL_V2, _ENCRYPTION_PROTOCOL_V2_1]
43
46
  _GCM_REGION_DATA_LENGTH = 4 * 1024 * 1024
44
47
  _GCM_NONCE_LENGTH = 12
45
48
  _GCM_TAG_LENGTH = 16
@@ -293,14 +296,14 @@ def encrypt_data_v2(data: bytes, nonce: int, key: bytes) -> bytes:
293
296
 
294
297
  def is_encryption_v2(encryption_data: Optional[_EncryptionData]) -> bool:
295
298
  """
296
- Determine whether the given encryption data signifies version 2.0.
299
+ Determine whether the given encryption data signifies version 2.0 or 2.1.
297
300
 
298
301
  :param Optional[_EncryptionData] encryption_data: The encryption data. Will return False if this is None.
299
302
  :return: True, if the encryption data indicates encryption V2, false otherwise.
300
303
  :rtype: bool
301
304
  """
302
305
  # If encryption_data is None, assume no encryption
303
- return bool(encryption_data and (encryption_data.encryption_agent.protocol == _ENCRYPTION_PROTOCOL_V2))
306
+ return bool(encryption_data and (encryption_data.encryption_agent.protocol in _ENCRYPTION_V2_PROTOCOLS))
304
307
 
305
308
 
306
309
  def modify_user_agent_for_encryption(
@@ -405,7 +408,7 @@ def get_adjusted_download_range_and_offset(
405
408
  end_offset = 15 - (end % 16)
406
409
  end += end_offset
407
410
 
408
- elif encryption_data.encryption_agent.protocol == _ENCRYPTION_PROTOCOL_V2:
411
+ elif encryption_data.encryption_agent.protocol in _ENCRYPTION_V2_PROTOCOLS:
409
412
  start_offset, end_offset = 0, end
410
413
 
411
414
  if encryption_data.encrypted_region_info is None:
@@ -505,6 +508,8 @@ def _generate_encryption_data_dict(
505
508
  # We must pad the version to 8 bytes for AES Keywrap algorithms
506
509
  to_wrap = _ENCRYPTION_PROTOCOL_V2.encode().ljust(8, b'\0') + cek
507
510
  wrapped_cek = kek.wrap_key(to_wrap)
511
+ else:
512
+ raise ValueError("Invalid encryption version specified.")
508
513
 
509
514
  # Build the encryption_data dict.
510
515
  # Use OrderedDict to comply with Java's ordering requirement.
@@ -550,7 +555,7 @@ def _dict_to_encryption_data(encryption_data_dict: Dict[str, Any]) -> _Encryptio
550
555
  """
551
556
  try:
552
557
  protocol = encryption_data_dict['EncryptionAgent']['Protocol']
553
- if protocol not in [_ENCRYPTION_PROTOCOL_V1, _ENCRYPTION_PROTOCOL_V2]:
558
+ if protocol not in _VALID_ENCRYPTION_PROTOCOLS:
554
559
  raise ValueError("Unsupported encryption version.")
555
560
  except KeyError as exc:
556
561
  raise ValueError("Unsupported encryption version.") from exc
@@ -636,7 +641,7 @@ def _validate_and_unwrap_cek(
636
641
  # Validate we have the right info for the specified version
637
642
  if encryption_data.encryption_agent.protocol == _ENCRYPTION_PROTOCOL_V1:
638
643
  _validate_not_none('content_encryption_IV', encryption_data.content_encryption_IV)
639
- elif encryption_data.encryption_agent.protocol == _ENCRYPTION_PROTOCOL_V2:
644
+ elif encryption_data.encryption_agent.protocol in _ENCRYPTION_V2_PROTOCOLS:
640
645
  _validate_not_none('encrypted_region_info', encryption_data.encrypted_region_info)
641
646
  else:
642
647
  raise ValueError('Specified encryption version is not supported.')
@@ -662,8 +667,8 @@ def _validate_and_unwrap_cek(
662
667
 
663
668
  # For V2, the version is included with the cek. We need to validate it
664
669
  # and remove it from the actual cek.
665
- if encryption_data.encryption_agent.protocol == _ENCRYPTION_PROTOCOL_V2:
666
- version_2_bytes = _ENCRYPTION_PROTOCOL_V2.encode().ljust(8, b'\0')
670
+ if encryption_data.encryption_agent.protocol in _ENCRYPTION_V2_PROTOCOLS:
671
+ version_2_bytes = encryption_data.encryption_agent.protocol.encode().ljust(8, b'\0')
667
672
  cek_version_bytes = content_encryption_key[:len(version_2_bytes)]
668
673
  if cek_version_bytes != version_2_bytes:
669
674
  raise ValueError('The encryption metadata is not valid and may have been modified.')
@@ -722,7 +727,7 @@ def _decrypt_message(
722
727
  unpadder = PKCS7(128).unpadder()
723
728
  decrypted_data = (unpadder.update(decrypted_data) + unpadder.finalize())
724
729
 
725
- elif encryption_data.encryption_agent.protocol == _ENCRYPTION_PROTOCOL_V2:
730
+ elif encryption_data.encryption_agent.protocol in _ENCRYPTION_V2_PROTOCOLS:
726
731
  block_info = encryption_data.encrypted_region_info
727
732
  if not block_info or not block_info.nonce_length:
728
733
  raise ValueError("Missing required metadata for decryption.")
@@ -894,7 +899,7 @@ def decrypt_blob( # pylint: disable=too-many-locals,too-many-statements
894
899
  raise ValueError('Specified encryption algorithm is not supported.')
895
900
 
896
901
  version = encryption_data.encryption_agent.protocol
897
- if version not in (_ENCRYPTION_PROTOCOL_V1, _ENCRYPTION_PROTOCOL_V2):
902
+ if version not in _VALID_ENCRYPTION_PROTOCOLS:
898
903
  raise ValueError('Specified encryption version is not supported.')
899
904
 
900
905
  content_encryption_key = _validate_and_unwrap_cek(encryption_data, key_encryption_key, key_resolver)
@@ -945,7 +950,7 @@ def decrypt_blob( # pylint: disable=too-many-locals,too-many-statements
945
950
 
946
951
  return content[start_offset: len(content) - end_offset]
947
952
 
948
- if version == _ENCRYPTION_PROTOCOL_V2:
953
+ if version in _ENCRYPTION_V2_PROTOCOLS:
949
954
  # We assume the content contains only full encryption regions
950
955
  total_size = len(content)
951
956
  offset = 0
@@ -8,6 +8,7 @@
8
8
 
9
9
  from copy import deepcopy
10
10
  from typing import Any
11
+ from typing_extensions import Self
11
12
 
12
13
  from azure.core import PipelineClient
13
14
  from azure.core.pipeline import policies
@@ -47,7 +48,7 @@ class AzureBlobStorage: # pylint: disable=client-accepts-api-version-keyword
47
48
  :param base_url: Service URL. Required. Default value is "".
48
49
  :type base_url: str
49
50
  :keyword version: Specifies the version of the operation to use for this request. Default value
50
- is "2024-08-04". Note that overriding this default value may result in unsupported behavior.
51
+ is "2025-01-05". Note that overriding this default value may result in unsupported behavior.
51
52
  :paramtype version: str
52
53
  """
53
54
 
@@ -110,7 +111,7 @@ class AzureBlobStorage: # pylint: disable=client-accepts-api-version-keyword
110
111
  def close(self) -> None:
111
112
  self._client.close()
112
113
 
113
- def __enter__(self) -> "AzureBlobStorage":
114
+ def __enter__(self) -> Self:
114
115
  self._client.__enter__()
115
116
  return self
116
117
 
@@ -23,12 +23,12 @@ class AzureBlobStorageConfiguration: # pylint: disable=too-many-instance-attrib
23
23
  desired operation. Required.
24
24
  :type url: str
25
25
  :keyword version: Specifies the version of the operation to use for this request. Default value
26
- is "2024-08-04". Note that overriding this default value may result in unsupported behavior.
26
+ is "2025-01-05". Note that overriding this default value may result in unsupported behavior.
27
27
  :paramtype version: str
28
28
  """
29
29
 
30
30
  def __init__(self, url: str, **kwargs: Any) -> None:
31
- version: Literal["2024-08-04"] = kwargs.pop("version", "2024-08-04")
31
+ version: Literal["2025-01-05"] = kwargs.pop("version", "2025-01-05")
32
32
 
33
33
  if url is None:
34
34
  raise ValueError("Parameter 'url' must not be None.")