Skip to content

Commit a92b36f

Browse files
committed
Update v2017_04_17 SDK to the 0.37.1 packages.
Additionally, add a script to automate the updating process.
1 parent d4329a8 commit a92b36f

File tree

16 files changed

+150
-25
lines changed

16 files changed

+150
-25
lines changed

README.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ Handles multi-API versions of Azure Storage Data Plane originally from https://g
1717
Change Log
1818
----------
1919

20+
0.1.7
21+
+++++
22+
* Upgrade:
23+
- 2017-04-17 (from 0.37.0 to 0.37.1)
24+
2025
0.1.6
2126
+++++
2227
* Integrate the latest Python Storage SDK as well as the CosmosDB table SDK

azure/multiapi/storage/v2017_04_17/blob/_constants.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
# --------------------------------------------------------------------------
1515

1616
__author__ = 'Microsoft Corp. <[email protected]>'
17-
__version__ = '0.36.0'
17+
__version__ = '0.37.1'
1818

1919
# x-ms-version for storage service.
2020
X_MS_VERSION = '2017-04-17'
21+
22+
# internal configurations, should not be changed
23+
_LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE = 4 * 1024 * 1024

azure/multiapi/storage/v2017_04_17/blob/_deserialization.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,9 @@ def _convert_xml_to_containers(response):
244244
'CopyCompletionTime': ('copy', 'completion_time', _to_str),
245245
'CopyStatusDescription': ('copy', 'status_description', _to_str),
246246
'AccessTier': (None, 'blob_tier', _to_str),
247-
'ArchiveStatus': (None, 'rehydration_status', _to_str)
247+
'AccessTierChangeTime': (None, 'blob_tier_change_time', parser.parse),
248+
'AccessTierInferred': (None, 'blob_tier_inferred', _bool),
249+
'ArchiveStatus': (None, 'rehydration_status', _to_str),
248250
}
249251

250252

@@ -281,6 +283,8 @@ def _convert_xml_to_blob_list(response):
281283
<CopyCompletionTime>datetime</CopyCompletionTime>
282284
<CopyStatusDescription>error string</CopyStatusDescription>
283285
<AccessTier>P4 | P6 | P10 | P20 | P30 | P40 | P50 | P60 | Archive | Cool | Hot</AccessTier>
286+
<AccessTierChangeTime>date-time-value</AccessTierChangeTime>
287+
<AccessTierInferred>true</AccessTierInferred>
284288
</Properties>
285289
<Metadata>
286290
<Name>value</Name>

azure/multiapi/storage/v2017_04_17/blob/_upload_chunking.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
_get_blob_encryptor_and_padder,
2828
)
2929
from .models import BlobBlock
30+
from ._constants import (
31+
_LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE
32+
)
3033

3134

3235
def _upload_blob_chunks(blob_service, container_name, blob_name,
@@ -342,6 +345,7 @@ def __init__(self, wrapped_stream, stream_begin_index, length, lockObj):
342345
# derivations of io.IOBase and thus do not implement seekable().
343346
# Python > 3.0: file-like objects created with open() are derived from io.IOBase.
344347
try:
348+
# only the main thread runs this, so there's no need grabbing the lock
345349
wrapped_stream.seek(0, SEEK_CUR)
346350
except:
347351
raise ValueError("Wrapped stream must support seek().")
@@ -351,9 +355,14 @@ def __init__(self, wrapped_stream, stream_begin_index, length, lockObj):
351355
self._position = 0
352356
self._stream_begin_index = stream_begin_index
353357
self._length = length
354-
self._count = 0
355358
self._buffer = BytesIO()
356-
self._read_buffer_size = 4 * 1024 * 1024
359+
360+
# we must avoid buffering more than necessary, and also not use up too much memory
361+
# so the max buffer size is capped at 4MB
362+
self._max_buffer_size = length if length < _LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE \
363+
else _LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE
364+
self._current_buffer_start = 0
365+
self._current_buffer_size = 0
357366

358367
def __len__(self):
359368
return self._length
@@ -382,35 +391,45 @@ def read(self, n):
382391
if n is 0 or self._buffer.closed:
383392
return b''
384393

385-
# attempt first read from the read buffer
394+
# attempt first read from the read buffer and update position
386395
read_buffer = self._buffer.read(n)
387396
bytes_read = len(read_buffer)
388397
bytes_remaining = n - bytes_read
398+
self._position += bytes_read
389399

390400
# repopulate the read buffer from the underlying stream to fulfill the request
391401
# ensure the seek and read operations are done atomically (only if a lock is provided)
392402
if bytes_remaining > 0:
393403
with self._buffer:
404+
# either read in the max buffer size specified on the class
405+
# or read in just enough data for the current block/sub stream
406+
current_max_buffer_size = min(self._max_buffer_size, self._length - self._position)
407+
394408
# lock is only defined if max_connections > 1 (parallel uploads)
395409
if self._lock:
396410
with self._lock:
397-
# reposition the underlying stream to match the start of the substream
411+
# reposition the underlying stream to match the start of the data to read
398412
absolute_position = self._stream_begin_index + self._position
399413
self._wrapped_stream.seek(absolute_position, SEEK_SET)
400414
# If we can't seek to the right location, our read will be corrupted so fail fast.
401415
if self._wrapped_stream.tell() != absolute_position:
402416
raise IOError("Stream failed to seek to the desired location.")
403-
buffer_from_stream = self._wrapped_stream.read(self._read_buffer_size)
417+
buffer_from_stream = self._wrapped_stream.read(current_max_buffer_size)
404418
else:
405-
buffer_from_stream = self._wrapped_stream.read(self._read_buffer_size)
419+
buffer_from_stream = self._wrapped_stream.read(current_max_buffer_size)
406420

407421
if buffer_from_stream:
422+
# update the buffer with new data from the wrapped stream
423+
# we need to note down the start position and size of the buffer, in case seek is performed later
408424
self._buffer = BytesIO(buffer_from_stream)
425+
self._current_buffer_start = self._position
426+
self._current_buffer_size = len(buffer_from_stream)
427+
428+
# read the remaining bytes from the new buffer and update position
409429
second_read_buffer = self._buffer.read(bytes_remaining)
410-
bytes_read += len(second_read_buffer)
411430
read_buffer += second_read_buffer
431+
self._position += len(second_read_buffer)
412432

413-
self._position += bytes_read
414433
return read_buffer
415434

416435
def readable(self):
@@ -437,6 +456,15 @@ def seek(self, offset, whence=0):
437456
elif pos < 0:
438457
pos = 0
439458

459+
# check if buffer is still valid
460+
# if not, drop buffer
461+
if pos < self._current_buffer_start or pos >= self._current_buffer_start + self._current_buffer_size:
462+
self._buffer.close()
463+
self._buffer = BytesIO()
464+
else: # if yes seek to correct position
465+
delta = pos - self._current_buffer_start
466+
self._buffer.seek(delta, SEEK_SET)
467+
440468
self._position = pos
441469
return pos
442470

azure/multiapi/storage/v2017_04_17/blob/blockblobservice.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,9 @@ def create_blob_from_path(
348348
that was sent. This is primarily valuable for detecting bitflips on
349349
the wire if using http instead of https as https (the default) will
350350
already validate. Note that this MD5 hash is not stored with the
351-
blob.
351+
blob. Also note that if enabled, the memory-efficient upload algorithm
352+
will not be used, because computing the MD5 hash requires buffering
353+
entire blocks, and doing so defeats the purpose of the memory-efficient algorithm.
352354
:param progress_callback:
353355
Callback for progress with signature function(current, total) where
354356
current is the number of bytes transfered so far, and total is the
@@ -441,7 +443,9 @@ def create_blob_from_stream(
441443
that was sent. This is primarily valuable for detecting bitflips on
442444
the wire if using http instead of https as https (the default) will
443445
already validate. Note that this MD5 hash is not stored with the
444-
blob.
446+
blob. Also note that if enabled, the memory-efficient upload algorithm
447+
will not be used, because computing the MD5 hash requires buffering
448+
entire blocks, and doing so defeats the purpose of the memory-efficient algorithm.
445449
:param progress_callback:
446450
Callback for progress with signature function(current, total) where
447451
current is the number of bytes transfered so far, and total is the
@@ -507,6 +511,7 @@ def create_blob_from_stream(
507511
if (self.key_encryption_key is not None) and (adjusted_count is not None):
508512
adjusted_count += (16 - (count % 16))
509513

514+
# Do single put if the size is smaller than MAX_SINGLE_PUT_SIZE
510515
if adjusted_count is not None and (adjusted_count < self.MAX_SINGLE_PUT_SIZE):
511516
if progress_callback:
512517
progress_callback(0, count)
@@ -530,10 +535,10 @@ def create_blob_from_stream(
530535
progress_callback(count, count)
531536

532537
return resp
533-
else:
538+
else: # Size is larger than MAX_SINGLE_PUT_SIZE, must upload with multiple put_block calls
534539
cek, iv, encryption_data = None, None, None
535540

536-
use_original_upload_path = use_byte_buffer or self.require_encryption or \
541+
use_original_upload_path = use_byte_buffer or validate_content or self.require_encryption or \
537542
self.MAX_BLOCK_SIZE < self.MIN_LARGE_BLOCK_UPLOAD_THRESHOLD or \
538543
hasattr(stream, 'seekable') and not stream.seekable() or \
539544
not hasattr(stream, 'seek') or not hasattr(stream, 'tell')

azure/multiapi/storage/v2017_04_17/blob/models.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,18 @@ class BlobProperties(object):
115115
Stores all the content settings for the blob.
116116
:ivar ~azure.storage.blob.models.LeaseProperties lease:
117117
Stores all the lease information for the blob.
118+
:ivar StandardBlobTier blob_tier:
119+
Indicates the access tier of the blob. The hot tier is optimized
120+
for storing data that is accessed frequently. The cool storage tier
121+
is optimized for storing data that is infrequently accessed and stored
122+
for at least a month. The archive tier is optimized for storing
123+
data that is rarely accessed and stored for at least six months
124+
with flexible latency requirements.
125+
:ivar datetime blob_tier_change_time:
126+
Indicates when the access tier was last changed.
127+
:ivar bool blob_tier_inferred:
128+
Indicates whether the access tier was inferred by the service.
129+
If false, it indicates that the tier was set explicitly.
118130
'''
119131

120132
def __init__(self):
@@ -129,6 +141,9 @@ def __init__(self):
129141
self.copy = CopyProperties()
130142
self.content_settings = ContentSettings()
131143
self.lease = LeaseProperties()
144+
self.blob_tier = None
145+
self.blob_tier_change_time = None
146+
self.blob_tier_inferred = False
132147

133148

134149
class ContentSettings(object):

azure/multiapi/storage/v2017_04_17/common/_constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import platform
1616

1717
__author__ = 'Microsoft Corp. <[email protected]>'
18-
__version__ = '0.37.0'
18+
__version__ = '0.37.1'
1919

2020
# UserAgent string sample: 'Azure-Storage/0.37.0-0.38.0 (Python CPython 3.4.2; Windows 8)'
2121
# First version(0.37.0) is the common package, and the second version(0.38.0) is the service package

azure/multiapi/storage/v2017_04_17/common/_deserialization.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def _get_download_size(start_range, end_range, resource_size):
6565
'x-ms-blob-sequence-number': (None, 'page_blob_sequence_number', _int_to_str),
6666
'x-ms-blob-committed-block-count': (None, 'append_blob_committed_block_count', _int_to_str),
6767
'x-ms-access-tier': (None, 'blob_tier', _to_str),
68+
'x-ms-access-tier-change-time': (None, 'blob_tier_change_time', parser.parse),
6869
'x-ms-access-tier-inferred': (None, 'blob_tier_inferred', _bool),
6970
'x-ms-archive-status': (None, 'rehydration_status', _to_str),
7071
'x-ms-share-quota': (None, 'quota', _int_to_str),

azure/multiapi/storage/v2017_04_17/common/_encryption.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def _validate_and_unwrap_cek(encryption_data, key_encryption_key=None, key_resol
214214
instance variables for more details.
215215
:param func key_resolver:
216216
A function used that, given a key_id, will return a key_encryption_key. Please refer
217-
to high service object instance variables for more details.
217+
to high-level service object instance variables for more details.
218218
:return: the content_encryption_key stored in the encryption_data object.
219219
:rtype: bytes[]
220220
'''

azure/multiapi/storage/v2017_04_17/common/models.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,14 +124,18 @@ class RetryContext(object):
124124
The request sent to the storage service.
125125
:ivar ~azure.storage.common._http.HTTPResponse response:
126126
The response returned by the storage service.
127-
:ivar LocationMode location_mode:
127+
:ivar LocationMode location_mode:
128128
The location the request was sent to.
129+
:ivar Exception exception:
130+
The exception that just occurred. The type could either be AzureException (for HTTP errors),
131+
or other Exception types from lower layers, which are kept unwrapped for easier processing.
129132
'''
130133

131134
def __init__(self):
132135
self.request = None
133136
self.response = None
134137
self.location_mode = None
138+
self.exception = None
135139

136140

137141
class LocationMode(object):
@@ -593,10 +597,10 @@ def __init__(self, read=False, write=False, delete=False, list=False,
593597
self.process = process or ('p' in _str)
594598

595599
def __or__(self, other):
596-
return ResourceTypes(_str=str(self) + str(other))
600+
return AccountPermissions(_str=str(self) + str(other))
597601

598602
def __add__(self, other):
599-
return ResourceTypes(_str=str(self) + str(other))
603+
return AccountPermissions(_str=str(self) + str(other))
600604

601605
def __str__(self):
602606
return (('r' if self.read else '') +

0 commit comments

Comments
 (0)