Skip to content

Commit 4e03ff8

Browse files
authored
Merge pull request #624 from Azure/dev
2.1.0 Release for Blob/File/Queue/Common
2 parents 85e1de3 + e355944 commit 4e03ff8

File tree

109 files changed

+28980
-445
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+28980
-445
lines changed

azure-storage-blob/ChangeLog.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@
22

33
> See [BreakingChanges](BreakingChanges.md) for a detailed list of API breaks.
44
5+
## Version 2.1.0:
6+
7+
- Support for 2019-02-02 REST version. Please see our REST API documentation and blog for information about the related added features.
8+
- Added Batch Delete Blob API.
9+
- Added Batch Set Standard Blob Tier API(for BlockBlob).
10+
- Added Blob Tier support for PutBlob/PutBlockList/CopyBlob APIs.
11+
- Added support for client provided encryption key to numerous APIs.
12+
513
## Version 2.0.1:
14+
615
- Updated dependency on azure-storage-common.
716

817
## Version 2.0.0:

azure-storage-blob/azure/storage/blob/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,11 @@
2727
PublicAccess,
2828
BlobPrefix,
2929
DeleteSnapshot,
30+
BatchDeleteSubRequest,
31+
BatchSetBlobTierSubRequest,
32+
BatchSubResponse,
33+
CustomerProvidedEncryptionKey,
34+
RehydratePriority,
3035
)
3136
from .pageblobservice import PageBlobService
37+
from ._constants import __version__

azure-storage-blob/azure/storage/blob/_constants.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
# --------------------------------------------------------------------------
66

77
__author__ = 'Microsoft Corp. <[email protected]>'
8-
__version__ = '2.0.1'
8+
__version__ = '2.1.0'
99

1010
# x-ms-version for storage service.
11-
X_MS_VERSION = '2018-11-09'
11+
X_MS_VERSION = '2019-02-02'
1212

1313
# internal configurations, should not be changed
1414
_LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE = 4 * 1024 * 1024

azure-storage-blob/azure/storage/blob/_deserialization.py

Lines changed: 111 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from azure.common import AzureException
77
from dateutil import parser
88

9+
from azure.storage.common._http import HTTPResponse
10+
911
try:
1012
from xml.etree import cElementTree as ETree
1113
except ImportError:
@@ -36,14 +38,23 @@
3638
ResourceProperties,
3739
BlobPrefix,
3840
AccountInformation,
39-
UserDelegationKey,
40-
)
41+
UserDelegationKey, BatchSubResponse)
4142
from ._encryption import _decrypt_blob
4243
from azure.storage.common.models import _list
4344
from azure.storage.common._error import (
4445
_validate_content_match,
4546
_ERROR_DECRYPTION_FAILURE,
4647
)
48+
from io import BytesIO
49+
50+
_HTTP_LINE_ENDING = "\r\n"
51+
52+
def _parse_cpk_headers(response, properties):
53+
server_encrypted = response.headers.get('x-ms-request-server-encrypted')
54+
if server_encrypted is not None:
55+
properties.request_server_encrypted = _bool(server_encrypted)
56+
57+
properties.encryption_key_sha256 = response.headers.get('x-ms-encryption-key-sha256')
4758

4859

4960
def _parse_base_properties(response):
@@ -53,6 +64,7 @@ def _parse_base_properties(response):
5364
resource_properties = ResourceProperties()
5465
resource_properties.last_modified = parser.parse(response.headers.get('last-modified'))
5566
resource_properties.etag = response.headers.get('etag')
67+
_parse_cpk_headers(response, resource_properties)
5668

5769
return resource_properties
5870

@@ -65,6 +77,7 @@ def _parse_page_properties(response):
6577
put_page.last_modified = parser.parse(response.headers.get('last-modified'))
6678
put_page.etag = response.headers.get('etag')
6779
put_page.sequence_number = _to_int(response.headers.get('x-ms-blob-sequence-number'))
80+
_parse_cpk_headers(response, put_page)
6881

6982
return put_page
7083

@@ -78,6 +91,7 @@ def _parse_append_block(response):
7891
append_block.etag = response.headers.get('etag')
7992
append_block.append_offset = _to_int(response.headers.get('x-ms-blob-append-offset'))
8093
append_block.committed_block_count = _to_int(response.headers.get('x-ms-blob-committed-block-count'))
94+
_parse_cpk_headers(response, append_block)
8195

8296
return append_block
8397

@@ -290,7 +304,7 @@ def _convert_xml_to_blob_list(response):
290304
<RemainingRetentionDays>int</RemainingRetentionDays>
291305
<Creation-Time>date-time-value</Creation-Time>
292306
</Properties>
293-
<Metadata>
307+
<Metadata>
294308
<Name>value</Name>
295309
</Metadata>
296310
</Blob>
@@ -392,7 +406,7 @@ def _convert_xml_to_blob_name_list(response):
392406
<RemainingRetentionDays>int</RemainingRetentionDays>
393407
<Creation-Time>date-time-value</Creation-Time>
394408
</Properties>
395-
<Metadata>
409+
<Metadata>
396410
<Name>value</Name>
397411
</Metadata>
398412
</Blob>
@@ -475,19 +489,19 @@ def _convert_xml_to_page_ranges(response):
475489
'''
476490
<?xml version="1.0" encoding="utf-8"?>
477491
<PageList>
478-
<PageRange>
479-
<Start>Start Byte</Start>
480-
<End>End Byte</End>
481-
</PageRange>
482-
<ClearRange>
483-
<Start>Start Byte</Start>
484-
<End>End Byte</End>
485-
</ClearRange>
486-
<PageRange>
487-
<Start>Start Byte</Start>
488-
<End>End Byte</End>
489-
</PageRange>
490-
</PageList>
492+
<PageRange>
493+
<Start>Start Byte</Start>
494+
<End>End Byte</End>
495+
</PageRange>
496+
<ClearRange>
497+
<Start>Start Byte</Start>
498+
<End>End Byte</End>
499+
</ClearRange>
500+
<PageRange>
501+
<Start>Start Byte</Start>
502+
<End>End Byte</End>
503+
</PageRange>
504+
</PageList>
491505
'''
492506
if response is None or response.body is None:
493507
return None
@@ -554,3 +568,83 @@ def _convert_xml_to_user_delegation_key(response):
554568
delegation_key.value = key_element.findtext('Value')
555569

556570
return delegation_key
571+
572+
573+
def _ingest_batch_response(batch_response, batch_sub_requests):
574+
"""
575+
Takes the response to a batch request and parses the response into the separate responses.
576+
577+
:param :class:`~azure.storage.common._http.HTTPResponse` batch_response:
578+
batchResponse The response of the HTTP batch request generated by this object.
579+
:return: sub-responses parsed from batch HTTP response
580+
:rtype: list of :class:`~azure.storage.common._http.HTTPResponse`
581+
"""
582+
parsed_batch_sub_response_list = []
583+
584+
# header value format: `multipart/mixed; boundary=<delimiter>`
585+
response_delimiter = batch_response.headers.get('content-type').split("=")[1]
586+
587+
response_body = batch_response.body.decode('utf-8')
588+
589+
# split byte[] on the "substring" "--<delim>\r\n"
590+
sub_response_list = response_body.split("--" + response_delimiter + _HTTP_LINE_ENDING)
591+
592+
# strip final, slightly different delim "\r\n--<delim>--" off last entry
593+
sub_response_list[len(sub_response_list) - 1] = \
594+
sub_response_list[len(sub_response_list) - 1].split(_HTTP_LINE_ENDING + "--" + response_delimiter + "--")[0]
595+
596+
for sub_response in sub_response_list:
597+
if len(sub_response) != 0:
598+
http_response = _parse_sub_response_to_http_response(sub_response)
599+
is_successful = 200 <= http_response.status < 300
600+
index_of_sub_request = _to_int(http_response.headers.get('Content-ID'))
601+
batch_sub_request = batch_sub_requests[index_of_sub_request]
602+
603+
parsed_batch_sub_response_list.append(BatchSubResponse(is_successful, http_response, batch_sub_request))
604+
605+
return parsed_batch_sub_response_list
606+
607+
608+
def _parse_sub_response_to_http_response(sub_response):
609+
"""
610+
Header: Value (1 or more times)
611+
612+
HTTP/<version> <statusCode> <statusName>
613+
Header: Value (1 or more times)
614+
615+
body (if any)
616+
617+
:param sub_response:
618+
The raw bytes of this sub-response.
619+
:return: An HttpResponse object.
620+
"""
621+
622+
empty_line = _HTTP_LINE_ENDING.encode('utf-8')
623+
num_empty_lines = 0
624+
batch_http_sub_response = HTTPResponse(None, '', dict(), b'')
625+
try:
626+
body_stream = BytesIO()
627+
body_stream.write(sub_response.encode('utf-8'))
628+
body_stream.seek(0)
629+
630+
while True:
631+
line = body_stream.readline()
632+
if line == b'':
633+
return batch_http_sub_response
634+
635+
if line.startswith("HTTP".encode('utf-8')):
636+
batch_http_sub_response.status = _to_int(line.decode('utf-8').split(" ")[1])
637+
elif line == empty_line:
638+
num_empty_lines += 1
639+
elif line.startswith("x-ms-error-code".encode('utf-8')):
640+
batch_http_sub_response.message = line.decode('utf-8').split(": ")[1].rstrip()
641+
elif num_empty_lines is 2:
642+
batch_http_sub_response.body += line
643+
else:
644+
header = line.decode('utf-8').split(": ")[0]
645+
value = line.decode('utf-8').split(": ")[1].rstrip()
646+
batch_http_sub_response.headers[header] = value
647+
finally:
648+
body_stream.close()
649+
650+
return batch_http_sub_response

azure-storage-blob/azure/storage/blob/_download_chunking.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def _download_blob_chunks(blob_service, container_name, blob_name, snapshot,
1010
download_size, block_size, progress, start_range, end_range,
1111
stream, max_connections, progress_callback, validate_content,
1212
lease_id, if_modified_since, if_unmodified_since, if_match,
13-
if_none_match, timeout, operation_context):
13+
if_none_match, timeout, operation_context, cpk):
1414

1515
downloader_class = _ParallelBlobChunkDownloader if max_connections > 1 else _SequentialBlobChunkDownloader
1616

@@ -34,6 +34,7 @@ def _download_blob_chunks(blob_service, container_name, blob_name, snapshot,
3434
if_none_match,
3535
timeout,
3636
operation_context,
37+
cpk,
3738
)
3839

3940
if max_connections > 1:
@@ -49,7 +50,7 @@ class _BlobChunkDownloader(object):
4950
def __init__(self, blob_service, container_name, blob_name, snapshot, download_size,
5051
chunk_size, progress, start_range, end_range, stream,
5152
progress_callback, validate_content, lease_id, if_modified_since,
52-
if_unmodified_since, if_match, if_none_match, timeout, operation_context):
53+
if_unmodified_since, if_match, if_none_match, timeout, operation_context, cpk):
5354
# identifiers for the blob
5455
self.blob_service = blob_service
5556
self.container_name = container_name
@@ -78,6 +79,7 @@ def __init__(self, blob_service, container_name, blob_name, snapshot, download_s
7879
self.if_unmodified_since = if_unmodified_since
7980
self.if_match = if_match
8081
self.if_none_match = if_none_match
82+
self.cpk = cpk
8183

8284
def get_chunk_offsets(self):
8385
index = self.start_index
@@ -119,7 +121,8 @@ def _download_chunk(self, chunk_start, chunk_end):
119121
if_match=self.if_match,
120122
if_none_match=self.if_none_match,
121123
timeout=self.timeout,
122-
_context=self.operation_context
124+
_context=self.operation_context,
125+
cpk=self.cpk,
123126
)
124127

125128
# This makes sure that if_match is set so that we can validate
@@ -132,15 +135,15 @@ class _ParallelBlobChunkDownloader(_BlobChunkDownloader):
132135
def __init__(self, blob_service, container_name, blob_name, snapshot, download_size,
133136
chunk_size, progress, start_range, end_range, stream,
134137
progress_callback, validate_content, lease_id, if_modified_since,
135-
if_unmodified_since, if_match, if_none_match, timeout, operation_context):
138+
if_unmodified_since, if_match, if_none_match, timeout, operation_context, cpk):
136139

137140
super(_ParallelBlobChunkDownloader, self).__init__(blob_service, container_name, blob_name, snapshot,
138141
download_size,
139142
chunk_size, progress, start_range, end_range, stream,
140143
progress_callback, validate_content, lease_id,
141144
if_modified_since,
142145
if_unmodified_since, if_match, if_none_match, timeout,
143-
operation_context)
146+
operation_context, cpk)
144147

145148
# for a parallel download, the stream is always seekable, so we note down the current position
146149
# in order to seek to the right place when out-of-order chunks come in

0 commit comments

Comments
 (0)