From c5a5fc7854f62092fd4b25706e1460eff91a1067 Mon Sep 17 00:00:00 2001 From: "evgeny.ezhov" Date: Wed, 18 Oct 2017 18:29:50 +0300 Subject: [PATCH] Fixing and refactoring of clean/info resource methods --- README.rst | 2 + tests/test_client_it.py | 17 +++- tests/test_client_unit.py | 14 +++ webdav3/client.py | 185 ++++++++++++++++++-------------------- 4 files changed, 119 insertions(+), 99 deletions(-) diff --git a/README.rst b/README.rst index 9a0b28b..5930284 100644 --- a/README.rst +++ b/README.rst @@ -13,6 +13,8 @@ Version 0.3 - TBD - Setting of WebDAV resource property value - Coping 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 * Refactoring of WebDAV client and making it works in following methods: diff --git a/tests/test_client_it.py b/tests/test_client_it.py index 6abe46e..51138e0 100644 --- a/tests/test_client_it.py +++ b/tests/test_client_it.py @@ -102,7 +102,6 @@ class ClientTestCase(TestCase): buff = StringIO(u'test content for testing of webdav client') 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.test_download_to() def test_upload(self): self._prepare_for_uploading() @@ -146,6 +145,20 @@ class ClientTestCase(TestCase): self.assertFalse(self.client.check(remote_path=self.remote_path_file)) 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): self._prepare_for_downloading() 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) 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): self.client.mkdir(remote_path=self.remote_path_dir) if not path.exists(path=self.local_path_dir): diff --git a/tests/test_client_unit.py b/tests/test_client_unit.py index dce51c9..c7d1630 100644 --- a/tests/test_client_unit.py +++ b/tests/test_client_unit.py @@ -32,6 +32,20 @@ class ClientTestCase(TestCase): result = utils.parse_free_space_response(content, 'localhost') self.assertEquals(result, 10737417543) + def test_parse_info_response(self): + content = '' \ + '/test_dir/test.txtHTTP/1.1 200 OK' \ + 'Wed, 18 Oct 2017 15:16:04 GMT' \ + 'ab0b4b7973803c03639b848682b5f38ctext/plain' \ + '41test.txt' \ + '2017-10-18T15:16:04Z' \ + '' + 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): option = { 'namespace': 'test', diff --git a/webdav3/client.py b/webdav3/client.py index 004ca39..8a3298c 100644 --- a/webdav3/client.py +++ b/webdav3/client.py @@ -158,10 +158,7 @@ class Client(object): # mapping of actions to WebDAV methods requests = { 'download': "GET", - 'download_to': "GET", - 'download_file': "GET", - 'upload_from': "PUT", - 'upload_file': "PUT", + 'upload': "PUT", 'copy': "COPY", 'move': "MOVE", 'mkdir': "MKCOL", @@ -222,6 +219,7 @@ class Client(object): @wrap_connection_error def list(self, remote_path=root): """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. :return: list of nested file or directory names. @@ -240,6 +238,7 @@ class Client(object): @wrap_connection_error def free(self): """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. """ @@ -249,7 +248,8 @@ class Client(object): @wrap_connection_error 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. :return: True if resource is exist or False otherwise @@ -267,6 +267,7 @@ class Client(object): @wrap_connection_error def mkdir(self, remote_path): """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 :return: True if request executed with code 200 and False otherwise. @@ -293,11 +294,12 @@ class Client(object): if not self.check(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) def download(self, remote_path, local_path, progress=None): """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 local_path: the path to save resource locally. @@ -334,6 +336,7 @@ class Client(object): @wrap_connection_error def download_file(self, remote_path, local_path, progress=None): """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 local_path: the path to save file locally. @@ -350,7 +353,7 @@ class Client(object): raise RemoteResourceNotFound(urn.path()) 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): local_file.write(block) @@ -378,6 +381,7 @@ class Client(object): @wrap_connection_error def upload_to(self, buff, remote_path): """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 remote_path: the path to save file remotely on WebDAV server. @@ -389,11 +393,12 @@ class Client(object): if not self.check(urn.parent()): 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): """Uploads resource to remote path on WebDAV server. 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 local_path: the path to local resource for uploading. @@ -435,8 +440,8 @@ class Client(object): @wrap_connection_error def upload_file(self, remote_path, local_path, progress=None): - """Uploads file to remote path on WebDAV server. - File should be 2Gb or less. + """Uploads file to remote path on WebDAV server. 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 local_path: the path to local file for uploading. @@ -460,7 +465,7 @@ class Client(object): if file_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): """Uploads resource to remote path on WebDAV server synchronously. @@ -489,6 +494,7 @@ class Client(object): @wrap_connection_error def copy(self, remote_path_from, remote_path_to): """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_to: the path where resource will be copied. @@ -507,6 +513,7 @@ class Client(object): @wrap_connection_error def move(self, remote_path_from, remote_path_to, overwrite=False): """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_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") 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 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) - - 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 + self.execute_request(action='clean', path=urn.quote()) @wrap_connection_error 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): - - try: - response_str = response.content - tree = etree.fromstring(response_str) - - 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) - + :param remote_path: the path to remote resource. + :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. + """ urn = Urn(remote_path) - # response = BytesIO() - if not self.check(urn.path()) and not self.check(Urn(remote_path, directory=True).path()): raise RemoteResourceNotFound(remote_path) - url = {'hostname': self.webdav.hostname, 'root': self.webdav.root, '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) - ) + response = self.execute_request(action='info', path=urn.quote()) 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 def is_dir(self, remote_path): @@ -665,8 +623,7 @@ class Client(object): @wrap_connection_error 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 :param remote_path: the path to remote resource. @@ -685,8 +642,7 @@ class Client(object): @wrap_connection_error 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 :param remote_path: the path to remote resource. @@ -866,8 +822,7 @@ class WebDavXmlUtils: @staticmethod 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. :return: list of extracted file or directory names. @@ -881,8 +836,7 @@ class WebDavXmlUtils: @staticmethod 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. """ @@ -895,8 +849,7 @@ class WebDavXmlUtils: @staticmethod def parse_free_space_response(content, hostname): - """ - Parses of response content XML from WebDAV server and extract na amount of free space. + """Parses of response content XML from WebDAV server and extract an amount of free space. :param content: the XML content of HTTP response from WebDAV server for getting free space. :param hostname: the server hostname. @@ -915,9 +868,52 @@ class WebDavXmlUtils: return str() @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: `namespace`: (optional) the namespace for XML property which will be get, @@ -932,8 +928,7 @@ class WebDavXmlUtils: @staticmethod 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 name: the name of property for finding a value in response @@ -944,8 +939,7 @@ class WebDavXmlUtils: @staticmethod 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: `namespace`: (optional) the namespace for XML property which will be set, @@ -963,8 +957,7 @@ class WebDavXmlUtils: @staticmethod 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 :return: the string of XML.