Compare commits
26 commits
Author | SHA1 | Date | |
---|---|---|---|
|
a3c75b7155 | ||
|
39afefffc5 | ||
|
eeaf66a278 | ||
|
af64110364 | ||
|
3aab20b02a | ||
|
0ac3c5e55f | ||
|
3c5ba516af | ||
|
f64efedd96 | ||
|
eced3b5fe5 | ||
|
fe4e56caad | ||
|
8c7ee2e372 | ||
|
85b10d53c9 | ||
|
b3c753c126 | ||
|
3d4751cdd0 | ||
|
c380dc56c7 | ||
|
f3e7d44276 | ||
|
fc14ed2be1 | ||
|
336db8ae19 | ||
|
95bea2182c | ||
|
7db1cfd274 | ||
|
5e2a9468c1 | ||
|
5aad617fbf | ||
|
5f84bb1343 | ||
|
0b9c61e7e7 | ||
|
1859e43daf | ||
|
120a6adfab |
19 changed files with 963 additions and 223 deletions
16
.travis.yml
16
.travis.yml
|
@ -4,15 +4,16 @@ dist: xenial
|
||||||
addons:
|
addons:
|
||||||
sonarcloud:
|
sonarcloud:
|
||||||
organization: "ezhov-evgeny"
|
organization: "ezhov-evgeny"
|
||||||
token:
|
token: f0f714f3bea6bd103e3eb82724ef3bb0d3b54d1d
|
||||||
secure: f0f714f3bea6bd103e3eb82724ef3bb0d3b54d1d
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
|
|
||||||
python:
|
python:
|
||||||
- "2.7"
|
- "3.5"
|
||||||
|
- "3.6"
|
||||||
- "3.7"
|
- "3.7"
|
||||||
|
- "3.8"
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- docker pull bytemark/webdav
|
- docker pull bytemark/webdav
|
||||||
|
@ -21,6 +22,11 @@ before_install:
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- python setup.py develop
|
- python setup.py develop
|
||||||
|
- pip install coverage
|
||||||
script:
|
script:
|
||||||
- python setup.py test
|
- coverage run setup.py test
|
||||||
- sonar-scanner -X
|
- coverage xml
|
||||||
|
- |
|
||||||
|
if [[ $TRAVIS_PYTHON_VERSION == "3.8" ]]; then
|
||||||
|
sonar-scanner
|
||||||
|
fi
|
||||||
|
|
317
README.md
317
README.md
|
@ -1,28 +1,315 @@
|
||||||
webdavclient3 [](https://travis-ci.com/ezhov-evgeny/webdav-client-python-3) [](https://pypi.org/project/webdavclient3/) 
|
webdavclient3
|
||||||
=========
|
=========
|
||||||
|
[](https://travis-ci.com/ezhov-evgeny/webdav-client-python-3)
|
||||||
|
[](https://sonarcloud.io/dashboard?id=ezhov-evgeny_webdav-client-python-3)
|
||||||
|
[](https://sonarcloud.io/dashboard?id=ezhov-evgeny_webdav-client-python-3)
|
||||||
|
[](https://pypi.org/project/webdavclient3/) 
|
||||||
|
|
||||||
Based on https://github.com/designerror/webdav-client-python
|
Package webdavclient3 based on https://github.com/designerror/webdav-client-python but uses `requests` instead of `PyCURL`.
|
||||||
But uses `requests` instead of `PyCURL`
|
It provides easy way to work with WebDAV-servers.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
```bash
|
||||||
|
$ pip install webdavclient3
|
||||||
|
```
|
||||||
|
|
||||||
Sample Usage
|
Sample Usage
|
||||||
------------
|
------------
|
||||||
`pip install webdavclient3`
|
```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')
|
||||||
```
|
```
|
||||||
>>> from webdav3.client
|
|
||||||
>>> options = {
|
Webdav API
|
||||||
... webdav_hostname : <hostname>
|
==========
|
||||||
... webdav_login : <login>
|
|
||||||
... webdav_password : <password>
|
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`.
|
||||||
>>> client = Client(options)
|
|
||||||
>>> client.verify = False # To not check SSL certificates (Default = True)
|
**Configuring the client**
|
||||||
>>> client.session.proxies(...) # To set proxy directly into the session (Optional)
|
|
||||||
>>> client.session.auth(...) # To set proxy auth directly into the session (Optional)
|
Required keys for configuring client connection with WevDAV-server are `webdav_hostname` and `webdav_login`, `webdav_password`.
|
||||||
>>> client.execute_request("mkdir", <directory_name>)
|
|
||||||
|
```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)
|
||||||
```
|
```
|
||||||
|
|
||||||
Release Notes
|
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**
|
**Version 0.13 – 27.11.2019**
|
||||||
* Main version of Python is updated up to 3.7
|
* Main version of Python is updated up to 3.7
|
||||||
* Switch to use python sessions rather than requests by https://github.com/delrey1
|
* Switch to use python sessions rather than requests by https://github.com/delrey1
|
||||||
|
|
10
setup.py
10
setup.py
|
@ -6,7 +6,7 @@ from setuptools import setup, find_packages
|
||||||
from setuptools.command.install import install as InstallCommand
|
from setuptools.command.install import install as InstallCommand
|
||||||
from setuptools.command.test import test as TestCommand
|
from setuptools.command.test import test as TestCommand
|
||||||
|
|
||||||
version = "0.13"
|
version = "3.14.1"
|
||||||
requirements = "libxml2-dev libxslt-dev python-dev"
|
requirements = "libxml2-dev libxslt-dev python-dev"
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,8 +51,8 @@ setup(
|
||||||
name='webdavclient3',
|
name='webdavclient3',
|
||||||
version=version,
|
version=version,
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
requires=['python (>= 2.7.6)'],
|
requires=['python (>= 3.3.0)'],
|
||||||
install_requires=['requests', 'lxml', 'argcomplete'],
|
install_requires=['requests', 'lxml', 'python-dateutil'],
|
||||||
scripts=['wdc'],
|
scripts=['wdc'],
|
||||||
test_suite='tests',
|
test_suite='tests',
|
||||||
tests_require=['pytest'],
|
tests_require=['pytest'],
|
||||||
|
@ -73,8 +73,10 @@ setup(
|
||||||
'Operating System :: MacOS',
|
'Operating System :: MacOS',
|
||||||
'Operating System :: Microsoft',
|
'Operating System :: Microsoft',
|
||||||
'Operating System :: Unix',
|
'Operating System :: Unix',
|
||||||
'Programming Language :: Python :: 2.7',
|
'Programming Language :: Python :: 3.5',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
'Programming Language :: Python :: 3.7',
|
'Programming Language :: Python :: 3.7',
|
||||||
|
'Programming Language :: Python :: 3.8',
|
||||||
'Topic :: Internet',
|
'Topic :: Internet',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
],
|
],
|
||||||
|
|
|
@ -4,6 +4,8 @@ sonar.organization=ezhov-evgeny
|
||||||
sonar.projectName=webdav-client-python-3
|
sonar.projectName=webdav-client-python-3
|
||||||
sonar.projectVersion=0.13
|
sonar.projectVersion=0.13
|
||||||
# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
|
# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
|
||||||
sonar.sources=.
|
sonar.sources=webdav3
|
||||||
|
sonar.tests=tests
|
||||||
|
sonar.python.coverage.reportPaths=coverage.xml
|
||||||
# Encoding of the source code. Default is default system encoding
|
# Encoding of the source code. Default is default system encoding
|
||||||
sonar.sourceEncoding=UTF-8
|
sonar.sourceEncoding=UTF-8
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
83
tests/base_client_it.py
Normal file
83
tests/base_client_it.py
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
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()
|
|
@ -3,41 +3,14 @@ import shutil
|
||||||
import unittest
|
import unittest
|
||||||
from io import BytesIO, StringIO
|
from io import BytesIO, StringIO
|
||||||
from os import path
|
from os import path
|
||||||
from unittest import TestCase
|
from time import sleep
|
||||||
|
|
||||||
from webdav3.client import Client
|
from tests.base_client_it import BaseClientTestCase
|
||||||
from webdav3.exceptions import MethodNotSupported
|
from webdav3.exceptions import MethodNotSupported, OptionNotValid, RemoteResourceNotFound
|
||||||
|
|
||||||
|
|
||||||
class ClientTestCase(TestCase):
|
class ClientTestCase(BaseClientTestCase):
|
||||||
remote_path_file = 'test_dir/test.txt'
|
pulled_file = BaseClientTestCase.local_path_dir + os.sep + BaseClientTestCase.local_file
|
||||||
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):
|
def test_list(self):
|
||||||
self._prepare_for_downloading()
|
self._prepare_for_downloading()
|
||||||
|
@ -46,7 +19,10 @@ class ClientTestCase(TestCase):
|
||||||
self.assertGreater(file_list.__len__(), 0, 'Expected that amount of files more then 0')
|
self.assertGreater(file_list.__len__(), 0, 'Expected that amount of files more then 0')
|
||||||
|
|
||||||
def test_free(self):
|
def test_free(self):
|
||||||
with self.assertRaises(MethodNotSupported):
|
if 'localhost' in self.options['webdav_hostname']:
|
||||||
|
with self.assertRaises(MethodNotSupported):
|
||||||
|
self.client.free()
|
||||||
|
else:
|
||||||
self.assertGreater(self.client.free(), 0, 'Expected that free space on WebDAV server is more then 0 bytes')
|
self.assertGreater(self.client.free(), 0, 'Expected that free space on WebDAV server is more then 0 bytes')
|
||||||
|
|
||||||
def test_check(self):
|
def test_check(self):
|
||||||
|
@ -58,12 +34,29 @@ class ClientTestCase(TestCase):
|
||||||
self.client.mkdir(remote_path=self.remote_path_dir)
|
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.')
|
self.assertTrue(self.client.check(remote_path=self.remote_path_dir), 'Expected the directory is created.')
|
||||||
|
|
||||||
def test_download_to(self):
|
def test_download_from(self):
|
||||||
self._prepare_for_downloading()
|
self._prepare_for_downloading()
|
||||||
buff = BytesIO()
|
buff = BytesIO()
|
||||||
self.client.download_from(buff=buff, remote_path=self.remote_path_file)
|
self.client.download_from(buff=buff, remote_path=self.remote_path_file)
|
||||||
self.assertEqual(buff.getvalue(), b'test content for testing of webdav client')
|
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):
|
def test_download(self):
|
||||||
self._prepare_for_downloading()
|
self._prepare_for_downloading()
|
||||||
self.client.download(local_path=self.local_path_dir, remote_path=self.remote_path_dir)
|
self.client.download(local_path=self.local_path_dir, remote_path=self.remote_path_dir)
|
||||||
|
@ -101,6 +94,8 @@ class ClientTestCase(TestCase):
|
||||||
remote_path=self.remote_path_file, callback=callback)
|
remote_path=self.remote_path_file, callback=callback)
|
||||||
self.assertFalse(path.exists(self.local_path_dir + os.path.sep + self.local_file),
|
self.assertFalse(path.exists(self.local_path_dir + os.path.sep + self.local_file),
|
||||||
'Expected the file has not been downloaded yet')
|
'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):
|
def test_upload_from(self):
|
||||||
self._prepare_for_uploading()
|
self._prepare_for_uploading()
|
||||||
|
@ -213,10 +208,10 @@ class ClientTestCase(TestCase):
|
||||||
self._prepare_for_downloading(True)
|
self._prepare_for_downloading(True)
|
||||||
self.client.pull(self.remote_path_dir, self.local_path_dir)
|
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), '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 + 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 + self.inner_dir_name), '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), '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.isdir(self.local_path_dir + os.path.sep + self.inner_dir_name), 'Expected this is a directory.')
|
||||||
self.assertTrue(path.exists(self.local_path_dir + os.path.sep + self.local_file),
|
self.assertTrue(path.exists(self.local_path_dir + os.path.sep + self.local_file),
|
||||||
'Expected the file is downloaded')
|
'Expected the file is downloaded')
|
||||||
self.assertTrue(path.isfile(self.local_path_dir + os.path.sep + self.local_file),
|
self.assertTrue(path.isfile(self.local_path_dir + os.path.sep + self.local_file),
|
||||||
|
@ -228,26 +223,60 @@ class ClientTestCase(TestCase):
|
||||||
self.assertTrue(self.client.check(self.remote_path_dir), 'Expected the directory is created.')
|
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.')
|
self.assertTrue(self.client.check(self.remote_path_file), 'Expected the file is uploaded.')
|
||||||
|
|
||||||
def _prepare_for_downloading(self, inner_dir=False):
|
def test_valid(self):
|
||||||
if not self.client.check(remote_path=self.remote_path_dir):
|
self.assertTrue(self.client.valid())
|
||||||
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 _prepare_for_uploading(self):
|
def test_check_is_overridden(self):
|
||||||
if not self.client.check(remote_path=self.remote_path_dir):
|
self.assertEqual('GET', self.client.requests['check'])
|
||||||
self.client.mkdir(remote_path=self.remote_path_dir)
|
|
||||||
|
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):
|
||||||
if not path.exists(path=self.local_path_dir):
|
if not path.exists(path=self.local_path_dir):
|
||||||
os.makedirs(self.local_path_dir)
|
os.mkdir(self.local_path_dir)
|
||||||
if not path.exists(path=self.local_path_dir + os.sep + self.local_file):
|
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)
|
shutil.copy(src=self.local_file_path, dst=self.pulled_file)
|
||||||
|
return os.path.getmtime(self.pulled_file)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
75
tests/test_client_resource_it.py
Normal file
75
tests/test_client_resource_it.py
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
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()
|
|
@ -4,7 +4,7 @@ from unittest import TestCase
|
||||||
|
|
||||||
from lxml.etree import ElementTree, Element
|
from lxml.etree import ElementTree, Element
|
||||||
|
|
||||||
from webdav3.client import WebDavXmlUtils as utils
|
from webdav3.client import WebDavXmlUtils as utils, listdir
|
||||||
|
|
||||||
|
|
||||||
class ClientTestCase(TestCase):
|
class ClientTestCase(TestCase):
|
||||||
|
@ -135,6 +135,7 @@ class ClientTestCase(TestCase):
|
||||||
except:
|
except:
|
||||||
f = open('./tests/response_dir.xml')
|
f = open('./tests/response_dir.xml')
|
||||||
content = f.read().decode('utf-8').encode('utf-8')
|
content = f.read().decode('utf-8').encode('utf-8')
|
||||||
|
f.close()
|
||||||
path = '/test_dir'
|
path = '/test_dir'
|
||||||
hostname = 'https://webdav.yandex.ru'
|
hostname = 'https://webdav.yandex.ru'
|
||||||
result = utils.parse_is_dir_response(content, path, hostname)
|
result = utils.parse_is_dir_response(content, path, hostname)
|
||||||
|
@ -155,6 +156,11 @@ class ClientTestCase(TestCase):
|
||||||
result = utils.parse_is_dir_response(content.encode('utf-8'), path, hostname)
|
result = utils.parse_is_dir_response(content.encode('utf-8'), path, hostname)
|
||||||
self.assertFalse(result, 'It should be file')
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
91
tests/test_connection.py
Normal file
91
tests/test_connection.py
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
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()
|
23
tests/test_cyrilic_client_it.py
Normal file
23
tests/test_cyrilic_client_it.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
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()
|
50
tests/test_exceptions.py
Normal file
50
tests/test_exceptions.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
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()
|
26
tests/test_multi_client_it.py
Normal file
26
tests/test_multi_client_it.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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()
|
26
tests/test_tailing_slash_client_it.py
Normal file
26
tests/test_tailing_slash_client_it.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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()
|
1
tests/тестовый.txt
Normal file
1
tests/тестовый.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
test content for testing of webdav client
|
|
@ -2,14 +2,16 @@
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import lxml.etree as etree
|
|
||||||
import os
|
import os
|
||||||
import requests
|
|
||||||
import shutil
|
import shutil
|
||||||
import threading
|
import threading
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from re import sub
|
from re import sub
|
||||||
|
|
||||||
|
import lxml.etree as etree
|
||||||
|
import requests
|
||||||
|
from dateutil import parser as dateutil_parser
|
||||||
|
|
||||||
from webdav3.connection import *
|
from webdav3.connection import *
|
||||||
from webdav3.exceptions import *
|
from webdav3.exceptions import *
|
||||||
from webdav3.urn import Urn
|
from webdav3.urn import Urn
|
||||||
|
@ -88,11 +90,8 @@ class Client(object):
|
||||||
# controls whether to verify the server's TLS certificate or not
|
# controls whether to verify the server's TLS certificate or not
|
||||||
verify = True
|
verify = True
|
||||||
|
|
||||||
# Sets the session for subsequent requests
|
|
||||||
session = requests.Session()
|
|
||||||
|
|
||||||
# HTTP headers for different actions
|
# HTTP headers for different actions
|
||||||
http_header = {
|
default_http_header = {
|
||||||
'list': ["Accept: */*", "Depth: 1"],
|
'list': ["Accept: */*", "Depth: 1"],
|
||||||
'free': ["Accept: */*", "Depth: 0", "Content-Type: text/xml"],
|
'free': ["Accept: */*", "Depth: 0", "Content-Type: text/xml"],
|
||||||
'copy': ["Accept: */*"],
|
'copy': ["Accept: */*"],
|
||||||
|
@ -105,89 +104,16 @@ class Client(object):
|
||||||
'set_property': ["Accept: */*", "Depth: 1", "Content-Type: application/x-www-form-urlencoded"]
|
'set_property': ["Accept: */*", "Depth: 1", "Content-Type: application/x-www-form-urlencoded"]
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_headers(self, action, headers_ext=None):
|
|
||||||
"""Returns HTTP headers of specified WebDAV actions.
|
|
||||||
|
|
||||||
:param action: the identifier of action.
|
|
||||||
:param headers_ext: (optional) the addition headers list witch sgould be added to basic HTTP headers for
|
|
||||||
the specified action.
|
|
||||||
:return: the dictionary of headers for specified action.
|
|
||||||
"""
|
|
||||||
if action in Client.http_header:
|
|
||||||
try:
|
|
||||||
headers = Client.http_header[action].copy()
|
|
||||||
except AttributeError:
|
|
||||||
headers = Client.http_header[action][:]
|
|
||||||
else:
|
|
||||||
headers = list()
|
|
||||||
|
|
||||||
if headers_ext:
|
|
||||||
headers.extend(headers_ext)
|
|
||||||
|
|
||||||
if 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])
|
|
||||||
|
|
||||||
def get_url(self, path):
|
|
||||||
"""Generates url by uri path.
|
|
||||||
|
|
||||||
:param path: uri path.
|
|
||||||
:return: the url string.
|
|
||||||
"""
|
|
||||||
url = {'hostname': self.webdav.hostname, 'root': self.webdav.root, 'path': path}
|
|
||||||
return "{hostname}{root}{path}".format(**url)
|
|
||||||
|
|
||||||
def get_full_path(self, urn):
|
|
||||||
"""Generates full path to remote resource exclude hostname.
|
|
||||||
|
|
||||||
:param urn: the URN to resource.
|
|
||||||
:return: full path to resource with root path.
|
|
||||||
"""
|
|
||||||
return "{root}{path}".format(root=self.webdav.root, path=urn.path())
|
|
||||||
|
|
||||||
def execute_request(self, action, path, data=None, headers_ext=None):
|
|
||||||
"""Generate request to WebDAV server for specified action and path and execute it.
|
|
||||||
|
|
||||||
:param action: the action for WebDAV server which should be executed.
|
|
||||||
:param path: the path to resource for action
|
|
||||||
:param data: (optional) Dictionary or list of tuples ``[(key, value)]`` (will be form-encoded), bytes,
|
|
||||||
or file-like object to send in the body of the :class:`Request`.
|
|
||||||
:param headers_ext: (optional) the addition headers list witch should be added to basic HTTP headers for
|
|
||||||
the specified action.
|
|
||||||
:return: HTTP response of request.
|
|
||||||
"""
|
|
||||||
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=Client.requests[action],
|
|
||||||
url=self.get_url(path),
|
|
||||||
auth=(self.webdav.login, self.webdav.password),
|
|
||||||
headers=self.get_headers(action, headers_ext),
|
|
||||||
timeout=self.timeout,
|
|
||||||
data=data,
|
|
||||||
stream=True,
|
|
||||||
verify=self.verify
|
|
||||||
)
|
|
||||||
if response.status_code == 507:
|
|
||||||
raise NotEnoughSpace()
|
|
||||||
if response.status_code == 404:
|
|
||||||
raise RemoteResourceNotFound(path=path)
|
|
||||||
if response.status_code == 405:
|
|
||||||
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
|
# mapping of actions to WebDAV methods
|
||||||
requests = {
|
default_requests = {
|
||||||
|
'options': 'OPTIONS',
|
||||||
'download': "GET",
|
'download': "GET",
|
||||||
'upload': "PUT",
|
'upload': "PUT",
|
||||||
'copy': "COPY",
|
'copy': "COPY",
|
||||||
'move': "MOVE",
|
'move': "MOVE",
|
||||||
'mkdir': "MKCOL",
|
'mkdir': "MKCOL",
|
||||||
'clean': "DELETE",
|
'clean': "DELETE",
|
||||||
'check': "GET",
|
'check': "HEAD",
|
||||||
'list': "PROPFIND",
|
'list': "PROPFIND",
|
||||||
'free': "PROPFIND",
|
'free': "PROPFIND",
|
||||||
'info': "PROPFIND",
|
'info': "PROPFIND",
|
||||||
|
@ -222,11 +148,90 @@ class Client(object):
|
||||||
`webdav_verbose`: (optional) set verbose mode on.off. By default verbose mode is off.
|
`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)
|
webdav_options = get_options(option_type=WebDAVSettings, from_options=options)
|
||||||
|
|
||||||
self.webdav = WebDAVSettings(webdav_options)
|
self.webdav = WebDAVSettings(webdav_options)
|
||||||
|
self.requests.update(self.webdav.override_methods)
|
||||||
self.default_options = {}
|
self.default_options = {}
|
||||||
|
|
||||||
|
def get_headers(self, action, headers_ext=None):
|
||||||
|
"""Returns HTTP headers of specified WebDAV actions.
|
||||||
|
|
||||||
|
:param action: the identifier of action.
|
||||||
|
:param headers_ext: (optional) the addition headers list witch sgould be added to basic HTTP headers for
|
||||||
|
the specified action.
|
||||||
|
:return: the dictionary of headers for specified action.
|
||||||
|
"""
|
||||||
|
if action in self.http_header:
|
||||||
|
try:
|
||||||
|
headers = self.http_header[action].copy()
|
||||||
|
except AttributeError:
|
||||||
|
headers = self.http_header[action][:]
|
||||||
|
else:
|
||||||
|
headers = list()
|
||||||
|
|
||||||
|
if headers_ext:
|
||||||
|
headers.extend(headers_ext)
|
||||||
|
|
||||||
|
if self.webdav.token:
|
||||||
|
webdav_token = "Authorization: Bearer {token}".format(token=self.webdav.token)
|
||||||
|
headers.append(webdav_token)
|
||||||
|
return dict([map(lambda s: s.strip(), i.split(':', 1)) for i in headers])
|
||||||
|
|
||||||
|
def get_url(self, path):
|
||||||
|
"""Generates url by uri path.
|
||||||
|
|
||||||
|
:param path: uri path.
|
||||||
|
:return: the url string.
|
||||||
|
"""
|
||||||
|
url = {'hostname': self.webdav.hostname, 'root': self.webdav.root, 'path': path}
|
||||||
|
return "{hostname}{root}{path}".format(**url)
|
||||||
|
|
||||||
|
def get_full_path(self, urn):
|
||||||
|
"""Generates full path to remote resource exclude hostname.
|
||||||
|
|
||||||
|
:param urn: the URN to resource.
|
||||||
|
:return: full path to resource with root path.
|
||||||
|
"""
|
||||||
|
return "{root}{path}".format(root=self.webdav.root, path=urn.path())
|
||||||
|
|
||||||
|
def execute_request(self, action, path, data=None, headers_ext=None):
|
||||||
|
"""Generate request to WebDAV server for specified action and path and execute it.
|
||||||
|
|
||||||
|
:param action: the action for WebDAV server which should be executed.
|
||||||
|
:param path: the path to resource for action
|
||||||
|
:param data: (optional) Dictionary or list of tuples ``[(key, value)]`` (will be form-encoded), bytes,
|
||||||
|
or file-like object to send in the body of the :class:`Request`.
|
||||||
|
:param headers_ext: (optional) the addition headers list witch should be added to basic HTTP headers for
|
||||||
|
the specified action.
|
||||||
|
:return: HTTP response of request.
|
||||||
|
"""
|
||||||
|
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],
|
||||||
|
url=self.get_url(path),
|
||||||
|
auth=(self.webdav.login, self.webdav.password) if (not self.webdav.token and not self.session.auth) else None,
|
||||||
|
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
|
||||||
|
)
|
||||||
|
if response.status_code == 507:
|
||||||
|
raise NotEnoughSpace()
|
||||||
|
if response.status_code == 404:
|
||||||
|
raise RemoteResourceNotFound(path=path)
|
||||||
|
if response.status_code == 405:
|
||||||
|
raise MethodNotSupported(name=action, server=self.webdav.hostname)
|
||||||
|
if response.status_code >= 400:
|
||||||
|
raise ResponseErrorCode(url=self.get_url(path), code=response.status_code, message=response.content)
|
||||||
|
return response
|
||||||
|
|
||||||
def valid(self):
|
def valid(self):
|
||||||
"""Validates of WebDAV settings.
|
"""Validates of WebDAV settings.
|
||||||
|
|
||||||
|
@ -243,9 +248,8 @@ class Client(object):
|
||||||
:return: list of nested file or directory names.
|
:return: list of nested file or directory names.
|
||||||
"""
|
"""
|
||||||
directory_urn = Urn(remote_path, directory=True)
|
directory_urn = Urn(remote_path, directory=True)
|
||||||
if directory_urn.path() != Client.root:
|
if directory_urn.path() != Client.root and not self.check(directory_urn.path()):
|
||||||
if not self.check(directory_urn.path()):
|
raise RemoteResourceNotFound(directory_urn.path())
|
||||||
raise RemoteResourceNotFound(directory_urn.path())
|
|
||||||
|
|
||||||
response = self.execute_request(action='list', path=directory_urn.quote())
|
response = self.execute_request(action='list', path=directory_urn.quote())
|
||||||
urns = WebDavXmlUtils.parse_get_list_response(response.content)
|
urns = WebDavXmlUtils.parse_get_list_response(response.content)
|
||||||
|
@ -272,9 +276,9 @@ class Client(object):
|
||||||
:param remote_path: (optional) path to resource on WebDAV server. Defaults is root directory of WebDAV.
|
:param remote_path: (optional) path to resource on WebDAV server. Defaults is root directory of WebDAV.
|
||||||
:return: True if resource is exist or False otherwise
|
:return: True if resource is exist or False otherwise
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.webdav.disable_check:
|
if self.webdav.disable_check:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
urn = Urn(remote_path)
|
urn = Urn(remote_path)
|
||||||
try:
|
try:
|
||||||
response = self.execute_request(action='check', path=urn.quote())
|
response = self.execute_request(action='check', path=urn.quote())
|
||||||
|
@ -300,7 +304,11 @@ class Client(object):
|
||||||
if not self.check(directory_urn.parent()):
|
if not self.check(directory_urn.parent()):
|
||||||
raise RemoteParentNotFound(directory_urn.path())
|
raise RemoteParentNotFound(directory_urn.path())
|
||||||
|
|
||||||
response = self.execute_request(action='mkdir', path=directory_urn.quote())
|
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
|
||||||
return response.status_code in (200, 201)
|
return response.status_code in (200, 201)
|
||||||
|
|
||||||
@wrap_connection_error
|
@wrap_connection_error
|
||||||
|
@ -457,7 +465,7 @@ class Client(object):
|
||||||
self.mkdir(remote_path)
|
self.mkdir(remote_path)
|
||||||
|
|
||||||
for resource_name in listdir(local_path):
|
for resource_name in listdir(local_path):
|
||||||
_remote_path = "{parent}{name}".format(parent=urn.path(), name=resource_name)
|
_remote_path = "{parent}{name}".format(parent=urn.path(), name=resource_name).replace('\\', '')
|
||||||
_local_path = os.path.join(local_path, resource_name)
|
_local_path = os.path.join(local_path, resource_name)
|
||||||
self.upload(local_path=_local_path, remote_path=_remote_path, progress=progress)
|
self.upload(local_path=_local_path, remote_path=_remote_path, progress=progress)
|
||||||
|
|
||||||
|
@ -528,7 +536,7 @@ class Client(object):
|
||||||
raise RemoteParentNotFound(urn_to.path())
|
raise RemoteParentNotFound(urn_to.path())
|
||||||
|
|
||||||
headers = [
|
headers = [
|
||||||
"Destination: {url}".format(url=self.get_url(urn_to))
|
"Destination: {url}".format(url=self.get_url(urn_to.quote()))
|
||||||
]
|
]
|
||||||
if self.is_dir(urn_from.path()):
|
if self.is_dir(urn_from.path()):
|
||||||
headers.append("Depth: {depth}".format(depth=depth))
|
headers.append("Depth: {depth}".format(depth=depth))
|
||||||
|
@ -551,7 +559,7 @@ class Client(object):
|
||||||
if not self.check(urn_to.parent()):
|
if not self.check(urn_to.parent()):
|
||||||
raise RemoteParentNotFound(urn_to.path())
|
raise RemoteParentNotFound(urn_to.path())
|
||||||
|
|
||||||
header_destination = "Destination: {path}".format(path=self.get_url(urn_to))
|
header_destination = "Destination: {path}".format(path=self.get_url(urn_to.quote()))
|
||||||
header_overwrite = "Overwrite: {flag}".format(flag="T" if overwrite else "F")
|
header_overwrite = "Overwrite: {flag}".format(flag="T" if overwrite else "F")
|
||||||
self.execute_request(action='move', path=urn_from.quote(), headers_ext=[header_destination, header_overwrite])
|
self.execute_request(action='move', path=urn_from.quote(), headers_ext=[header_destination, header_overwrite])
|
||||||
|
|
||||||
|
@ -579,13 +587,16 @@ class Client(object):
|
||||||
`modified`: date of resource modification.
|
`modified`: date of resource modification.
|
||||||
"""
|
"""
|
||||||
urn = Urn(remote_path)
|
urn = Urn(remote_path)
|
||||||
if not self.check(urn.path()) and not self.check(Urn(remote_path, directory=True).path()):
|
self._check_remote_resource(remote_path, urn)
|
||||||
raise RemoteResourceNotFound(remote_path)
|
|
||||||
|
|
||||||
response = self.execute_request(action='info', path=urn.quote())
|
response = self.execute_request(action='info', path=urn.quote())
|
||||||
path = self.get_full_path(urn)
|
path = self.get_full_path(urn)
|
||||||
return WebDavXmlUtils.parse_info_response(content=response.content, path=path, hostname=self.webdav.hostname)
|
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
|
@wrap_connection_error
|
||||||
def is_dir(self, remote_path):
|
def is_dir(self, remote_path):
|
||||||
"""Checks is the remote resource directory.
|
"""Checks is the remote resource directory.
|
||||||
|
@ -596,8 +607,7 @@ class Client(object):
|
||||||
"""
|
"""
|
||||||
urn = Urn(remote_path)
|
urn = Urn(remote_path)
|
||||||
parent_urn = Urn(urn.parent())
|
parent_urn = Urn(urn.parent())
|
||||||
if not self.check(urn.path()) and not self.check(Urn(remote_path, directory=True).path()):
|
self._check_remote_resource(remote_path, urn)
|
||||||
raise RemoteResourceNotFound(remote_path)
|
|
||||||
|
|
||||||
response = self.execute_request(action='info', path=parent_urn.quote())
|
response = self.execute_request(action='info', path=parent_urn.quote())
|
||||||
path = self.get_full_path(urn)
|
path = self.get_full_path(urn)
|
||||||
|
@ -662,50 +672,39 @@ class Client(object):
|
||||||
def prune(src, exp):
|
def prune(src, exp):
|
||||||
return [sub(exp, "", item) for item in src]
|
return [sub(exp, "", item) for item in src]
|
||||||
|
|
||||||
|
updated = False
|
||||||
urn = Urn(remote_directory, directory=True)
|
urn = Urn(remote_directory, directory=True)
|
||||||
|
self._validate_remote_directory(urn)
|
||||||
if not self.is_dir(urn.path()):
|
self._validate_local_directory(local_directory)
|
||||||
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())
|
paths = self.list(urn.path())
|
||||||
expression = "{begin}{end}".format(begin="^", end=urn.path())
|
expression = "{begin}{end}".format(begin="^", end=urn.path())
|
||||||
remote_resource_names = prune(paths, expression)
|
remote_resource_names = prune(paths, expression)
|
||||||
|
|
||||||
for local_resource_name in listdir(local_directory):
|
for local_resource_name in listdir(local_directory):
|
||||||
|
|
||||||
local_path = os.path.join(local_directory, local_resource_name)
|
local_path = os.path.join(local_directory, local_resource_name)
|
||||||
remote_path = "{remote_directory}{resource_name}".format(remote_directory=urn.path(),
|
remote_path = "{remote_directory}{resource_name}".format(remote_directory=urn.path(), resource_name=local_resource_name)
|
||||||
resource_name=local_resource_name)
|
|
||||||
|
|
||||||
if os.path.isdir(local_path):
|
if os.path.isdir(local_path):
|
||||||
if not self.check(remote_path=remote_path):
|
if not self.check(remote_path=remote_path):
|
||||||
self.mkdir(remote_path=remote_path)
|
self.mkdir(remote_path=remote_path)
|
||||||
self.push(remote_directory=remote_path, local_directory=local_path)
|
result = self.push(remote_directory=remote_path, local_directory=local_path)
|
||||||
|
updated = updated or result
|
||||||
else:
|
else:
|
||||||
if local_resource_name in remote_resource_names:
|
if local_resource_name in remote_resource_names and not self.is_local_more_recent(local_path, remote_path):
|
||||||
continue
|
continue
|
||||||
self.upload_file(remote_path=remote_path, local_path=local_path)
|
self.upload_file(remote_path=remote_path, local_path=local_path)
|
||||||
|
updated = True
|
||||||
|
return updated
|
||||||
|
|
||||||
def pull(self, remote_directory, local_directory):
|
def pull(self, remote_directory, local_directory):
|
||||||
|
|
||||||
def prune(src, exp):
|
def prune(src, exp):
|
||||||
return [sub(exp, "", item) for item in src]
|
return [sub(exp, "", item) for item in src]
|
||||||
|
|
||||||
updated = False
|
updated = False
|
||||||
|
|
||||||
urn = Urn(remote_directory, directory=True)
|
urn = Urn(remote_directory, directory=True)
|
||||||
|
self._validate_remote_directory(urn)
|
||||||
if not self.is_dir(urn.path()):
|
self._validate_local_directory(local_directory)
|
||||||
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)
|
local_resource_names = listdir(local_directory)
|
||||||
|
|
||||||
|
@ -714,30 +713,62 @@ class Client(object):
|
||||||
remote_resource_names = prune(paths, expression)
|
remote_resource_names = prune(paths, expression)
|
||||||
|
|
||||||
for remote_resource_name in remote_resource_names:
|
for remote_resource_name in remote_resource_names:
|
||||||
|
|
||||||
local_path = os.path.join(local_directory, remote_resource_name)
|
local_path = os.path.join(local_directory, remote_resource_name)
|
||||||
remote_path = "{remote_directory}{resource_name}".format(remote_directory=urn.path(),
|
remote_path = "{remote_directory}{resource_name}".format(remote_directory=urn.path(), resource_name=remote_resource_name)
|
||||||
resource_name=remote_resource_name)
|
|
||||||
|
|
||||||
remote_urn = Urn(remote_path)
|
remote_urn = Urn(remote_path)
|
||||||
|
|
||||||
if remote_urn.path().endswith("/"):
|
if remote_urn.path().endswith("/"):
|
||||||
if not os.path.exists(local_path):
|
if not os.path.exists(local_path):
|
||||||
updated = True
|
updated = True
|
||||||
os.mkdir(local_path)
|
os.mkdir(local_path)
|
||||||
self.pull(remote_directory=remote_path, local_directory=local_path)
|
result = self.pull(remote_directory=remote_path, local_directory=local_path)
|
||||||
|
updated = updated or result
|
||||||
else:
|
else:
|
||||||
if remote_resource_name in local_resource_names:
|
if remote_resource_name in local_resource_names and self.is_local_more_recent(local_path, remote_path):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.download_file(remote_path=remote_path, local_path=local_path)
|
self.download_file(remote_path=remote_path, local_path=local_path)
|
||||||
updated = True
|
updated = True
|
||||||
return updated
|
return updated
|
||||||
|
|
||||||
def sync(self, remote_directory, local_directory):
|
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.pull(remote_directory=remote_directory, local_directory=local_directory)
|
||||||
self.push(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):
|
class Resource(object):
|
||||||
def __init__(self, client, urn):
|
def __init__(self, client, urn):
|
||||||
|
|
|
@ -6,10 +6,13 @@ from webdav3.urn import Urn
|
||||||
|
|
||||||
class ConnectionSettings:
|
class ConnectionSettings:
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
|
"""
|
||||||
|
Method checks is settings are valid
|
||||||
|
:return: True if settings are valid otherwise False
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def valid(self):
|
def valid(self):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.is_valid()
|
self.is_valid()
|
||||||
except OptionNotValid:
|
except OptionNotValid:
|
||||||
|
@ -22,21 +25,21 @@ class WebDAVSettings(ConnectionSettings):
|
||||||
ns = "webdav:"
|
ns = "webdav:"
|
||||||
prefix = "webdav_"
|
prefix = "webdav_"
|
||||||
keys = {'hostname', 'login', 'password', 'token', 'root', 'cert_path', 'key_path', 'recv_speed', 'send_speed',
|
keys = {'hostname', 'login', 'password', 'token', 'root', 'cert_path', 'key_path', 'recv_speed', 'send_speed',
|
||||||
'verbose', 'disable_check'}
|
'verbose', 'disable_check', 'override_methods'}
|
||||||
|
|
||||||
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):
|
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()
|
self.options = dict()
|
||||||
|
|
||||||
|
@ -47,9 +50,9 @@ class WebDAVSettings(ConnectionSettings):
|
||||||
|
|
||||||
self.root = Urn(self.root).quote() if self.root else ''
|
self.root = Urn(self.root).quote() if self.root else ''
|
||||||
self.root = self.root.rstrip(Urn.separate)
|
self.root = self.root.rstrip(Urn.separate)
|
||||||
|
self.hostname = self.hostname.rstrip(Urn.separate)
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
|
|
||||||
if not self.hostname:
|
if not self.hostname:
|
||||||
raise OptionNotValid(name="hostname", value=self.hostname, ns=self.ns)
|
raise OptionNotValid(name="hostname", value=self.hostname, ns=self.ns)
|
||||||
|
|
||||||
|
@ -67,3 +70,4 @@ class WebDAVSettings(ConnectionSettings):
|
||||||
|
|
||||||
if not self.token and not self.login:
|
if not self.token and not self.login:
|
||||||
raise OptionNotValid(name="login", value=self.login, ns=self.ns)
|
raise OptionNotValid(name="login", value=self.login, ns=self.ns)
|
||||||
|
return True
|
||||||
|
|
|
@ -71,7 +71,7 @@ class NoConnection(WebDavException):
|
||||||
self.hostname = hostname
|
self.hostname = hostname
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Not connection with {hostname}".format(hostname=self.hostname)
|
return "No connection with {hostname}".format(hostname=self.hostname)
|
||||||
|
|
||||||
|
|
||||||
# This exception left only for supporting original library interface.
|
# This exception left only for supporting original library interface.
|
||||||
|
@ -96,7 +96,7 @@ class ResponseErrorCode(WebDavException):
|
||||||
|
|
||||||
class NotEnoughSpace(WebDavException):
|
class NotEnoughSpace(WebDavException):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
self.message = "Not enough space on the server"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Not enough space on the server"
|
return self.message
|
||||||
|
|
|
@ -34,13 +34,11 @@ class Urn(object):
|
||||||
return self._path
|
return self._path
|
||||||
|
|
||||||
def filename(self):
|
def filename(self):
|
||||||
|
|
||||||
path_split = self._path.split(Urn.separate)
|
path_split = self._path.split(Urn.separate)
|
||||||
name = path_split[-2] + Urn.separate if path_split[-1] == '' else path_split[-1]
|
name = path_split[-2] + Urn.separate if path_split[-1] == '' else path_split[-1]
|
||||||
return unquote(name)
|
return unquote(name)
|
||||||
|
|
||||||
def parent(self):
|
def parent(self):
|
||||||
|
|
||||||
path_split = self._path.split(Urn.separate)
|
path_split = self._path.split(Urn.separate)
|
||||||
nesting_level = self.nesting_level()
|
nesting_level = self.nesting_level()
|
||||||
parent_path_split = path_split[:nesting_level]
|
parent_path_split = path_split[:nesting_level]
|
||||||
|
|
Loading…
Add table
Reference in a new issue