Compare commits

..

No commits in common. "develop" and "v0.13" have entirely different histories.

19 changed files with 202 additions and 942 deletions

View file

@ -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

317
README.md
View file

@ -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 : <hostname>
... 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>)
```
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

View file

@ -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',
],

View file

@ -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

View file

View file

@ -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()

View file

@ -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__':

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -1 +0,0 @@
test content for testing of webdav client

View file

@ -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):

View file

@ -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

View file

@ -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"

View file

@ -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]