Hello community,
here is the log from the commit of package python-azure-storage-file for
openSUSE:Factory checked in at 2019-10-10 14:32:01
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-azure-storage-file (Old)
and /work/SRC/openSUSE:Factory/.python-azure-storage-file.new.2352 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-azure-storage-file"
Thu Oct 10 14:32:01 2019 rev:3 rq:735762 version:2.1.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-azure-storage-file/python-azure-storage-file.changes
2019-05-14 13:38:56.520427135 +0200
+++
/work/SRC/openSUSE:Factory/.python-azure-storage-file.new.2352/python-azure-storage-file.changes
2019-10-10 14:32:03.796385782 +0200
@@ -1,0 +2,13 @@
+Fri Oct 4 12:21:54 UTC 2019 - John Paul Adrian Glaubitz
<[email protected]>
+
+- New upstream release
+ + Version 2.1.0
+ + For detailed information about changes see the
+ HISTORY.txt file provided with this package
+- Drop patches to support older versions of setuptools as
+ SLE-12 is now shipping with a recent enough version
+ + asf_drop-compatible-releases-operator.patch
+ + asf_drop-extras-require.patch
+- Update Requires from setup.py
+
+-------------------------------------------------------------------
Old:
----
asf_drop-compatible-releases-operator.patch
asf_drop-extras-require.patch
azure-storage-file-1.4.0.tar.gz
New:
----
azure-storage-file-2.1.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-azure-storage-file.spec ++++++
--- /var/tmp/diff_new_pack.SbrCS8/_old 2019-10-10 14:32:04.640383814 +0200
+++ /var/tmp/diff_new_pack.SbrCS8/_new 2019-10-10 14:32:04.668383749 +0200
@@ -18,7 +18,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-azure-storage-file
-Version: 1.4.0
+Version: 2.1.0
Release: 0
Summary: Microsoft Azure Storage File Client Library for Python
License: MIT
@@ -26,8 +26,6 @@
Url: https://github.com/Azure/azure-sdk-for-python
Source:
https://files.pythonhosted.org/packages/source/a/azure-storage-file/azure-storage-file-%{version}.tar.gz
Source1: LICENSE.txt
-Patch1: asf_drop-compatible-releases-operator.patch
-Patch2: asf_drop-extras-require.patch
BuildRequires: %{python_module azure-nspkg >= 3.0.0}
BuildRequires: %{python_module azure-storage-nspkg >= 3.0.0}
BuildRequires: %{python_module setuptools}
@@ -35,8 +33,8 @@
BuildRequires: python-rpm-macros
Requires: python-azure-common >= 1.1.5
Requires: python-azure-nspkg >= 3.0.0
-Requires: python-azure-storage-common < 2.0.0
-Requires: python-azure-storage-common >= 1.4
+Requires: python-azure-storage-common < 3.0.0
+Requires: python-azure-storage-common >= 2.1.0
Requires: python-azure-storage-nspkg >= 3.0.0
Conflicts: python-azure-sdk <= 2.0.0
Conflicts: python-azure-storage <= 0.36.0
@@ -53,8 +51,6 @@
%prep
%setup -q -n azure-storage-file-%{version}
-%patch1 -p1
-%patch2 -p1
%build
install -m 644 %{SOURCE1} %{_builddir}/azure-storage-file-%{version}
++++++ azure-storage-file-1.4.0.tar.gz -> azure-storage-file-2.1.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure-storage-file-1.4.0/PKG-INFO
new/azure-storage-file-2.1.0/PKG-INFO
--- old/azure-storage-file-1.4.0/PKG-INFO 2018-11-10 03:01:24.000000000
+0100
+++ new/azure-storage-file-2.1.0/PKG-INFO 2019-08-02 06:12:46.000000000
+0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: azure-storage-file
-Version: 1.4.0
+Version: 2.1.0
Summary: Microsoft Azure Storage File Client Library for Python
Home-page: https://github.com/Azure/azure-storage-python
Author: Microsoft Corporation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure-storage-file-1.4.0/azure/storage/file/__init__.py
new/azure-storage-file-2.1.0/azure/storage/file/__init__.py
--- old/azure-storage-file-1.4.0/azure/storage/file/__init__.py 2018-09-10
23:24:14.000000000 +0200
+++ new/azure-storage-file-2.1.0/azure/storage/file/__init__.py 2019-08-01
23:45:21.000000000 +0200
@@ -17,4 +17,7 @@
SharePermissions,
FilePermissions,
DeleteSnapshot,
+ SMBProperties,
+ NTFSAttributes,
)
+from ._constants import __version__
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure-storage-file-1.4.0/azure/storage/file/_constants.py
new/azure-storage-file-2.1.0/azure/storage/file/_constants.py
--- old/azure-storage-file-1.4.0/azure/storage/file/_constants.py
2018-11-10 00:24:52.000000000 +0100
+++ new/azure-storage-file-2.1.0/azure/storage/file/_constants.py
2019-08-02 06:03:05.000000000 +0200
@@ -5,7 +5,7 @@
# --------------------------------------------------------------------------
__author__ = 'Microsoft Corp. <[email protected]>'
-__version__ = '1.4.0'
+__version__ = '2.1.0'
# x-ms-version for storage service.
-X_MS_VERSION = '2018-03-28'
+X_MS_VERSION = '2019-02-02'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure-storage-file-1.4.0/azure/storage/file/_deserialization.py
new/azure-storage-file-2.1.0/azure/storage/file/_deserialization.py
--- old/azure-storage-file-1.4.0/azure/storage/file/_deserialization.py
2018-09-10 23:24:14.000000000 +0200
+++ new/azure-storage-file-2.1.0/azure/storage/file/_deserialization.py
2019-07-29 21:41:49.000000000 +0200
@@ -13,6 +13,7 @@
Share,
Directory,
File,
+ Handle,
FileProperties,
FileRange,
ShareProperties,
@@ -31,6 +32,7 @@
_to_str,
)
+
def _parse_snapshot_share(response, name):
'''
Extracts snapshot return header.
@@ -39,6 +41,7 @@
return _parse_share(response, name, snapshot)
+
def _parse_share(response, name, snapshot=None):
if response is None:
return None
@@ -57,6 +60,24 @@
return Directory(name, props, metadata)
+def _parse_permission_key(response):
+ '''
+ Extracts out file permission key
+ '''
+
+ if response is None or response.headers is None:
+ return None
+ return response.headers.get('x-ms-file-permission-key', None)
+
+
+def _parse_permission(response):
+ '''
+ Extracts out file permission
+ '''
+
+ return response.body
+
+
def _parse_file(response, name, validate_content=False):
if response is None:
return None
@@ -197,6 +218,70 @@
return entries
+def _convert_xml_to_handles(response):
+ """
+ <?xml version="1.0" encoding="utf-8"?>
+ <EnumerationResults>
+ <Entries>
+ <Handle>
+ <HandleId>21123954401</HandleId>
+ <Path />
+ <FileId>0</FileId>
+ <ParentId>0</ParentId>
+ <SessionId>9385737614310506553</SessionId>
+ <ClientIp>167.220.2.92:27553</ClientIp>
+ <OpenTime>Fri, 03 May 2019 05:59:43 GMT</OpenTime>
+ </Handle>
+ ...
+ </Entries>
+ <NextMarker />
+ </EnumerationResults>'
+ """
+ if response is None or response.body is None:
+ return None
+
+ entries = _list()
+ list_element = ETree.fromstring(response.body)
+
+ # Set next marker
+ next_marker = list_element.findtext('NextMarker') or None
+ setattr(entries, 'next_marker', next_marker)
+
+ handles_list_element = list_element.find('Entries')
+
+ for handle_element in handles_list_element.findall('Handle'):
+ # Name element
+ handle = Handle()
+ handle.handle_id = handle_element.findtext('HandleId')
+ handle.path = handle_element.findtext('Path')
+ handle.file_id = handle_element.findtext('FileId')
+ handle.parent_id = handle_element.findtext('ParentId')
+ handle.session_id = handle_element.findtext('SessionId')
+ handle.client_ip = handle_element.findtext('ClientIp')
+ handle.open_time = parser.parse(handle_element.findtext('OpenTime'))
+
+ last_connect_time_string = handle_element.findtext('LastReconnectTime')
+ if last_connect_time_string is not None:
+ handle.last_reconnect_time = parser.parse(last_connect_time_string)
+
+ # Add file to list
+ entries.append(handle)
+
+ return entries
+
+
+def _parse_close_handle_response(response):
+ if response is None or response.body is None:
+ return 0
+
+ results = _list()
+ results.append(int(response.headers['x-ms-number-of-handles-closed']))
+
+ next_marker = None if 'x-ms-marker' not in response.headers else
response.headers['x-ms-marker']
+ setattr(results, 'next_marker', next_marker)
+ return results
+
+
def _convert_xml_to_ranges(response):
'''
<?xml version="1.0" encoding="utf-8"?>
@@ -231,11 +316,11 @@
'''
<?xml version="1.0" encoding="utf-8"?>
<ShareStats>
- <ShareUsage>15</ShareUsage>
+ <ShareUsageBytes>15</ShareUsageBytes>
</ShareStats>
'''
if response is None or response.body is None:
return None
share_stats_element = ETree.fromstring(response.body)
- return int(share_stats_element.findtext('ShareUsage'))
+ return int(share_stats_element.findtext('ShareUsageBytes'))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure-storage-file-1.4.0/azure/storage/file/_serialization.py
new/azure-storage-file-2.1.0/azure/storage/file/_serialization.py
--- old/azure-storage-file-1.4.0/azure/storage/file/_serialization.py
2018-09-10 23:24:14.000000000 +0200
+++ new/azure-storage-file-2.1.0/azure/storage/file/_serialization.py
2019-08-02 05:03:23.000000000 +0200
@@ -3,12 +3,17 @@
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
+from sys import getsizeof
+
from azure.storage.common._common_conversion import _str
from azure.storage.common._error import (
_validate_not_none,
_ERROR_START_END_NEEDED_FOR_MD5,
_ERROR_RANGE_TOO_LARGE_FOR_MD5,
)
+_ERROR_TOO_MANY_FILE_PERMISSIONS = 'file_permission and file_permission_key
should not be set at the same time'
+_FILE_PERMISSION_TOO_LONG = 'Size of file_permission is too large.
file_permission should be <=8KB, else' \
+ 'please use file_permission_key'
def _get_path(share_name=None, directory_name=None, file_name=None):
@@ -42,7 +47,7 @@
def _validate_and_format_range_headers(request, start_range, end_range,
start_range_required=True,
- end_range_required=True,
check_content_md5=False):
+ end_range_required=True,
check_content_md5=False, is_source=False):
# If end range is provided, start range must be provided
if start_range_required or end_range is not None:
_validate_not_none('start_range', start_range)
@@ -51,10 +56,11 @@
# Format based on whether end_range is present
request.headers = request.headers or {}
+ header_name = 'x-ms-source-range' if is_source else 'x-ms-range'
if end_range is not None:
- request.headers['x-ms-range'] = 'bytes={0}-{1}'.format(start_range,
end_range)
+ request.headers[header_name] = 'bytes={0}-{1}'.format(start_range,
end_range)
elif start_range is not None:
- request.headers['x-ms-range'] = 'bytes={0}-'.format(start_range)
+ request.headers[header_name] = 'bytes={0}-'.format(start_range)
# Content MD5 can only be provided for a complete range less than 4MB in
size
if check_content_md5:
@@ -64,3 +70,26 @@
raise ValueError(_ERROR_RANGE_TOO_LARGE_FOR_MD5)
request.headers['x-ms-range-get-content-md5'] = 'true'
+
+
+def _validate_and_return_file_permission(file_permission, file_permission_key,
default_permission):
+ # if file_permission and file_permission_key are both empty, then use the
default_permission
+ # value as file permission, file_permission size should be <= 8KB, else
file permission_key should be used
+ empty_file_permission = file_permission is None or len(file_permission) == 0
+ empty_file_permission_key = file_permission_key is None or
len(file_permission_key) == 0
+ file_permission_size_too_big = False if file_permission is None \
+ else len(str(file_permission).encode('utf-8')) > 8 * 1024
+
+ if file_permission_size_too_big:
+ raise ValueError(_FILE_PERMISSION_TOO_LONG)
+
+ if empty_file_permission:
+ if empty_file_permission_key:
+ return default_permission
+ else:
+ return None
+
+ if empty_file_permission_key:
+ return file_permission
+
+ raise ValueError(_ERROR_TOO_MANY_FILE_PERMISSIONS)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure-storage-file-1.4.0/azure/storage/file/fileservice.py
new/azure-storage-file-2.1.0/azure/storage/file/fileservice.py
--- old/azure-storage-file-1.4.0/azure/storage/file/fileservice.py
2018-10-23 23:30:56.000000000 +0200
+++ new/azure-storage-file-2.1.0/azure/storage/file/fileservice.py
2019-08-01 23:43:12.000000000 +0200
@@ -1,9 +1,14 @@
+# coding: utf-8
+
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
import sys
+from datetime import datetime
+
+import math
from os import path
from azure.common import AzureHttpError
@@ -61,22 +66,24 @@
from ._deserialization import (
_convert_xml_to_shares,
_convert_xml_to_directories_and_files,
+ _convert_xml_to_handles,
+ _parse_close_handle_response,
_convert_xml_to_ranges,
_convert_xml_to_share_stats,
_parse_file,
_parse_share,
_parse_snapshot_share,
_parse_directory,
-)
+ _parse_permission_key, _parse_permission)
from ._download_chunking import _download_file_chunks
from ._serialization import (
_get_path,
_validate_and_format_range_headers,
-)
+ _validate_and_return_file_permission)
from ._upload_chunking import _upload_file_chunks
from .models import (
FileProperties,
-)
+ SMBProperties)
from ._constants import (
X_MS_VERSION,
@@ -89,6 +96,8 @@
_RESOURCE_ALREADY_EXISTS_ERROR_CODE = 'ResourceAlreadyExists'
_SHARE_ALREADY_EXISTS_ERROR_CODE = 'ShareAlreadyExists'
+_GB = 1024 * 1024 * 1024
+
if sys.version_info >= (3,):
from io import BytesIO
else:
@@ -223,7 +232,7 @@
)
if sas_token:
- url += '?' + sas_token
+ url += (sas_token if sas_token.startswith('?') else '?' +
sas_token)
return url
@@ -874,7 +883,7 @@
rounded up to the nearest gigabyte.
Note that this value may not include all recently created
- or recently resized files.
+ or recently re-sized files.
:param str share_name:
Name of existing share.
@@ -894,6 +903,34 @@
'timeout': _int_to_str(timeout),
}
+ usage = self._perform_request(request, _convert_xml_to_share_stats)
+ return int(math.ceil(float(usage) / _GB))
+
+ def get_share_stats_in_bytes(self, share_name, timeout=None):
+ """
+ Gets the approximate size of the data stored on the share in bytes.
+
+ Note that this value may not include all recently created
+ or recently re-sized files.
+
+ :param str share_name:
+ Name of existing share.
+ :param int timeout:
+ The timeout parameter is expressed in seconds.
+ :return: the approximate size of the data stored on the share.
+ :rtype: int
+ """
+ _validate_not_none('share_name', share_name)
+ request = HTTPRequest()
+ request.method = 'GET'
+ request.host_locations = self._get_host_locations()
+ request.path = _get_path(share_name)
+ request.query = {
+ 'restype': 'share',
+ 'comp': 'stats',
+ 'timeout': _int_to_str(timeout),
+ }
+
return self._perform_request(request, _convert_xml_to_share_stats)
def delete_share(self, share_name, fail_not_exist=False, timeout=None,
snapshot=None, delete_snapshots=None):
@@ -945,7 +982,7 @@
return True
def create_directory(self, share_name, directory_name, metadata=None,
- fail_on_exist=False, timeout=None):
+ fail_on_exist=False, timeout=None,
file_permission=None, smb_properties=SMBProperties()):
'''
Creates a new directory under the specified share or parent directory.
If the directory with the same name already exists, the operation fails
@@ -964,6 +1001,10 @@
:param bool fail_on_exist:
specify whether to throw an exception when the directory exists.
False by default.
+ :param str file_permission:
+ File permission, a portable SDDL
+ :param ~azure.storage.file.models.SMBProperties smb_properties:
+ Sets the SMB related file properties
:param int timeout:
The timeout parameter is expressed in seconds.
:return: True if directory is created, False if directory already
exists.
@@ -971,6 +1012,17 @@
'''
_validate_not_none('share_name', share_name)
_validate_not_none('directory_name', directory_name)
+ current_time = datetime.utcnow()
+ if smb_properties.ntfs_attributes is None:
+ smb_properties.ntfs_attributes = 'Directory'
+ if smb_properties.creation_time is None:
+ smb_properties.creation_time = current_time
+ if smb_properties.last_write_time is None:
+ smb_properties.last_write_time = current_time
+ file_permission = _validate_and_return_file_permission(file_permission,
+
smb_properties.permission_key,
+ 'Inherit')
+
request = HTTPRequest()
request.method = 'PUT'
request.host_locations = self._get_host_locations()
@@ -980,6 +1032,8 @@
'timeout': _int_to_str(timeout),
}
_add_metadata_headers(metadata, request)
+ request.headers.update({'x-ms-file-permission': file_permission})
+ request.headers.update(smb_properties._to_request_headers())
if not fail_on_exist:
try:
@@ -992,6 +1046,29 @@
self._perform_request(request)
return True
+ def set_directory_properties(self, share_name, directory_name,
file_permission=None,
+ smb_properties=SMBProperties(), timeout=None):
+ """
+
+ :param share_name:
+ Name of the share
+ :param directory_name:
+ Name of the directory
+ :param str file_permission:
+ File permission, a portable SDDL
+ :param ~azure.storage.file.models.SMBProperties smb_properties:
+ Sets the SMB related file properties
+ :param int timeout:
+ The timeout parameter is expressed in seconds.
+ """
+ _validate_not_none('share_name', share_name)
+ _validate_not_none('directory_name', directory_name)
+ request =
self._get_basic_set_file_or_directory_properties_http_request(share_name,
directory_name, None,
+
file_permission, smb_properties,
+
timeout)
+ request.query.update({'restype': 'directory'})
+ self._perform_request(request)
+
def delete_directory(self, share_name, directory_name,
fail_not_exist=False, timeout=None):
'''
@@ -1228,6 +1305,187 @@
return self._perform_request(request,
_convert_xml_to_directories_and_files,
operation_context=_context)
+ def list_handles(self, share_name, directory_name=None, file_name=None,
recursive=None,
+ max_results=None, marker=None, snapshot=None,
timeout=None):
+
+ """
+ Returns a generator to list opened handles on a directory or a file
under the specified share.
+ The generator will lazily follow the continuation tokens returned by
+ the service and stop when all handles have been returned or
+ num_results is reached.
+
+ If num_results is specified and the share has more than that number of
+ files and directories, the generator will have a populated next_marker
+ field once it finishes. This marker can be used to create a new
generator
+ if more results are desired.
+
+ :param str share_name:
+ Name of existing share.
+ :param str directory_name:
+ The path to the directory.
+ :param str file_name:
+ Name of existing file.
+ :param bool recursive:
+ Boolean that specifies if operation should apply to the directory
specified in the URI,
+ its files, its subdirectories and their files.
+ :param int max_results:
+ Specifies the maximum number of handles taken on files and/or
directories to return.
+ If the request does not specify max_results or specifies a value
greater than 5,000,
+ the server will return up to 5,000 items.
+ Setting max_results to a value less than or equal to zero results
in error response code 400 (Bad Request).
+ :param str marker:
+ An opaque continuation token. This value can be retrieved from the
+ next_marker field of a previous generator object if max_results was
+ specified and that generator has finished enumerating results. If
+ specified, this generator will begin returning results from the
point
+ where the previous generator stopped.
+ :param str snapshot:
+ A string that represents the snapshot version, if applicable.
+ :param int timeout:
+ The timeout parameter is expressed in seconds.
+ """
+ operation_context = _OperationContext(location_lock=True)
+ args = (share_name, directory_name, file_name)
+ kwargs = {'marker': marker, 'max_results': max_results, 'timeout':
timeout, 'recursive': recursive,
+ '_context': operation_context, 'snapshot': snapshot}
+
+ resp = self._list_handles(*args, **kwargs)
+
+ return ListGenerator(resp, self._list_handles, args, kwargs)
+
+ def _list_handles(self, share_name, directory_name=None, file_name=None,
recursive=None,
+ marker=None, max_results=None, timeout=None,
_context=None, snapshot=None):
+ """
+ Returns a list of the directories and files under the specified share.
+
+ :param str share_name:
+ Name of existing share.
+ :param str directory_name:
+ The path to the directory.
+ :param str file_name:
+ Name of existing file.
+ :param bool recursive:
+ Boolean that specifies if operation should apply to the directory
specified in the URI,
+ its files, its subdirectories and their files.
+ :param str marker:
+ An opaque continuation token. This value can be retrieved from the
+ next_marker field of a previous generator object if max_results was
+ specified and that generator has finished enumerating results. If
+ specified, this generator will begin returning results from the
point
+ where the previous generator stopped.
+ :param int max_results:
+ Specifies the maximum number of handles to return,
+ including all directory elements. If the request does not specify
+ max_results or specifies a value greater than 5,000, the server
will
+ return up to 5,000 items. Setting max_results to a value less than
+ or equal to zero results in error response code 400 (Bad Request).
+ :param int timeout:
+ The timeout parameter is expressed in seconds.
+ :param str snapshot:
+ A string that represents the snapshot version, if applicable.
+ """
+ _validate_not_none('share_name', share_name)
+ request = HTTPRequest()
+ request.method = 'GET'
+ request.host_locations = self._get_host_locations()
+ request.path = _get_path(share_name, directory_name, file_name)
+ request.query = {
+ 'comp': 'listhandles',
+ 'marker': _to_str(marker),
+ 'maxresults': _int_to_str(max_results),
+ 'timeout': _int_to_str(timeout),
+ 'sharesnapshot': _to_str(snapshot)
+ }
+ request.headers = {
+ 'x-ms-recursive': _to_str(recursive)
+ }
+
+ return self._perform_request(request, _convert_xml_to_handles,
+ operation_context=_context)
+
+ def close_handles(self, share_name, directory_name=None, file_name=None,
recursive=None,
+ handle_id=None, marker=None, snapshot=None,
timeout=None):
+
+ """
+ Returns a generator to close opened handles on a directory or a file
under the specified share.
+ The generator will lazily follow the continuation tokens returned by
+ the service and stop when all handles have been closed.
+ The yielded values represent the number of handles that were closed in
each transaction.
+
+
+ :param str share_name:
+ Name of existing share.
+ :param str directory_name:
+ The path to the directory.
+ :param str file_name:
+ Name of existing file.
+ :param bool recursive:
+ Boolean that specifies if operation should apply to the directory
specified in the URI,
+ its files, its subdirectories and their files.
+ :param str handle_id:
+ Required. Specifies handle ID opened on the file or directory to
be closed.
+ Astrix (‘*’) is a wildcard that specifies all handles.
+ :param str marker:
+ An opaque continuation token. This value can be retrieved from the
+ next_marker field of a previous generator object if it has not
finished closing handles. If
+ specified, this generator will begin closing handles from the point
+ where the previous generator stopped.
+ :param str snapshot:
+ A string that represents the snapshot version, if applicable.
+ :param int timeout:
+ The timeout parameter is expressed in seconds.
+ """
+ operation_context = _OperationContext(location_lock=True)
+ args = (share_name, directory_name, file_name)
+ kwargs = {'marker': marker, 'handle_id': handle_id, 'timeout':
timeout, 'recursive': recursive,
+ '_context': operation_context, 'snapshot': snapshot}
+
+ resp = self._close_handles(*args, **kwargs)
+
+ return ListGenerator(resp, self._close_handles, args, kwargs)
+
+ def _close_handles(self, share_name, directory_name=None, file_name=None,
recursive=None, handle_id=None,
+ marker=None, timeout=None, _context=None,
snapshot=None):
+ """
+ Returns the number of handles that got closed.
+
+ :param str share_name:
+ Name of existing share.
+ :param str directory_name:
+ The path to the directory.
+ :param str file_name:
+ Name of existing file.
+ :param bool recursive:
+ Boolean that specifies if operation should apply to the directory
specified in the URI,
+ its files, its subdirectories and their files.
+ :param str handle_id:
+ Required. Specifies handle ID opened on the file or directory to
be closed.
+ Astrix (‘*’) is a wildcard that specifies all handles.
+ :param str marker:
+ Specifies the maximum number of handles taken on files and/or
directories to return.
+ :param int timeout:
+ The timeout parameter is expressed in seconds.
+ :param str snapshot:
+ A string that represents the snapshot version, if applicable.
+ """
+ _validate_not_none('share_name', share_name)
+ request = HTTPRequest()
+ request.method = 'PUT'
+ request.host_locations = self._get_host_locations()
+ request.path = _get_path(share_name, directory_name, file_name)
+ request.query = {
+ 'comp': 'forceclosehandles',
+ 'marker': _to_str(marker),
+ 'timeout': _int_to_str(timeout),
+ 'sharesnapshot': _to_str(snapshot)
+ }
+ request.headers = {
+ 'x-ms-recursive': _to_str(recursive),
+ 'x-ms-handle-id': _to_str(handle_id),
+ }
+
+ return self._perform_request(request, _parse_close_handle_response,
operation_context=_context)
+
def get_file_properties(self, share_name, directory_name, file_name,
timeout=None, snapshot=None):
'''
Returns all user-defined metadata, standard HTTP properties, and
@@ -1306,8 +1564,7 @@
_dont_fail_not_exist(ex)
return False
- def resize_file(self, share_name, directory_name,
- file_name, content_length, timeout=None):
+ def resize_file(self, share_name, directory_name, file_name,
content_length, timeout=None):
'''
Resizes a file to the specified size. If the specified byte
value is less than the current size of the file, then all
@@ -1327,22 +1584,14 @@
_validate_not_none('share_name', share_name)
_validate_not_none('file_name', file_name)
_validate_not_none('content_length', content_length)
- request = HTTPRequest()
- request.method = 'PUT'
- request.host_locations = self._get_host_locations()
- request.path = _get_path(share_name, directory_name, file_name)
- request.query = {
- 'comp': 'properties',
- 'timeout': _int_to_str(timeout),
- }
- request.headers = {
- 'x-ms-content-length': _to_str(content_length)
- }
+ request =
self._get_basic_set_file_or_directory_properties_http_request(share_name,
directory_name, file_name,
+
None, SMBProperties(), timeout)
+ request.headers.update({'x-ms-content-length':
_to_str(content_length)})
self._perform_request(request)
def set_file_properties(self, share_name, directory_name, file_name,
- content_settings, timeout=None):
+ content_settings, timeout=None,
file_permission=None, smb_properties=SMBProperties()):
'''
Sets system properties on the file. If one property is set for the
content_settings, all properties will be overriden.
@@ -1355,12 +1604,36 @@
Name of existing file.
:param ~azure.storage.file.models.ContentSettings content_settings:
ContentSettings object used to set the file properties.
+ :param str file_permission:
+ File permission, a portable SDDL
+ :param ~azure.storage.file.models.SMBProperties smb_properties:
+ Sets the SMB related file properties
:param int timeout:
The timeout parameter is expressed in seconds.
'''
_validate_not_none('share_name', share_name)
_validate_not_none('file_name', file_name)
_validate_not_none('content_settings', content_settings)
+ request =
self._get_basic_set_file_or_directory_properties_http_request(share_name,
directory_name, file_name,
+
file_permission, smb_properties,
+
timeout)
+ request.headers.update(content_settings._to_headers())
+
+ self._perform_request(request)
+
+ def _get_basic_set_file_or_directory_properties_http_request(self,
share_name, directory_name, file_name,
+
file_permission, smb_properties, timeout):
+ common_default_value = 'Preserve'
+ if smb_properties.ntfs_attributes is None:
+ smb_properties.ntfs_attributes = common_default_value
+ if smb_properties.creation_time is None:
+ smb_properties.creation_time = common_default_value
+ if smb_properties.last_write_time is None:
+ smb_properties.last_write_time = common_default_value
+ file_permission = _validate_and_return_file_permission(file_permission,
+
smb_properties.permission_key,
+
common_default_value)
+
request = HTTPRequest()
request.method = 'PUT'
request.host_locations = self._get_host_locations()
@@ -1369,9 +1642,10 @@
'comp': 'properties',
'timeout': _int_to_str(timeout),
}
- request.headers = content_settings._to_headers()
+ request.headers = {'x-ms-file-permission': file_permission}
+ request.headers.update(smb_properties._to_request_headers())
- self._perform_request(request)
+ return request
def get_file_metadata(self, share_name, directory_name, file_name,
timeout=None, snapshot=None):
'''
@@ -1553,8 +1827,8 @@
self._perform_request(request)
def create_file(self, share_name, directory_name, file_name,
- content_length, content_settings=None, metadata=None,
- timeout=None):
+ content_length, content_settings=None, metadata=None,
timeout=None,
+ file_permission=None, smb_properties=SMBProperties()):
'''
Creates a new file.
@@ -1575,12 +1849,27 @@
:param metadata:
Name-value pairs associated with the file as metadata.
:type metadata: dict(str, str)
+ :param str file_permission:
+ File permission, a portable SDDL
+ :param ~azure.storage.file.models.SMBProperties smb_properties:
+ Sets the SMB related file properties
:param int timeout:
The timeout parameter is expressed in seconds.
'''
_validate_not_none('share_name', share_name)
_validate_not_none('file_name', file_name)
_validate_not_none('content_length', content_length)
+ current_time = datetime.utcnow()
+ if smb_properties.ntfs_attributes is None:
+ smb_properties.ntfs_attributes = 'Archive'
+ if smb_properties.creation_time is None:
+ smb_properties.creation_time = current_time
+ if smb_properties.last_write_time is None:
+ smb_properties.last_write_time = current_time
+
+ file_permission = _validate_and_return_file_permission(file_permission,
+
smb_properties.permission_key,
+ 'Inherit')
request = HTTPRequest()
request.method = 'PUT'
request.host_locations = self._get_host_locations()
@@ -1588,18 +1877,20 @@
request.query = {'timeout': _int_to_str(timeout)}
request.headers = {
'x-ms-content-length': _to_str(content_length),
- 'x-ms-type': 'file'
+ 'x-ms-type': 'file',
+ 'x-ms-file-permission': file_permission
}
_add_metadata_headers(metadata, request)
if content_settings is not None:
request.headers.update(content_settings._to_headers())
+ request.headers.update(smb_properties._to_request_headers())
self._perform_request(request)
def create_file_from_path(self, share_name, directory_name, file_name,
local_file_path, content_settings=None,
metadata=None, validate_content=False,
progress_callback=None,
- max_connections=2, timeout=None):
+ max_connections=2, file_permission=None,
smb_properties=SMBProperties(), timeout=None):
'''
Creates a new azure file from a local file path, or updates the
content of an
existing file, with automatic chunking and progress notifications.
@@ -1631,6 +1922,10 @@
:type progress_callback: func(current, total)
:param int max_connections:
Maximum number of parallel connections to use.
+ :param str file_permission:
+ File permission, a portable SDDL
+ :param ~azure.storage.file.models.SMBProperties smb_properties:
+ Sets the SMB related file properties
:param int timeout:
The timeout parameter is expressed in seconds. This method may
make
multiple calls to the Azure service and the timeout will apply to
@@ -1645,11 +1940,12 @@
self.create_file_from_stream(
share_name, directory_name, file_name, stream,
count, content_settings, metadata, validate_content,
progress_callback,
- max_connections, timeout)
+ max_connections, file_permission=file_permission,
smb_properties=smb_properties, timeout=timeout)
def create_file_from_text(self, share_name, directory_name, file_name,
text, encoding='utf-8', content_settings=None,
- metadata=None, validate_content=False,
timeout=None):
+ metadata=None, validate_content=False,
timeout=None, file_permission=None,
+ smb_properties=SMBProperties()):
'''
Creates a new file from str/unicode, or updates the content of an
existing file, with automatic chunking and progress notifications.
@@ -1676,6 +1972,10 @@
the wire if using http instead of https as https (the default)
will
already validate. Note that this MD5 hash is not stored with the
file.
+ :param str file_permission:
+ File permission, a portable SDDL
+ :param ~azure.storage.file.models.SMBProperties smb_properties:
+ Sets the SMB related file properties
:param int timeout:
The timeout parameter is expressed in seconds. This method may
make
multiple calls to the Azure service and the timeout will apply to
@@ -1692,13 +1992,14 @@
self.create_file_from_bytes(
share_name, directory_name, file_name, text, count=len(text),
content_settings=content_settings, metadata=metadata,
- validate_content=validate_content, timeout=timeout)
+ validate_content=validate_content,
file_permission=file_permission, smb_properties=smb_properties,
+ timeout=timeout)
def create_file_from_bytes(
self, share_name, directory_name, file_name, file,
index=0, count=None, content_settings=None, metadata=None,
- validate_content=False, progress_callback=None, max_connections=2,
- timeout=None):
+ validate_content=False, progress_callback=None, max_connections=2,
timeout=None,
+ file_permission=None, smb_properties=SMBProperties()):
'''
Creates a new file from an array of bytes, or updates the content
of an existing file, with automatic chunking and progress
@@ -1736,6 +2037,10 @@
:type progress_callback: func(current, total)
:param int max_connections:
Maximum number of parallel connections to use.
+ :param str file_permission:
+ File permission, a portable SDDL
+ :param ~azure.storage.file.models.SMBProperties smb_properties:
+ Sets the SMB related file properties
:param int timeout:
The timeout parameter is expressed in seconds. This method may
make
multiple calls to the Azure service and the timeout will apply to
@@ -1758,12 +2063,13 @@
self.create_file_from_stream(
share_name, directory_name, file_name, stream, count,
content_settings, metadata, validate_content, progress_callback,
- max_connections, timeout)
+ max_connections, file_permission=file_permission,
smb_properties=smb_properties, timeout=timeout)
def create_file_from_stream(
self, share_name, directory_name, file_name, stream, count,
content_settings=None, metadata=None, validate_content=False,
- progress_callback=None, max_connections=2, timeout=None):
+ progress_callback=None, max_connections=2, timeout=None,
+ file_permission=None, smb_properties=SMBProperties()):
'''
Creates a new file from a file/stream, or updates the content of an
existing file, with automatic chunking and progress notifications.
@@ -1799,6 +2105,10 @@
:param int max_connections:
Maximum number of parallel connections to use. Note that parallel
upload
requires the stream to be seekable.
+ :param str file_permission:
+ File permission, a portable SDDL
+ :param ~azure.storage.file.models.SMBProperties smb_properties:
+ Sets the SMB related file properties
:param int timeout:
The timeout parameter is expressed in seconds. This method may
make
multiple calls to the Azure service and the timeout will apply to
@@ -1819,7 +2129,9 @@
count,
content_settings,
metadata,
- timeout
+ file_permission=file_permission,
+ smb_properties=smb_properties,
+ timeout=timeout
)
_upload_file_chunks(
@@ -2366,9 +2678,76 @@
:param int timeout:
The timeout parameter is expressed in seconds.
'''
+ request = self._get_basic_update_file_http_request(share_name,
directory_name, file_name, timeout=timeout)
+
+ _validate_not_none('data', data)
+ _validate_and_format_range_headers(request, start_range, end_range)
+ request.body = _get_data_bytes_only('data', data)
+
+ if validate_content:
+ computed_md5 = _get_content_md5(request.body)
+ request.headers['Content-MD5'] = _to_str(computed_md5)
+
+ self._perform_request(request)
+
+ def update_range_from_file_url(self, share_name, directory_name,
file_name, start_range, end_range, source,
+ source_start_range, timeout=None):
+ '''
+ Writes the bytes from one Azure File endpoint into the specified range
of another Azure File endpoint.
+
+ :param str share_name:
+ Name of existing share.
+ :param str directory_name:
+ The path to the directory.
+ :param str file_name:
+ Name of existing file.
+ :param int start_range:
+ Start of byte range to use for updating a section of the file.
+ The range can be up to 4 MB in size.
+ The start_range and end_range params are inclusive.
+ Ex: start_range=0, end_range=511 will download first 512 bytes of
file.
+ :param int end_range:
+ End of byte range to use for updating a section of the file.
+ The range can be up to 4 MB in size.
+ The start_range and end_range params are inclusive.
+ Ex: start_range=0, end_range=511 will download first 512 bytes of
file.
+ :param str source:
+ A URL of up to 2 KB in length that specifies an Azure file or blob.
+ The value should be URL-encoded as it would appear in a request
URI.
+ If the source is in another account, the source must either be
public
+ or must be authenticated via a shared access signature. If the
source
+ is public, no authentication is required.
+ Examples:
+ https://myaccount.file.core.windows.net/myshare/mydir/myfile
+
https://otheraccount.file.core.windows.net/myshare/mydir/myfile?sastoken
+ :param int source_start_range:
+ Start of byte range to use for updating a section of the file.
+ The range can be up to 4 MB in size.
+ The start_range and end_range params are inclusive.
+ Ex: start_range=0, end_range=511 will download first 512 bytes of
file.
+ :param int timeout:
+ The timeout parameter is expressed in seconds.
+ '''
+
+ request = self._get_basic_update_file_http_request(share_name,
directory_name, file_name, timeout=timeout)
+
+ _validate_not_none('source', source)
+ _validate_and_format_range_headers(request, start_range, end_range)
+ _validate_and_format_range_headers(request,
+ source_start_range,
+
source_start_range+(end_range-start_range),
+ is_source=True)
+
+ request.headers.update({
+ 'x-ms-copy-source': _to_str(source),
+ 'Content-Length': _int_to_str(0)
+ })
+
+ self._perform_request(request)
+
+ def _get_basic_update_file_http_request(self, share_name, directory_name,
file_name, timeout=None):
_validate_not_none('share_name', share_name)
_validate_not_none('file_name', file_name)
- _validate_not_none('data', data)
request = HTTPRequest()
request.method = 'PUT'
request.host_locations = self._get_host_locations()
@@ -2380,15 +2759,8 @@
request.headers = {
'x-ms-write': 'update',
}
- _validate_and_format_range_headers(
- request, start_range, end_range)
- request.body = _get_data_bytes_only('data', data)
- if validate_content:
- computed_md5 = _get_content_md5(request.body)
- request.headers['Content-MD5'] = _to_str(computed_md5)
-
- self._perform_request(request)
+ return request
def clear_range(self, share_name, directory_name, file_name, start_range,
end_range, timeout=None):
@@ -2480,3 +2852,65 @@
end_range_required=False)
return self._perform_request(request, _convert_xml_to_ranges)
+
+ def create_permission_for_share(self, share_name, file_permission,
timeout=None):
+ """
+ Create a permission(a security descriptor) at the share level.
+ This 'permission' can be used for the files/directories in the share.
+ If a 'permission' already exists, it shall return the key of it, else
+ creates a new permission at the share level and return its key.
+
+ :param share_name:
+ Name of share.
+ :param file_permission:
+ File permission, a Portable SDDL
+ :param timeout:
+ The timeout parameter is expressed in seconds.
+ :returns a file permission key
+ :rtype str
+ """
+ _validate_not_none('share_name', share_name)
+ request = HTTPRequest()
+ request.method = 'PUT'
+ request.host_locations = self._get_host_locations()
+ request.path = _get_path(share_name)
+ request.query = {
+ 'restype': 'share',
+ 'comp': 'filepermission',
+ 'timeout': _int_to_str(timeout),
+ }
+ request.body = file_permission
+ return self._perform_request(request, parser=_parse_permission_key)
+
+ def get_permission_for_share(self, share_name, file_permission_key,
timeout=None):
+ """
+ Create a permission(a security descriptor) at the share level.
+ This 'permission' can be used for the files/directories in the share.
+ If a 'permission' already exists, it shall return the key of it, else
+ creates a new permission at the share level and return its key.
+
+ :param share_name:
+ Name of share.
+ :param file_permission_key:
+ Key of the file permission to retrieve
+ :param timeout:
+ The timeout parameter is expressed in seconds.
+ :returns a file permission(a portable SDDL)
+ :rtype str
+ """
+ _validate_not_none('share_name', share_name)
+ request = HTTPRequest()
+ request.method = 'GET'
+ request.host_locations = self._get_host_locations()
+ request.path = _get_path(share_name)
+ request.query = {
+ 'restype': 'share',
+ 'comp': 'filepermission',
+ 'timeout': _int_to_str(timeout),
+ }
+ request.headers = {
+ 'x-ms-file-permission-key': file_permission_key
+ }
+ request.body = None
+
+ return self._perform_request(request, parser=_parse_permission)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure-storage-file-1.4.0/azure/storage/file/models.py
new/azure-storage-file-2.1.0/azure/storage/file/models.py
--- old/azure-storage-file-1.4.0/azure/storage/file/models.py 2018-09-10
23:24:14.000000000 +0200
+++ new/azure-storage-file-2.1.0/azure/storage/file/models.py 2019-07-29
21:41:49.000000000 +0200
@@ -85,12 +85,15 @@
conditionally.
:ivar bool server_encrypted:
Set to true if the directory metadata is encrypted on the server.
+ :ivar ~azure.storage.file.models.SMBProperties smb_properties:
+ SMB related file properties
'''
def __init__(self):
self.last_modified = None
self.etag = None
self.server_encrypted = None
+ self.smb_properties = SMBProperties()
class File(object):
@@ -139,8 +142,12 @@
Stores all the content settings for the file.
:ivar ~azure.storage.file.models.CopyProperties copy:
Stores all the copy properties for the file.
- ivar bool server_encrypted:
+ :ivar bool server_encrypted:
Set to true if the file data and application metadata are completely
encrypted.
+ :ivar ~azure.storage.file.models.SMBProperties smb_properties:
+ SMB related file properties
+ :ivar ~azure.storage.file.models.LeaseProperties lease:
+ Stores all the lease information for the file.
'''
def __init__(self):
@@ -151,6 +158,111 @@
self.content_settings = ContentSettings()
self.copy = CopyProperties()
self.server_encrypted = None
+ self.smb_properties = SMBProperties()
+ self.lease = LeaseProperties()
+
+
+class SMBProperties(object):
+ """
+ SMB related properties to get/set for for file/directory
+
+ :ivar str or :class:`~azure.storage.file.models.NTFSAttributes`
ntfs_attributes:
+ The file system attributes for files and directories.
+ If not set, indicates preservation of existing values.
+ Here is an example for when the var type is str: 'Temporary|Archive'
+ :ivar str or datetime creation_time:
+ When the File or Directory was created.
+ If it is a string type, time should have 7 decimal digits, eg.
'2019-07-07T02:52:46.5540162Z'
+ :ivar str or datetime last_write_time:
+ When the File or Directory was last modified. eg.
'2019-07-07T02:52:46.5540162Z'
+ If it is a string type, time should have 7 decimal digits, eg.
'2019-07-07T02:52:46.5540162Z'
+ :ivar str permission_key:
+ The file's File Permission Key
+ :ivar str change_time:
+ When the File was last changed. This is what will be returned by
service. Users don't need to specify.
+ :ivar str file_id:
+ The Id of this directory. This is what will be returned by service.
Users don't need to specify.
+ :ivar str parent_id:
+ The Id of this directory's parent. This is what will be returned by
service. Users don't need to specify.
+ """
+ def __init__(self, ntfs_attributes=None, creation_time=None,
last_write_time=None, permission_key=None):
+ self.ntfs_attributes = ntfs_attributes
+ self.creation_time = creation_time
+ self.last_write_time = last_write_time
+ self.permission_key = permission_key
+ self.change_time = None
+ self.file_id = None
+ self.parent_id = None
+
+ def _to_request_headers(self):
+ creation_time = self.creation_time if isinstance(self.creation_time,
str) \
+ else self.creation_time.isoformat() + '0Z'
+ last_write_time = self.last_write_time if
isinstance(self.last_write_time, str) \
+ else self.last_write_time.isoformat() + '0Z'
+ return {
+ 'x-ms-file-attributes': _to_str(self.ntfs_attributes),
+ 'x-ms-file-creation-time': creation_time,
+ 'x-ms-file-last-write-time': last_write_time,
+ 'x-ms-file-permission-key': _to_str(self.permission_key)
+ }
+
+
+class LeaseProperties(object):
+ '''
+ File Lease Properties.
+
+ :ivar str status:
+ The lease status of the file.
+ Possible values: locked|unlocked
+ :ivar str state:
+ Lease state of the file.
+ Possible values: available|leased|expired|breaking|broken
+ :ivar str duration:
+ When a file is leased, specifies whether the lease is of infinite or
fixed duration.
+ '''
+
+ def __init__(self):
+ self.status = None
+ self.state = None
+ self.duration = None
+
+
+class Handle(object):
+ """
+ Represents a file handle.
+
+ :ivar str handle_id:
+ Used to identify handle.
+ :ivar str path:
+ Used to identify the name of the object for which the handle is open.
+ :ivar str file_id:
+ Uniquely identifies the file.
+ This is useful when renames are happening as the file ID does not
change.
+ :ivar str parent_id:
+ Uniquely identifies the parent directory.
+ This is useful when renames are happening as the parent ID does not
change.
+ :ivar str session_id:
+ Session ID in context of which the file handle was opened.
+ :ivar str client_ip:
+ Used to identify client that has opened the handle.
+ The field is included only if client IP is known by the service.
+ :ivar datetime open_time:
+ Used to decide if handle may have been leaked.
+ :ivar datetime last_reconnect_time:
+ Used to decide if handle was reopened after client/server disconnect
due to networking or other faults.
+ The field is included only if disconnect event occurred and handle was
reopened.
+ """
+
+ def __init__(self, handle_id=None, path=None, file_id=None,
parent_id=None, session_id=None,
+ client_ip=None, open_time=None, last_reconnect_time=None):
+ self.handle_id = handle_id
+ self.path = path
+ self.file_id = file_id
+ self.parent_id = parent_id
+ self.session_id = session_id
+ self.client_ip = client_ip
+ self.open_time = open_time
+ self.last_reconnect_time = last_reconnect_time
class ContentSettings(object):
@@ -405,3 +517,77 @@
SharePermissions.LIST = SharePermissions(list=True)
SharePermissions.READ = SharePermissions(read=True)
SharePermissions.WRITE = SharePermissions(write=True)
+
+
+class NTFSAttributes(object):
+ """
+ Valid set of attributes to set for file or directory.
+ To set attribute for directory, 'Directory' should always be enabled
except setting 'None' for directory.
+
+ :ivar bool read_only:
+ Enable/disable 'ReadOnly' attribute for DIRECTORY or FILE
+ :ivar bool hidden:
+ Enable/disable 'Hidden' attribute for DIRECTORY or FILE
+ :ivar bool system:
+ Enable/disable 'System' attribute for DIRECTORY or FILE
+ :ivar bool none:
+ Enable/disable 'None' attribute for DIRECTORY or FILE to clear all
attributes of FILE/DIRECTORY
+ :ivar bool directory:
+ Enable/disable 'Directory' attribute for DIRECTORY
+ :ivar bool archive:
+ Enable/disable 'Archive' attribute for DIRECTORY or FILE
+ :ivar bool temporary:
+ Enable/disable 'Temporary' attribute for FILE
+ :ivar bool offline:
+ Enable/disable 'Offline' attribute for DIRECTORY or FILE
+ :ivar bool not_content_indexed:
+ Enable/disable 'NotContentIndexed' attribute for DIRECTORY or FILE
+ :ivar bool no_scrub_data:
+ Enable/disable 'NoScrubData' attribute for DIRECTORY or FILE
+ """
+ def __init__(self, read_only=False, hidden=False, system=False,
none=False, directory=False, archive=False,
+ temporary=False, offline=False, not_content_indexed=False,
no_scrub_data=False, _str=None):
+ if not _str:
+ _str = ''
+ self.read_only = read_only or ('ReadOnly' in _str)
+ self.hidden = hidden or ('Hidden' in _str)
+ self.system = system or ('System' in _str)
+ self.none = none or ('None' in _str)
+ self.directory = directory or ('Directory' in _str)
+ self.archive = archive or ('Archive' in _str)
+ self.temporary = temporary or ('Temporary' in _str)
+ self.offline = offline or ('Offline' in _str)
+ self.not_content_indexed = not_content_indexed or ('NotContentIndexed'
in _str)
+ self.no_scrub_data = no_scrub_data or ('NoScrubData' in _str)
+
+ def __or__(self, other):
+ return NTFSAttributes(_str=str(self) + str(other))
+
+ def __add__(self, other):
+ return NTFSAttributes(_str=str(self) + str(other))
+
+ def __str__(self):
+ concatenated_params = (('ReadOnly|' if self.read_only else '') +
+ ('Hidden|' if self.hidden else '') +
+ ('System|' if self.system else '') +
+ ('None|' if self.none else '') +
+ ('Directory|' if self.directory else '') +
+ ('Archive|' if self.archive else '') +
+ ('Temporary|' if self.temporary else '') +
+ ('Offline|' if self.offline else '') +
+ ('NotContentIndexed|' if
self.not_content_indexed else '') +
+ ('NoScrubData|' if self.no_scrub_data else ''))
+
+ return concatenated_params.strip('|')
+
+
+NTFSAttributes.READ_ONLY = NTFSAttributes(read_only=True)
+NTFSAttributes.HIDDEN = NTFSAttributes(hidden=True)
+NTFSAttributes.SYSTEM = NTFSAttributes(system=True)
+NTFSAttributes.NONE = NTFSAttributes(none=True)
+NTFSAttributes.DIRECTORY = NTFSAttributes(directory=True)
+NTFSAttributes.ARCHIVE = NTFSAttributes(archive=True)
+NTFSAttributes.TEMPORARY = NTFSAttributes(temporary=True)
+NTFSAttributes.OFFLINE = NTFSAttributes(offline=True)
+NTFSAttributes.NOT_CONTENT_INDEXED = NTFSAttributes(not_content_indexed=True)
+NTFSAttributes.NO_SCRUB_DATA = NTFSAttributes(no_scrub_data=True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure-storage-file-1.4.0/azure/storage/file/sharedaccesssignature.py
new/azure-storage-file-2.1.0/azure/storage/file/sharedaccesssignature.py
--- old/azure-storage-file-1.4.0/azure/storage/file/sharedaccesssignature.py
2018-10-17 22:48:05.000000000 +0200
+++ new/azure-storage-file-2.1.0/azure/storage/file/sharedaccesssignature.py
2019-06-07 09:59:17.000000000 +0200
@@ -7,6 +7,8 @@
from azure.storage.common.sharedaccesssignature import (
SharedAccessSignature,
_SharedAccessHelper,
+ _QueryStringConstants,
+ _sign_string,
)
from azure.storage.common._common_conversion import (
_to_str,
@@ -103,14 +105,14 @@
resource_path += '/' + _to_str(directory_name)
resource_path += '/' + _to_str(file_name)
- sas = _SharedAccessHelper()
+ sas = _FileSharedAccessHelper()
sas.add_base(permission, expiry, start, ip, protocol,
self.x_ms_version)
sas.add_id(id)
sas.add_resource('f')
sas.add_override_response_headers(cache_control, content_disposition,
content_encoding, content_language,
content_type)
- sas.add_resource_signature(self.account_name, self.account_key,
'file', resource_path)
+ sas.add_resource_signature(self.account_name, self.account_key,
resource_path)
return sas.get_token()
@@ -176,13 +178,52 @@
Response header value for Content-Type when resource is accessed
using this shared access signature.
'''
- sas = _SharedAccessHelper()
+ sas = _FileSharedAccessHelper()
sas.add_base(permission, expiry, start, ip, protocol,
self.x_ms_version)
sas.add_id(id)
sas.add_resource('s')
sas.add_override_response_headers(cache_control, content_disposition,
content_encoding, content_language,
content_type)
- sas.add_resource_signature(self.account_name, self.account_key,
'file', share_name)
+ sas.add_resource_signature(self.account_name, self.account_key,
share_name)
return sas.get_token()
+
+
+class _FileSharedAccessHelper(_SharedAccessHelper):
+ def __init__(self):
+ super(_FileSharedAccessHelper, self).__init__()
+
+ def add_resource_signature(self, account_name, account_key, path):
+ def get_value_to_append(query):
+ return_value = self.query_dict.get(query) or ''
+ return return_value + '\n'
+
+ if path[0] != '/':
+ path = '/' + path
+
+ canonicalized_resource = '/file/' + account_name + path + '\n'
+
+ # Form the string to sign from shared_access_policy and canonicalized
+ # resource. The order of values is important.
+ string_to_sign = \
+ (get_value_to_append(_QueryStringConstants.SIGNED_PERMISSION) +
+ get_value_to_append(_QueryStringConstants.SIGNED_START) +
+ get_value_to_append(_QueryStringConstants.SIGNED_EXPIRY) +
+ canonicalized_resource +
+ get_value_to_append(_QueryStringConstants.SIGNED_IDENTIFIER) +
+ get_value_to_append(_QueryStringConstants.SIGNED_IP) +
+ get_value_to_append(_QueryStringConstants.SIGNED_PROTOCOL) +
+ get_value_to_append(_QueryStringConstants.SIGNED_VERSION) +
+ get_value_to_append(_QueryStringConstants.SIGNED_CACHE_CONTROL) +
+
get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_DISPOSITION) +
+
get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_ENCODING) +
+
get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_LANGUAGE) +
+ get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_TYPE))
+
+ # remove the trailing newline
+ if string_to_sign[-1] == '\n':
+ string_to_sign = string_to_sign[:-1]
+
+ self._add_query(_QueryStringConstants.SIGNED_SIGNATURE,
+ _sign_string(account_key, string_to_sign))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure-storage-file-1.4.0/azure_storage_file.egg-info/PKG-INFO
new/azure-storage-file-2.1.0/azure_storage_file.egg-info/PKG-INFO
--- old/azure-storage-file-1.4.0/azure_storage_file.egg-info/PKG-INFO
2018-11-10 03:01:24.000000000 +0100
+++ new/azure-storage-file-2.1.0/azure_storage_file.egg-info/PKG-INFO
2019-08-02 06:12:46.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: azure-storage-file
-Version: 1.4.0
+Version: 2.1.0
Summary: Microsoft Azure Storage File Client Library for Python
Home-page: https://github.com/Azure/azure-storage-python
Author: Microsoft Corporation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/azure-storage-file-1.4.0/azure_storage_file.egg-info/requires.txt
new/azure-storage-file-2.1.0/azure_storage_file.egg-info/requires.txt
--- old/azure-storage-file-1.4.0/azure_storage_file.egg-info/requires.txt
2018-11-10 03:01:24.000000000 +0100
+++ new/azure-storage-file-2.1.0/azure_storage_file.egg-info/requires.txt
2019-08-02 06:12:46.000000000 +0200
@@ -1,5 +1,5 @@
azure-common>=1.1.5
-azure-storage-common~=1.4
+azure-storage-common~=2.1
[:python_version<'3.0']
futures
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/azure-storage-file-1.4.0/setup.py
new/azure-storage-file-2.1.0/setup.py
--- old/azure-storage-file-1.4.0/setup.py 2018-11-10 00:24:52.000000000
+0100
+++ new/azure-storage-file-2.1.0/setup.py 2019-08-02 06:03:32.000000000
+0200
@@ -44,7 +44,7 @@
setup(
name='azure-storage-file',
- version='1.4.0',
+ version='2.1.0',
description='Microsoft Azure Storage File Client Library for Python',
long_description=open('README.rst', 'r').read(),
license='MIT License',
@@ -72,7 +72,7 @@
]),
install_requires=[
'azure-common>=1.1.5',
- 'azure-storage-common~=1.4'
+ 'azure-storage-common~=2.1'
],
extras_require={
":python_version<'3.0'": ['futures'],