diff --git a/.travis.yml b/.travis.yml index 0873913..9ccd034 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,16 +4,15 @@ dist: xenial addons: sonarcloud: organization: "ezhov-evgeny" - token: f0f714f3bea6bd103e3eb82724ef3bb0d3b54d1d + token: + secure: f0f714f3bea6bd103e3eb82724ef3bb0d3b54d1d services: - docker python: - - "3.5" - - "3.6" + - "2.7" - "3.7" - - "3.8" before_install: - docker pull bytemark/webdav @@ -22,11 +21,6 @@ before_install: install: - python setup.py develop - - pip install coverage script: - - coverage run setup.py test - - coverage xml - - | - if [[ $TRAVIS_PYTHON_VERSION == "3.8" ]]; then - sonar-scanner - fi + - python setup.py test + - sonar-scanner -X diff --git a/README.md b/README.md index a6eef1a..03a51a9 100644 --- a/README.md +++ b/README.md @@ -1,315 +1,28 @@ -webdavclient3 +webdavclient3 [![Build Status](https://travis-ci.com/ezhov-evgeny/webdav-client-python-3.svg?branch=develop)](https://travis-ci.com/ezhov-evgeny/webdav-client-python-3) [![PyPI](https://img.shields.io/pypi/v/webdavclient3)](https://pypi.org/project/webdavclient3/) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/webdavclient3) ========= -[![Build Status](https://travis-ci.com/ezhov-evgeny/webdav-client-python-3.svg?branch=develop)](https://travis-ci.com/ezhov-evgeny/webdav-client-python-3) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ezhov-evgeny_webdav-client-python-3&metric=alert_status)](https://sonarcloud.io/dashboard?id=ezhov-evgeny_webdav-client-python-3) -[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=ezhov-evgeny_webdav-client-python-3&metric=coverage)](https://sonarcloud.io/dashboard?id=ezhov-evgeny_webdav-client-python-3) -[![PyPI](https://img.shields.io/pypi/v/webdavclient3)](https://pypi.org/project/webdavclient3/) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/webdavclient3) -Package webdavclient3 based on https://github.com/designerror/webdav-client-python but uses `requests` instead of `PyCURL`. -It provides easy way to work with WebDAV-servers. - -Installation ------------- -```bash -$ pip install webdavclient3 -``` +Based on https://github.com/designerror/webdav-client-python +But uses `requests` instead of `PyCURL` Sample Usage ------------ -```python -from webdav3.client import Client -options = { - 'webdav_hostname': "https://webdav.server.ru", - 'webdav_login': "login", - 'webdav_password': "password" -} -client = Client(options) -client.verify = False # To not check SSL certificates (Default = True) -client.session.proxies(...) # To set proxy directly into the session (Optional) -client.session.auth(...) # To set proxy auth directly into the session (Optional) -client.execute_request("mkdir", 'directory_name') +`pip install webdavclient3` ``` - -Webdav API -========== - -Webdav API is a set of webdav actions of work with cloud storage. This set includes the following actions: -`check`, `free`, `info`, `list`, `mkdir`, `clean`, `copy`, `move`, `download`, `upload`, `publish` and `unpublish`. - -**Configuring the client** - -Required keys for configuring client connection with WevDAV-server are `webdav_hostname` and `webdav_login`, `webdav_password`. - -```python -from webdav3.client import Client - -options = { - 'webdav_hostname': "https://webdav.server.ru", - 'webdav_login': "login", - 'webdav_password': "password" -} -client = Client(options) -``` - -If your server does not support `HEAD` method or there are other reasons to override default WebDAV methods for actions use a dictionary option `webdav_override_methods`. -The key should be in the following list: `check`, `free`, `info`, `list`, `mkdir`, `clean`, `copy`, `move`, `download`, `upload`, - `publish` and `unpublish`. The value should a string name of WebDAV method, for example `GET`. - -```python -from webdav3.client import Client - -options = { - 'webdav_hostname': "https://webdav.server.ru", - 'webdav_login': "login", - 'webdav_password': "password", - 'webdav_override_methods': { - 'check': 'GET' - } - -} -client = Client(options) -``` - -When a proxy server you need to specify settings to connect through it. - -```python -from webdav3.client import Client -options = { - 'webdav_hostname': "https://webdav.server.ru", - 'webdav_login': "w_login", - 'webdav_password': "w_password", - 'proxy_hostname': "http://127.0.0.1:8080", - 'proxy_login': "p_login", - 'proxy_password': "p_password" -} -client = Client(options) -``` - -If you want to use the certificate path to certificate and private key is defined as follows: - -```python -from webdav3.client import Client -options = { - 'webdav_hostname': "https://webdav.server.ru", - 'webdav_login': "w_login", - 'webdav_password': "w_password", - 'cert_path': "/etc/ssl/certs/certificate.crt", - 'key_path': "/etc/ssl/private/certificate.key" -} -client = Client(options) -``` - -Or you want to limit the speed or turn on verbose mode: - -```python -options = { - ... - 'recv_speed' : 3000000, - 'send_speed' : 3000000, - 'verbose' : True -} -client = Client(options) -``` - -recv_speed: rate limit data download speed in Bytes per second. Defaults to unlimited speed. -send_speed: rate limit data upload speed in Bytes per second. Defaults to unlimited speed. -verbose: set verbose mode on/off. By default verbose mode is off. - -Also if your server does not support `check` it is possible to disable it: - -```python -options = { - ... - 'disable_check': True -} -client = Client(options) -``` - -By default checking of remote resources is enabled. - -**Synchronous methods** - -```python -# Checking existence of the resource - -client.check("dir1/file1") -client.check("dir1") -``` - -```python -# Get information about the resource - -client.info("dir1/file1") -client.info("dir1/") -``` - -```python -# Check free space - -free_size = client.free() -``` - -```python -# Get a list of resources - -files1 = client.list() -files2 = client.list("dir1") -``` - -```python -# Create directory - -client.mkdir("dir1/dir2") -``` - -```python -# Delete resource - -client.clean("dir1/dir2") -``` - -```python -# Copy resource - -client.copy(remote_path_from="dir1/file1", remote_path_to="dir2/file1") -client.copy(remote_path_from="dir2", remote_path_to="dir3") -``` - -```python -# Move resource - -client.move(remote_path_from="dir1/file1", remote_path_to="dir2/file1") -client.move(remote_path_from="dir2", remote_path_to="dir3") -``` - -```python -# Move resource - -client.download_sync(remote_path="dir1/file1", local_path="~/Downloads/file1") -client.download_sync(remote_path="dir1/dir2/", local_path="~/Downloads/dir2/") -``` - -```python -# Unload resource - -client.upload_sync(remote_path="dir1/file1", local_path="~/Documents/file1") -client.upload_sync(remote_path="dir1/dir2/", local_path="~/Documents/dir2/") -``` - -```python -# Publish the resource - -link = client.publish("dir1/file1") -link = client.publish("dir2") -``` - -```python -# Unpublish resource - -client.unpublish("dir1/file1") -client.unpublish("dir2") -``` - -```python -# Exception handling - -from webdav3.client import WebDavException -try: -... -except WebDavException as exception: -... -``` - -```python -# Get the missing files - -client.pull(remote_directory='dir1', local_directory='~/Documents/dir1') -``` - -```python -# Send missing files - -client.push(remote_directory='dir1', local_directory='~/Documents/dir1') -``` - -**Asynchronous methods** - -```python -# Load resource - -kwargs = { - 'remote_path': "dir1/file1", - 'local_path': "~/Downloads/file1", - 'callback': callback -} -client.download_async(**kwargs) - -kwargs = { - 'remote_path': "dir1/dir2/", - 'local_path': "~/Downloads/dir2/", - 'callback': callback -} -client.download_async(**kwargs) -``` - -```python -# Unload resource - -kwargs = { - 'remote_path': "dir1/file1", - 'local_path': "~/Downloads/file1", - 'callback': callback -} -client.upload_async(**kwargs) - -kwargs = { - 'remote_path': "dir1/dir2/", - 'local_path': "~/Downloads/dir2/", - 'callback': callback -} -client.upload_async(**kwargs) -``` - -Resource API -============ - -Resource API using the concept of OOP that enables cloud-level resources. - -```python -# Get a resource - -res1 = client.resource("dir1/file1") -``` - -```python -# Work with the resource - -res1.rename("file2") -res1.move("dir1/file2") -res1.copy("dir2/file1") -info = res1.info() -res1.read_from(buffer) -res1.read(local_path="~/Documents/file1") -res1.read_async(local_path="~/Documents/file1", callback) -res1.write_to(buffer) -res1.write(local_path="~/Downloads/file1") -res1.write_async(local_path="~/Downloads/file1", callback) +>>> from webdav3.client +>>> options = { +... webdav_hostname : +... webdav_login : +... webdav_password : +... } +>>> client = Client(options) +>>> client.verify = False # To not check SSL certificates (Default = True) +>>> client.session.proxies(...) # To set proxy directly into the session (Optional) +>>> client.session.auth(...) # To set proxy auth directly into the session (Optional) +>>> client.execute_request("mkdir", ) ``` Release Notes ------------- -**Version 3.14.1** - * Fixed issue during coping and moving files with cyrillic names - * Support OAuth2 bearer tokens by https://github.com/danielloader - -**Version 3.14** - * Override methods for customizing communication with WebDAV servers - * Support multiple clients simultaneously - * Sync modified files during pull and push by https://github.com/mont5piques - -**Version 0.14** - * Fixed an issue with checking resources on Yandex WebDAV server - **Version 0.13 – 27.11.2019** * Main version of Python is updated up to 3.7 * Switch to use python sessions rather than requests by https://github.com/delrey1 diff --git a/setup.py b/setup.py index abd81c1..d278cc7 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import setup, find_packages from setuptools.command.install import install as InstallCommand from setuptools.command.test import test as TestCommand -version = "3.14.1" +version = "0.13" requirements = "libxml2-dev libxslt-dev python-dev" @@ -51,8 +51,8 @@ setup( name='webdavclient3', version=version, packages=find_packages(), - requires=['python (>= 3.3.0)'], - install_requires=['requests', 'lxml', 'python-dateutil'], + requires=['python (>= 2.7.6)'], + install_requires=['requests', 'lxml', 'argcomplete'], scripts=['wdc'], test_suite='tests', tests_require=['pytest'], @@ -73,10 +73,8 @@ setup( 'Operating System :: MacOS', 'Operating System :: Microsoft', 'Operating System :: Unix', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', 'Topic :: Internet', 'Topic :: Software Development :: Libraries :: Python Modules', ], diff --git a/sonar-project.properties b/sonar-project.properties index 935a751..9b05135 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -4,8 +4,6 @@ sonar.organization=ezhov-evgeny sonar.projectName=webdav-client-python-3 sonar.projectVersion=0.13 # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. -sonar.sources=webdav3 -sonar.tests=tests -sonar.python.coverage.reportPaths=coverage.xml +sonar.sources=. # Encoding of the source code. Default is default system encoding sonar.sourceEncoding=UTF-8 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/base_client_it.py b/tests/base_client_it.py deleted file mode 100644 index 5b4290f..0000000 --- a/tests/base_client_it.py +++ /dev/null @@ -1,83 +0,0 @@ -import os -import shutil -import unittest -from os import path - -from webdav3.client import Client - - -class BaseClientTestCase(unittest.TestCase): - remote_path_file = 'test_dir/test.txt' - remote_path_file2 = 'test_dir2/test.txt' - remote_inner_path_file = 'test_dir/inner/test.txt' - remote_path_dir = 'test_dir' - remote_path_dir2 = 'test_dir2' - remote_inner_path_dir = 'test_dir/inner' - inner_dir_name = 'inner' - local_base_dir = 'tests/' - local_file = 'test.txt' - local_file_path = local_base_dir + 'test.txt' - local_path_dir = local_base_dir + 'res/test_dir' - - options = { - 'webdav_hostname': 'http://localhost:8585', - 'webdav_login': 'alice', - 'webdav_password': 'secret1234', - 'webdav_override_methods': { - 'check': 'GET' - } - } - - # options = { - # 'webdav_hostname': 'https://webdav.yandex.ru', - # 'webdav_login': 'webdavclient.test2', - # 'webdav_password': 'Qwerty123!' - # } - - def setUp(self): - self.client = Client(self.options) - self.clean_local_dir(self.local_path_dir) - - def tearDown(self): - self.clean_local_dir(self.local_path_dir) - self.clean_remote_dir(self.remote_path_dir) - self.clean_remote_dir(self.remote_path_dir2) - - def clean_remote_dir(self, remote_path_dir): - if self.client.check(remote_path=remote_path_dir): - self.client.clean(remote_path=remote_path_dir) - - @staticmethod - def clean_local_dir(local_path_dir): - if path.exists(path=local_path_dir): - shutil.rmtree(path=local_path_dir) - - def _prepare_for_downloading(self, inner_dir=False, base_path=''): - if base_path: - self._create_remote_dir_if_needed(base_path) - self._prepare_dir_for_downloading(base_path + self.remote_path_dir, base_path + self.remote_path_file, self.local_file_path) - if not path.exists(self.local_path_dir): - os.makedirs(self.local_path_dir) - if inner_dir: - self._prepare_dir_for_downloading(base_path + self.remote_inner_path_dir, base_path + self.remote_inner_path_file, self.local_file_path) - - def _prepare_dir_for_downloading(self, remote_path_dir, remote_path_file, local_file_path): - self._create_remote_dir_if_needed(remote_path_dir) - if not self.client.check(remote_path=remote_path_file): - self.client.upload_file(remote_path=remote_path_file, local_path=local_file_path) - - def _create_remote_dir_if_needed(self, remote_dir): - if not self.client.check(remote_path=remote_dir): - self.client.mkdir(remote_path=remote_dir) - - def _prepare_for_uploading(self): - 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): - os.makedirs(self.local_path_dir) - if not path.exists(path=self.local_path_dir + os.sep + self.local_file): - shutil.copy(src=self.local_file_path, dst=self.local_path_dir + os.sep + self.local_file) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_client_it.py b/tests/test_client_it.py index 515cca6..f8c5856 100644 --- a/tests/test_client_it.py +++ b/tests/test_client_it.py @@ -3,14 +3,41 @@ import shutil import unittest from io import BytesIO, StringIO from os import path -from time import sleep +from unittest import TestCase -from tests.base_client_it import BaseClientTestCase -from webdav3.exceptions import MethodNotSupported, OptionNotValid, RemoteResourceNotFound +from webdav3.client import Client +from webdav3.exceptions import MethodNotSupported -class ClientTestCase(BaseClientTestCase): - pulled_file = BaseClientTestCase.local_path_dir + os.sep + BaseClientTestCase.local_file +class ClientTestCase(TestCase): + remote_path_file = 'test_dir/test.txt' + remote_path_file2 = 'test_dir2/test.txt' + remote_inner_path_file = 'test_dir/inner/test.txt' + remote_path_dir = 'test_dir' + remote_path_dir2 = 'test_dir2' + remote_inner_path_dir = 'test_dir/inner' + local_base_dir = 'tests/' + local_file = 'test.txt' + local_file_path = local_base_dir + 'test.txt' + local_path_dir = local_base_dir + 'res/test_dir' + + def setUp(self): + options = { + 'webdav_hostname': 'http://localhost:8585', + 'webdav_login': 'alice', + 'webdav_password': 'secret1234' + } + self.client = Client(options) + if path.exists(path=self.local_path_dir): + shutil.rmtree(path=self.local_path_dir) + + def tearDown(self): + if path.exists(path=self.local_path_dir): + shutil.rmtree(path=self.local_path_dir) + if self.client.check(remote_path=self.remote_path_dir): + self.client.clean(remote_path=self.remote_path_dir) + if self.client.check(remote_path=self.remote_path_dir2): + self.client.clean(remote_path=self.remote_path_dir2) def test_list(self): self._prepare_for_downloading() @@ -19,10 +46,7 @@ class ClientTestCase(BaseClientTestCase): self.assertGreater(file_list.__len__(), 0, 'Expected that amount of files more then 0') def test_free(self): - if 'localhost' in self.options['webdav_hostname']: - with self.assertRaises(MethodNotSupported): - self.client.free() - else: + with self.assertRaises(MethodNotSupported): self.assertGreater(self.client.free(), 0, 'Expected that free space on WebDAV server is more then 0 bytes') def test_check(self): @@ -34,29 +58,12 @@ class ClientTestCase(BaseClientTestCase): self.client.mkdir(remote_path=self.remote_path_dir) self.assertTrue(self.client.check(remote_path=self.remote_path_dir), 'Expected the directory is created.') - def test_download_from(self): + def test_download_to(self): self._prepare_for_downloading() buff = BytesIO() self.client.download_from(buff=buff, remote_path=self.remote_path_file) self.assertEqual(buff.getvalue(), b'test content for testing of webdav client') - def test_download_from_dir(self): - self._prepare_for_downloading() - buff = BytesIO() - with self.assertRaises(OptionNotValid): - self.client.download_from(buff=buff, remote_path=self.remote_path_dir) - - def test_download_from_wrong_file(self): - self._prepare_for_downloading() - buff = BytesIO() - with self.assertRaises(RemoteResourceNotFound): - self.client.download_from(buff=buff, remote_path='wrong') - - def test_download_directory_wrong(self): - self._prepare_for_downloading() - with self.assertRaises(RemoteResourceNotFound): - self.client.download_directory(remote_path=self.remote_path_file, local_path=self.local_path_dir) - def test_download(self): self._prepare_for_downloading() self.client.download(local_path=self.local_path_dir, remote_path=self.remote_path_dir) @@ -94,8 +101,6 @@ class ClientTestCase(BaseClientTestCase): remote_path=self.remote_path_file, callback=callback) self.assertFalse(path.exists(self.local_path_dir + os.path.sep + self.local_file), 'Expected the file has not been downloaded yet') - # It needs for ending download before environment will be cleaned in tearDown - sleep(0.4) def test_upload_from(self): self._prepare_for_uploading() @@ -208,10 +213,10 @@ class ClientTestCase(BaseClientTestCase): self._prepare_for_downloading(True) self.client.pull(self.remote_path_dir, self.local_path_dir) self.assertTrue(path.exists(self.local_path_dir), 'Expected the directory is downloaded.') - self.assertTrue(path.exists(self.local_path_dir + os.path.sep + self.inner_dir_name), 'Expected the directory is downloaded.') - self.assertTrue(path.exists(self.local_path_dir + os.path.sep + self.inner_dir_name), 'Expected the directory is downloaded.') + self.assertTrue(path.exists(self.local_path_dir + os.path.sep + 'inner'), 'Expected the directory is downloaded.') + self.assertTrue(path.exists(self.local_path_dir + os.path.sep + 'inner'), 'Expected the directory is downloaded.') self.assertTrue(path.isdir(self.local_path_dir), 'Expected this is a directory.') - self.assertTrue(path.isdir(self.local_path_dir + os.path.sep + self.inner_dir_name), 'Expected this is a directory.') + self.assertTrue(path.isdir(self.local_path_dir + os.path.sep + 'inner'), 'Expected this is a directory.') self.assertTrue(path.exists(self.local_path_dir + os.path.sep + self.local_file), 'Expected the file is downloaded') self.assertTrue(path.isfile(self.local_path_dir + os.path.sep + self.local_file), @@ -223,60 +228,26 @@ class ClientTestCase(BaseClientTestCase): self.assertTrue(self.client.check(self.remote_path_dir), 'Expected the directory is created.') self.assertTrue(self.client.check(self.remote_path_file), 'Expected the file is uploaded.') - def test_valid(self): - self.assertTrue(self.client.valid()) + def _prepare_for_downloading(self, inner_dir=False): + if not self.client.check(remote_path=self.remote_path_dir): + self.client.mkdir(remote_path=self.remote_path_dir) + if not self.client.check(remote_path=self.remote_path_file): + self.client.upload_file(remote_path=self.remote_path_file, local_path=self.local_file_path) + if not path.exists(self.local_path_dir): + os.makedirs(self.local_path_dir) + if inner_dir: + if not self.client.check(remote_path=self.remote_inner_path_dir): + self.client.mkdir(remote_path=self.remote_inner_path_dir) + if not self.client.check(remote_path=self.remote_inner_path_file): + self.client.upload_file(remote_path=self.remote_inner_path_file, local_path=self.local_file_path) - def test_check_is_overridden(self): - self.assertEqual('GET', self.client.requests['check']) - - def test_pull_newer(self): - init_modification_time = int(self._prepare_local_test_file_and_get_modification_time()) - sleep(1) - self._prepare_for_downloading(base_path='time/') - result = self.client.pull('time/' + self.remote_path_dir, self.local_path_dir) - update_modification_time = int(os.path.getmtime(self.pulled_file)) - self.assertTrue(result) - self.assertGreater(update_modification_time, init_modification_time) - self.client.clean(remote_path='time/' + self.remote_path_dir) - - def test_pull_older(self): - self._prepare_for_downloading(base_path='time/') - sleep(1) - init_modification_time = int(self._prepare_local_test_file_and_get_modification_time()) - result = self.client.pull('time/' + self.remote_path_dir, self.local_path_dir) - update_modification_time = int(os.path.getmtime(self.pulled_file)) - self.assertFalse(result) - self.assertEqual(update_modification_time, init_modification_time) - self.client.clean(remote_path='time/' + self.remote_path_dir) - - def test_push_newer(self): - self._prepare_for_downloading(base_path='time/') - sleep(1) - self._prepare_for_uploading() - init_modification_time = self.client.info('time/' + self.remote_path_file)['modified'] - result = self.client.push('time/' + self.remote_path_dir, self.local_path_dir) - update_modification_time = self.client.info('time/' + self.remote_path_file)['modified'] - self.assertTrue(result) - self.assertNotEqual(init_modification_time, update_modification_time) - self.client.clean(remote_path='time/' + self.remote_path_dir) - - def test_push_older(self): - self._prepare_for_uploading() - sleep(1) - self._prepare_for_downloading(base_path='time/') - init_modification_time = self.client.info('time/' + self.remote_path_file)['modified'] - result = self.client.push('time/' + self.remote_path_dir, self.local_path_dir) - update_modification_time = self.client.info('time/' + self.remote_path_file)['modified'] - self.assertFalse(result) - self.assertEqual(init_modification_time, update_modification_time) - self.client.clean(remote_path='time/' + self.remote_path_dir) - - def _prepare_local_test_file_and_get_modification_time(self): + def _prepare_for_uploading(self): + 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): - os.mkdir(self.local_path_dir) + os.makedirs(self.local_path_dir) if not path.exists(path=self.local_path_dir + os.sep + self.local_file): - shutil.copy(src=self.local_file_path, dst=self.pulled_file) - return os.path.getmtime(self.pulled_file) + shutil.copy(src=self.local_file_path, dst=self.local_path_dir + os.sep + self.local_file) if __name__ == '__main__': diff --git a/tests/test_client_resource_it.py b/tests/test_client_resource_it.py deleted file mode 100644 index 4f69022..0000000 --- a/tests/test_client_resource_it.py +++ /dev/null @@ -1,75 +0,0 @@ -import unittest - -from tests.base_client_it import BaseClientTestCase -from webdav3.client import Resource -from webdav3.urn import Urn - - -class ResourceTestCase(BaseClientTestCase): - - def test_str(self): - self._prepare_for_downloading() - resource = Resource(self.client, Urn(self.remote_path_file)) - self.assertEqual('resource /test_dir/test.txt', resource.__str__()) - - def test_is_not_dir(self): - self._prepare_for_downloading() - resource = Resource(self.client, Urn(self.remote_path_file)) - self.assertFalse(resource.is_dir()) - - def test_is_dir(self): - self._prepare_for_downloading() - resource = Resource(self.client, Urn(self.remote_path_dir)) - self.assertTrue(resource.is_dir()) - - def test_rename(self): - self._prepare_for_downloading() - resource = Resource(self.client, Urn(self.remote_path_file)) - resource.rename('new_name.text') - self.assertTrue(self.client.check(self.remote_path_dir + '/new_name.text')) - - def test_move(self): - self._prepare_for_downloading() - resource = Resource(self.client, Urn(self.remote_path_file)) - self.client.mkdir(self.remote_path_dir2) - resource.move(self.remote_path_file2) - self.assertFalse(self.client.check(self.remote_path_file)) - self.assertTrue(self.client.check(self.remote_path_file2)) - - def test_copy(self): - self._prepare_for_downloading() - resource = Resource(self.client, Urn(self.remote_path_file)) - self.client.mkdir(self.remote_path_dir2) - resource.copy(self.remote_path_file2) - self.assertTrue(self.client.check(self.remote_path_file)) - self.assertTrue(self.client.check(self.remote_path_file2)) - - def test_info(self): - self._prepare_for_downloading() - resource = Resource(self.client, Urn(self.remote_path_file)) - info = resource.info() - self.assertIsNotNone(info) - self.assertGreater(len(info), 0) - - def test_info_params(self): - self._prepare_for_downloading() - resource = Resource(self.client, Urn(self.remote_path_file)) - info = resource.info(['size']) - self.assertIsNotNone(info) - self.assertEqual(1, len(info)) - self.assertTrue('size' in info) - - def test_clean(self): - self._prepare_for_downloading() - resource = Resource(self.client, Urn(self.remote_path_file)) - resource.clean() - self.assertFalse(self.client.check(self.remote_path_file)) - - def test_check(self): - self._prepare_for_downloading() - resource = Resource(self.client, Urn(self.remote_path_file)) - self.assertTrue(resource.check()) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_client_unit.py b/tests/test_client_unit.py index 538c415..be544d1 100644 --- a/tests/test_client_unit.py +++ b/tests/test_client_unit.py @@ -4,7 +4,7 @@ from unittest import TestCase from lxml.etree import ElementTree, Element -from webdav3.client import WebDavXmlUtils as utils, listdir +from webdav3.client import WebDavXmlUtils as utils class ClientTestCase(TestCase): @@ -135,7 +135,6 @@ class ClientTestCase(TestCase): except: f = open('./tests/response_dir.xml') content = f.read().decode('utf-8').encode('utf-8') - f.close() path = '/test_dir' hostname = 'https://webdav.yandex.ru' result = utils.parse_is_dir_response(content, path, hostname) @@ -156,11 +155,6 @@ class ClientTestCase(TestCase): result = utils.parse_is_dir_response(content.encode('utf-8'), path, hostname) self.assertFalse(result, 'It should be file') - def test_listdir_inner_dir(self): - file_names = listdir('.') - self.assertGreater(len(file_names), 0) - self.assertTrue('webdav3/' in file_names) - if __name__ == '__main__': unittest.main() diff --git a/tests/test_connection.py b/tests/test_connection.py deleted file mode 100644 index dff3772..0000000 --- a/tests/test_connection.py +++ /dev/null @@ -1,91 +0,0 @@ -import unittest - -from webdav3.client import get_options -from webdav3.connection import WebDAVSettings, OptionNotValid, ConnectionSettings - - -class ConnectionTestCase(unittest.TestCase): - def test_connection_settings_valid(self): - options = { - 'webdav_hostname': 'http://localhost:8585', - 'webdav_login': 'alice', - 'webdav_password': 'secret1234' - } - webdav_options = get_options(option_type=WebDAVSettings, from_options=options) - settings = WebDAVSettings(webdav_options) - self.assertTrue(settings.is_valid()) - self.assertTrue(settings.valid()) - - def test_connection_settings_no_hostname(self): - options = { - 'webdav_login': 'alice', - 'webdav_password': 'secret1234' - } - webdav_options = get_options(option_type=WebDAVSettings, from_options=options) - settings = WebDAVSettings(webdav_options) - self.assertRaises(OptionNotValid, settings.is_valid) - self.assertFalse(settings.valid()) - - def test_connection_settings_no_login(self): - options = { - 'webdav_hostname': 'http://localhost:8585', - 'webdav_password': 'secret1234' - } - webdav_options = get_options(option_type=WebDAVSettings, from_options=options) - settings = WebDAVSettings(webdav_options) - self.assertRaises(OptionNotValid, settings.is_valid) - self.assertFalse(settings.valid()) - - def test_connection_settings_no_token_and_no_login(self): - options = { - 'webdav_hostname': 'http://localhost:8585' - } - webdav_options = get_options(option_type=WebDAVSettings, from_options=options) - settings = WebDAVSettings(webdav_options) - self.assertRaises(OptionNotValid, settings.is_valid) - self.assertFalse(settings.valid()) - - def test_connection_settings_wrong_cert_path(self): - options = { - 'webdav_hostname': 'http://localhost:8585', - 'webdav_login': 'alice', - 'cert_path': './wrong.file', - 'webdav_password': 'secret1234' - } - webdav_options = get_options(option_type=WebDAVSettings, from_options=options) - settings = WebDAVSettings(webdav_options) - self.assertRaises(OptionNotValid, settings.is_valid) - self.assertFalse(settings.valid()) - - def test_connection_settings_wrong_key_path(self): - options = { - 'webdav_hostname': 'http://localhost:8585', - 'webdav_login': 'alice', - 'key_path': './wrong.file', - 'webdav_password': 'secret1234' - } - webdav_options = get_options(option_type=WebDAVSettings, from_options=options) - settings = WebDAVSettings(webdav_options) - self.assertRaises(OptionNotValid, settings.is_valid) - self.assertFalse(settings.valid()) - - def test_connection_settings_with_key_path_an_no_cert_path(self): - options = { - 'webdav_hostname': 'http://localhost:8585', - 'webdav_login': 'alice', - 'key_path': './publish.sh', - 'webdav_password': 'secret1234' - } - webdav_options = get_options(option_type=WebDAVSettings, from_options=options) - settings = WebDAVSettings(webdav_options) - self.assertRaises(OptionNotValid, settings.is_valid) - self.assertFalse(settings.valid()) - - def test_connection_settings_does_nothing(self): - settings = ConnectionSettings() - settings.is_valid() - self.assertTrue(settings.valid()) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_cyrilic_client_it.py b/tests/test_cyrilic_client_it.py deleted file mode 100644 index 87add07..0000000 --- a/tests/test_cyrilic_client_it.py +++ /dev/null @@ -1,23 +0,0 @@ -import os -import unittest - -from tests.test_client_it import ClientTestCase - - -class MultiClientTestCase(ClientTestCase): - remote_path_file = 'директория/тестовый.txt' - remote_path_file2 = 'директория/тестовый2.txt' - remote_inner_path_file = 'директория/вложенная/тестовый.txt' - remote_path_dir = 'директория' - remote_path_dir2 = 'директория2' - remote_inner_path_dir = 'директория/вложенная' - inner_dir_name = 'вложенная' - local_base_dir = 'tests/' - local_file = 'тестовый.txt' - local_file_path = local_base_dir + 'тестовый.txt' - local_path_dir = local_base_dir + 'res/директория' - pulled_file = local_path_dir + os.sep + local_file - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py deleted file mode 100644 index d25d313..0000000 --- a/tests/test_exceptions.py +++ /dev/null @@ -1,50 +0,0 @@ -import unittest - -from webdav3.exceptions import OptionNotValid, LocalResourceNotFound, RemoteResourceNotFound, MethodNotSupported, ConnectionException, NoConnection, \ - RemoteParentNotFound, NotConnection, ResponseErrorCode, NotEnoughSpace - - -class ExceptionsTestCase(unittest.TestCase): - def test_option_not_valid(self): - exception = OptionNotValid('Name', 'Value', 'Namespace/') - self.assertEqual("Option (Namespace/Name=Value) have invalid name or value", exception.__str__()) - - def test_local_resource_not_found(self): - exception = LocalResourceNotFound('Path') - self.assertEqual("Local file: Path not found", exception.__str__()) - - def test_remote_resource_not_found(self): - exception = RemoteResourceNotFound('Path') - self.assertEqual("Remote resource: Path not found", exception.__str__()) - - def test_remote_parent_not_found(self): - exception = RemoteParentNotFound('Path') - self.assertEqual("Remote parent for: Path not found", exception.__str__()) - - def test_method_not_supported(self): - exception = MethodNotSupported('HEAD', 'Server') - self.assertEqual("Method 'HEAD' not supported for Server", exception.__str__()) - - def test_connection_exception(self): - exception = ConnectionException(MethodNotSupported('HEAD', 'Server')) - self.assertEqual("Method 'HEAD' not supported for Server", exception.__str__()) - - def test_no_connection(self): - exception = NoConnection('Server') - self.assertEqual("No connection with Server", exception.__str__()) - - def test_not_connection_legacy(self): - exception = NotConnection('Server') - self.assertEqual("No connection with Server", exception.__str__()) - - def test_response_error_code(self): - exception = ResponseErrorCode('http://text/', 502, 'Service Unavailable') - self.assertEqual("Request to http://text/ failed with code 502 and message: Service Unavailable", exception.__str__()) - - def test_not_enough_space(self): - exception = NotEnoughSpace() - self.assertEqual("Not enough space on the server", exception.__str__()) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_multi_client_it.py b/tests/test_multi_client_it.py deleted file mode 100644 index d8a1d15..0000000 --- a/tests/test_multi_client_it.py +++ /dev/null @@ -1,26 +0,0 @@ -import unittest - -from tests.test_client_it import ClientTestCase -from webdav3.client import Client - - -class MultiClientTestCase(ClientTestCase): - options2 = { - 'webdav_hostname': 'https://wrong.url.ru', - 'webdav_login': 'webdavclient.test2', - 'webdav_password': 'Qwerty123!', - 'webdav_override_methods': { - 'check': 'FAKE', - 'download': 'FAKE', - 'upload': 'FAKE', - 'clean': 'FAKE', - } - } - - def setUp(self): - super(ClientTestCase, self).setUp() - self.second_client = Client(self.options2) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_tailing_slash_client_it.py b/tests/test_tailing_slash_client_it.py deleted file mode 100644 index 6a8744c..0000000 --- a/tests/test_tailing_slash_client_it.py +++ /dev/null @@ -1,26 +0,0 @@ -import unittest - -from tests.test_client_it import ClientTestCase - - -class TailingSlashClientTestCase(ClientTestCase): - options = { - 'webdav_hostname': 'http://localhost:8585/', - 'webdav_login': 'alice', - 'webdav_password': 'secret1234', - 'webdav_override_methods': { - 'check': 'GET' - } - } - - def test_list_inner(self): - self._prepare_for_downloading(True) - file_list = self.client.list(self.remote_inner_path_dir) - self.assertIsNotNone(file_list, 'List of files should not be None') - - def test_hostname_no_tailing_slash(self): - self.assertEqual('5', self.client.webdav.hostname[-1]) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/тестовый.txt b/tests/тестовый.txt deleted file mode 100644 index 1cc4810..0000000 --- a/tests/тестовый.txt +++ /dev/null @@ -1 +0,0 @@ -test content for testing of webdav client \ No newline at end of file diff --git a/webdav3/client.py b/webdav3/client.py index 9e495d1..8d138b8 100644 --- a/webdav3/client.py +++ b/webdav3/client.py @@ -2,16 +2,14 @@ import functools import logging +import lxml.etree as etree import os +import requests import shutil import threading from io import BytesIO from re import sub -import lxml.etree as etree -import requests -from dateutil import parser as dateutil_parser - from webdav3.connection import * from webdav3.exceptions import * from webdav3.urn import Urn @@ -90,8 +88,11 @@ class Client(object): # controls whether to verify the server's TLS certificate or not verify = True + # Sets the session for subsequent requests + session = requests.Session() + # HTTP headers for different actions - default_http_header = { + http_header = { 'list': ["Accept: */*", "Depth: 1"], 'free': ["Accept: */*", "Depth: 0", "Content-Type: text/xml"], 'copy': ["Accept: */*"], @@ -104,59 +105,6 @@ class Client(object): 'set_property': ["Accept: */*", "Depth: 1", "Content-Type: application/x-www-form-urlencoded"] } - # mapping of actions to WebDAV methods - default_requests = { - 'options': 'OPTIONS', - 'download': "GET", - 'upload': "PUT", - 'copy': "COPY", - 'move': "MOVE", - 'mkdir': "MKCOL", - 'clean': "DELETE", - 'check': "HEAD", - 'list': "PROPFIND", - 'free': "PROPFIND", - 'info': "PROPFIND", - 'publish': "PROPPATCH", - 'unpublish': "PROPPATCH", - 'published': "PROPPATCH", - 'get_property': "PROPFIND", - 'set_property': "PROPPATCH" - } - - meta_xmlns = { - 'https://webdav.yandex.ru': "urn:yandex:disk:meta", - } - - def __init__(self, options): - """Constructor of WebDAV client - - :param options: the dictionary of connection options to WebDAV. - WebDev settings: - `webdav_hostname`: url for WebDAV server should contain protocol and ip address or domain name. - Example: `https://webdav.server.com`. - `webdav_login`: (optional) login name for WebDAV server can be empty in case using of token auth. - `webdav_password`: (optional) password for WebDAV server can be empty in case using of token auth. - `webdav_token': (optional) token for WebDAV server can be empty in case using of login/password auth. - `webdav_root`: (optional) root directory of WebDAV server. Defaults is `/`. - `webdav_cert_path`: (optional) path to certificate. - `webdav_key_path`: (optional) path to private key. - `webdav_recv_speed`: (optional) rate limit data download speed in Bytes per second. - Defaults to unlimited speed. - `webdav_send_speed`: (optional) rate limit data upload speed in Bytes per second. - Defaults to unlimited speed. - `webdav_verbose`: (optional) set verbose mode on.off. By default verbose mode is off. - - """ - self.session = requests.Session() - self.http_header = Client.default_http_header.copy() - self.requests = Client.default_requests.copy() - webdav_options = get_options(option_type=WebDAVSettings, from_options=options) - - self.webdav = WebDAVSettings(webdav_options) - self.requests.update(self.webdav.override_methods) - self.default_options = {} - def get_headers(self, action, headers_ext=None): """Returns HTTP headers of specified WebDAV actions. @@ -165,11 +113,11 @@ class Client(object): the specified action. :return: the dictionary of headers for specified action. """ - if action in self.http_header: + if action in Client.http_header: try: - headers = self.http_header[action].copy() + headers = Client.http_header[action].copy() except AttributeError: - headers = self.http_header[action][:] + headers = Client.http_header[action][:] else: headers = list() @@ -177,7 +125,7 @@ class Client(object): headers.extend(headers_ext) if self.webdav.token: - webdav_token = "Authorization: Bearer {token}".format(token=self.webdav.token) + webdav_token = "Authorization: OAuth {token}".format(token=self.webdav.token) headers.append(webdav_token) return dict([map(lambda s: s.strip(), i.split(':', 1)) for i in headers]) @@ -212,12 +160,11 @@ class Client(object): if self.session.auth: self.session.request(method="GET", url=self.webdav.hostname, verify=self.verify) # (Re)Authenticates against the proxy response = self.session.request( - method=self.requests[action], + method=Client.requests[action], url=self.get_url(path), - auth=(self.webdav.login, self.webdav.password) if (not self.webdav.token and not self.session.auth) else None, + auth=(self.webdav.login, self.webdav.password), headers=self.get_headers(action, headers_ext), timeout=self.timeout, - cert=(self.webdav.cert_path, self.webdav.key_path) if (self.webdav.cert_path and self.webdav.key_path) else None, data=data, stream=True, verify=self.verify @@ -227,11 +174,59 @@ class Client(object): if response.status_code == 404: raise RemoteResourceNotFound(path=path) if response.status_code == 405: - raise MethodNotSupported(name=action, server=self.webdav.hostname) + raise MethodNotSupported(name=action, server=hostname) if response.status_code >= 400: raise ResponseErrorCode(url=self.get_url(path), code=response.status_code, message=response.content) return response + # mapping of actions to WebDAV methods + requests = { + 'download': "GET", + 'upload': "PUT", + 'copy': "COPY", + 'move': "MOVE", + 'mkdir': "MKCOL", + 'clean': "DELETE", + 'check': "GET", + 'list': "PROPFIND", + 'free': "PROPFIND", + 'info': "PROPFIND", + 'publish': "PROPPATCH", + 'unpublish': "PROPPATCH", + 'published': "PROPPATCH", + 'get_property': "PROPFIND", + 'set_property': "PROPPATCH" + } + + meta_xmlns = { + 'https://webdav.yandex.ru': "urn:yandex:disk:meta", + } + + def __init__(self, options): + """Constructor of WebDAV client + + :param options: the dictionary of connection options to WebDAV. + WebDev settings: + `webdav_hostname`: url for WebDAV server should contain protocol and ip address or domain name. + Example: `https://webdav.server.com`. + `webdav_login`: (optional) login name for WebDAV server can be empty in case using of token auth. + `webdav_password`: (optional) password for WebDAV server can be empty in case using of token auth. + `webdav_token': (optional) token for WebDAV server can be empty in case using of login/password auth. + `webdav_root`: (optional) root directory of WebDAV server. Defaults is `/`. + `webdav_cert_path`: (optional) path to certificate. + `webdav_key_path`: (optional) path to private key. + `webdav_recv_speed`: (optional) rate limit data download speed in Bytes per second. + Defaults to unlimited speed. + `webdav_send_speed`: (optional) rate limit data upload speed in Bytes per second. + Defaults to unlimited speed. + `webdav_verbose`: (optional) set verbose mode on.off. By default verbose mode is off. + + """ + webdav_options = get_options(option_type=WebDAVSettings, from_options=options) + + self.webdav = WebDAVSettings(webdav_options) + self.default_options = {} + def valid(self): """Validates of WebDAV settings. @@ -248,8 +243,9 @@ class Client(object): :return: list of nested file or directory names. """ directory_urn = Urn(remote_path, directory=True) - if directory_urn.path() != Client.root and not self.check(directory_urn.path()): - raise RemoteResourceNotFound(directory_urn.path()) + if directory_urn.path() != Client.root: + if not self.check(directory_urn.path()): + raise RemoteResourceNotFound(directory_urn.path()) response = self.execute_request(action='list', path=directory_urn.quote()) urns = WebDavXmlUtils.parse_get_list_response(response.content) @@ -276,9 +272,9 @@ class Client(object): :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 """ + if self.webdav.disable_check: return True - urn = Urn(remote_path) try: response = self.execute_request(action='check', path=urn.quote()) @@ -304,11 +300,7 @@ class Client(object): if not self.check(directory_urn.parent()): raise RemoteParentNotFound(directory_urn.path()) - try: - response = self.execute_request(action='mkdir', path=directory_urn.quote()) - except MethodNotSupported: - # Yandex WebDAV returns 405 status code when directory already exists - return True + response = self.execute_request(action='mkdir', path=directory_urn.quote()) return response.status_code in (200, 201) @wrap_connection_error @@ -465,7 +457,7 @@ class Client(object): self.mkdir(remote_path) for resource_name in listdir(local_path): - _remote_path = "{parent}{name}".format(parent=urn.path(), name=resource_name).replace('\\', '') + _remote_path = "{parent}{name}".format(parent=urn.path(), name=resource_name) _local_path = os.path.join(local_path, resource_name) self.upload(local_path=_local_path, remote_path=_remote_path, progress=progress) @@ -536,7 +528,7 @@ class Client(object): raise RemoteParentNotFound(urn_to.path()) headers = [ - "Destination: {url}".format(url=self.get_url(urn_to.quote())) + "Destination: {url}".format(url=self.get_url(urn_to)) ] if self.is_dir(urn_from.path()): headers.append("Depth: {depth}".format(depth=depth)) @@ -559,7 +551,7 @@ class Client(object): if not self.check(urn_to.parent()): raise RemoteParentNotFound(urn_to.path()) - header_destination = "Destination: {path}".format(path=self.get_url(urn_to.quote())) + header_destination = "Destination: {path}".format(path=self.get_url(urn_to)) 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]) @@ -587,16 +579,13 @@ class Client(object): `modified`: date of resource modification. """ urn = Urn(remote_path) - self._check_remote_resource(remote_path, urn) + if not self.check(urn.path()) and not self.check(Urn(remote_path, directory=True).path()): + raise RemoteResourceNotFound(remote_path) response = self.execute_request(action='info', path=urn.quote()) path = self.get_full_path(urn) return WebDavXmlUtils.parse_info_response(content=response.content, path=path, hostname=self.webdav.hostname) - def _check_remote_resource(self, remote_path, urn): - if not self.check(urn.path()) and not self.check(Urn(remote_path, directory=True).path()): - raise RemoteResourceNotFound(remote_path) - @wrap_connection_error def is_dir(self, remote_path): """Checks is the remote resource directory. @@ -607,7 +596,8 @@ class Client(object): """ urn = Urn(remote_path) parent_urn = Urn(urn.parent()) - self._check_remote_resource(remote_path, urn) + if not self.check(urn.path()) and not self.check(Urn(remote_path, directory=True).path()): + raise RemoteResourceNotFound(remote_path) response = self.execute_request(action='info', path=parent_urn.quote()) path = self.get_full_path(urn) @@ -672,39 +662,50 @@ class Client(object): def prune(src, exp): return [sub(exp, "", item) for item in src] - updated = False urn = Urn(remote_directory, directory=True) - self._validate_remote_directory(urn) - self._validate_local_directory(local_directory) + + if not self.is_dir(urn.path()): + raise OptionNotValid(name="remote_path", value=remote_directory) + + if not os.path.isdir(local_directory): + raise OptionNotValid(name="local_path", value=local_directory) + + if not os.path.exists(local_directory): + raise LocalResourceNotFound(local_directory) paths = self.list(urn.path()) expression = "{begin}{end}".format(begin="^", end=urn.path()) remote_resource_names = prune(paths, expression) for local_resource_name in listdir(local_directory): + local_path = os.path.join(local_directory, local_resource_name) - remote_path = "{remote_directory}{resource_name}".format(remote_directory=urn.path(), resource_name=local_resource_name) + remote_path = "{remote_directory}{resource_name}".format(remote_directory=urn.path(), + resource_name=local_resource_name) if os.path.isdir(local_path): if not self.check(remote_path=remote_path): self.mkdir(remote_path=remote_path) - result = self.push(remote_directory=remote_path, local_directory=local_path) - updated = updated or result + self.push(remote_directory=remote_path, local_directory=local_path) else: - if local_resource_name in remote_resource_names and not self.is_local_more_recent(local_path, remote_path): + if local_resource_name in remote_resource_names: continue self.upload_file(remote_path=remote_path, local_path=local_path) - updated = True - return updated def pull(self, remote_directory, local_directory): + def prune(src, exp): return [sub(exp, "", item) for item in src] updated = False + urn = Urn(remote_directory, directory=True) - self._validate_remote_directory(urn) - self._validate_local_directory(local_directory) + + if not self.is_dir(urn.path()): + raise OptionNotValid(name="remote_path", value=remote_directory) + + if not os.path.exists(local_directory): + raise LocalResourceNotFound(local_directory) local_resource_names = listdir(local_directory) @@ -713,62 +714,30 @@ class Client(object): remote_resource_names = prune(paths, expression) for remote_resource_name in remote_resource_names: + local_path = os.path.join(local_directory, remote_resource_name) - remote_path = "{remote_directory}{resource_name}".format(remote_directory=urn.path(), resource_name=remote_resource_name) + remote_path = "{remote_directory}{resource_name}".format(remote_directory=urn.path(), + resource_name=remote_resource_name) + remote_urn = Urn(remote_path) if remote_urn.path().endswith("/"): if not os.path.exists(local_path): updated = True os.mkdir(local_path) - result = self.pull(remote_directory=remote_path, local_directory=local_path) - updated = updated or result + self.pull(remote_directory=remote_path, local_directory=local_path) else: - if remote_resource_name in local_resource_names and self.is_local_more_recent(local_path, remote_path): + if remote_resource_name in local_resource_names: continue - self.download_file(remote_path=remote_path, local_path=local_path) updated = True return updated - def is_local_more_recent(self, local_path, remote_path): - """Tells if local resource is more recent that the remote on if possible - - :param local_path: the path to local resource. - :param remote_path: the path to remote resource. - - :return: True if local resource is more recent, False if the remote one is - None if comparison is not possible - """ - try: - remote_info = self.info(remote_path) - remote_last_mod_date = remote_info['modified'] - remote_last_mod_date = dateutil_parser.parse(remote_last_mod_date) - remote_last_mod_date_unix_ts = int(remote_last_mod_date.timestamp()) - local_last_mod_date_unix_ts = int(os.stat(local_path).st_mtime) - - return remote_last_mod_date_unix_ts < local_last_mod_date_unix_ts - except ValueError or RuntimeWarning or KeyError: - # If there is problem when parsing dates, or cannot get - # last modified information, return None - return None - def sync(self, remote_directory, local_directory): + self.pull(remote_directory=remote_directory, local_directory=local_directory) self.push(remote_directory=remote_directory, local_directory=local_directory) - def _validate_remote_directory(self, urn): - if not self.is_dir(urn.path()): - raise OptionNotValid(name="remote_path", value=urn.path()) - - @staticmethod - def _validate_local_directory(local_directory): - if not os.path.isdir(local_directory): - raise OptionNotValid(name="local_path", value=local_directory) - - if not os.path.exists(local_directory): - raise LocalResourceNotFound(local_directory) - class Resource(object): def __init__(self, client, urn): diff --git a/webdav3/connection.py b/webdav3/connection.py index 499ba23..f24593c 100644 --- a/webdav3/connection.py +++ b/webdav3/connection.py @@ -6,13 +6,10 @@ from webdav3.urn import Urn class ConnectionSettings: def is_valid(self): - """ - Method checks is settings are valid - :return: True if settings are valid otherwise False - """ pass def valid(self): + try: self.is_valid() except OptionNotValid: @@ -25,21 +22,21 @@ class WebDAVSettings(ConnectionSettings): ns = "webdav:" prefix = "webdav_" keys = {'hostname', 'login', 'password', 'token', 'root', 'cert_path', 'key_path', 'recv_speed', 'send_speed', - 'verbose', 'disable_check', 'override_methods'} + 'verbose', 'disable_check'} + + hostname = None + login = None + password = None + token = None + root = None + cert_path = None + key_path = None + recv_speed = None + send_speed = None + verbose = None + disable_check = False def __init__(self, options): - self.hostname = None - self.login = None - self.password = None - self.token = None - self.root = None - self.cert_path = None - self.key_path = None - self.recv_speed = None - self.send_speed = None - self.verbose = None - self.disable_check = False - self.override_methods = {} self.options = dict() @@ -50,9 +47,9 @@ class WebDAVSettings(ConnectionSettings): self.root = Urn(self.root).quote() if self.root else '' self.root = self.root.rstrip(Urn.separate) - self.hostname = self.hostname.rstrip(Urn.separate) def is_valid(self): + if not self.hostname: raise OptionNotValid(name="hostname", value=self.hostname, ns=self.ns) @@ -70,4 +67,3 @@ class WebDAVSettings(ConnectionSettings): if not self.token and not self.login: raise OptionNotValid(name="login", value=self.login, ns=self.ns) - return True diff --git a/webdav3/exceptions.py b/webdav3/exceptions.py index 7ccf020..096c1f3 100644 --- a/webdav3/exceptions.py +++ b/webdav3/exceptions.py @@ -71,7 +71,7 @@ class NoConnection(WebDavException): self.hostname = hostname def __str__(self): - return "No connection with {hostname}".format(hostname=self.hostname) + return "Not connection with {hostname}".format(hostname=self.hostname) # This exception left only for supporting original library interface. @@ -96,7 +96,7 @@ class ResponseErrorCode(WebDavException): class NotEnoughSpace(WebDavException): def __init__(self): - self.message = "Not enough space on the server" + pass def __str__(self): - return self.message + return "Not enough space on the server" diff --git a/webdav3/urn.py b/webdav3/urn.py index e78fa24..6279de2 100644 --- a/webdav3/urn.py +++ b/webdav3/urn.py @@ -34,11 +34,13 @@ class Urn(object): return self._path def filename(self): + path_split = self._path.split(Urn.separate) name = path_split[-2] + Urn.separate if path_split[-1] == '' else path_split[-1] return unquote(name) def parent(self): + path_split = self._path.split(Urn.separate) nesting_level = self.nesting_level() parent_path_split = path_split[:nesting_level]