Fixing and refactoring of clean/info resource methods

This commit is contained in:
evgeny.ezhov 2017-10-18 18:29:50 +03:00
parent 825ae24a72
commit c5a5fc7854
4 changed files with 119 additions and 99 deletions

View file

@ -13,6 +13,8 @@ Version 0.3 - TBD
- Setting of WebDAV resource property value - Setting of WebDAV resource property value
- Coping of resource on WebDAV server - Coping of resource on WebDAV server
- Moving of resource on WebDAV server - Moving of resource on WebDAV server
- Deleting of resource on WebDAV server
- Getting of information about WebDAV resource
Version 0.2 - 11.09.2017 Version 0.2 - 11.09.2017
* Refactoring of WebDAV client and making it works in following methods: * Refactoring of WebDAV client and making it works in following methods:

View file

@ -102,7 +102,6 @@ class ClientTestCase(TestCase):
buff = StringIO(u'test content for testing of webdav client') buff = StringIO(u'test content for testing of webdav client')
self.client.upload_to(buff=buff, remote_path=self.remote_path_file) self.client.upload_to(buff=buff, remote_path=self.remote_path_file)
self.assertTrue(self.client.check(self.remote_path_file), 'Expected the file is uploaded.') self.assertTrue(self.client.check(self.remote_path_file), 'Expected the file is uploaded.')
self.test_download_to()
def test_upload(self): def test_upload(self):
self._prepare_for_uploading() self._prepare_for_uploading()
@ -146,6 +145,20 @@ class ClientTestCase(TestCase):
self.assertFalse(self.client.check(remote_path=self.remote_path_file)) self.assertFalse(self.client.check(remote_path=self.remote_path_file))
self.assertTrue(self.client.check(remote_path=self.remote_path_file2)) self.assertTrue(self.client.check(remote_path=self.remote_path_file2))
def test_clean(self):
self._prepare_for_downloading()
self.client.clean(remote_path=self.remote_path_dir)
self.assertFalse(self.client.check(remote_path=self.remote_path_file))
self.assertFalse(self.client.check(remote_path=self.remote_path_dir))
def test_info(self):
self._prepare_for_downloading()
result = self.client.info(remote_path=self.remote_path_file)
self.assertEquals(result['name'], 'test.txt')
self.assertEquals(result['size'], '41')
self.assertTrue('created' in result)
self.assertTrue('modified' in result)
def test_get_property(self): def test_get_property(self):
self._prepare_for_downloading() self._prepare_for_downloading()
result = self.client.get_property(remote_path=self.remote_path_file, option={'name': 'aProperty'}) result = self.client.get_property(remote_path=self.remote_path_file, option={'name': 'aProperty'})
@ -169,8 +182,6 @@ class ClientTestCase(TestCase):
self.client.upload_file(remote_path=self.remote_path_file, local_path=self.local_path_file) self.client.upload_file(remote_path=self.remote_path_file, local_path=self.local_path_file)
def _prepare_for_uploading(self): def _prepare_for_uploading(self):
if self.client.check(remote_path=self.remote_path_file):
self.client.clean(remote_path=self.remote_path_file)
if not self.client.check(remote_path=self.remote_path_dir): if not self.client.check(remote_path=self.remote_path_dir):
self.client.mkdir(remote_path=self.remote_path_dir) self.client.mkdir(remote_path=self.remote_path_dir)
if not path.exists(path=self.local_path_dir): if not path.exists(path=self.local_path_dir):

View file

@ -32,6 +32,20 @@ class ClientTestCase(TestCase):
result = utils.parse_free_space_response(content, 'localhost') result = utils.parse_free_space_response(content, 'localhost')
self.assertEquals(result, 10737417543) self.assertEquals(result, 10737417543)
def test_parse_info_response(self):
content = '<?xml version="1.0" encoding="utf-8"?><d:multistatus xmlns:d="DAV:"><d:response>' \
'<d:href>/test_dir/test.txt</d:href><d:propstat><d:status>HTTP/1.1 200 OK</d:status><d:prop>' \
'<d:resourcetype/><d:getlastmodified>Wed, 18 Oct 2017 15:16:04 GMT</d:getlastmodified>' \
'<d:getetag>ab0b4b7973803c03639b848682b5f38c</d:getetag><d:getcontenttype>text/plain' \
'</d:getcontenttype><d:getcontentlength>41</d:getcontentlength><d:displayname>test.txt' \
'</d:displayname><d:creationdate>2017-10-18T15:16:04Z</d:creationdate></d:prop></d:propstat>' \
'</d:response></d:multistatus>'
result = utils.parse_info_response(content, '/test_dir/test.txt', 'localhost')
self.assertEquals(result['created'], '2017-10-18T15:16:04Z')
self.assertEquals(result['name'], 'test.txt')
self.assertEquals(result['modified'], 'Wed, 18 Oct 2017 15:16:04 GMT')
self.assertEquals(result['size'], '41')
def test_create_get_property_request_content(self): def test_create_get_property_request_content(self):
option = { option = {
'namespace': 'test', 'namespace': 'test',

View file

@ -158,10 +158,7 @@ class Client(object):
# mapping of actions to WebDAV methods # mapping of actions to WebDAV methods
requests = { requests = {
'download': "GET", 'download': "GET",
'download_to': "GET", 'upload': "PUT",
'download_file': "GET",
'upload_from': "PUT",
'upload_file': "PUT",
'copy': "COPY", 'copy': "COPY",
'move': "MOVE", 'move': "MOVE",
'mkdir': "MKCOL", 'mkdir': "MKCOL",
@ -222,6 +219,7 @@ class Client(object):
@wrap_connection_error @wrap_connection_error
def list(self, remote_path=root): def list(self, remote_path=root):
"""Returns list of nested files and directories for remote WebDAV directory by path. """Returns list of nested files and directories for remote WebDAV directory by path.
More information you can find by link http://webdav.org/specs/rfc4918.html#METHOD_PROPFIND
:param remote_path: path to remote directory. :param remote_path: path to remote directory.
:return: list of nested file or directory names. :return: list of nested file or directory names.
@ -240,6 +238,7 @@ class Client(object):
@wrap_connection_error @wrap_connection_error
def free(self): def free(self):
"""Returns an amount of free space on remote WebDAV server. """Returns an amount of free space on remote WebDAV server.
More information you can find by link http://webdav.org/specs/rfc4918.html#METHOD_PROPFIND
:return: an amount of free space in bytes. :return: an amount of free space in bytes.
""" """
@ -249,7 +248,8 @@ class Client(object):
@wrap_connection_error @wrap_connection_error
def check(self, remote_path=root): def check(self, remote_path=root):
"""Checks an existence of remote resource on WebDAV server by remote path """Checks an existence of remote resource on WebDAV server by remote path.
More information you can find by link http://webdav.org/specs/rfc4918.html#rfc.section.9.4
:param remote_path: (optional) path to resource on WebDAV server. Defaults is root directory of WebDAV. :param remote_path: (optional) path to resource on WebDAV server. Defaults is root directory of WebDAV.
:return: True if resource is exist or False otherwise :return: True if resource is exist or False otherwise
@ -267,6 +267,7 @@ class Client(object):
@wrap_connection_error @wrap_connection_error
def mkdir(self, remote_path): def mkdir(self, remote_path):
"""Makes new directory on WebDAV server. """Makes new directory on WebDAV server.
More information you can find by link http://webdav.org/specs/rfc4918.html#METHOD_MKCOL
:param remote_path: path to directory :param remote_path: path to directory
:return: True if request executed with code 200 and False otherwise. :return: True if request executed with code 200 and False otherwise.
@ -293,11 +294,12 @@ class Client(object):
if not self.check(urn.path()): if not self.check(urn.path()):
raise RemoteResourceNotFound(urn.path()) raise RemoteResourceNotFound(urn.path())
response = self.execute_request(action='download_to', path=urn.quote()) response = self.execute_request(action='download', path=urn.quote())
buff.write(response.content) buff.write(response.content)
def download(self, remote_path, local_path, progress=None): def download(self, remote_path, local_path, progress=None):
"""Downloads remote resource from WebDAV and save it in local path. """Downloads remote resource from WebDAV and save it in local path.
More information you can find by link http://webdav.org/specs/rfc4918.html#rfc.section.9.4
:param remote_path: the path to remote resource for downloading can be file and directory. :param remote_path: the path to remote resource for downloading can be file and directory.
:param local_path: the path to save resource locally. :param local_path: the path to save resource locally.
@ -334,6 +336,7 @@ class Client(object):
@wrap_connection_error @wrap_connection_error
def download_file(self, remote_path, local_path, progress=None): def download_file(self, remote_path, local_path, progress=None):
"""Downloads file from WebDAV server and save it locally. """Downloads file from WebDAV server and save it locally.
More information you can find by link http://webdav.org/specs/rfc4918.html#rfc.section.9.4
:param remote_path: the path to remote file for downloading. :param remote_path: the path to remote file for downloading.
:param local_path: the path to save file locally. :param local_path: the path to save file locally.
@ -350,7 +353,7 @@ class Client(object):
raise RemoteResourceNotFound(urn.path()) raise RemoteResourceNotFound(urn.path())
with open(local_path, 'wb') as local_file: with open(local_path, 'wb') as local_file:
response = self.execute_request('download_file', urn.quote()) response = self.execute_request('download', urn.quote())
for block in response.iter_content(1024): for block in response.iter_content(1024):
local_file.write(block) local_file.write(block)
@ -378,6 +381,7 @@ class Client(object):
@wrap_connection_error @wrap_connection_error
def upload_to(self, buff, remote_path): def upload_to(self, buff, remote_path):
"""Uploads file from buffer to remote path on WebDAV server. """Uploads file from buffer to remote path on WebDAV server.
More information you can find by link http://webdav.org/specs/rfc4918.html#METHOD_PUT
:param buff: the buffer with content for file. :param buff: the buffer with content for file.
:param remote_path: the path to save file remotely on WebDAV server. :param remote_path: the path to save file remotely on WebDAV server.
@ -389,11 +393,12 @@ class Client(object):
if not self.check(urn.parent()): if not self.check(urn.parent()):
raise RemoteParentNotFound(urn.path()) raise RemoteParentNotFound(urn.path())
self.execute_request(action='upload_from', path=urn.quote(), data=buff) self.execute_request(action='upload', path=urn.quote(), data=buff)
def upload(self, remote_path, local_path, progress=None): def upload(self, remote_path, local_path, progress=None):
"""Uploads resource to remote path on WebDAV server. """Uploads resource to remote path on WebDAV server.
In case resource is directory it will upload all nested files and directories. In case resource is directory it will upload all nested files and directories.
More information you can find by link http://webdav.org/specs/rfc4918.html#METHOD_PUT
:param remote_path: the path for uploading resources on WebDAV server. Can be file and directory. :param remote_path: the path for uploading resources on WebDAV server. Can be file and directory.
:param local_path: the path to local resource for uploading. :param local_path: the path to local resource for uploading.
@ -435,8 +440,8 @@ class Client(object):
@wrap_connection_error @wrap_connection_error
def upload_file(self, remote_path, local_path, progress=None): def upload_file(self, remote_path, local_path, progress=None):
"""Uploads file to remote path on WebDAV server. """Uploads file to remote path on WebDAV server. File should be 2Gb or less.
File should be 2Gb or less. More information you can find by link http://webdav.org/specs/rfc4918.html#METHOD_PUT
:param remote_path: the path to uploading file on WebDAV server. :param remote_path: the path to uploading file on WebDAV server.
:param local_path: the path to local file for uploading. :param local_path: the path to local file for uploading.
@ -460,7 +465,7 @@ class Client(object):
if file_size > self.large_size: if file_size > self.large_size:
raise ResourceTooBig(path=local_path, size=file_size, max_size=self.large_size) raise ResourceTooBig(path=local_path, size=file_size, max_size=self.large_size)
self.execute_request(action='upload_file', path=urn.quote(), data=local_file) self.execute_request(action='upload', path=urn.quote(), data=local_file)
def upload_sync(self, remote_path, local_path, callback=None): def upload_sync(self, remote_path, local_path, callback=None):
"""Uploads resource to remote path on WebDAV server synchronously. """Uploads resource to remote path on WebDAV server synchronously.
@ -489,6 +494,7 @@ class Client(object):
@wrap_connection_error @wrap_connection_error
def copy(self, remote_path_from, remote_path_to): def copy(self, remote_path_from, remote_path_to):
"""Copies resource from one place to another on WebDAV server. """Copies resource from one place to another on WebDAV server.
More information you can find by link http://webdav.org/specs/rfc4918.html#METHOD_COPY
:param remote_path_from: the path to resource which will be copied, :param remote_path_from: the path to resource which will be copied,
:param remote_path_to: the path where resource will be copied. :param remote_path_to: the path where resource will be copied.
@ -507,6 +513,7 @@ class Client(object):
@wrap_connection_error @wrap_connection_error
def move(self, remote_path_from, remote_path_to, overwrite=False): def move(self, remote_path_from, remote_path_to, overwrite=False):
"""Moves resource from one place to another on WebDAV server. """Moves resource from one place to another on WebDAV server.
More information you can find by link http://webdav.org/specs/rfc4918.html#METHOD_MOVE
:param remote_path_from: the path to resource which will be moved, :param remote_path_from: the path to resource which will be moved,
:param remote_path_to: the path where resource will be moved. :param remote_path_to: the path where resource will be moved.
@ -524,87 +531,38 @@ class Client(object):
header_overwrite = "Overwrite: {flag}".format(flag="T" if overwrite else "F") header_overwrite = "Overwrite: {flag}".format(flag="T" if overwrite else "F")
self.execute_request(action='move', path=urn_from.quote(), headers_ext=[header_destination, header_overwrite]) self.execute_request(action='move', path=urn_from.quote(), headers_ext=[header_destination, header_overwrite])
# TODO refactor code below and write tests for it.
@wrap_connection_error @wrap_connection_error
def clean(self, remote_path): def clean(self, remote_path):
"""Cleans (Deletes) a remote resource on WebDAV server. The name of method is not changed for back compatibility
with original library.
More information you can find by link http://webdav.org/specs/rfc4918.html#METHOD_DELETE
:param remote_path: the remote resource whisch will be deleted.
"""
urn = Urn(remote_path) urn = Urn(remote_path)
self.execute_request(action='clean', path=urn.quote())
url = {'hostname': self.webdav.hostname, 'root': self.webdav.root, 'path': urn.quote()}
options = {
'URL': "{hostname}{root}{path}".format(**url),
'CUSTOMREQUEST': Client.requests['clean'],
'HTTPHEADER': self.get_headers('clean', None)
}
response = requests.request(
options["CUSTOMREQUEST"],
options["URL"],
auth=(self.webdav.login, self.webdav.password),
headers=self.get_headers('clean', None),
)
# TODO: check response status
@wrap_connection_error @wrap_connection_error
def info(self, remote_path): def info(self, remote_path):
"""Gets information about resource on WebDAV.
More information you can find by link http://webdav.org/specs/rfc4918.html#METHOD_PROPFIND
def parse(response, path): :param remote_path: the path to remote resource.
:return: a dictionary of information attributes and them values with following keys:
try: `created`: date of resource creation,
response_str = response.content `name`: name of resource,
tree = etree.fromstring(response_str) `size`: size of resource,
`modified`: date of resource modification.
find_attributes = { """
'created': ".//{DAV:}creationdate",
'name': ".//{DAV:}displayname",
'size': ".//{DAV:}getcontentlength",
'modified': ".//{DAV:}getlastmodified"
}
resps = tree.findall("{DAV:}response")
for resp in resps:
href = resp.findtext("{DAV:}href")
urn = unquote(href)
if path[-1] == Urn.separate:
if not path == urn:
continue
else:
path_with_sep = "{path}{sep}".format(path=path, sep=Urn.separate)
if not path == urn and not path_with_sep == urn:
continue
info = dict()
for (name, value) in find_attributes.items():
info[name] = resp.findtext(value)
return info
raise RemoteResourceNotFound(path)
except etree.XMLSyntaxError:
raise MethodNotSupported(name="info", server=self.webdav.hostname)
urn = Urn(remote_path) urn = Urn(remote_path)
# response = BytesIO()
if not self.check(urn.path()) and not self.check(Urn(remote_path, directory=True).path()): if not self.check(urn.path()) and not self.check(Urn(remote_path, directory=True).path()):
raise RemoteResourceNotFound(remote_path) raise RemoteResourceNotFound(remote_path)
url = {'hostname': self.webdav.hostname, 'root': self.webdav.root, 'path': urn.quote()} response = self.execute_request(action='info', path=urn.quote())
options = {
'URL': "{hostname}{root}{path}".format(**url),
'CUSTOMREQUEST': Client.requests['info'],
}
response = requests.request(
options["CUSTOMREQUEST"],
options["URL"],
auth=(self.webdav.login, self.webdav.password),
headers=self.get_headers('info', None)
)
path = "{root}{path}".format(root=self.webdav.root, path=urn.path()) path = "{root}{path}".format(root=self.webdav.root, path=urn.path())
return parse(response, path) return WebDavXmlUtils.parse_info_response(content=response.content, path=path, hostname=self.webdav.hostname)
# TODO refactor code below and write tests for it.
@wrap_connection_error @wrap_connection_error
def is_dir(self, remote_path): def is_dir(self, remote_path):
@ -665,8 +623,7 @@ class Client(object):
@wrap_connection_error @wrap_connection_error
def get_property(self, remote_path, option): def get_property(self, remote_path, option):
""" """Gets metadata property of remote resource on WebDAV server.
Gets metadata property of remote resource on WebDAV server.
More information you can find by link http://webdav.org/specs/rfc4918.html#METHOD_PROPFIND More information you can find by link http://webdav.org/specs/rfc4918.html#METHOD_PROPFIND
:param remote_path: the path to remote resource. :param remote_path: the path to remote resource.
@ -685,8 +642,7 @@ class Client(object):
@wrap_connection_error @wrap_connection_error
def set_property(self, remote_path, option): def set_property(self, remote_path, option):
""" """Sets metadata property of remote resource on WebDAV server.
Sets metadata property of remote resource on WebDAV server.
More information you can find by link http://webdav.org/specs/rfc4918.html#METHOD_PROPPATCH More information you can find by link http://webdav.org/specs/rfc4918.html#METHOD_PROPPATCH
:param remote_path: the path to remote resource. :param remote_path: the path to remote resource.
@ -866,8 +822,7 @@ class WebDavXmlUtils:
@staticmethod @staticmethod
def parse_get_list_response(content): def parse_get_list_response(content):
""" """Parses of response content XML from WebDAV server and extract file and directory names.
Parses of response content XML from WebDAV server and extract file and directory names.
:param content: the XML content of HTTP response from WebDAV server for getting list of files by remote path. :param content: the XML content of HTTP response from WebDAV server for getting list of files by remote path.
:return: list of extracted file or directory names. :return: list of extracted file or directory names.
@ -881,8 +836,7 @@ class WebDavXmlUtils:
@staticmethod @staticmethod
def create_free_space_request_content(): def create_free_space_request_content():
""" """Creates an XML for requesting of free space on remote WebDAV server.
Creates an XML for requesting of free space on remote WebDAV server.
:return: the XML string of request content. :return: the XML string of request content.
""" """
@ -895,8 +849,7 @@ class WebDavXmlUtils:
@staticmethod @staticmethod
def parse_free_space_response(content, hostname): def parse_free_space_response(content, hostname):
""" """Parses of response content XML from WebDAV server and extract an amount of free space.
Parses of response content XML from WebDAV server and extract na amount of free space.
:param content: the XML content of HTTP response from WebDAV server for getting free space. :param content: the XML content of HTTP response from WebDAV server for getting free space.
:param hostname: the server hostname. :param hostname: the server hostname.
@ -915,9 +868,52 @@ class WebDavXmlUtils:
return str() return str()
@staticmethod @staticmethod
def create_get_property_request_content(option): def parse_info_response(content, path, hostname):
"""Parses of response content XML from WebDAV server and extract an information about resource.
:param content: the XML content of HTTP response from WebDAV server.
:param path: the path to resource.
:param hostname: the server hostname.
:return: a dictionary of information attributes and them values with following keys:
`created`: date of resource creation,
`name`: name of resource,
`size`: size of resource,
`modified`: date of resource modification.
""" """
Creates an XML for requesting of getting a property value of remote WebDAV resource. try:
tree = etree.fromstring(content)
find_attributes = {
'created': ".//{DAV:}creationdate",
'name': ".//{DAV:}displayname",
'size': ".//{DAV:}getcontentlength",
'modified': ".//{DAV:}getlastmodified"
}
responses = tree.findall("{DAV:}response")
for resp in responses:
href = resp.findtext("{DAV:}href")
urn = unquote(href)
if path[-1] == Urn.separate:
if not path == urn:
continue
else:
path_with_sep = "{path}{sep}".format(path=path, sep=Urn.separate)
if not path == urn and not path_with_sep == urn:
continue
info = dict()
for (name, value) in find_attributes.items():
info[name] = resp.findtext(value)
return info
raise RemoteResourceNotFound(path)
except etree.XMLSyntaxError:
raise MethodNotSupported(name="info", server=hostname)
@staticmethod
def create_get_property_request_content(option):
"""Creates an XML for requesting of getting a property value of remote WebDAV resource.
:param option: the property attributes as dictionary with following keys: :param option: the property attributes as dictionary with following keys:
`namespace`: (optional) the namespace for XML property which will be get, `namespace`: (optional) the namespace for XML property which will be get,
@ -932,8 +928,7 @@ class WebDavXmlUtils:
@staticmethod @staticmethod
def parse_get_property_response(content, name): def parse_get_property_response(content, name):
""" """Parses of response content XML from WebDAV server for getting metadata property value for some resource.
Parses of response content XML from WebDAV server for getting metadata property value for some resource.
:param content: the XML content of response as string. :param content: the XML content of response as string.
:param name: the name of property for finding a value in response :param name: the name of property for finding a value in response
@ -944,8 +939,7 @@ class WebDavXmlUtils:
@staticmethod @staticmethod
def create_set_property_request_content(option): def create_set_property_request_content(option):
""" """Creates an XML for requesting of setting a property value for remote WebDAV resource.
Creates an XML for requesting of setting a property value for remote WebDAV resource.
:param option: the property attributes as dictionary with following keys: :param option: the property attributes as dictionary with following keys:
`namespace`: (optional) the namespace for XML property which will be set, `namespace`: (optional) the namespace for XML property which will be set,
@ -963,8 +957,7 @@ class WebDavXmlUtils:
@staticmethod @staticmethod
def etree_to_string(tree): def etree_to_string(tree):
""" """Creates string from lxml.etree.ElementTree with XML declaration and UTF-8 encoding.
Creates string from lxml.etree.ElementTree with XML declaration and UTF-8 encoding.
:param tree: the instance of ElementTree :param tree: the instance of ElementTree
:return: the string of XML. :return: the string of XML.