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
azure/storage/blob/_download.py
CHANGED
@@ -9,13 +9,17 @@ import threading
|
|
9
9
|
import time
|
10
10
|
import warnings
|
11
11
|
from io import BytesIO, StringIO
|
12
|
-
from typing import
|
12
|
+
from typing import (
|
13
|
+
Any, Callable, cast, Dict, Generator,
|
14
|
+
Generic, IO, Iterator, List, Optional,
|
15
|
+
overload, Tuple, TypeVar, Union, TYPE_CHECKING
|
16
|
+
)
|
13
17
|
|
14
18
|
from azure.core.exceptions import DecodeError, HttpResponseError, IncompleteReadError
|
15
19
|
from azure.core.tracing.common import with_current_context
|
16
20
|
|
17
21
|
from ._shared.request_handlers import validate_and_format_range_headers
|
18
|
-
from ._shared.response_handlers import
|
22
|
+
from ._shared.response_handlers import parse_length_from_content_range, process_storage_error
|
19
23
|
from ._deserialize import deserialize_blob_properties, get_page_ranges_result
|
20
24
|
from ._encryption import (
|
21
25
|
adjust_blob_size_for_encryption,
|
@@ -25,10 +29,25 @@ from ._encryption import (
|
|
25
29
|
parse_encryption_data
|
26
30
|
)
|
27
31
|
|
32
|
+
if TYPE_CHECKING:
|
33
|
+
from codecs import IncrementalDecoder
|
34
|
+
from ._encryption import _EncryptionData
|
35
|
+
from ._generated import AzureBlobStorage
|
36
|
+
from ._generated.operations import BlobOperations
|
37
|
+
from ._models import BlobProperties
|
38
|
+
from ._shared.models import StorageConfiguration
|
39
|
+
|
40
|
+
|
28
41
|
T = TypeVar('T', bytes, str)
|
29
42
|
|
30
43
|
|
31
|
-
def process_range_and_offset(
|
44
|
+
def process_range_and_offset(
|
45
|
+
start_range: int,
|
46
|
+
end_range: int,
|
47
|
+
length: Optional[int],
|
48
|
+
encryption_options: Dict[str, Any],
|
49
|
+
encryption_data: Optional["_EncryptionData"]
|
50
|
+
) -> Tuple[Tuple[int, int], Tuple[int, int]]:
|
32
51
|
start_offset, end_offset = 0, 0
|
33
52
|
if encryption_options.get("key") is not None or encryption_options.get("resolver") is not None:
|
34
53
|
return get_adjusted_download_range_and_offset(
|
@@ -40,7 +59,7 @@ def process_range_and_offset(start_range, end_range, length, encryption_options,
|
|
40
59
|
return (start_range, end_range), (start_offset, end_offset)
|
41
60
|
|
42
61
|
|
43
|
-
def process_content(data, start_offset, end_offset, encryption):
|
62
|
+
def process_content(data: Any, start_offset: int, end_offset: int, encryption: Dict[str, Any]) -> bytes:
|
44
63
|
if data is None:
|
45
64
|
raise ValueError("Response cannot be None.")
|
46
65
|
|
@@ -49,7 +68,7 @@ def process_content(data, start_offset, end_offset, encryption):
|
|
49
68
|
if content and encryption.get("key") is not None or encryption.get("resolver") is not None:
|
50
69
|
try:
|
51
70
|
return decrypt_blob(
|
52
|
-
encryption.get("required"),
|
71
|
+
encryption.get("required") or False,
|
53
72
|
encryption.get("key"),
|
54
73
|
encryption.get("resolver"),
|
55
74
|
content,
|
@@ -65,21 +84,21 @@ def process_content(data, start_offset, end_offset, encryption):
|
|
65
84
|
class _ChunkDownloader(object): # pylint: disable=too-many-instance-attributes
|
66
85
|
def __init__(
|
67
86
|
self,
|
68
|
-
client
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
progress_hook=None,
|
81
|
-
**kwargs
|
82
|
-
):
|
87
|
+
client: "BlobOperations",
|
88
|
+
total_size: int,
|
89
|
+
chunk_size: int,
|
90
|
+
current_progress: int,
|
91
|
+
start_range: int,
|
92
|
+
end_range: int,
|
93
|
+
validate_content: bool,
|
94
|
+
encryption_options: Dict[str, Any],
|
95
|
+
encryption_data: Optional["_EncryptionData"] = None,
|
96
|
+
stream: Any = None,
|
97
|
+
parallel: Optional[int] = None,
|
98
|
+
non_empty_ranges: Optional[List[Dict[str, Any]]] = None,
|
99
|
+
progress_hook: Optional[Callable[[int, Optional[int]], None]] = None,
|
100
|
+
**kwargs: Any
|
101
|
+
) -> None:
|
83
102
|
self.client = client
|
84
103
|
self.non_empty_ranges = non_empty_ranges
|
85
104
|
|
@@ -110,20 +129,20 @@ class _ChunkDownloader(object): # pylint: disable=too-many-instance-attributes
|
|
110
129
|
self.validate_content = validate_content
|
111
130
|
self.request_options = kwargs
|
112
131
|
|
113
|
-
def _calculate_range(self, chunk_start):
|
132
|
+
def _calculate_range(self, chunk_start: int) -> Tuple[int, int]:
|
114
133
|
if chunk_start + self.chunk_size > self.end_index:
|
115
134
|
chunk_end = self.end_index
|
116
135
|
else:
|
117
136
|
chunk_end = chunk_start + self.chunk_size
|
118
137
|
return chunk_start, chunk_end
|
119
138
|
|
120
|
-
def get_chunk_offsets(self):
|
139
|
+
def get_chunk_offsets(self) -> Generator[int, None, None]:
|
121
140
|
index = self.start_index
|
122
141
|
while index < self.end_index:
|
123
142
|
yield index
|
124
143
|
index += self.chunk_size
|
125
144
|
|
126
|
-
def process_chunk(self, chunk_start):
|
145
|
+
def process_chunk(self, chunk_start: int) -> None:
|
127
146
|
chunk_start, chunk_end = self._calculate_range(chunk_start)
|
128
147
|
chunk_data, _ = self._download_chunk(chunk_start, chunk_end - 1)
|
129
148
|
length = chunk_end - chunk_start
|
@@ -131,11 +150,11 @@ class _ChunkDownloader(object): # pylint: disable=too-many-instance-attributes
|
|
131
150
|
self._write_to_stream(chunk_data, chunk_start)
|
132
151
|
self._update_progress(length)
|
133
152
|
|
134
|
-
def yield_chunk(self, chunk_start):
|
153
|
+
def yield_chunk(self, chunk_start: int) -> Tuple[bytes, int]:
|
135
154
|
chunk_start, chunk_end = self._calculate_range(chunk_start)
|
136
155
|
return self._download_chunk(chunk_start, chunk_end - 1)
|
137
156
|
|
138
|
-
def _update_progress(self, length):
|
157
|
+
def _update_progress(self, length: int) -> None:
|
139
158
|
if self.progress_lock:
|
140
159
|
with self.progress_lock: # pylint: disable=not-context-manager
|
141
160
|
self.progress_total += length
|
@@ -145,7 +164,7 @@ class _ChunkDownloader(object): # pylint: disable=too-many-instance-attributes
|
|
145
164
|
if self.progress_hook:
|
146
165
|
self.progress_hook(self.progress_total, self.total_size)
|
147
166
|
|
148
|
-
def _write_to_stream(self, chunk_data, chunk_start):
|
167
|
+
def _write_to_stream(self, chunk_data: bytes, chunk_start: int) -> None:
|
149
168
|
if self.stream_lock:
|
150
169
|
with self.stream_lock: # pylint: disable=not-context-manager
|
151
170
|
self.stream.seek(self.stream_start + (chunk_start - self.start_index))
|
@@ -153,7 +172,7 @@ class _ChunkDownloader(object): # pylint: disable=too-many-instance-attributes
|
|
153
172
|
else:
|
154
173
|
self.stream.write(chunk_data)
|
155
174
|
|
156
|
-
def _do_optimize(self, given_range_start, given_range_end):
|
175
|
+
def _do_optimize(self, given_range_start: int, given_range_end: int) -> bool:
|
157
176
|
# If we have no page range list stored, then assume there's data everywhere for that page blob
|
158
177
|
# or it's a block blob or append blob
|
159
178
|
if self.non_empty_ranges is None:
|
@@ -178,7 +197,9 @@ class _ChunkDownloader(object): # pylint: disable=too-many-instance-attributes
|
|
178
197
|
# Went through all src_ranges, but nothing overlapped. Optimization will be applied.
|
179
198
|
return True
|
180
199
|
|
181
|
-
def _download_chunk(self, chunk_start, chunk_end):
|
200
|
+
def _download_chunk(self, chunk_start: int, chunk_end: int) -> Tuple[bytes, int]:
|
201
|
+
if self.encryption_options is None:
|
202
|
+
raise ValueError("Required argument is missing: encryption_options")
|
182
203
|
download_range, offset = process_range_and_offset(
|
183
204
|
chunk_start, chunk_end, chunk_end, self.encryption_options, self.encryption_data
|
184
205
|
)
|
@@ -198,6 +219,7 @@ class _ChunkDownloader(object): # pylint: disable=too-many-instance-attributes
|
|
198
219
|
retry_active = True
|
199
220
|
retry_total = 3
|
200
221
|
while retry_active:
|
222
|
+
response: Any = None
|
201
223
|
try:
|
202
224
|
_, response = self.client.download(
|
203
225
|
range=range_header,
|
@@ -229,24 +251,24 @@ class _ChunkDownloader(object): # pylint: disable=too-many-instance-attributes
|
|
229
251
|
|
230
252
|
|
231
253
|
class _ChunkIterator(object):
|
232
|
-
"""
|
254
|
+
"""Iterator for chunks in blob download stream."""
|
233
255
|
|
234
|
-
def __init__(self, size, content, downloader, chunk_size):
|
256
|
+
def __init__(self, size: int, content: bytes, downloader: Optional[_ChunkDownloader], chunk_size: int) -> None:
|
235
257
|
self.size = size
|
236
258
|
self._chunk_size = chunk_size
|
237
259
|
self._current_content = content
|
238
260
|
self._iter_downloader = downloader
|
239
|
-
self._iter_chunks = None
|
261
|
+
self._iter_chunks: Optional[Generator[int, None, None]] = None
|
240
262
|
self._complete = size == 0
|
241
263
|
|
242
|
-
def __len__(self):
|
264
|
+
def __len__(self) -> int:
|
243
265
|
return self.size
|
244
266
|
|
245
|
-
def __iter__(self):
|
267
|
+
def __iter__(self) -> Iterator[bytes]:
|
246
268
|
return self
|
247
269
|
|
248
270
|
# Iterate through responses.
|
249
|
-
def __next__(self):
|
271
|
+
def __next__(self) -> bytes:
|
250
272
|
if self._complete:
|
251
273
|
raise StopIteration("Download complete")
|
252
274
|
if not self._iter_downloader:
|
@@ -264,8 +286,8 @@ class _ChunkIterator(object):
|
|
264
286
|
return self._get_chunk_data()
|
265
287
|
|
266
288
|
try:
|
267
|
-
|
268
|
-
self._current_content += self._iter_downloader.yield_chunk(
|
289
|
+
next_chunk = next(self._iter_chunks)
|
290
|
+
self._current_content += self._iter_downloader.yield_chunk(next_chunk)[0]
|
269
291
|
except StopIteration as e:
|
270
292
|
self._complete = True
|
271
293
|
if self._current_content:
|
@@ -278,46 +300,46 @@ class _ChunkIterator(object):
|
|
278
300
|
|
279
301
|
next = __next__ # Python 2 compatibility.
|
280
302
|
|
281
|
-
def _get_chunk_data(self):
|
303
|
+
def _get_chunk_data(self) -> bytes:
|
282
304
|
chunk_data = self._current_content[: self._chunk_size]
|
283
305
|
self._current_content = self._current_content[self._chunk_size:]
|
284
306
|
return chunk_data
|
285
307
|
|
286
308
|
|
287
309
|
class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-attributes
|
288
|
-
"""A streaming object to download from Azure Storage.
|
289
|
-
|
290
|
-
:ivar str name:
|
291
|
-
The name of the blob being downloaded.
|
292
|
-
:ivar str container:
|
293
|
-
The name of the container where the blob is.
|
294
|
-
:ivar ~azure.storage.blob.BlobProperties properties:
|
295
|
-
The properties of the blob being downloaded. If only a range of the data is being
|
296
|
-
downloaded, this will be reflected in the properties.
|
297
|
-
:ivar int size:
|
298
|
-
The size of the total data in the stream. This will be the byte range if specified,
|
299
|
-
otherwise the total size of the blob.
|
300
310
|
"""
|
311
|
+
A streaming object to download from Azure Storage.
|
312
|
+
"""
|
313
|
+
|
314
|
+
name: str
|
315
|
+
"""The name of the blob being downloaded."""
|
316
|
+
container: str
|
317
|
+
"""The name of the container where the blob is."""
|
318
|
+
properties: "BlobProperties"
|
319
|
+
"""The properties of the blob being downloaded. If only a range of the data is being
|
320
|
+
downloaded, this will be reflected in the properties."""
|
321
|
+
size: int
|
322
|
+
"""The size of the total data in the stream. This will be the byte range if specified,
|
323
|
+
otherwise the total size of the blob."""
|
301
324
|
|
302
325
|
def __init__(
|
303
326
|
self,
|
304
|
-
clients=None,
|
305
|
-
config=None,
|
306
|
-
start_range=None,
|
307
|
-
end_range=None,
|
308
|
-
validate_content=None,
|
309
|
-
encryption_options=None,
|
310
|
-
max_concurrency=1,
|
311
|
-
name=None,
|
312
|
-
container=None,
|
313
|
-
encoding=None,
|
314
|
-
download_cls=None,
|
315
|
-
**kwargs
|
316
|
-
):
|
327
|
+
clients: "AzureBlobStorage" = None, # type: ignore [assignment]
|
328
|
+
config: "StorageConfiguration" = None, # type: ignore [assignment]
|
329
|
+
start_range: Optional[int] = None,
|
330
|
+
end_range: Optional[int] = None,
|
331
|
+
validate_content: bool = None, # type: ignore [assignment]
|
332
|
+
encryption_options: Dict[str, Any] = None, # type: ignore [assignment]
|
333
|
+
max_concurrency: int = 1,
|
334
|
+
name: str = None, # type: ignore [assignment]
|
335
|
+
container: str = None, # type: ignore [assignment]
|
336
|
+
encoding: Optional[str] = None,
|
337
|
+
download_cls: Optional[Callable] = None,
|
338
|
+
**kwargs: Any
|
339
|
+
) -> None:
|
317
340
|
self.name = name
|
318
341
|
self.container = container
|
319
|
-
self.
|
320
|
-
self.size = None
|
342
|
+
self.size = 0
|
321
343
|
|
322
344
|
self._clients = clients
|
323
345
|
self._config = config
|
@@ -331,10 +353,10 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
331
353
|
self._request_options = kwargs
|
332
354
|
self._response = None
|
333
355
|
self._location_mode = None
|
334
|
-
self._current_content = b''
|
356
|
+
self._current_content: Union[str, bytes] = b''
|
335
357
|
self._file_size = 0
|
336
358
|
self._non_empty_ranges = None
|
337
|
-
self._encryption_data = None
|
359
|
+
self._encryption_data: Optional["_EncryptionData"] = None
|
338
360
|
|
339
361
|
# The content download offset, after any processing (decryption), in bytes
|
340
362
|
self._download_offset = 0
|
@@ -346,7 +368,7 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
346
368
|
self._current_content_offset = 0
|
347
369
|
|
348
370
|
self._text_mode: Optional[bool] = None
|
349
|
-
self._decoder = None
|
371
|
+
self._decoder: Optional["IncrementalDecoder"] = None
|
350
372
|
# Whether the current content is the first chunk of download content or not
|
351
373
|
self._first_chunk = True
|
352
374
|
self._download_start = self._start_range or 0
|
@@ -379,7 +401,7 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
379
401
|
)
|
380
402
|
|
381
403
|
self._response = self._initial_request()
|
382
|
-
self.properties = self._response.properties
|
404
|
+
self.properties = cast("BlobProperties", self._response.properties)
|
383
405
|
self.properties.name = self.name
|
384
406
|
self.properties.container = self.container
|
385
407
|
|
@@ -392,18 +414,18 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
392
414
|
# Overwrite the content MD5 as it is the MD5 for the last range instead
|
393
415
|
# of the stored MD5
|
394
416
|
# TODO: Set to the stored MD5 when the service returns this
|
395
|
-
self.properties.content_md5 = None
|
417
|
+
self.properties.content_md5 = None # type: ignore [attr-defined]
|
396
418
|
|
397
419
|
def __len__(self):
|
398
420
|
return self.size
|
399
421
|
|
400
|
-
def _get_encryption_data_request(self):
|
422
|
+
def _get_encryption_data_request(self) -> None:
|
401
423
|
# Save current request cls
|
402
424
|
download_cls = self._request_options.pop('cls', None)
|
403
425
|
# Adjust cls for get_properties
|
404
426
|
self._request_options['cls'] = deserialize_blob_properties
|
405
427
|
|
406
|
-
properties = self._clients.blob.get_properties(**self._request_options)
|
428
|
+
properties = cast("BlobProperties", self._clients.blob.get_properties(**self._request_options))
|
407
429
|
# This will return None if there is no encryption metadata or there are parsing errors.
|
408
430
|
# That is acceptable here, the proper error will be caught and surfaced when attempting
|
409
431
|
# to decrypt the blob.
|
@@ -431,14 +453,14 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
431
453
|
retry_total = 3
|
432
454
|
while retry_active:
|
433
455
|
try:
|
434
|
-
location_mode, response = self._clients.blob.download(
|
456
|
+
location_mode, response = cast(Tuple[Optional[str], Any], self._clients.blob.download(
|
435
457
|
range=range_header,
|
436
458
|
range_get_content_md5=range_validation,
|
437
459
|
validate_content=self._validate_content,
|
438
460
|
data_stream_total=None,
|
439
461
|
download_stream_current=0,
|
440
462
|
**self._request_options
|
441
|
-
)
|
463
|
+
))
|
442
464
|
|
443
465
|
# Check the location we read from to ensure we use the same one
|
444
466
|
# for subsequent requests.
|
@@ -452,7 +474,7 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
452
474
|
# Remove any extra encryption data size from blob size
|
453
475
|
self._file_size = adjust_blob_size_for_encryption(self._file_size, self._encryption_data)
|
454
476
|
|
455
|
-
if self._end_range is not None:
|
477
|
+
if self._end_range is not None and self._start_range is not None:
|
456
478
|
# Use the end range index unless it is over the end of the file
|
457
479
|
self.size = min(self._file_size - self._start_range, self._end_range - self._start_range + 1)
|
458
480
|
elif self._start_range is not None:
|
@@ -517,8 +539,7 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
517
539
|
|
518
540
|
return response
|
519
541
|
|
520
|
-
def chunks(self):
|
521
|
-
# type: () -> Iterator[bytes]
|
542
|
+
def chunks(self) -> Iterator[bytes]:
|
522
543
|
"""
|
523
544
|
Iterate over chunks in the download stream. Note, the iterator returned will
|
524
545
|
iterate over the entire download content, regardless of any data that was
|
@@ -573,7 +594,7 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
573
594
|
initial_content = self._current_content if self._first_chunk else b''
|
574
595
|
return _ChunkIterator(
|
575
596
|
size=self.size,
|
576
|
-
content=initial_content,
|
597
|
+
content=cast(bytes, initial_content),
|
577
598
|
downloader=iter_downloader,
|
578
599
|
chunk_size=self._config.max_chunk_get_size)
|
579
600
|
|
@@ -622,12 +643,13 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
622
643
|
# Empty blob or already read to the end
|
623
644
|
if (size == 0 or chars == 0 or
|
624
645
|
(self._download_complete and self._current_content_offset >= len(self._current_content))):
|
625
|
-
return b'' if not self._encoding else ''
|
646
|
+
return b'' if not self._encoding else '' # type: ignore [return-value]
|
626
647
|
|
627
|
-
if not self._text_mode and chars is not None:
|
648
|
+
if not self._text_mode and chars is not None and self._encoding is not None:
|
628
649
|
self._text_mode = True
|
629
650
|
self._decoder = codecs.getincrementaldecoder(self._encoding)('strict')
|
630
|
-
self._current_content = self._decoder.decode(
|
651
|
+
self._current_content = self._decoder.decode(
|
652
|
+
cast(bytes, self._current_content), final=self._download_complete)
|
631
653
|
elif self._text_mode is None:
|
632
654
|
self._text_mode = False
|
633
655
|
|
@@ -644,7 +666,7 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
644
666
|
# Start by reading from current_content
|
645
667
|
start = self._current_content_offset
|
646
668
|
length = min(len(self._current_content) - self._current_content_offset, size - count)
|
647
|
-
read = output_stream.write(self._current_content[start:start + length])
|
669
|
+
read = output_stream.write(self._current_content[start:start + length]) # type: ignore [arg-type]
|
648
670
|
|
649
671
|
count += read
|
650
672
|
self._current_content_offset += read
|
@@ -691,8 +713,8 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
691
713
|
downloader.get_chunk_offsets()
|
692
714
|
))
|
693
715
|
else:
|
694
|
-
for
|
695
|
-
downloader.process_chunk(
|
716
|
+
for next_chunk in chunks_iter:
|
717
|
+
downloader.process_chunk(next_chunk)
|
696
718
|
|
697
719
|
self._complete_read()
|
698
720
|
|
@@ -701,13 +723,15 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
701
723
|
chunk_data, content_length = downloader.yield_chunk(chunk)
|
702
724
|
self._download_offset += len(chunk_data)
|
703
725
|
self._raw_download_offset += content_length
|
704
|
-
self.
|
705
|
-
chunk_data, final=self._download_complete)
|
726
|
+
if self._text_mode and self._decoder is not None:
|
727
|
+
self._current_content = self._decoder.decode(chunk_data, final=self._download_complete)
|
728
|
+
else:
|
729
|
+
self._current_content = chunk_data
|
706
730
|
|
707
731
|
if remaining < len(self._current_content):
|
708
|
-
read = output_stream.write(self._current_content[:remaining])
|
732
|
+
read = output_stream.write(self._current_content[:remaining]) # type: ignore [arg-type]
|
709
733
|
else:
|
710
|
-
read = output_stream.write(self._current_content)
|
734
|
+
read = output_stream.write(self._current_content) # type: ignore [arg-type]
|
711
735
|
|
712
736
|
self._current_content_offset = read
|
713
737
|
self._read_offset += read
|
@@ -718,7 +742,7 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
718
742
|
if not self._text_mode and self._encoding:
|
719
743
|
try:
|
720
744
|
# This is technically incorrect to do, but we have it for backwards compatibility.
|
721
|
-
data = data.decode(self._encoding)
|
745
|
+
data = cast(bytes, data).decode(self._encoding)
|
722
746
|
except UnicodeDecodeError:
|
723
747
|
warnings.warn(
|
724
748
|
"Encountered a decoding error while decoding blob data from a partial read. "
|
@@ -726,7 +750,7 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
726
750
|
)
|
727
751
|
raise
|
728
752
|
|
729
|
-
return data
|
753
|
+
return data # type: ignore [return-value]
|
730
754
|
|
731
755
|
def readall(self) -> T:
|
732
756
|
"""
|
@@ -774,7 +798,7 @@ class StorageStreamDownloader(Generic[T]): # pylint: disable=too-many-instance-
|
|
774
798
|
# Write the current content to the user stream
|
775
799
|
current_remaining = len(self._current_content) - self._current_content_offset
|
776
800
|
start = self._current_content_offset
|
777
|
-
count = stream.write(self._current_content[start:start + current_remaining])
|
801
|
+
count = stream.write(cast(bytes, self._current_content[start:start + current_remaining]))
|
778
802
|
|
779
803
|
self._current_content_offset += count
|
780
804
|
self._read_offset += count
|
@@ -47,6 +47,10 @@ _GCM_TAG_LENGTH = 16
|
|
47
47
|
_ERROR_OBJECT_INVALID = \
|
48
48
|
'{0} does not define a complete interface. Value of {1} is either missing or invalid.'
|
49
49
|
|
50
|
+
_ERROR_UNSUPPORTED_METHOD_FOR_ENCRYPTION = (
|
51
|
+
'The require_encryption flag is set, but encryption is not supported'
|
52
|
+
' for this method.')
|
53
|
+
|
50
54
|
|
51
55
|
class KeyEncryptionKey(Protocol):
|
52
56
|
|
@@ -357,7 +361,7 @@ def get_adjusted_upload_size(length: int, encryption_version: str) -> int:
|
|
357
361
|
def get_adjusted_download_range_and_offset(
|
358
362
|
start: int,
|
359
363
|
end: int,
|
360
|
-
length: int,
|
364
|
+
length: Optional[int],
|
361
365
|
encryption_data: Optional[_EncryptionData]) -> Tuple[Tuple[int, int], Tuple[int, int]]:
|
362
366
|
"""
|
363
367
|
Gets the new download range and offsets into the decrypted data for
|
@@ -374,7 +378,7 @@ def get_adjusted_download_range_and_offset(
|
|
374
378
|
|
375
379
|
:param int start: The user-requested start index.
|
376
380
|
:param int end: The user-requested end index.
|
377
|
-
:param int length: The user-requested length. Only used for V1.
|
381
|
+
:param Optional[int] length: The user-requested length. Only used for V1.
|
378
382
|
:param Optional[_EncryptionData] encryption_data: The encryption data to determine version and sizes.
|
379
383
|
:return: (new start, new end), (start offset, end offset)
|
380
384
|
:rtype: Tuple[Tuple[int, int], Tuple[int, int]]
|
@@ -451,17 +455,20 @@ def parse_encryption_data(metadata: Dict[str, Any]) -> Optional[_EncryptionData]
|
|
451
455
|
return None
|
452
456
|
|
453
457
|
|
454
|
-
def adjust_blob_size_for_encryption(size: int, encryption_data: _EncryptionData) -> int:
|
458
|
+
def adjust_blob_size_for_encryption(size: int, encryption_data: Optional[_EncryptionData]) -> int:
|
455
459
|
"""
|
456
460
|
Adjusts the given blob size for encryption by subtracting the size of
|
457
461
|
the encryption data (nonce + tag). This only has an affect for encryption V2.
|
458
462
|
|
459
463
|
:param int size: The original blob size.
|
460
|
-
:param _EncryptionData encryption_data: The encryption data to determine version and sizes.
|
464
|
+
:param Optional[_EncryptionData] encryption_data: The encryption data to determine version and sizes.
|
461
465
|
:return: The new blob size.
|
462
466
|
:rtype: int
|
463
467
|
"""
|
464
|
-
if
|
468
|
+
if (encryption_data is not None and
|
469
|
+
encryption_data.encrypted_region_info is not None and
|
470
|
+
is_encryption_v2(encryption_data)):
|
471
|
+
|
465
472
|
nonce_length = encryption_data.encrypted_region_info.nonce_length
|
466
473
|
data_length = encryption_data.encrypted_region_info.data_length
|
467
474
|
tag_length = encryption_data.encrypted_region_info.tag_length
|
@@ -836,7 +843,7 @@ def generate_blob_encryption_data(
|
|
836
843
|
|
837
844
|
def decrypt_blob( # pylint: disable=too-many-locals,too-many-statements
|
838
845
|
require_encryption: bool,
|
839
|
-
key_encryption_key: KeyEncryptionKey,
|
846
|
+
key_encryption_key: Optional[KeyEncryptionKey],
|
840
847
|
key_resolver: Optional[Callable[[str], KeyEncryptionKey]],
|
841
848
|
content: bytes,
|
842
849
|
start_offset: int,
|
@@ -848,7 +855,7 @@ def decrypt_blob( # pylint: disable=too-many-locals,too-many-statements
|
|
848
855
|
|
849
856
|
:param bool require_encryption:
|
850
857
|
Whether the calling blob service requires objects to be decrypted.
|
851
|
-
:param KeyEncryptionKey key_encryption_key:
|
858
|
+
:param Optional[KeyEncryptionKey] key_encryption_key:
|
852
859
|
The user-provided key-encryption-key. Must implement the following methods:
|
853
860
|
wrap_key(key)
|
854
861
|
- Wraps the specified key using an algorithm of the user's choice.
|