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.
- azure/storage/blob/_blob_client.py +34 -10
- azure/storage/blob/_blob_client_helpers.py +7 -3
- azure/storage/blob/_blob_service_client.py +1 -1
- azure/storage/blob/_container_client.py +8 -2
- azure/storage/blob/_container_client_helpers.py +11 -6
- azure/storage/blob/_deserialize.py +2 -2
- azure/storage/blob/_encryption.py +15 -10
- azure/storage/blob/_generated/_azure_blob_storage.py +3 -2
- azure/storage/blob/_generated/_configuration.py +2 -2
- azure/storage/blob/_generated/_serialization.py +267 -150
- azure/storage/blob/_generated/aio/_azure_blob_storage.py +3 -2
- azure/storage/blob/_generated/aio/_configuration.py +2 -2
- azure/storage/blob/_generated/aio/operations/_append_blob_operations.py +23 -11
- azure/storage/blob/_generated/aio/operations/_blob_operations.py +137 -73
- azure/storage/blob/_generated/aio/operations/_block_blob_operations.py +42 -16
- azure/storage/blob/_generated/aio/operations/_container_operations.py +49 -44
- azure/storage/blob/_generated/aio/operations/_page_blob_operations.py +35 -23
- azure/storage/blob/_generated/aio/operations/_service_operations.py +30 -25
- azure/storage/blob/_generated/models/_azure_blob_storage_enums.py +1 -0
- azure/storage/blob/_generated/operations/_append_blob_operations.py +35 -15
- azure/storage/blob/_generated/operations/_blob_operations.py +187 -98
- azure/storage/blob/_generated/operations/_block_blob_operations.py +64 -22
- azure/storage/blob/_generated/operations/_container_operations.py +67 -62
- azure/storage/blob/_generated/operations/_page_blob_operations.py +52 -32
- azure/storage/blob/_generated/operations/_service_operations.py +38 -33
- azure/storage/blob/_list_blobs_helper.py +1 -1
- azure/storage/blob/_models.py +4 -3
- azure/storage/blob/_serialize.py +1 -0
- azure/storage/blob/_shared/avro/schema.py +1 -0
- azure/storage/blob/_shared/base_client.py +10 -8
- azure/storage/blob/_shared/base_client_async.py +5 -5
- azure/storage/blob/_shared/models.py +5 -2
- azure/storage/blob/_shared/policies.py +14 -16
- azure/storage/blob/_shared/policies_async.py +19 -6
- azure/storage/blob/_shared/request_handlers.py +2 -3
- azure/storage/blob/_shared/response_handlers.py +2 -2
- azure/storage/blob/_shared/uploads.py +4 -4
- azure/storage/blob/_shared/uploads_async.py +4 -4
- azure/storage/blob/_shared_access_signature.py +0 -1
- azure/storage/blob/_version.py +1 -1
- azure/storage/blob/aio/_blob_client_async.py +36 -13
- azure/storage/blob/aio/_blob_service_client_async.py +7 -3
- azure/storage/blob/aio/_container_client_async.py +10 -4
- azure/storage/blob/aio/_download_async.py +94 -71
- azure/storage/blob/aio/_lease_async.py +1 -1
- azure/storage/blob/aio/_list_blobs_helper.py +1 -2
- azure/storage/blob/aio/_models.py +1 -2
- {azure_storage_blob-12.23.0b1.dist-info → azure_storage_blob-12.24.0.dist-info}/METADATA +10 -10
- azure_storage_blob-12.24.0.dist-info/RECORD +84 -0
- {azure_storage_blob-12.23.0b1.dist-info → azure_storage_blob-12.24.0.dist-info}/WHEEL +1 -1
- azure/storage/blob/_generated/_vendor.py +0 -16
- azure_storage_blob-12.23.0b1.dist-info/RECORD +0 -85
- {azure_storage_blob-12.23.0b1.dist-info → azure_storage_blob-12.24.0.dist-info}/LICENSE +0 -0
- {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]
|
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(
|
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
|
536
|
-
|
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
|
-
|
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(
|
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.
|
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
|
-
|
1276
|
-
|
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
|
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
|
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):
|
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]
|
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":
|
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
|
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(
|
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(
|
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"
|
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
|
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
|
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"
|
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,
|
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)
|
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
|
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
|
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
|
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
|
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
|
666
|
-
version_2_bytes =
|
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
|
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
|
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
|
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 "
|
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) ->
|
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 "
|
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["
|
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.")
|