Skip to content

Commit b8c5c9a

Browse files
authored
Merge pull request #592 from Azure/dev
Release 2.0.0 for Blob, File, and Queue
2 parents 5232735 + a58acd9 commit b8c5c9a

File tree

646 files changed

+36853
-25236
lines changed

Some content is hidden

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

646 files changed

+36853
-25236
lines changed

azure-storage-blob/ChangeLog.md

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

33
> See [BreakingChanges](BreakingChanges.md) for a detailed list of API breaks.
44
5+
## Version 2.0.0:
6+
- Support for 2018-11-09 REST version. Please see our REST API documentation and blog for information about the related added features.
7+
- Added support for append block from URL(synchronously) for append blobs.
8+
- Added support for update page from URL(synchronously) for page blobs.
9+
- Added support for generating and using blob snapshot SAS tokens.
10+
- Added support for generating user delegation SAS tokens.
11+
512
## Version 1.5.0:
613

714
- Added new method list_blob_names to efficiently list only blob names in an efficient way.

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__ = '1.5.0'
8+
__version__ = '2.0.0'
99

1010
# x-ms-version for storage service.
11-
X_MS_VERSION = '2018-03-28'
11+
X_MS_VERSION = '2018-11-09'
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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
ResourceProperties,
3737
BlobPrefix,
3838
AccountInformation,
39+
UserDelegationKey,
3940
)
4041
from ._encryption import _decrypt_blob
4142
from azure.storage.common.models import _list
@@ -520,3 +521,36 @@ def _parse_account_information(response):
520521
account_info.account_kind = response.headers['x-ms-account-kind']
521522

522523
return account_info
524+
525+
526+
def _convert_xml_to_user_delegation_key(response):
527+
"""
528+
<?xml version="1.0" encoding="utf-8"?>
529+
<UserDelegationKey>
530+
<SignedOid> Guid </SignedOid>
531+
<SignedTid> Guid </SignedTid>
532+
<SignedStart> String, formatted ISO Date </SignedStart>
533+
<SignedExpiry> String, formatted ISO Date </SignedExpiry>
534+
<SignedService>b</SignedService>
535+
<SignedVersion> String, rest api version used to create delegation key </SignedVersion>
536+
<Value>Ovg+o0K/0/2V8upg7AwlyAPCriEcOSXKuBu2Gv/PU70Y7aWDW3C2ZRmw6kYWqPWBaM1GosLkcSZkgsobAlT+Sw==</value>
537+
</UserDelegationKey >
538+
539+
Converts xml response to UserDelegationKey class.
540+
"""
541+
542+
if response is None or response.body is None:
543+
return None
544+
545+
delegation_key = UserDelegationKey()
546+
547+
key_element = ETree.fromstring(response.body)
548+
delegation_key.signed_oid = key_element.findtext('SignedOid')
549+
delegation_key.signed_tid = key_element.findtext('SignedTid')
550+
delegation_key.signed_start = key_element.findtext('SignedStart')
551+
delegation_key.signed_expiry = key_element.findtext('SignedExpiry')
552+
delegation_key.signed_service = key_element.findtext('SignedService')
553+
delegation_key.signed_version = key_element.findtext('SignedVersion')
554+
delegation_key.value = key_element.findtext('Value')
555+
556+
return delegation_key

azure-storage-blob/azure/storage/blob/_serialization.py

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# license information.
55
# --------------------------------------------------------------------------
66
from xml.sax.saxutils import escape as xml_escape
7-
7+
from datetime import date
88
try:
99
from xml.etree import cElementTree as ETree
1010
except ImportError:
@@ -13,6 +13,9 @@
1313
_encode_base64,
1414
_str,
1515
)
16+
from azure.storage.common._serialization import (
17+
_to_utc_datetime,
18+
)
1619
from azure.storage.common._error import (
1720
_validate_not_none,
1821
_ERROR_START_END_NEEDED_FOR_MD5,
@@ -46,7 +49,8 @@ def _get_path(container_name=None, blob_name=None):
4649

4750

4851
def _validate_and_format_range_headers(request, start_range, end_range, start_range_required=True,
49-
end_range_required=True, check_content_md5=False, align_to_page=False):
52+
end_range_required=True, check_content_md5=False, align_to_page=False,
53+
range_header_name='x-ms-range'):
5054
# If end range is provided, start range must be provided
5155
if start_range_required or end_range is not None:
5256
_validate_not_none('start_range', start_range)
@@ -63,9 +67,9 @@ def _validate_and_format_range_headers(request, start_range, end_range, start_ra
6367
# Format based on whether end_range is present
6468
request.headers = request.headers or {}
6569
if end_range is not None:
66-
request.headers['x-ms-range'] = 'bytes={0}-{1}'.format(start_range, end_range)
70+
request.headers[range_header_name] = 'bytes={0}-{1}'.format(start_range, end_range)
6771
elif start_range is not None:
68-
request.headers['x-ms-range'] = "bytes={0}-".format(start_range)
72+
request.headers[range_header_name] = "bytes={0}-".format(start_range)
6973

7074
# Content MD5 can only be provided for a complete range less than 4MB in size
7175
if check_content_md5:
@@ -116,3 +120,34 @@ def _convert_block_list_to_xml(block_id_list):
116120

117121
# return xml value
118122
return output
123+
124+
125+
def _convert_delegation_key_info_to_xml(start_time, expiry_time):
126+
"""
127+
<?xml version="1.0" encoding="utf-8"?>
128+
<KeyInfo>
129+
<Start> String, formatted ISO Date </Start>
130+
<Expiry> String, formatted ISO Date </Expiry>
131+
</KeyInfo>
132+
133+
Convert key info to xml to send.
134+
"""
135+
if start_time is None or expiry_time is None:
136+
raise ValueError("delegation key start/end times are required")
137+
138+
key_info_element = ETree.Element('KeyInfo')
139+
ETree.SubElement(key_info_element, 'Start').text = \
140+
_to_utc_datetime(start_time) if isinstance(start_time, date) else start_time
141+
ETree.SubElement(key_info_element, 'Expiry').text = \
142+
_to_utc_datetime(expiry_time) if isinstance(expiry_time, date) else expiry_time
143+
144+
# Add xml declaration and serialize
145+
try:
146+
stream = BytesIO()
147+
ETree.ElementTree(key_info_element).write(stream, xml_declaration=True, encoding='utf-8', method='xml')
148+
finally:
149+
output = stream.getvalue()
150+
stream.close()
151+
152+
# return xml value
153+
return output

azure-storage-blob/azure/storage/blob/appendblobservice.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
)
3434
from ._serialization import (
3535
_get_path,
36+
_validate_and_format_range_headers,
3637
)
3738
from ._upload_chunking import (
3839
_AppendBlobChunkUploader,
@@ -286,6 +287,125 @@ def append_block(self, container_name, blob_name, block,
286287

287288
return self._perform_request(request, _parse_append_block)
288289

290+
def append_block_from_url(self, container_name, blob_name, copy_source_url, source_range_start=None,
291+
source_range_end=None, source_content_md5=None, source_if_modified_since=None,
292+
source_if_unmodified_since=None, source_if_match=None,
293+
source_if_none_match=None, maxsize_condition=None,
294+
appendpos_condition=None, lease_id=None, if_modified_since=None,
295+
if_unmodified_since=None, if_match=None,
296+
if_none_match=None, timeout=None):
297+
"""
298+
Creates a new block to be committed as part of a blob, where the contents are read from a source url.
299+
300+
:param str container_name:
301+
Name of existing container.
302+
:param str blob_name:
303+
Name of blob.
304+
:param str copy_source_url:
305+
The URL of the source data. It can point to any Azure Blob or File, that is either public or has a
306+
shared access signature attached.
307+
:param int source_range_start:
308+
This indicates the start of the range of bytes(inclusive) that has to be taken from the copy source.
309+
:param int source_range_end:
310+
This indicates the end of the range of bytes(inclusive) that has to be taken from the copy source.
311+
:param str source_content_md5:
312+
If given, the service will calculate the MD5 hash of the block content and compare against this value.
313+
:param datetime source_if_modified_since:
314+
A DateTime value. Azure expects the date value passed in to be UTC.
315+
If timezone is included, any non-UTC datetimes will be converted to UTC.
316+
If a date is passed in without timezone info, it is assumed to be UTC.
317+
Specify this header to perform the operation only
318+
if the source resource has been modified since the specified time.
319+
:param datetime source_if_unmodified_since:
320+
A DateTime value. Azure expects the date value passed in to be UTC.
321+
If timezone is included, any non-UTC datetimes will be converted to UTC.
322+
If a date is passed in without timezone info, it is assumed to be UTC.
323+
Specify this header to perform the operation only if
324+
the source resource has not been modified since the specified date/time.
325+
:param str source_if_match:
326+
An ETag value, or the wildcard character (*). Specify this header to perform
327+
the operation only if the source resource's ETag matches the value specified.
328+
:param str source_if_none_match:
329+
An ETag value, or the wildcard character (*). Specify this header
330+
to perform the operation only if the source resource's ETag does not match
331+
the value specified. Specify the wildcard character (*) to perform
332+
the operation only if the source resource does not exist, and fail the
333+
operation if it does exist.
334+
:param int maxsize_condition:
335+
Optional conditional header. The max length in bytes permitted for
336+
the append blob. If the Append Block operation would cause the blob
337+
to exceed that limit or if the blob size is already greater than the
338+
value specified in this header, the request will fail with
339+
MaxBlobSizeConditionNotMet error (HTTP status code 412 - Precondition Failed).
340+
:param int appendpos_condition:
341+
Optional conditional header, used only for the Append Block operation.
342+
A number indicating the byte offset to compare. Append Block will
343+
succeed only if the append position is equal to this number. If it
344+
is not, the request will fail with the
345+
AppendPositionConditionNotMet error
346+
(HTTP status code 412 - Precondition Failed).
347+
:param str lease_id:
348+
Required if the blob has an active lease.
349+
:param datetime if_modified_since:
350+
A DateTime value. Azure expects the date value passed in to be UTC.
351+
If timezone is included, any non-UTC datetimes will be converted to UTC.
352+
If a date is passed in without timezone info, it is assumed to be UTC.
353+
Specify this header to perform the operation only
354+
if the resource has been modified since the specified time.
355+
:param datetime if_unmodified_since:
356+
A DateTime value. Azure expects the date value passed in to be UTC.
357+
If timezone is included, any non-UTC datetimes will be converted to UTC.
358+
If a date is passed in without timezone info, it is assumed to be UTC.
359+
Specify this header to perform the operation only if
360+
the resource has not been modified since the specified date/time.
361+
:param str if_match:
362+
An ETag value, or the wildcard character (*). Specify this header to perform
363+
the operation only if the resource's ETag matches the value specified.
364+
:param str if_none_match:
365+
An ETag value, or the wildcard character (*). Specify this header
366+
to perform the operation only if the resource's ETag does not match
367+
the value specified. Specify the wildcard character (*) to perform
368+
the operation only if the resource does not exist, and fail the
369+
operation if it does exist.
370+
:param int timeout:
371+
The timeout parameter is expressed in seconds.
372+
"""
373+
_validate_encryption_unsupported(self.require_encryption, self.key_encryption_key)
374+
_validate_not_none('container_name', container_name)
375+
_validate_not_none('blob_name', blob_name)
376+
_validate_not_none('copy_source_url', copy_source_url)
377+
378+
request = HTTPRequest()
379+
request.method = 'PUT'
380+
request.host_locations = self._get_host_locations()
381+
request.path = _get_path(container_name, blob_name)
382+
request.query = {
383+
'comp': 'appendblock',
384+
'timeout': _int_to_str(timeout),
385+
}
386+
request.headers = {
387+
'x-ms-copy-source': copy_source_url,
388+
'x-ms-source-content-md5': source_content_md5,
389+
'x-ms-source-if-Modified-Since': _datetime_to_utc_string(source_if_modified_since),
390+
'x-ms-source-if-Unmodified-Since': _datetime_to_utc_string(source_if_unmodified_since),
391+
'x-ms-source-if-Match': _to_str(source_if_match),
392+
'x-ms-source-if-None-Match': _to_str(source_if_none_match),
393+
'x-ms-blob-condition-maxsize': _to_str(maxsize_condition),
394+
'x-ms-blob-condition-appendpos': _to_str(appendpos_condition),
395+
'x-ms-lease-id': _to_str(lease_id),
396+
'If-Modified-Since': _datetime_to_utc_string(if_modified_since),
397+
'If-Unmodified-Since': _datetime_to_utc_string(if_unmodified_since),
398+
'If-Match': _to_str(if_match),
399+
'If-None-Match': _to_str(if_none_match)
400+
}
401+
402+
_validate_and_format_range_headers(request, source_range_start, source_range_end,
403+
start_range_required=False,
404+
end_range_required=False,
405+
range_header_name="x-ms-source-range")
406+
407+
return self._perform_request(request, _parse_append_block)
408+
289409
# ----Convenience APIs----------------------------------------------
290410

291411
def append_blob_from_path(

0 commit comments

Comments
 (0)