azure-storage-blob 12.21.0__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.
- azure/storage/blob/__init__.py +19 -18
- azure/storage/blob/_blob_client.py +417 -1507
- azure/storage/blob/_blob_client_helpers.py +1242 -0
- azure/storage/blob/_blob_service_client.py +82 -101
- azure/storage/blob/_blob_service_client_helpers.py +27 -0
- azure/storage/blob/_container_client.py +147 -356
- azure/storage/blob/_container_client_helpers.py +261 -0
- azure/storage/blob/_deserialize.py +68 -44
- azure/storage/blob/_download.py +114 -90
- azure/storage/blob/_encryption.py +14 -7
- azure/storage/blob/_lease.py +47 -58
- azure/storage/blob/_list_blobs_helper.py +129 -135
- azure/storage/blob/_models.py +479 -276
- azure/storage/blob/_quick_query_helper.py +30 -31
- azure/storage/blob/_serialize.py +38 -56
- azure/storage/blob/_shared/avro/datafile.py +1 -1
- azure/storage/blob/_shared/avro/datafile_async.py +1 -1
- azure/storage/blob/_shared/base_client.py +1 -1
- azure/storage/blob/_shared/base_client_async.py +1 -1
- azure/storage/blob/_shared/policies.py +8 -6
- azure/storage/blob/_shared/policies_async.py +3 -1
- azure/storage/blob/_shared/response_handlers.py +6 -2
- azure/storage/blob/_shared/shared_access_signature.py +2 -2
- azure/storage/blob/_shared/uploads.py +1 -1
- azure/storage/blob/_shared/uploads_async.py +1 -1
- azure/storage/blob/_shared_access_signature.py +70 -53
- azure/storage/blob/_upload_helpers.py +75 -68
- azure/storage/blob/_version.py +1 -1
- azure/storage/blob/aio/__init__.py +19 -11
- azure/storage/blob/aio/_blob_client_async.py +505 -255
- azure/storage/blob/aio/_blob_service_client_async.py +138 -87
- azure/storage/blob/aio/_container_client_async.py +260 -120
- azure/storage/blob/aio/_download_async.py +104 -87
- azure/storage/blob/aio/_lease_async.py +56 -55
- azure/storage/blob/aio/_list_blobs_helper.py +94 -96
- azure/storage/blob/aio/_models.py +60 -38
- azure/storage/blob/aio/_upload_helpers.py +75 -66
- {azure_storage_blob-12.21.0.dist-info → azure_storage_blob-12.22.0.dist-info}/METADATA +1 -1
- {azure_storage_blob-12.21.0.dist-info → azure_storage_blob-12.22.0.dist-info}/RECORD +42 -39
- {azure_storage_blob-12.21.0.dist-info → azure_storage_blob-12.22.0.dist-info}/LICENSE +0 -0
- {azure_storage_blob-12.21.0.dist-info → azure_storage_blob-12.22.0.dist-info}/WHEEL +0 -0
- {azure_storage_blob-12.21.0.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)
|