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.
Files changed (42) hide show
  1. azure/storage/blob/__init__.py +19 -18
  2. azure/storage/blob/_blob_client.py +417 -1507
  3. azure/storage/blob/_blob_client_helpers.py +1242 -0
  4. azure/storage/blob/_blob_service_client.py +82 -101
  5. azure/storage/blob/_blob_service_client_helpers.py +27 -0
  6. azure/storage/blob/_container_client.py +147 -356
  7. azure/storage/blob/_container_client_helpers.py +261 -0
  8. azure/storage/blob/_deserialize.py +68 -44
  9. azure/storage/blob/_download.py +114 -90
  10. azure/storage/blob/_encryption.py +14 -7
  11. azure/storage/blob/_lease.py +47 -58
  12. azure/storage/blob/_list_blobs_helper.py +129 -135
  13. azure/storage/blob/_models.py +479 -276
  14. azure/storage/blob/_quick_query_helper.py +30 -31
  15. azure/storage/blob/_serialize.py +38 -56
  16. azure/storage/blob/_shared/avro/datafile.py +1 -1
  17. azure/storage/blob/_shared/avro/datafile_async.py +1 -1
  18. azure/storage/blob/_shared/base_client.py +1 -1
  19. azure/storage/blob/_shared/base_client_async.py +1 -1
  20. azure/storage/blob/_shared/policies.py +8 -6
  21. azure/storage/blob/_shared/policies_async.py +3 -1
  22. azure/storage/blob/_shared/response_handlers.py +6 -2
  23. azure/storage/blob/_shared/shared_access_signature.py +2 -2
  24. azure/storage/blob/_shared/uploads.py +1 -1
  25. azure/storage/blob/_shared/uploads_async.py +1 -1
  26. azure/storage/blob/_shared_access_signature.py +70 -53
  27. azure/storage/blob/_upload_helpers.py +75 -68
  28. azure/storage/blob/_version.py +1 -1
  29. azure/storage/blob/aio/__init__.py +19 -11
  30. azure/storage/blob/aio/_blob_client_async.py +505 -255
  31. azure/storage/blob/aio/_blob_service_client_async.py +138 -87
  32. azure/storage/blob/aio/_container_client_async.py +260 -120
  33. azure/storage/blob/aio/_download_async.py +104 -87
  34. azure/storage/blob/aio/_lease_async.py +56 -55
  35. azure/storage/blob/aio/_list_blobs_helper.py +94 -96
  36. azure/storage/blob/aio/_models.py +60 -38
  37. azure/storage/blob/aio/_upload_helpers.py +75 -66
  38. {azure_storage_blob-12.21.0.dist-info → azure_storage_blob-12.22.0.dist-info}/METADATA +1 -1
  39. {azure_storage_blob-12.21.0.dist-info → azure_storage_blob-12.22.0.dist-info}/RECORD +42 -39
  40. {azure_storage_blob-12.21.0.dist-info → azure_storage_blob-12.22.0.dist-info}/LICENSE +0 -0
  41. {azure_storage_blob-12.21.0.dist-info → azure_storage_blob-12.22.0.dist-info}/WHEEL +0 -0
  42. {azure_storage_blob-12.21.0.dist-info → azure_storage_blob-12.22.0.dist-info}/top_level.txt +0 -0
@@ -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 Generic, IO, Iterator, Optional, overload, TypeVar, Union
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 process_storage_error, parse_length_from_content_range
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(start_range, end_range, length, encryption_options, encryption_data):
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=None,
69
- non_empty_ranges=None,
70
- total_size=None,
71
- chunk_size=None,
72
- current_progress=None,
73
- start_range=None,
74
- end_range=None,
75
- stream=None,
76
- parallel=None,
77
- validate_content=None,
78
- encryption_options=None,
79
- encryption_data=None,
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
- """Async iterator for chunks in blob download stream."""
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
- chunk = next(self._iter_chunks)
268
- self._current_content += self._iter_downloader.yield_chunk(chunk)[0]
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.properties = None
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(self._current_content, final=self._download_complete)
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 chunk in chunks_iter:
695
- downloader.process_chunk(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._current_content = self._decoder.decode(
705
- chunk_data, final=self._download_complete) if self._text_mode else chunk_data
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 is_encryption_v2(encryption_data) and encryption_data.encrypted_region_info is not None:
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.