azure-storage-blob 12.21.0b1__py3-none-any.whl → 12.22.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 (43) hide show
  1. azure/storage/blob/__init__.py +19 -18
  2. azure/storage/blob/_blob_client.py +470 -1555
  3. azure/storage/blob/_blob_client_helpers.py +1242 -0
  4. azure/storage/blob/_blob_service_client.py +93 -112
  5. azure/storage/blob/_blob_service_client_helpers.py +27 -0
  6. azure/storage/blob/_container_client.py +169 -376
  7. azure/storage/blob/_container_client_helpers.py +261 -0
  8. azure/storage/blob/_deserialize.py +68 -44
  9. azure/storage/blob/_download.py +375 -241
  10. azure/storage/blob/_encryption.py +14 -7
  11. azure/storage/blob/_generated/py.typed +1 -0
  12. azure/storage/blob/_lease.py +52 -63
  13. azure/storage/blob/_list_blobs_helper.py +129 -135
  14. azure/storage/blob/_models.py +480 -277
  15. azure/storage/blob/_quick_query_helper.py +30 -31
  16. azure/storage/blob/_serialize.py +38 -56
  17. azure/storage/blob/_shared/avro/datafile.py +1 -1
  18. azure/storage/blob/_shared/avro/datafile_async.py +1 -1
  19. azure/storage/blob/_shared/base_client.py +1 -1
  20. azure/storage/blob/_shared/base_client_async.py +1 -1
  21. azure/storage/blob/_shared/policies.py +8 -6
  22. azure/storage/blob/_shared/policies_async.py +3 -1
  23. azure/storage/blob/_shared/response_handlers.py +6 -2
  24. azure/storage/blob/_shared/shared_access_signature.py +2 -2
  25. azure/storage/blob/_shared/uploads.py +1 -1
  26. azure/storage/blob/_shared/uploads_async.py +1 -1
  27. azure/storage/blob/_shared_access_signature.py +70 -53
  28. azure/storage/blob/_upload_helpers.py +75 -68
  29. azure/storage/blob/_version.py +1 -1
  30. azure/storage/blob/aio/__init__.py +19 -11
  31. azure/storage/blob/aio/_blob_client_async.py +554 -301
  32. azure/storage/blob/aio/_blob_service_client_async.py +148 -97
  33. azure/storage/blob/aio/_container_client_async.py +282 -139
  34. azure/storage/blob/aio/_download_async.py +408 -283
  35. azure/storage/blob/aio/_lease_async.py +61 -60
  36. azure/storage/blob/aio/_list_blobs_helper.py +94 -96
  37. azure/storage/blob/aio/_models.py +60 -38
  38. azure/storage/blob/aio/_upload_helpers.py +75 -66
  39. {azure_storage_blob-12.21.0b1.dist-info → azure_storage_blob-12.22.0.dist-info}/METADATA +7 -7
  40. {azure_storage_blob-12.21.0b1.dist-info → azure_storage_blob-12.22.0.dist-info}/RECORD +43 -39
  41. {azure_storage_blob-12.21.0b1.dist-info → azure_storage_blob-12.22.0.dist-info}/WHEEL +1 -1
  42. {azure_storage_blob-12.21.0b1.dist-info → azure_storage_blob-12.22.0.dist-info}/LICENSE +0 -0
  43. {azure_storage_blob-12.21.0b1.dist-info → azure_storage_blob-12.22.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1242 @@
1
+ # -------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See License.txt in the project root for
4
+ # license information.
5
+ # --------------------------------------------------------------------------
6
+ # pylint: disable=too-many-lines
7
+
8
+ from io import BytesIO
9
+ from typing import (
10
+ Any, AnyStr, AsyncGenerator, AsyncIterable, cast,
11
+ Dict, IO, Iterable, List, Optional, Tuple, Union,
12
+ TYPE_CHECKING
13
+ )
14
+ from urllib.parse import quote, unquote, urlparse
15
+
16
+ from ._deserialize import deserialize_blob_stream
17
+ from ._encryption import modify_user_agent_for_encryption, _ERROR_UNSUPPORTED_METHOD_FOR_ENCRYPTION
18
+ from ._generated.models import (
19
+ AppendPositionAccessConditions,
20
+ BlobHTTPHeaders,
21
+ BlockList,
22
+ BlockLookupList,
23
+ CpkInfo,
24
+ DeleteSnapshotsOptionType,
25
+ QueryRequest,
26
+ SequenceNumberAccessConditions
27
+ )
28
+ from ._models import (
29
+ BlobBlock,
30
+ BlobProperties,
31
+ BlobType,
32
+ DelimitedJsonDialect,
33
+ DelimitedTextDialect,
34
+ PremiumPageBlobTier,
35
+ QuickQueryDialect
36
+ )
37
+ from ._serialize import (
38
+ get_access_conditions,
39
+ get_cpk_scope_info,
40
+ get_modify_conditions,
41
+ get_source_conditions,
42
+ serialize_blob_tags_header,
43
+ serialize_blob_tags,
44
+ serialize_query_format
45
+ )
46
+ from ._shared import encode_base64
47
+ from ._shared.base_client import parse_query
48
+ from ._shared.request_handlers import (
49
+ add_metadata_headers,
50
+ get_length,
51
+ read_length,
52
+ validate_and_format_range_headers
53
+ )
54
+ from ._shared.response_handlers import return_headers_and_deserialized, return_response_headers
55
+ from ._shared.uploads import IterStreamer
56
+ from ._shared.uploads_async import AsyncIterStreamer
57
+ from ._upload_helpers import _any_conditions
58
+
59
+ if TYPE_CHECKING:
60
+ from urllib.parse import ParseResult
61
+ from ._generated import AzureBlobStorage
62
+ from ._models import ContentSettings
63
+ from ._shared.models import StorageConfiguration
64
+
65
+
66
+ def _parse_url(
67
+ account_url: str,
68
+ container_name: str,
69
+ blob_name: str
70
+ ) -> Tuple["ParseResult", Optional[str], Optional[str]]:
71
+ try:
72
+ if not account_url.lower().startswith('http'):
73
+ account_url = "https://" + account_url
74
+ except AttributeError as exc:
75
+ raise ValueError("Account URL must be a string.") from exc
76
+ parsed_url = urlparse(account_url.rstrip('/'))
77
+
78
+ if not (container_name and blob_name):
79
+ raise ValueError("Please specify a container name and blob name.")
80
+ if not parsed_url.netloc:
81
+ raise ValueError(f"Invalid URL: {account_url}")
82
+
83
+ path_snapshot, sas_token = parse_query(parsed_url.query)
84
+
85
+ return parsed_url, sas_token, path_snapshot
86
+
87
+ def _format_url(container_name: Union[bytes, str], scheme: str, blob_name: str, query_str: str, hostname: str) -> str:
88
+ if isinstance(container_name, str):
89
+ container_name = container_name.encode('UTF-8')
90
+ return f"{scheme}://{hostname}/{quote(container_name)}/{quote(blob_name, safe='~/')}{query_str}"
91
+
92
+ def _encode_source_url(source_url: str) -> str:
93
+ parsed_source_url = urlparse(source_url)
94
+ source_scheme = parsed_source_url.scheme
95
+ source_hostname = parsed_source_url.netloc.rstrip('/')
96
+ source_path = unquote(parsed_source_url.path)
97
+ source_query = parsed_source_url.query
98
+ result = [f"{source_scheme}://{source_hostname}{quote(source_path, safe='~/')}"]
99
+ if source_query:
100
+ result.append(source_query)
101
+ return '?'.join(result)
102
+
103
+ def _upload_blob_options( # pylint:disable=too-many-statements
104
+ data: Union[bytes, str, Iterable[AnyStr], AsyncIterable[AnyStr], IO[bytes]],
105
+ blob_type: Union[str, BlobType],
106
+ length: Optional[int],
107
+ metadata: Optional[Dict[str, str]],
108
+ encryption_options: Dict[str, Any],
109
+ config: "StorageConfiguration",
110
+ sdk_moniker: str,
111
+ client: "AzureBlobStorage",
112
+ **kwargs: Any
113
+ ) -> Dict[str, Any]:
114
+ encoding = kwargs.pop('encoding', 'UTF-8')
115
+ if isinstance(data, str):
116
+ data = data.encode(encoding)
117
+ if length is None:
118
+ length = get_length(data)
119
+ if isinstance(data, bytes):
120
+ data = data[:length]
121
+
122
+ stream: Optional[Any] = None
123
+ if isinstance(data, bytes):
124
+ stream = BytesIO(data)
125
+ elif hasattr(data, 'read'):
126
+ stream = data
127
+ elif hasattr(data, '__iter__') and not isinstance(data, (list, tuple, set, dict)):
128
+ stream = IterStreamer(data, encoding=encoding)
129
+ elif hasattr(data, '__aiter__'):
130
+ stream = AsyncIterStreamer(cast(AsyncGenerator, data), encoding=encoding)
131
+ else:
132
+ raise TypeError(f"Unsupported data type: {type(data)}")
133
+
134
+ validate_content = kwargs.pop('validate_content', False)
135
+ content_settings = kwargs.pop('content_settings', None)
136
+ overwrite = kwargs.pop('overwrite', False)
137
+ max_concurrency = kwargs.pop('max_concurrency', 1)
138
+ cpk = kwargs.pop('cpk', None)
139
+ cpk_info = None
140
+ if cpk:
141
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
142
+ encryption_algorithm=cpk.algorithm)
143
+ kwargs['cpk_info'] = cpk_info
144
+
145
+ headers = kwargs.pop('headers', {})
146
+ headers.update(add_metadata_headers(metadata))
147
+ kwargs['lease_access_conditions'] = get_access_conditions(kwargs.pop('lease', None))
148
+ kwargs['modified_access_conditions'] = get_modify_conditions(kwargs)
149
+ kwargs['cpk_scope_info'] = get_cpk_scope_info(kwargs)
150
+ if content_settings:
151
+ kwargs['blob_headers'] = BlobHTTPHeaders(
152
+ blob_cache_control=content_settings.cache_control,
153
+ blob_content_type=content_settings.content_type,
154
+ blob_content_md5=content_settings.content_md5,
155
+ blob_content_encoding=content_settings.content_encoding,
156
+ blob_content_language=content_settings.content_language,
157
+ blob_content_disposition=content_settings.content_disposition
158
+ )
159
+ kwargs['blob_tags_string'] = serialize_blob_tags_header(kwargs.pop('tags', None))
160
+ kwargs['stream'] = stream
161
+ kwargs['length'] = length
162
+ kwargs['overwrite'] = overwrite
163
+ kwargs['headers'] = headers
164
+ kwargs['validate_content'] = validate_content
165
+ kwargs['blob_settings'] = config
166
+ kwargs['max_concurrency'] = max_concurrency
167
+ kwargs['encryption_options'] = encryption_options
168
+ # Add feature flag to user agent for encryption
169
+ if encryption_options['key']:
170
+ modify_user_agent_for_encryption(
171
+ config.user_agent_policy.user_agent,
172
+ sdk_moniker,
173
+ encryption_options['version'],
174
+ kwargs)
175
+
176
+ if blob_type == BlobType.BlockBlob:
177
+ kwargs['client'] = client.block_blob
178
+ elif blob_type == BlobType.PageBlob:
179
+ if (encryption_options['version'] == '2.0' and
180
+ (encryption_options['required'] or encryption_options['key'] is not None)):
181
+ raise ValueError("Encryption version 2.0 does not currently support page blobs.")
182
+ kwargs['client'] = client.page_blob
183
+ elif blob_type == BlobType.AppendBlob:
184
+ if encryption_options['required'] or (encryption_options['key'] is not None):
185
+ raise ValueError(_ERROR_UNSUPPORTED_METHOD_FOR_ENCRYPTION)
186
+ kwargs['client'] = client.append_blob
187
+ else:
188
+ raise ValueError(f"Unsupported BlobType: {blob_type}")
189
+ return kwargs
190
+
191
+ def _upload_blob_from_url_options(source_url: str, **kwargs: Any ) -> Dict[str, Any]:
192
+ source_url = _encode_source_url(source_url=source_url)
193
+ tier = kwargs.pop('standard_blob_tier', None)
194
+ overwrite = kwargs.pop('overwrite', False)
195
+ content_settings = kwargs.pop('content_settings', None)
196
+ source_authorization = kwargs.pop('source_authorization', None)
197
+ if content_settings:
198
+ kwargs['blob_http_headers'] = BlobHTTPHeaders(
199
+ blob_cache_control=content_settings.cache_control,
200
+ blob_content_type=content_settings.content_type,
201
+ blob_content_md5=None,
202
+ blob_content_encoding=content_settings.content_encoding,
203
+ blob_content_language=content_settings.content_language,
204
+ blob_content_disposition=content_settings.content_disposition
205
+ )
206
+ cpk = kwargs.pop('cpk', None)
207
+ cpk_info = None
208
+ if cpk:
209
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
210
+ encryption_algorithm=cpk.algorithm)
211
+
212
+ options = {
213
+ 'copy_source_authorization': source_authorization,
214
+ 'content_length': 0,
215
+ 'copy_source_blob_properties': kwargs.pop('include_source_blob_properties', True),
216
+ 'source_content_md5': kwargs.pop('source_content_md5', None),
217
+ 'copy_source': source_url,
218
+ 'modified_access_conditions': get_modify_conditions(kwargs),
219
+ 'blob_tags_string': serialize_blob_tags_header(kwargs.pop('tags', None)),
220
+ 'cls': return_response_headers,
221
+ 'lease_access_conditions': get_access_conditions(kwargs.pop('destination_lease', None)),
222
+ 'tier': tier.value if tier else None,
223
+ 'source_modified_access_conditions': get_source_conditions(kwargs),
224
+ 'cpk_info': cpk_info,
225
+ 'cpk_scope_info': get_cpk_scope_info(kwargs)
226
+ }
227
+ options.update(kwargs)
228
+ if not overwrite and not _any_conditions(**options): # pylint: disable=protected-access
229
+ options['modified_access_conditions'].if_none_match = '*'
230
+ return options
231
+
232
+ def _download_blob_options(
233
+ blob_name: str,
234
+ container_name: str,
235
+ version_id: Optional[str],
236
+ offset: Optional[int],
237
+ length: Optional[int],
238
+ encoding: Optional[str],
239
+ encryption_options: Dict[str, Any],
240
+ config: "StorageConfiguration",
241
+ sdk_moniker: str,
242
+ client: "AzureBlobStorage",
243
+ **kwargs
244
+ ) -> Dict[str, Any]:
245
+ """Creates a dictionary containing the options for a download blob operation.
246
+
247
+ :param str blob_name:
248
+ The name of the blob.
249
+ :param str container_name:
250
+ The name of the container.
251
+ :param Optional[str] version_id:
252
+ The version id parameter is a value that, when present, specifies the version of the blob to download.
253
+ :param Optional[int] offset:
254
+ Start of byte range to use for downloading a section of the blob. Must be set if length is provided.
255
+ :param Optional[int] length:
256
+ Number of bytes to read from the stream. This is optional, but should be supplied for optimal performance.
257
+ :param Optional[str] encoding:
258
+ Encoding to decode the downloaded bytes. Default is None, i.e. no decoding.
259
+ :param Dict[str, Any] encryption_options:
260
+ The options for encryption, if enabled.
261
+ :param StorageConfiguration config:
262
+ The Storage configuration options.
263
+ :param str sdk_moniker:
264
+ The string representing the SDK package version.
265
+ :param AzureBlobStorage client:
266
+ The generated Blob Storage client.
267
+ :returns: A dictionary containing the download blob options.
268
+ :rtype: Dict[str, Any]
269
+ """
270
+ if length is not None:
271
+ if offset is None:
272
+ raise ValueError("Offset must be provided if length is provided.")
273
+ length = offset + length - 1 # Service actually uses an end-range inclusive index
274
+
275
+ validate_content = kwargs.pop('validate_content', False)
276
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
277
+ mod_conditions = get_modify_conditions(kwargs)
278
+
279
+ cpk = kwargs.pop('cpk', None)
280
+ cpk_info = None
281
+ if cpk:
282
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
283
+ encryption_algorithm=cpk.algorithm)
284
+
285
+ # Add feature flag to user agent for encryption
286
+ if encryption_options['key'] or encryption_options['resolver']:
287
+ modify_user_agent_for_encryption(
288
+ config.user_agent_policy.user_agent,
289
+ sdk_moniker,
290
+ encryption_options['version'],
291
+ kwargs)
292
+
293
+ options = {
294
+ 'clients': client,
295
+ 'config': config,
296
+ 'start_range': offset,
297
+ 'end_range': length,
298
+ 'version_id': version_id,
299
+ 'validate_content': validate_content,
300
+ 'encryption_options': {
301
+ 'required': encryption_options['required'],
302
+ 'key': encryption_options['key'],
303
+ 'resolver': encryption_options['resolver']},
304
+ 'lease_access_conditions': access_conditions,
305
+ 'modified_access_conditions': mod_conditions,
306
+ 'cpk_info': cpk_info,
307
+ 'download_cls': kwargs.pop('cls', None) or deserialize_blob_stream,
308
+ 'max_concurrency':kwargs.pop('max_concurrency', 1),
309
+ 'encoding': encoding,
310
+ 'timeout': kwargs.pop('timeout', None),
311
+ 'name': blob_name,
312
+ 'container': container_name}
313
+ options.update(kwargs)
314
+ return options
315
+
316
+ def _quick_query_options(snapshot: Optional[str], query_expression: str, **kwargs: Any ) -> Tuple[Dict[str, Any], str]:
317
+ delimiter = '\n'
318
+ input_format = kwargs.pop('blob_format', None)
319
+ if input_format == QuickQueryDialect.DelimitedJson:
320
+ input_format = DelimitedJsonDialect()
321
+ if input_format == QuickQueryDialect.DelimitedText:
322
+ input_format = DelimitedTextDialect()
323
+ input_parquet_format = input_format == "ParquetDialect"
324
+ if input_format and not input_parquet_format:
325
+ try:
326
+ delimiter = input_format.lineterminator
327
+ except AttributeError:
328
+ try:
329
+ delimiter = input_format.delimiter
330
+ except AttributeError as exc:
331
+ raise ValueError("The Type of blob_format can only be DelimitedTextDialect or "
332
+ "DelimitedJsonDialect or ParquetDialect") from exc
333
+ output_format = kwargs.pop('output_format', None)
334
+ if output_format == QuickQueryDialect.DelimitedJson:
335
+ output_format = DelimitedJsonDialect()
336
+ if output_format == QuickQueryDialect.DelimitedText:
337
+ output_format = DelimitedTextDialect()
338
+ if output_format:
339
+ if output_format == "ParquetDialect":
340
+ raise ValueError("ParquetDialect is invalid as an output format.")
341
+ try:
342
+ delimiter = output_format.lineterminator
343
+ except AttributeError:
344
+ try:
345
+ delimiter = output_format.delimiter
346
+ except AttributeError:
347
+ pass
348
+ else:
349
+ output_format = input_format if not input_parquet_format else None
350
+ query_request = QueryRequest(
351
+ expression=query_expression,
352
+ input_serialization=serialize_query_format(input_format),
353
+ output_serialization=serialize_query_format(output_format)
354
+ )
355
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
356
+ mod_conditions = get_modify_conditions(kwargs)
357
+
358
+ cpk = kwargs.pop('cpk', None)
359
+ cpk_info = None
360
+ if cpk:
361
+ cpk_info = CpkInfo(
362
+ encryption_key=cpk.key_value,
363
+ encryption_key_sha256=cpk.key_hash,
364
+ encryption_algorithm=cpk.algorithm
365
+ )
366
+ options = {
367
+ 'query_request': query_request,
368
+ 'lease_access_conditions': access_conditions,
369
+ 'modified_access_conditions': mod_conditions,
370
+ 'cpk_info': cpk_info,
371
+ 'snapshot': snapshot,
372
+ 'timeout': kwargs.pop('timeout', None),
373
+ 'cls': return_headers_and_deserialized,
374
+ }
375
+ options.update(kwargs)
376
+ return options, delimiter
377
+
378
+ def _generic_delete_blob_options(delete_snapshots: Optional[str] = None, **kwargs: Any) -> Dict[str, Any]:
379
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
380
+ mod_conditions = get_modify_conditions(kwargs)
381
+ if delete_snapshots:
382
+ delete_snapshots = DeleteSnapshotsOptionType(delete_snapshots)
383
+ options = {
384
+ 'timeout': kwargs.pop('timeout', None),
385
+ 'snapshot': kwargs.pop('snapshot', None), # this is added for delete_blobs
386
+ 'delete_snapshots': delete_snapshots or None,
387
+ 'lease_access_conditions': access_conditions,
388
+ 'modified_access_conditions': mod_conditions}
389
+ options.update(kwargs)
390
+ return options
391
+
392
+ def _delete_blob_options(
393
+ snapshot: Optional[str],
394
+ version_id: Optional[str],
395
+ delete_snapshots: Optional[str] = None,
396
+ **kwargs: Any
397
+ ) -> Dict[str, Any]:
398
+ if snapshot and delete_snapshots:
399
+ raise ValueError("The delete_snapshots option cannot be used with a specific snapshot.")
400
+ options = _generic_delete_blob_options(delete_snapshots, **kwargs)
401
+ options['snapshot'] = snapshot
402
+ options['version_id'] = version_id
403
+ options['blob_delete_type'] = kwargs.pop('blob_delete_type', None)
404
+ return options
405
+
406
+ def _set_http_headers_options(content_settings: Optional["ContentSettings"] = None, **kwargs: Any) -> Dict[str, Any]:
407
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
408
+ mod_conditions = get_modify_conditions(kwargs)
409
+ blob_headers = None
410
+ if content_settings:
411
+ blob_headers = BlobHTTPHeaders(
412
+ blob_cache_control=content_settings.cache_control,
413
+ blob_content_type=content_settings.content_type,
414
+ blob_content_md5=content_settings.content_md5,
415
+ blob_content_encoding=content_settings.content_encoding,
416
+ blob_content_language=content_settings.content_language,
417
+ blob_content_disposition=content_settings.content_disposition
418
+ )
419
+ options = {
420
+ 'timeout': kwargs.pop('timeout', None),
421
+ 'blob_http_headers': blob_headers,
422
+ 'lease_access_conditions': access_conditions,
423
+ 'modified_access_conditions': mod_conditions,
424
+ 'cls': return_response_headers}
425
+ options.update(kwargs)
426
+ return options
427
+
428
+ def _set_blob_metadata_options(metadata: Optional[Dict[str, str]] = None, **kwargs: Any):
429
+ headers = kwargs.pop('headers', {})
430
+ headers.update(add_metadata_headers(metadata))
431
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
432
+ mod_conditions = get_modify_conditions(kwargs)
433
+ cpk_scope_info = get_cpk_scope_info(kwargs)
434
+
435
+ cpk = kwargs.pop('cpk', None)
436
+ cpk_info = None
437
+ if cpk:
438
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
439
+ encryption_algorithm=cpk.algorithm)
440
+ options = {
441
+ 'timeout': kwargs.pop('timeout', None),
442
+ 'lease_access_conditions': access_conditions,
443
+ 'modified_access_conditions': mod_conditions,
444
+ 'cpk_scope_info': cpk_scope_info,
445
+ 'cpk_info': cpk_info,
446
+ 'cls': return_response_headers,
447
+ 'headers': headers}
448
+ options.update(kwargs)
449
+ return options
450
+
451
+ def _create_page_blob_options(
452
+ size: int,
453
+ content_settings: Optional["ContentSettings"] = None,
454
+ metadata: Optional[Dict[str, str]] = None,
455
+ premium_page_blob_tier: Optional[Union[str, "PremiumPageBlobTier"]] = None,
456
+ **kwargs: Any
457
+ ) -> Dict[str, Any]:
458
+ headers = kwargs.pop('headers', {})
459
+ headers.update(add_metadata_headers(metadata))
460
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
461
+ mod_conditions = get_modify_conditions(kwargs)
462
+ cpk_scope_info = get_cpk_scope_info(kwargs)
463
+ blob_headers = None
464
+ if content_settings:
465
+ blob_headers = BlobHTTPHeaders(
466
+ blob_cache_control=content_settings.cache_control,
467
+ blob_content_type=content_settings.content_type,
468
+ blob_content_md5=content_settings.content_md5,
469
+ blob_content_encoding=content_settings.content_encoding,
470
+ blob_content_language=content_settings.content_language,
471
+ blob_content_disposition=content_settings.content_disposition
472
+ )
473
+
474
+ sequence_number = kwargs.pop('sequence_number', None)
475
+ cpk = kwargs.pop('cpk', None)
476
+ cpk_info = None
477
+ if cpk:
478
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
479
+ encryption_algorithm=cpk.algorithm)
480
+
481
+ immutability_policy = kwargs.pop('immutability_policy', None)
482
+ if immutability_policy:
483
+ kwargs['immutability_policy_expiry'] = immutability_policy.expiry_time
484
+ kwargs['immutability_policy_mode'] = immutability_policy.policy_mode
485
+
486
+ tier = None
487
+ if premium_page_blob_tier:
488
+ try:
489
+ tier = premium_page_blob_tier.value # type: ignore
490
+ except AttributeError:
491
+ tier = premium_page_blob_tier # type: ignore
492
+
493
+ blob_tags_string = serialize_blob_tags_header(kwargs.pop('tags', None))
494
+
495
+ options = {
496
+ 'content_length': 0,
497
+ 'blob_content_length': size,
498
+ 'blob_sequence_number': sequence_number,
499
+ 'blob_http_headers': blob_headers,
500
+ 'timeout': kwargs.pop('timeout', None),
501
+ 'lease_access_conditions': access_conditions,
502
+ 'modified_access_conditions': mod_conditions,
503
+ 'cpk_scope_info': cpk_scope_info,
504
+ 'cpk_info': cpk_info,
505
+ 'blob_tags_string': blob_tags_string,
506
+ 'cls': return_response_headers,
507
+ "tier": tier,
508
+ 'headers': headers}
509
+ options.update(kwargs)
510
+ return options
511
+
512
+ def _create_append_blob_options(
513
+ content_settings: Optional["ContentSettings"] = None,
514
+ metadata: Optional[Dict[str, str]] = None,
515
+ **kwargs: Any
516
+ ) -> Dict[str, Any]:
517
+ headers = kwargs.pop('headers', {})
518
+ headers.update(add_metadata_headers(metadata))
519
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
520
+ mod_conditions = get_modify_conditions(kwargs)
521
+ cpk_scope_info = get_cpk_scope_info(kwargs)
522
+ blob_headers = None
523
+ if content_settings:
524
+ blob_headers = BlobHTTPHeaders(
525
+ blob_cache_control=content_settings.cache_control,
526
+ blob_content_type=content_settings.content_type,
527
+ blob_content_md5=content_settings.content_md5,
528
+ blob_content_encoding=content_settings.content_encoding,
529
+ blob_content_language=content_settings.content_language,
530
+ blob_content_disposition=content_settings.content_disposition
531
+ )
532
+
533
+ cpk = kwargs.pop('cpk', None)
534
+ cpk_info = None
535
+ if cpk:
536
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
537
+ encryption_algorithm=cpk.algorithm)
538
+
539
+ immutability_policy = kwargs.pop('immutability_policy', None)
540
+ if immutability_policy:
541
+ kwargs['immutability_policy_expiry'] = immutability_policy.expiry_time
542
+ kwargs['immutability_policy_mode'] = immutability_policy.policy_mode
543
+
544
+ blob_tags_string = serialize_blob_tags_header(kwargs.pop('tags', None))
545
+
546
+ options = {
547
+ 'content_length': 0,
548
+ 'blob_http_headers': blob_headers,
549
+ 'timeout': kwargs.pop('timeout', None),
550
+ 'lease_access_conditions': access_conditions,
551
+ 'modified_access_conditions': mod_conditions,
552
+ 'cpk_scope_info': cpk_scope_info,
553
+ 'cpk_info': cpk_info,
554
+ 'blob_tags_string': blob_tags_string,
555
+ 'cls': return_response_headers,
556
+ 'headers': headers}
557
+ options.update(kwargs)
558
+ return options
559
+
560
+ def _create_snapshot_options(metadata: Optional[Dict[str, str]] = None, **kwargs: Any) -> Dict[str, Any]:
561
+ headers = kwargs.pop('headers', {})
562
+ headers.update(add_metadata_headers(metadata))
563
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
564
+ mod_conditions = get_modify_conditions(kwargs)
565
+ cpk_scope_info = get_cpk_scope_info(kwargs)
566
+ cpk = kwargs.pop('cpk', None)
567
+ cpk_info = None
568
+ if cpk:
569
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
570
+ encryption_algorithm=cpk.algorithm)
571
+
572
+ options = {
573
+ 'timeout': kwargs.pop('timeout', None),
574
+ 'lease_access_conditions': access_conditions,
575
+ 'modified_access_conditions': mod_conditions,
576
+ 'cpk_scope_info': cpk_scope_info,
577
+ 'cpk_info': cpk_info,
578
+ 'cls': return_response_headers,
579
+ 'headers': headers}
580
+ options.update(kwargs)
581
+ return options
582
+
583
+ def _start_copy_from_url_options( # pylint:disable=too-many-statements
584
+ source_url: str,
585
+ metadata: Optional[Dict[str, str]] = None,
586
+ incremental_copy: bool = False,
587
+ **kwargs: Any
588
+ ) -> Dict[str, Any]:
589
+ source_url = _encode_source_url(source_url=source_url)
590
+ headers = kwargs.pop('headers', {})
591
+ headers.update(add_metadata_headers(metadata))
592
+ if 'source_lease' in kwargs:
593
+ source_lease = kwargs.pop('source_lease')
594
+ try:
595
+ headers['x-ms-source-lease-id'] = source_lease.id
596
+ except AttributeError:
597
+ headers['x-ms-source-lease-id'] = source_lease
598
+
599
+ tier = kwargs.pop('premium_page_blob_tier', None) or kwargs.pop('standard_blob_tier', None)
600
+ tags = kwargs.pop('tags', None)
601
+
602
+ # Options only available for sync copy
603
+ requires_sync = kwargs.pop('requires_sync', None)
604
+ encryption_scope_str = kwargs.pop('encryption_scope', None)
605
+ source_authorization = kwargs.pop('source_authorization', None)
606
+ # If tags is a str, interpret that as copy_source_tags
607
+ copy_source_tags = isinstance(tags, str)
608
+
609
+ if incremental_copy:
610
+ if source_authorization:
611
+ raise ValueError("Source authorization tokens are not applicable for incremental copying.")
612
+ if copy_source_tags:
613
+ raise ValueError("Copying source tags is not applicable for incremental copying.")
614
+
615
+ # TODO: refactor start_copy_from_url api in _blob_client.py. Call _generated/_blob_operations.py copy_from_url
616
+ # when requires_sync=True is set.
617
+ # Currently both sync copy and async copy are calling _generated/_blob_operations.py start_copy_from_url.
618
+ # As sync copy diverges more from async copy, more problem will surface.
619
+ if requires_sync is True:
620
+ headers['x-ms-requires-sync'] = str(requires_sync)
621
+ if encryption_scope_str:
622
+ headers['x-ms-encryption-scope'] = encryption_scope_str
623
+ if source_authorization:
624
+ headers['x-ms-copy-source-authorization'] = source_authorization
625
+ if copy_source_tags:
626
+ headers['x-ms-copy-source-tag-option'] = tags
627
+ else:
628
+ if encryption_scope_str:
629
+ raise ValueError(
630
+ "Encryption_scope is only supported for sync copy, please specify requires_sync=True")
631
+ if source_authorization:
632
+ raise ValueError(
633
+ "Source authorization tokens are only supported for sync copy, please specify requires_sync=True")
634
+ if copy_source_tags:
635
+ raise ValueError(
636
+ "Copying source tags is only supported for sync copy, please specify requires_sync=True")
637
+
638
+ timeout = kwargs.pop('timeout', None)
639
+ dest_mod_conditions = get_modify_conditions(kwargs)
640
+ blob_tags_string = serialize_blob_tags_header(tags) if not copy_source_tags else None
641
+
642
+ immutability_policy = kwargs.pop('immutability_policy', None)
643
+ if immutability_policy:
644
+ kwargs['immutability_policy_expiry'] = immutability_policy.expiry_time
645
+ kwargs['immutability_policy_mode'] = immutability_policy.policy_mode
646
+
647
+ options = {
648
+ 'copy_source': source_url,
649
+ 'seal_blob': kwargs.pop('seal_destination_blob', None),
650
+ 'timeout': timeout,
651
+ 'modified_access_conditions': dest_mod_conditions,
652
+ 'blob_tags_string': blob_tags_string,
653
+ 'headers': headers,
654
+ 'cls': return_response_headers,
655
+ }
656
+ if not incremental_copy:
657
+ source_mod_conditions = get_source_conditions(kwargs)
658
+ dest_access_conditions = get_access_conditions(kwargs.pop('destination_lease', None))
659
+ options['source_modified_access_conditions'] = source_mod_conditions
660
+ options['lease_access_conditions'] = dest_access_conditions
661
+ options['tier'] = tier.value if tier else None
662
+ options.update(kwargs)
663
+ return options
664
+
665
+ def _abort_copy_options(copy_id: Union[str, Dict[str, Any], BlobProperties], **kwargs: Any) -> Dict[str, Any]:
666
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
667
+ if isinstance(copy_id, BlobProperties):
668
+ copy_id = copy_id.copy.id # type: ignore [assignment]
669
+ elif isinstance(copy_id, dict):
670
+ copy_id = copy_id['copy_id']
671
+ options = {
672
+ 'copy_id': copy_id,
673
+ 'lease_access_conditions': access_conditions,
674
+ 'timeout': kwargs.pop('timeout', None)}
675
+ options.update(kwargs)
676
+ return options
677
+
678
+ def _stage_block_options(
679
+ block_id: str,
680
+ data: Union[bytes, str, Iterable[AnyStr], IO[AnyStr]],
681
+ length: Optional[int] = None,
682
+ **kwargs: Any
683
+ ) -> Dict[str, Any]:
684
+ block_id = encode_base64(str(block_id))
685
+ if isinstance(data, str):
686
+ data = data.encode(kwargs.pop('encoding', 'UTF-8')) # type: ignore
687
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
688
+ if length is None:
689
+ length = get_length(data)
690
+ if length is None:
691
+ length, data = read_length(data)
692
+ if isinstance(data, bytes):
693
+ data = data[:length]
694
+
695
+ validate_content = kwargs.pop('validate_content', False)
696
+ cpk_scope_info = get_cpk_scope_info(kwargs)
697
+ cpk = kwargs.pop('cpk', None)
698
+ cpk_info = None
699
+ if cpk:
700
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
701
+ encryption_algorithm=cpk.algorithm)
702
+
703
+ options = {
704
+ 'block_id': block_id,
705
+ 'content_length': length,
706
+ 'body': data,
707
+ 'transactional_content_md5': None,
708
+ 'timeout': kwargs.pop('timeout', None),
709
+ 'lease_access_conditions': access_conditions,
710
+ 'validate_content': validate_content,
711
+ 'cpk_scope_info': cpk_scope_info,
712
+ 'cpk_info': cpk_info,
713
+ 'cls': return_response_headers,
714
+ }
715
+ options.update(kwargs)
716
+ return options
717
+
718
+ def _stage_block_from_url_options(
719
+ block_id: str,
720
+ source_url: str,
721
+ source_offset: Optional[int] = None,
722
+ source_length: Optional[int] = None,
723
+ source_content_md5: Optional[Union[bytes, bytearray]] = None,
724
+ **kwargs: Any
725
+ ) -> Dict[str, Any]:
726
+ source_url = _encode_source_url(source_url=source_url)
727
+ source_authorization = kwargs.pop('source_authorization', None)
728
+ if source_length is not None and source_offset is None:
729
+ raise ValueError("Source offset value must not be None if length is set.")
730
+ if source_length is not None and source_offset is not None:
731
+ source_length = source_offset + source_length - 1
732
+ block_id = encode_base64(str(block_id))
733
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
734
+ range_header = None
735
+ if source_offset is not None:
736
+ range_header, _ = validate_and_format_range_headers(source_offset, source_length)
737
+
738
+ cpk_scope_info = get_cpk_scope_info(kwargs)
739
+ cpk = kwargs.pop('cpk', None)
740
+ cpk_info = None
741
+ if cpk:
742
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
743
+ encryption_algorithm=cpk.algorithm)
744
+ options = {
745
+ 'copy_source_authorization': source_authorization,
746
+ 'block_id': block_id,
747
+ 'content_length': 0,
748
+ 'source_url': source_url,
749
+ 'source_range': range_header,
750
+ 'source_content_md5': bytearray(source_content_md5) if source_content_md5 else None,
751
+ 'timeout': kwargs.pop('timeout', None),
752
+ 'lease_access_conditions': access_conditions,
753
+ 'cpk_scope_info': cpk_scope_info,
754
+ 'cpk_info': cpk_info,
755
+ 'cls': return_response_headers,
756
+ }
757
+ options.update(kwargs)
758
+ return options
759
+
760
+ def _get_block_list_result(blocks: BlockList) -> Tuple[List[BlobBlock], List[BlobBlock]]:
761
+ committed = []
762
+ uncommitted = []
763
+ if blocks.committed_blocks:
764
+ committed = [BlobBlock._from_generated(b) for b in blocks.committed_blocks] # pylint: disable=protected-access
765
+ if blocks.uncommitted_blocks:
766
+ uncommitted = [BlobBlock._from_generated(b) for b in blocks.uncommitted_blocks] # pylint: disable=protected-access
767
+ return committed, uncommitted
768
+
769
+ def _commit_block_list_options(
770
+ block_list: List[BlobBlock],
771
+ content_settings: Optional["ContentSettings"] = None,
772
+ metadata: Optional[Dict[str, str]] = None,
773
+ **kwargs: Any
774
+ ) -> Dict[str, Any]:
775
+ block_lookup = BlockLookupList(committed=[], uncommitted=[], latest=[])
776
+ for block in block_list:
777
+ if isinstance(block, BlobBlock):
778
+ if block.state.value == 'committed':
779
+ cast(List[str], block_lookup.committed).append(encode_base64(str(block.id)))
780
+ elif block.state.value == 'uncommitted':
781
+ cast(List[str], block_lookup.uncommitted).append(encode_base64(str(block.id)))
782
+ elif block_lookup.latest is not None:
783
+ block_lookup.latest.append(encode_base64(str(block.id)))
784
+ else:
785
+ block_lookup.latest.append(encode_base64(str(block)))
786
+ headers = kwargs.pop('headers', {})
787
+ headers.update(add_metadata_headers(metadata))
788
+ blob_headers = None
789
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
790
+ mod_conditions = get_modify_conditions(kwargs)
791
+ if content_settings:
792
+ blob_headers = BlobHTTPHeaders(
793
+ blob_cache_control=content_settings.cache_control,
794
+ blob_content_type=content_settings.content_type,
795
+ blob_content_md5=content_settings.content_md5,
796
+ blob_content_encoding=content_settings.content_encoding,
797
+ blob_content_language=content_settings.content_language,
798
+ blob_content_disposition=content_settings.content_disposition
799
+ )
800
+
801
+ validate_content = kwargs.pop('validate_content', False)
802
+ cpk_scope_info = get_cpk_scope_info(kwargs)
803
+ cpk = kwargs.pop('cpk', None)
804
+ cpk_info = None
805
+ if cpk:
806
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
807
+ encryption_algorithm=cpk.algorithm)
808
+
809
+ immutability_policy = kwargs.pop('immutability_policy', None)
810
+ if immutability_policy:
811
+ kwargs['immutability_policy_expiry'] = immutability_policy.expiry_time
812
+ kwargs['immutability_policy_mode'] = immutability_policy.policy_mode
813
+
814
+ tier = kwargs.pop('standard_blob_tier', None)
815
+ blob_tags_string = serialize_blob_tags_header(kwargs.pop('tags', None))
816
+
817
+ options = {
818
+ 'blocks': block_lookup,
819
+ 'blob_http_headers': blob_headers,
820
+ 'lease_access_conditions': access_conditions,
821
+ 'timeout': kwargs.pop('timeout', None),
822
+ 'modified_access_conditions': mod_conditions,
823
+ 'cls': return_response_headers,
824
+ 'validate_content': validate_content,
825
+ 'cpk_scope_info': cpk_scope_info,
826
+ 'cpk_info': cpk_info,
827
+ 'tier': tier.value if tier else None,
828
+ 'blob_tags_string': blob_tags_string,
829
+ 'headers': headers
830
+ }
831
+ options.update(kwargs)
832
+ return options
833
+
834
+ def _set_blob_tags_options(
835
+ version_id: Optional[str],
836
+ tags: Optional[Dict[str, str]] = None,
837
+ **kwargs: Any
838
+ )-> Dict[str, Any]:
839
+ serialized_tags = serialize_blob_tags(tags)
840
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
841
+ mod_conditions = get_modify_conditions(kwargs)
842
+
843
+ options = {
844
+ 'tags': serialized_tags,
845
+ 'lease_access_conditions': access_conditions,
846
+ 'modified_access_conditions': mod_conditions,
847
+ 'version_id': version_id,
848
+ 'cls': return_response_headers}
849
+ options.update(kwargs)
850
+ return options
851
+
852
+ def _get_blob_tags_options(version_id: Optional[str], snapshot: Optional[str], **kwargs: Any) -> Dict[str, Any]:
853
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
854
+ mod_conditions = get_modify_conditions(kwargs)
855
+
856
+ options = {
857
+ 'version_id': version_id,
858
+ 'snapshot': snapshot,
859
+ 'lease_access_conditions': access_conditions,
860
+ 'modified_access_conditions': mod_conditions,
861
+ 'timeout': kwargs.pop('timeout', None),
862
+ 'cls': return_headers_and_deserialized}
863
+ return options
864
+
865
+ def _get_page_ranges_options(
866
+ snapshot: Optional[str],
867
+ offset: Optional[int] = None,
868
+ length: Optional[int] = None,
869
+ previous_snapshot_diff: Optional[Union[str, Dict[str, Any]]] = None,
870
+ **kwargs: Any
871
+ ) -> Dict[str, Any]:
872
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
873
+ mod_conditions = get_modify_conditions(kwargs)
874
+ if length is not None and offset is None:
875
+ raise ValueError("Offset value must not be None if length is set.")
876
+ if length is not None and offset is not None:
877
+ length = offset + length - 1 # Reformat to an inclusive range index
878
+ page_range, _ = validate_and_format_range_headers(
879
+ offset, length, start_range_required=False, end_range_required=False, align_to_page=True
880
+ )
881
+ options = {
882
+ 'snapshot': snapshot,
883
+ 'lease_access_conditions': access_conditions,
884
+ 'modified_access_conditions': mod_conditions,
885
+ 'timeout': kwargs.pop('timeout', None),
886
+ 'range': page_range}
887
+ if previous_snapshot_diff:
888
+ try:
889
+ options['prevsnapshot'] = previous_snapshot_diff.snapshot # type: ignore
890
+ except AttributeError:
891
+ try:
892
+ options['prevsnapshot'] = previous_snapshot_diff['snapshot'] # type: ignore
893
+ except TypeError:
894
+ options['prevsnapshot'] = previous_snapshot_diff
895
+ options.update(kwargs)
896
+ return options
897
+
898
+ def _set_sequence_number_options(
899
+ sequence_number_action: str,
900
+ sequence_number: Optional[str] = None,
901
+ **kwargs: Any
902
+ ) -> Dict[str, Any]:
903
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
904
+ mod_conditions = get_modify_conditions(kwargs)
905
+ if sequence_number_action is None:
906
+ raise ValueError("A sequence number action must be specified")
907
+ options = {
908
+ 'sequence_number_action': sequence_number_action,
909
+ 'timeout': kwargs.pop('timeout', None),
910
+ 'blob_sequence_number': sequence_number,
911
+ 'lease_access_conditions': access_conditions,
912
+ 'modified_access_conditions': mod_conditions,
913
+ 'cls': return_response_headers}
914
+ options.update(kwargs)
915
+ return options
916
+
917
+ def _resize_blob_options(size: int, **kwargs: Any) -> Dict[str, Any]:
918
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
919
+ mod_conditions = get_modify_conditions(kwargs)
920
+ if size is None:
921
+ raise ValueError("A content length must be specified for a Page Blob.")
922
+
923
+ cpk = kwargs.pop('cpk', None)
924
+ cpk_info = None
925
+ if cpk:
926
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
927
+ encryption_algorithm=cpk.algorithm)
928
+ options = {
929
+ 'blob_content_length': size,
930
+ 'timeout': kwargs.pop('timeout', None),
931
+ 'lease_access_conditions': access_conditions,
932
+ 'modified_access_conditions': mod_conditions,
933
+ 'cpk_info': cpk_info,
934
+ 'cls': return_response_headers}
935
+ options.update(kwargs)
936
+ return options
937
+
938
+ def _upload_page_options(
939
+ page: bytes,
940
+ offset: int,
941
+ length: int,
942
+ **kwargs: Any
943
+ ) -> Dict[str, Any]:
944
+ if isinstance(page, str):
945
+ page = page.encode(kwargs.pop('encoding', 'UTF-8'))
946
+ if offset is None or offset % 512 != 0:
947
+ raise ValueError("offset must be an integer that aligns with 512 page size")
948
+ if length is None or length % 512 != 0:
949
+ raise ValueError("length must be an integer that aligns with 512 page size")
950
+ end_range = offset + length - 1 # Reformat to an inclusive range index
951
+ content_range = f'bytes={offset}-{end_range}' # type: ignore
952
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
953
+ seq_conditions = SequenceNumberAccessConditions(
954
+ if_sequence_number_less_than_or_equal_to=kwargs.pop('if_sequence_number_lte', None),
955
+ if_sequence_number_less_than=kwargs.pop('if_sequence_number_lt', None),
956
+ if_sequence_number_equal_to=kwargs.pop('if_sequence_number_eq', None)
957
+ )
958
+ mod_conditions = get_modify_conditions(kwargs)
959
+ cpk_scope_info = get_cpk_scope_info(kwargs)
960
+ validate_content = kwargs.pop('validate_content', False)
961
+ cpk = kwargs.pop('cpk', None)
962
+ cpk_info = None
963
+ if cpk:
964
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
965
+ encryption_algorithm=cpk.algorithm)
966
+ options = {
967
+ 'body': page[:length],
968
+ 'content_length': length,
969
+ 'transactional_content_md5': None,
970
+ 'timeout': kwargs.pop('timeout', None),
971
+ 'range': content_range,
972
+ 'lease_access_conditions': access_conditions,
973
+ 'sequence_number_access_conditions': seq_conditions,
974
+ 'modified_access_conditions': mod_conditions,
975
+ 'validate_content': validate_content,
976
+ 'cpk_scope_info': cpk_scope_info,
977
+ 'cpk_info': cpk_info,
978
+ 'cls': return_response_headers}
979
+ options.update(kwargs)
980
+ return options
981
+
982
+ def _upload_pages_from_url_options(
983
+ source_url: str,
984
+ offset: int,
985
+ length: int,
986
+ source_offset: int,
987
+ **kwargs: Any
988
+ ) -> Dict[str, Any]:
989
+ source_url = _encode_source_url(source_url=source_url)
990
+ # TODO: extract the code to a method format_range
991
+ if offset is None or offset % 512 != 0:
992
+ raise ValueError("offset must be an integer that aligns with 512 page size")
993
+ if length is None or length % 512 != 0:
994
+ raise ValueError("length must be an integer that aligns with 512 page size")
995
+ if source_offset is None or offset % 512 != 0:
996
+ raise ValueError("source_offset must be an integer that aligns with 512 page size")
997
+
998
+ # Format range
999
+ end_range = offset + length - 1
1000
+ destination_range = f'bytes={offset}-{end_range}'
1001
+ source_range = f'bytes={source_offset}-{source_offset + length - 1}' # should subtract 1 here?
1002
+
1003
+ seq_conditions = SequenceNumberAccessConditions(
1004
+ if_sequence_number_less_than_or_equal_to=kwargs.pop('if_sequence_number_lte', None),
1005
+ if_sequence_number_less_than=kwargs.pop('if_sequence_number_lt', None),
1006
+ if_sequence_number_equal_to=kwargs.pop('if_sequence_number_eq', None)
1007
+ )
1008
+ source_authorization = kwargs.pop('source_authorization', None)
1009
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
1010
+ mod_conditions = get_modify_conditions(kwargs)
1011
+ source_mod_conditions = get_source_conditions(kwargs)
1012
+ cpk_scope_info = get_cpk_scope_info(kwargs)
1013
+ source_content_md5 = kwargs.pop('source_content_md5', None)
1014
+ cpk = kwargs.pop('cpk', None)
1015
+ cpk_info = None
1016
+ if cpk:
1017
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
1018
+ encryption_algorithm=cpk.algorithm)
1019
+
1020
+ options = {
1021
+ 'copy_source_authorization': source_authorization,
1022
+ 'source_url': source_url,
1023
+ 'content_length': 0,
1024
+ 'source_range': source_range,
1025
+ 'range': destination_range,
1026
+ 'source_content_md5': bytearray(source_content_md5) if source_content_md5 else None,
1027
+ 'timeout': kwargs.pop('timeout', None),
1028
+ 'lease_access_conditions': access_conditions,
1029
+ 'sequence_number_access_conditions': seq_conditions,
1030
+ 'modified_access_conditions': mod_conditions,
1031
+ 'source_modified_access_conditions': source_mod_conditions,
1032
+ 'cpk_scope_info': cpk_scope_info,
1033
+ 'cpk_info': cpk_info,
1034
+ 'cls': return_response_headers}
1035
+ options.update(kwargs)
1036
+ return options
1037
+
1038
+ def _clear_page_options(
1039
+ offset: int,
1040
+ length: int,
1041
+ **kwargs: Any
1042
+ ) -> Dict[str, Any]:
1043
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
1044
+ seq_conditions = SequenceNumberAccessConditions(
1045
+ if_sequence_number_less_than_or_equal_to=kwargs.pop('if_sequence_number_lte', None),
1046
+ if_sequence_number_less_than=kwargs.pop('if_sequence_number_lt', None),
1047
+ if_sequence_number_equal_to=kwargs.pop('if_sequence_number_eq', None)
1048
+ )
1049
+ mod_conditions = get_modify_conditions(kwargs)
1050
+ if offset is None or offset % 512 != 0:
1051
+ raise ValueError("offset must be an integer that aligns with 512 page size")
1052
+ if length is None or length % 512 != 0:
1053
+ raise ValueError("length must be an integer that aligns with 512 page size")
1054
+ end_range = length + offset - 1 # Reformat to an inclusive range index
1055
+ content_range = f'bytes={offset}-{end_range}'
1056
+
1057
+ cpk = kwargs.pop('cpk', None)
1058
+ cpk_info = None
1059
+ if cpk:
1060
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
1061
+ encryption_algorithm=cpk.algorithm)
1062
+
1063
+ options = {
1064
+ 'content_length': 0,
1065
+ 'timeout': kwargs.pop('timeout', None),
1066
+ 'range': content_range,
1067
+ 'lease_access_conditions': access_conditions,
1068
+ 'sequence_number_access_conditions': seq_conditions,
1069
+ 'modified_access_conditions': mod_conditions,
1070
+ 'cpk_info': cpk_info,
1071
+ 'cls': return_response_headers}
1072
+ options.update(kwargs)
1073
+ return options
1074
+
1075
+ def _append_block_options(
1076
+ data: Union[bytes, str, Iterable[AnyStr], IO[AnyStr]],
1077
+ length: Optional[int] = None,
1078
+ **kwargs: Any
1079
+ ) -> Dict[str, Any]:
1080
+ if isinstance(data, str):
1081
+ data = data.encode(kwargs.pop('encoding', 'UTF-8'))
1082
+ if length is None:
1083
+ length = get_length(data)
1084
+ if length is None:
1085
+ length, data = read_length(data)
1086
+ if length == 0:
1087
+ return {}
1088
+ if isinstance(data, bytes):
1089
+ data = data[:length]
1090
+
1091
+ appendpos_condition = kwargs.pop('appendpos_condition', None)
1092
+ maxsize_condition = kwargs.pop('maxsize_condition', None)
1093
+ validate_content = kwargs.pop('validate_content', False)
1094
+ append_conditions = None
1095
+ if maxsize_condition or appendpos_condition is not None:
1096
+ append_conditions = AppendPositionAccessConditions(
1097
+ max_size=maxsize_condition,
1098
+ append_position=appendpos_condition
1099
+ )
1100
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
1101
+ mod_conditions = get_modify_conditions(kwargs)
1102
+ cpk_scope_info = get_cpk_scope_info(kwargs)
1103
+ cpk = kwargs.pop('cpk', None)
1104
+ cpk_info = None
1105
+ if cpk:
1106
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
1107
+ encryption_algorithm=cpk.algorithm)
1108
+ options = {
1109
+ 'body': data,
1110
+ 'content_length': length,
1111
+ 'timeout': kwargs.pop('timeout', None),
1112
+ 'transactional_content_md5': None,
1113
+ 'lease_access_conditions': access_conditions,
1114
+ 'append_position_access_conditions': append_conditions,
1115
+ 'modified_access_conditions': mod_conditions,
1116
+ 'validate_content': validate_content,
1117
+ 'cpk_scope_info': cpk_scope_info,
1118
+ 'cpk_info': cpk_info,
1119
+ 'cls': return_response_headers}
1120
+ options.update(kwargs)
1121
+ return options
1122
+
1123
+ def _append_block_from_url_options(
1124
+ copy_source_url: str,
1125
+ source_offset: Optional[int] = None,
1126
+ source_length: Optional[int] = None,
1127
+ **kwargs: Any
1128
+ ) -> Dict[str, Any]:
1129
+ copy_source_url = _encode_source_url(source_url=copy_source_url)
1130
+ # If end range is provided, start range must be provided
1131
+ if source_length is not None and source_offset is None:
1132
+ raise ValueError("source_offset should also be specified if source_length is specified")
1133
+ # Format based on whether length is present
1134
+ source_range = None
1135
+ if source_length is not None and source_offset is not None:
1136
+ end_range = source_offset + source_length - 1
1137
+ source_range = f'bytes={source_offset}-{end_range}'
1138
+ elif source_offset is not None:
1139
+ source_range = f"bytes={source_offset}-"
1140
+
1141
+ appendpos_condition = kwargs.pop('appendpos_condition', None)
1142
+ maxsize_condition = kwargs.pop('maxsize_condition', None)
1143
+ source_content_md5 = kwargs.pop('source_content_md5', None)
1144
+ append_conditions = None
1145
+ if maxsize_condition or appendpos_condition is not None:
1146
+ append_conditions = AppendPositionAccessConditions(
1147
+ max_size=maxsize_condition,
1148
+ append_position=appendpos_condition
1149
+ )
1150
+ source_authorization = kwargs.pop('source_authorization', None)
1151
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
1152
+ mod_conditions = get_modify_conditions(kwargs)
1153
+ source_mod_conditions = get_source_conditions(kwargs)
1154
+ cpk_scope_info = get_cpk_scope_info(kwargs)
1155
+ cpk = kwargs.pop('cpk', None)
1156
+ cpk_info = None
1157
+ if cpk:
1158
+ cpk_info = CpkInfo(encryption_key=cpk.key_value, encryption_key_sha256=cpk.key_hash,
1159
+ encryption_algorithm=cpk.algorithm)
1160
+
1161
+ options = {
1162
+ 'copy_source_authorization': source_authorization,
1163
+ 'source_url': copy_source_url,
1164
+ 'content_length': 0,
1165
+ 'source_range': source_range,
1166
+ 'source_content_md5': source_content_md5,
1167
+ 'transactional_content_md5': None,
1168
+ 'lease_access_conditions': access_conditions,
1169
+ 'append_position_access_conditions': append_conditions,
1170
+ 'modified_access_conditions': mod_conditions,
1171
+ 'source_modified_access_conditions': source_mod_conditions,
1172
+ 'cpk_scope_info': cpk_scope_info,
1173
+ 'cpk_info': cpk_info,
1174
+ 'cls': return_response_headers,
1175
+ 'timeout': kwargs.pop('timeout', None)}
1176
+ options.update(kwargs)
1177
+ return options
1178
+
1179
+ def _seal_append_blob_options(**kwargs: Any) -> Dict[str, Any]:
1180
+ appendpos_condition = kwargs.pop('appendpos_condition', None)
1181
+ append_conditions = None
1182
+ if appendpos_condition is not None:
1183
+ append_conditions = AppendPositionAccessConditions(
1184
+ append_position=appendpos_condition
1185
+ )
1186
+ access_conditions = get_access_conditions(kwargs.pop('lease', None))
1187
+ mod_conditions = get_modify_conditions(kwargs)
1188
+
1189
+ options = {
1190
+ 'timeout': kwargs.pop('timeout', None),
1191
+ 'lease_access_conditions': access_conditions,
1192
+ 'append_position_access_conditions': append_conditions,
1193
+ 'modified_access_conditions': mod_conditions,
1194
+ 'cls': return_response_headers}
1195
+ options.update(kwargs)
1196
+ return options
1197
+
1198
+ def _from_blob_url(
1199
+ blob_url: str,
1200
+ snapshot: Optional[Union[BlobProperties, str, Dict[str, Any]]]
1201
+ ) -> Tuple[str, str, str, Optional[str]]:
1202
+ try:
1203
+ if not blob_url.lower().startswith('http'):
1204
+ blob_url = "https://" + blob_url
1205
+ except AttributeError as exc:
1206
+ raise ValueError("Blob URL must be a string.") from exc
1207
+ parsed_url = urlparse(blob_url.rstrip('/'))
1208
+
1209
+ if not parsed_url.netloc:
1210
+ raise ValueError(f"Invalid URL: {blob_url}")
1211
+
1212
+ account_path = ""
1213
+ if ".core." in parsed_url.netloc:
1214
+ # .core. is indicating non-customized url. Blob name with directory info can also be parsed.
1215
+ path_blob = parsed_url.path.lstrip('/').split('/', maxsplit=1)
1216
+ elif "localhost" in parsed_url.netloc or "127.0.0.1" in parsed_url.netloc:
1217
+ path_blob = parsed_url.path.lstrip('/').split('/', maxsplit=2)
1218
+ account_path += '/' + path_blob[0]
1219
+ else:
1220
+ # for customized url. blob name that has directory info cannot be parsed.
1221
+ path_blob = parsed_url.path.lstrip('/').split('/')
1222
+ if len(path_blob) > 2:
1223
+ account_path = "/" + "/".join(path_blob[:-2])
1224
+
1225
+ account_url = f"{parsed_url.scheme}://{parsed_url.netloc.rstrip('/')}{account_path}?{parsed_url.query}"
1226
+
1227
+ msg_invalid_url = "Invalid URL. Provide a blob_url with a valid blob and container name."
1228
+ if len(path_blob) <= 1:
1229
+ raise ValueError(msg_invalid_url)
1230
+ container_name, blob_name = unquote(path_blob[-2]), unquote(path_blob[-1])
1231
+ if not container_name or not blob_name:
1232
+ raise ValueError(msg_invalid_url)
1233
+
1234
+ path_snapshot, _ = parse_query(parsed_url.query)
1235
+ if snapshot:
1236
+ if isinstance(snapshot, BlobProperties):
1237
+ path_snapshot = snapshot.snapshot
1238
+ elif isinstance(snapshot, dict):
1239
+ path_snapshot = snapshot['snapshot']
1240
+ else:
1241
+ path_snapshot = snapshot
1242
+ return (account_url, container_name, blob_name, path_snapshot)