Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion BreakingChanges.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> See the [Change Log](ChangeLog.md) for a summary of storage library changes.

**Note: This changelog is deprecated starting with version XX.XX.XX, please refer to the ChangeLog.md in each package for future change logs.**
**Note: This changelog is deprecated starting with version 0.37.0, please refer to the ChangeLog.md in each package for future change logs.**

## Version 0.37.0:

Expand Down
7 changes: 7 additions & 0 deletions azure-storage-blob/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

> See [BreakingChanges](BreakingChanges.md) for a detailed list of API breaks.

## Version 1.3.0:

- Support for 2018-03-28 REST version. Please see our REST API documentation and blog for information about the related added features.
- Added support for setting static website service properties.
- Added support for getting account information, such as SKU name and account kind.
- Added support for put block from URL(synchronously).

## Version 1.2.0rc1:

- Support for 2017-11-09 REST version. Please see our REST API documentation and blog for information about the related added features.
Expand Down
4 changes: 2 additions & 2 deletions azure-storage-blob/azure/storage/blob/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
# --------------------------------------------------------------------------

__author__ = 'Microsoft Corp. <ptvshelp@microsoft.com>'
__version__ = '1.2.0rc1'
__version__ = '1.3.0'

# x-ms-version for storage service.
X_MS_VERSION = '2017-11-09'
X_MS_VERSION = '2018-03-28'

# internal configurations, should not be changed
_LARGE_BLOB_UPLOAD_MAX_READ_BUFFER_SIZE = 4 * 1024 * 1024
9 changes: 9 additions & 0 deletions azure-storage-blob/azure/storage/blob/_deserialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
PageBlobProperties,
ResourceProperties,
BlobPrefix,
AccountInformation,
)
from ._encryption import _decrypt_blob
from azure.storage.common.models import _list
Expand Down Expand Up @@ -440,3 +441,11 @@ def _convert_xml_to_page_ranges(response):
)

return page_list


def _parse_account_information(response):
account_info = AccountInformation()
account_info.sku_name = response.headers['x-ms-sku-name']
account_info.account_kind = response.headers['x-ms-account-kind']

return account_info
64 changes: 56 additions & 8 deletions azure-storage-blob/azure/storage/blob/baseblobservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
_parse_lease,
_convert_xml_to_signed_identifiers_and_access,
_parse_base_properties,
_parse_account_information,
)
from ._download_chunking import _download_blob_chunks
from ._error import (
Expand All @@ -85,6 +86,10 @@
__version__ as package_version,
)

_CONTAINER_ALREADY_EXISTS_ERROR_CODE = 'ContainerAlreadyExists'
_BLOB_NOT_FOUND_ERROR_CODE = 'BlobNotFound'
_CONTAINER_NOT_FOUND_ERROR_CODE = 'ContainerNotFound'

if sys.version_info >= (3,):
from io import BytesIO
else:
Expand Down Expand Up @@ -622,7 +627,7 @@ def create_container(self, container_name, metadata=None,

if not fail_on_exist:
try:
self._perform_request(request)
self._perform_request(request, expected_errors=[_CONTAINER_ALREADY_EXISTS_ERROR_CODE])
return True
except AzureHttpError as ex:
_dont_fail_on_exist(ex)
Expand Down Expand Up @@ -873,7 +878,7 @@ def delete_container(self, container_name, fail_not_exist=False,

if not fail_not_exist:
try:
self._perform_request(request)
self._perform_request(request, expected_errors=[_CONTAINER_NOT_FOUND_ERROR_CODE])
return True
except AzureHttpError as ex:
_dont_fail_not_exist(ex)
Expand Down Expand Up @@ -1316,6 +1321,33 @@ def _list_blobs(self, container_name, prefix=None, marker=None,

return self._perform_request(request, _convert_xml_to_blob_list, operation_context=_context)

def get_blob_account_information(self, container_name=None, blob_name=None, timeout=None):
"""
Gets information related to the storage account.
The information can also be retrieved if the user has a SAS to a container or blob.

:param str container_name:
Name of existing container.
Optional, unless using a SAS token to a specific container or blob, in which case it's required.
:param str blob_name:
Name of existing blob.
Optional, unless using a SAS token to a specific blob, in which case it's required.
:param int timeout:
The timeout parameter is expressed in seconds.
:return: The :class:`~azure.storage.blob.models.AccountInformation`.
"""
request = HTTPRequest()
request.method = 'HEAD'
request.host_locations = self._get_host_locations(secondary=True)
request.path = _get_path(container_name, blob_name)
request.query = {
'restype': 'account',
'comp': 'properties',
'timeout': _int_to_str(timeout),
}

return self._perform_request(request, _parse_account_information)

def get_blob_service_stats(self, timeout=None):
'''
Retrieves statistics related to replication for the Blob service. It is
Expand Down Expand Up @@ -1354,7 +1386,7 @@ def get_blob_service_stats(self, timeout=None):

def set_blob_service_properties(
self, logging=None, hour_metrics=None, minute_metrics=None,
cors=None, target_version=None, timeout=None, delete_retention_policy=None):
cors=None, target_version=None, timeout=None, delete_retention_policy=None, static_website=None):
'''
Sets the properties of a storage account's Blob service, including
Azure Storage Analytics. If an element (ex Logging) is left as None, the
Expand Down Expand Up @@ -1389,6 +1421,11 @@ def set_blob_service_properties(
It also specifies the number of days and versions of blob to keep.
:type delete_retention_policy:
:class:`~azure.storage.common.models.DeleteRetentionPolicy`
:param static_website:
Specifies whether the static website feature is enabled,
and if yes, indicates the index document and 404 error document to use.
:type static_website:
:class:`~azure.storage.common.models.StaticWebsite`
'''
request = HTTPRequest()
request.method = 'PUT'
Expand All @@ -1401,7 +1438,7 @@ def set_blob_service_properties(
}
request.body = _get_request_body(
_convert_service_properties_to_xml(logging, hour_metrics, minute_metrics,
cors, target_version, delete_retention_policy))
cors, target_version, delete_retention_policy, static_website))

self._perform_request(request)

Expand Down Expand Up @@ -1575,10 +1612,21 @@ def exists(self, container_name, blob_name=None, snapshot=None, timeout=None):
'''
_validate_not_none('container_name', container_name)
try:
if blob_name is None:
self.get_container_properties(container_name, timeout=timeout)
else:
self.get_blob_properties(container_name, blob_name, snapshot=snapshot, timeout=timeout)
# make head request to see if container/blob/snapshot exists
request = HTTPRequest()
request.method = 'GET' if blob_name is None else 'HEAD'
request.host_locations = self._get_host_locations(secondary=True)
request.path = _get_path(container_name, blob_name)
request.query = {
'snapshot': _to_str(snapshot),
'timeout': _int_to_str(timeout),
'restype': 'container' if blob_name is None else None,
}

expected_errors = [_CONTAINER_NOT_FOUND_ERROR_CODE] if blob_name is None \
else [_CONTAINER_NOT_FOUND_ERROR_CODE, _BLOB_NOT_FOUND_ERROR_CODE]
self._perform_request(request, expected_errors=expected_errors)

return True
except AzureHttpError as ex:
_dont_fail_not_exist(ex)
Expand Down
59 changes: 57 additions & 2 deletions azure-storage-blob/azure/storage/blob/blockblobservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def put_block(self, container_name, blob_name, block, block_id,
:param str container_name:
Name of existing container.
:param str blob_name:
Name of existing blob.
Name of blob.
:param block: Content of the block.
:type block: io.IOBase or bytes
Content of the block.
Expand Down Expand Up @@ -315,6 +315,61 @@ def get_block_list(self, container_name, blob_name, snapshot=None,

return self._perform_request(request, _convert_xml_to_block_list)

def put_block_from_url(self, container_name, blob_name, copy_source_url, source_range_start, source_range_end,
block_id, source_content_md5=None, lease_id=None, timeout=None):
"""
Creates a new block to be committed as part of a blob.

:param str container_name:
Name of existing container.
:param str blob_name:
Name of blob.
:param str copy_source_url:
The URL of the source data. It can point to any Azure Blob or File, that is either public or has a
shared access signature attached.
:param int source_range_start:
This indicates the start of the range of bytes(inclusive) that has to be taken from the copy source.
:param int source_range_end:
This indicates the end of the range of bytes(inclusive) that has to be taken from the copy source.
:param str block_id:
A valid Base64 string value that identifies the block. Prior to
encoding, the string must be less than or equal to 64 bytes in size.
For a given blob, the length of the value specified for the blockid
parameter must be the same size for each block. Note that the Base64
string must be URL-encoded.
:param str source_content_md5:
If given, the service will calculate the MD5 hash of the block content and compare against this value.
:param str lease_id:
Required if the blob has an active lease.
:param int timeout:
The timeout parameter is expressed in seconds.
"""
_validate_encryption_unsupported(self.require_encryption, self.key_encryption_key)
_validate_not_none('container_name', container_name)
_validate_not_none('blob_name', blob_name)
_validate_not_none('copy_source_url', copy_source_url)
_validate_not_none('source_range_start', source_range_start)
_validate_not_none('source_range_end', source_range_end)
_validate_not_none('block_id', block_id)

request = HTTPRequest()
request.method = 'PUT'
request.host_locations = self._get_host_locations()
request.path = _get_path(container_name, blob_name)
request.query = {
'comp': 'block',
'blockid': _encode_base64(_to_str(block_id)),
'timeout': _int_to_str(timeout),
}
request.headers = {
'x-ms-lease-id': _to_str(lease_id),
'x-ms-copy-source': copy_source_url,
'x-ms-source-range': 'bytes=' + _to_str(source_range_start) + '-' + _to_str(source_range_end),
'x-ms-source-content-md5': source_content_md5,
}

self._perform_request(request)

# ----Convenience APIs-----------------------------------------------------

def create_blob_from_path(
Expand Down Expand Up @@ -791,7 +846,7 @@ def create_blob_from_text(
timeout=timeout)

def set_standard_blob_tier(
self, container_name, blob_name, standard_blob_tier, timeout=None):
self, container_name, blob_name, standard_blob_tier, timeout=None):
'''
Sets the block blob tiers on the blob. This API is only supported for block blobs on standard storage accounts.

Expand Down
16 changes: 16 additions & 0 deletions azure-storage-blob/azure/storage/blob/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,3 +762,19 @@ class StandardBlobTier(object):

Hot = 'Hot'
''' Hot '''


class AccountInformation(object):
"""
Holds information related to the storage account.

:ivar str sku_name:
Name of the storage SKU, also known as account type.
Example: Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS, Premium_ZRS
:ivar str account_kind:
Describes the flavour of the storage account, also known as account kind.
Example: Storage, StorageV2, BlobStorage
"""
def __init__(self):
self.sku_name = None
self.account_kind = None
4 changes: 2 additions & 2 deletions azure-storage-blob/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

setup(
name='azure-storage-blob',
version='1.2.0rc1',
version='1.3.0',
description='Microsoft Azure Storage Blob Client Library for Python',
long_description=open('README.rst', 'r').read(),
license='MIT License',
Expand All @@ -74,7 +74,7 @@
packages=find_packages(),
install_requires=[
'azure-common>=1.1.5',
'azure-storage-common>=1.2.0rc1,<1.3.0'
'azure-storage-common>=1.3.0,<1.4.0'
],
extras_require={
":python_version<'3.0'": ['futures'],
Expand Down
4 changes: 4 additions & 0 deletions azure-storage-common/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

> See [BreakingChanges](BreakingChanges.md) for a detailed list of API breaks.

## Version 1.3.0:

- Support for 2018-03-28 REST version. Please see our REST API documentation and blog for information about the related added features.

## Version 1.2.0rc1:

- Increased default socket timeout to a more reasonable number for Python 3.5+.
Expand Down
1 change: 1 addition & 0 deletions azure-storage-common/azure/storage/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Metrics,
CorsRule,
DeleteRetentionPolicy,
StaticWebsite,
ServiceProperties,
AccessPolicy,
ResourceTypes,
Expand Down
4 changes: 2 additions & 2 deletions azure-storage-common/azure/storage/common/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import sys

__author__ = 'Microsoft Corp. <ptvshelp@microsoft.com>'
__version__ = '1.2.0rc1'
__version__ = '1.3.0'

# UserAgent string sample: 'Azure-Storage/0.37.0-0.38.0 (Python CPython 3.4.2; Windows 8)'
# First version(0.37.0) is the common package, and the second version(0.38.0) is the service package
Expand All @@ -17,7 +17,7 @@
platform.release())

# default values for common package, in case it is used directly
DEFAULT_X_MS_VERSION = '2017-11-09'
DEFAULT_X_MS_VERSION = '2018-03-28'
DEFAULT_USER_AGENT_STRING = '{}None {}'.format(USER_AGENT_STRING_PREFIX, USER_AGENT_STRING_SUFFIX)

# Live ServiceClient URLs
Expand Down
20 changes: 20 additions & 0 deletions azure-storage-common/azure/storage/common/_deserialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
GeoReplication,
ServiceStats,
DeleteRetentionPolicy,
StaticWebsite,
)


Expand Down Expand Up @@ -253,6 +254,11 @@ def _convert_xml_to_service_properties(response):
<Enabled>true|false</Enabled>
<Days>number-of-days</Days>
</DeleteRetentionPolicy>
<StaticWebsite>
<Enabled>true|false</Enabled>
<IndexDocument></IndexDocument>
<ErrorDocument404Path></ErrorDocument404Path>
</StaticWebsite>
</StorageServiceProperties>
'''
if response is None or response.body is None:
Expand Down Expand Up @@ -322,6 +328,20 @@ def _convert_xml_to_service_properties(response):
if policy_enabled:
service_properties.delete_retention_policy.days = int(delete_retention_policy_element.find('Days').text)

# StaticWebsite
static_website_element = service_properties_element.find('StaticWebsite')
if static_website_element is not None:
service_properties.static_website = StaticWebsite()
service_properties.static_website.enabled = _bool(static_website_element.find('Enabled').text)

index_document_element = static_website_element.find('IndexDocument')
if index_document_element is not None:
service_properties.static_website.index_document = index_document_element.text

error_document_element = static_website_element.find('ErrorDocument404Path')
if error_document_element is not None:
service_properties.static_website.error_document_404_path = error_document_element.text

return service_properties


Expand Down
Loading