mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
commit
68ff37e677
63 changed files with 286 additions and 303 deletions
|
|
@ -14,6 +14,10 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies: [flake8-import-order, flake8-bugbear]
|
additional_dependencies: [flake8-import-order, flake8-bugbear]
|
||||||
|
- repo: https://github.com/asottile/reorder_python_imports
|
||||||
|
rev: v2.3.0
|
||||||
|
hooks:
|
||||||
|
- id: reorder-python-imports
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: update-travis
|
- id: update-travis
|
||||||
|
|
|
||||||
62
.travis.yml
62
.travis.yml
|
|
@ -19,74 +19,22 @@
|
||||||
"include": [
|
"include": [
|
||||||
{
|
{
|
||||||
"env": "BUILD=style",
|
"env": "BUILD=style",
|
||||||
"python": "3.6"
|
"python": "3.7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"env": "BUILD=test REQUIREMENTS=release",
|
"env": "BUILD=test REQUIREMENTS=release",
|
||||||
"python": "3.5"
|
"python": "3.7"
|
||||||
},
|
|
||||||
{
|
|
||||||
"dist": "trusty",
|
|
||||||
"env": "BUILD=test-storage DAV_SERVER=radicale REQUIREMENTS=release ",
|
|
||||||
"python": "3.5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dist": "trusty",
|
|
||||||
"env": "BUILD=test-storage DAV_SERVER=xandikos REQUIREMENTS=release ",
|
|
||||||
"python": "3.5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"env": "BUILD=test REQUIREMENTS=minimal",
|
|
||||||
"python": "3.5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dist": "trusty",
|
|
||||||
"env": "BUILD=test-storage DAV_SERVER=radicale REQUIREMENTS=minimal ",
|
|
||||||
"python": "3.5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dist": "trusty",
|
|
||||||
"env": "BUILD=test-storage DAV_SERVER=xandikos REQUIREMENTS=minimal ",
|
|
||||||
"python": "3.5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"env": "BUILD=test REQUIREMENTS=release",
|
|
||||||
"python": "3.6"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"env": "BUILD=test-storage DAV_SERVER=radicale REQUIREMENTS=release ",
|
"env": "BUILD=test-storage DAV_SERVER=radicale REQUIREMENTS=release ",
|
||||||
"python": "3.6"
|
"python": "3.7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"env": "BUILD=test-storage DAV_SERVER=xandikos REQUIREMENTS=release ",
|
"env": "BUILD=test-storage DAV_SERVER=xandikos REQUIREMENTS=release ",
|
||||||
"python": "3.6"
|
"python": "3.7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"env": "BUILD=test-storage DAV_SERVER=fastmail REQUIREMENTS=release ",
|
"env": "BUILD=test-storage DAV_SERVER=fastmail REQUIREMENTS=release ",
|
||||||
"python": "3.6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"env": "BUILD=test REQUIREMENTS=minimal",
|
|
||||||
"python": "3.6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"env": "BUILD=test-storage DAV_SERVER=radicale REQUIREMENTS=minimal ",
|
|
||||||
"python": "3.6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"env": "BUILD=test-storage DAV_SERVER=xandikos REQUIREMENTS=minimal ",
|
|
||||||
"python": "3.6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"env": "BUILD=test REQUIREMENTS=release",
|
|
||||||
"python": "3.7"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"env": "BUILD=test-storage DAV_SERVER=radicale REQUIREMENTS=release ",
|
|
||||||
"python": "3.7"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"env": "BUILD=test-storage DAV_SERVER=xandikos REQUIREMENTS=release ",
|
|
||||||
"python": "3.7"
|
"python": "3.7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -127,7 +75,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"env": "BUILD=test ETESYNC_TESTS=true REQUIREMENTS=latest",
|
"env": "BUILD=test ETESYNC_TESTS=true REQUIREMENTS=latest",
|
||||||
"python": "3.6"
|
"python": "3.7"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
8
Makefile
8
Makefile
|
|
@ -30,11 +30,6 @@ PYTEST_ARGS =
|
||||||
|
|
||||||
TEST_EXTRA_PACKAGES =
|
TEST_EXTRA_PACKAGES =
|
||||||
|
|
||||||
ifeq ($(COVERAGE), true)
|
|
||||||
TEST_EXTRA_PACKAGES += pytest-cov
|
|
||||||
PYTEST_ARGS += --cov-config .coveragerc --cov vdirsyncer
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifeq ($(ETESYNC_TESTS), true)
|
ifeq ($(ETESYNC_TESTS), true)
|
||||||
TEST_EXTRA_PACKAGES += git+https://github.com/etesync/journal-manager@v0.5.2
|
TEST_EXTRA_PACKAGES += git+https://github.com/etesync/journal-manager@v0.5.2
|
||||||
TEST_EXTRA_PACKAGES += django djangorestframework==3.8.2 wsgi_intercept drf-nested-routers
|
TEST_EXTRA_PACKAGES += django djangorestframework==3.8.2 wsgi_intercept drf-nested-routers
|
||||||
|
|
@ -106,9 +101,6 @@ docs:
|
||||||
linkcheck:
|
linkcheck:
|
||||||
sphinx-build -W -b linkcheck ./docs/ ./docs/_build/linkcheck/
|
sphinx-build -W -b linkcheck ./docs/ ./docs/_build/linkcheck/
|
||||||
|
|
||||||
release:
|
|
||||||
python setup.py sdist bdist_wheel upload
|
|
||||||
|
|
||||||
release-deb:
|
release-deb:
|
||||||
sh scripts/release-deb.sh debian jessie
|
sh scripts/release-deb.sh debian jessie
|
||||||
sh scripts/release-deb.sh debian stretch
|
sh scripts/release-deb.sh debian stretch
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ def github_issue_role(name, rawtext, text, lineno, inliner,
|
||||||
if issue_num <= 0:
|
if issue_num <= 0:
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
msg = inliner.reporter.error('Invalid GitHub issue: {}'.format(text),
|
msg = inliner.reporter.error(f'Invalid GitHub issue: {text}',
|
||||||
line=lineno)
|
line=lineno)
|
||||||
prb = inliner.problematic(rawtext, rawtext, msg)
|
prb = inliner.problematic(rawtext, rawtext, msg)
|
||||||
return [prb], [msg]
|
return [prb], [msg]
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ If your distribution doesn't provide a package for vdirsyncer, you still can
|
||||||
use Python's package manager "pip". First, you'll have to check that the
|
use Python's package manager "pip". First, you'll have to check that the
|
||||||
following things are installed:
|
following things are installed:
|
||||||
|
|
||||||
- Python 3.5+ and pip.
|
- Python 3.7+ and pip.
|
||||||
- ``libxml`` and ``libxslt``
|
- ``libxml`` and ``libxslt``
|
||||||
- ``zlib``
|
- ``zlib``
|
||||||
- Linux or OS X. **Windows is not supported**, see :gh:`535`.
|
- Linux or OS X. **Windows is not supported**, see :gh:`535`.
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
import json
|
import json
|
||||||
|
|
||||||
python_versions = ("3.5", "3.6", "3.7", "3.8")
|
python_versions = ["3.7", "3.8"]
|
||||||
latest_python = "3.6"
|
|
||||||
|
|
||||||
cfg = {}
|
cfg = {}
|
||||||
|
|
||||||
|
|
@ -34,7 +32,7 @@ matrix = []
|
||||||
cfg['matrix'] = {'include': matrix, 'fast_finish': True}
|
cfg['matrix'] = {'include': matrix, 'fast_finish': True}
|
||||||
|
|
||||||
matrix.append({
|
matrix.append({
|
||||||
'python': latest_python,
|
'python': python_versions[0],
|
||||||
'env': 'BUILD=style'
|
'env': 'BUILD=style'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -51,7 +49,7 @@ for python, requirements in itertools.product(
|
||||||
'env': f"BUILD=test REQUIREMENTS={requirements}",
|
'env': f"BUILD=test REQUIREMENTS={requirements}",
|
||||||
})
|
})
|
||||||
|
|
||||||
if python == latest_python and requirements == "release":
|
if python == python_versions[0] and requirements == "release":
|
||||||
dav_servers += ("fastmail",)
|
dav_servers += ("fastmail",)
|
||||||
|
|
||||||
for dav_server in dav_servers:
|
for dav_server in dav_servers:
|
||||||
|
|
@ -61,8 +59,6 @@ for python, requirements in itertools.product(
|
||||||
f"DAV_SERVER={dav_server} "
|
f"DAV_SERVER={dav_server} "
|
||||||
f"REQUIREMENTS={requirements} ")
|
f"REQUIREMENTS={requirements} ")
|
||||||
}
|
}
|
||||||
if python == '3.5':
|
|
||||||
job['dist'] = 'trusty'
|
|
||||||
|
|
||||||
if dav_server in ("davical", "icloud"):
|
if dav_server in ("davical", "icloud"):
|
||||||
job['if'] = 'NOT (type IN (pull_request))'
|
job['if'] = 'NOT (type IN (pull_request))'
|
||||||
|
|
@ -70,7 +66,7 @@ for python, requirements in itertools.product(
|
||||||
matrix.append(job)
|
matrix.append(job)
|
||||||
|
|
||||||
matrix.append({
|
matrix.append({
|
||||||
'python': latest_python,
|
'python': python_versions[0],
|
||||||
'env': ("BUILD=test "
|
'env': ("BUILD=test "
|
||||||
"ETESYNC_TESTS=true "
|
"ETESYNC_TESTS=true "
|
||||||
"REQUIREMENTS=latest")
|
"REQUIREMENTS=latest")
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,12 @@ universal = 1
|
||||||
|
|
||||||
[tool:pytest]
|
[tool:pytest]
|
||||||
norecursedirs = tests/storage/servers/*
|
norecursedirs = tests/storage/servers/*
|
||||||
addopts = --tb=short
|
addopts =
|
||||||
|
--tb=short
|
||||||
|
--cov-config .coveragerc
|
||||||
|
--cov=vdirsyncer
|
||||||
|
--cov-report=term-missing
|
||||||
|
--no-cov-on-fail
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
# E731: Use a def instead of lambda expr
|
# E731: Use a def instead of lambda expr
|
||||||
|
|
|
||||||
10
setup.py
10
setup.py
|
|
@ -4,14 +4,14 @@ Vdirsyncer synchronizes calendars and contacts.
|
||||||
Please refer to https://vdirsyncer.pimutils.org/en/stable/packaging.html for
|
Please refer to https://vdirsyncer.pimutils.org/en/stable/packaging.html for
|
||||||
how to package vdirsyncer.
|
how to package vdirsyncer.
|
||||||
'''
|
'''
|
||||||
|
from setuptools import Command
|
||||||
|
from setuptools import find_packages
|
||||||
from setuptools import Command, find_packages, setup
|
from setuptools import setup
|
||||||
|
|
||||||
|
|
||||||
requirements = [
|
requirements = [
|
||||||
# https://github.com/mitsuhiko/click/issues/200
|
# https://github.com/mitsuhiko/click/issues/200
|
||||||
'click>=5.0,<6.0',
|
'click>=5.0',
|
||||||
'click-log>=0.3.0, <0.4.0',
|
'click-log>=0.3.0, <0.4.0',
|
||||||
|
|
||||||
# https://github.com/pimutils/vdirsyncer/issues/478
|
# https://github.com/pimutils/vdirsyncer/issues/478
|
||||||
|
|
@ -87,8 +87,6 @@ setup(
|
||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: BSD License',
|
||||||
'Operating System :: POSIX',
|
'Operating System :: POSIX',
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
'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',
|
'Programming Language :: Python :: 3.8',
|
||||||
'Topic :: Internet',
|
'Topic :: Internet',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
hypothesis>=5.0.0
|
hypothesis>=5.0.0
|
||||||
pytest
|
pytest
|
||||||
|
pytest-cov
|
||||||
pytest-localserver
|
pytest-localserver
|
||||||
pytest-subtesthack
|
pytest-subtesthack
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
'''
|
'''
|
||||||
Test suite for vdirsyncer.
|
Test suite for vdirsyncer.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
|
import urllib3.exceptions
|
||||||
|
|
||||||
from vdirsyncer.vobject import normalize_item
|
from vdirsyncer.vobject import normalize_item
|
||||||
|
|
||||||
import urllib3
|
|
||||||
import urllib3.exceptions
|
|
||||||
|
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import click_log
|
import click_log
|
||||||
|
|
||||||
from hypothesis import HealthCheck, Verbosity, settings
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from hypothesis import HealthCheck
|
||||||
|
from hypothesis import settings
|
||||||
|
from hypothesis import Verbosity
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,27 @@
|
||||||
import random
|
import random
|
||||||
import uuid
|
|
||||||
|
|
||||||
import textwrap
|
import textwrap
|
||||||
from urllib.parse import quote as urlquote, unquote as urlunquote
|
import uuid
|
||||||
|
from urllib.parse import quote as urlquote
|
||||||
|
from urllib.parse import unquote as urlunquote
|
||||||
|
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
|
import pytest
|
||||||
from hypothesis import given
|
from hypothesis import given
|
||||||
|
|
||||||
import pytest
|
from .. import assert_item_equals
|
||||||
|
from .. import EVENT_TEMPLATE
|
||||||
|
from .. import normalize_item
|
||||||
|
from .. import printable_characters_strategy
|
||||||
|
from .. import TASK_TEMPLATE
|
||||||
|
from .. import VCARD_TEMPLATE
|
||||||
from vdirsyncer import exceptions
|
from vdirsyncer import exceptions
|
||||||
from vdirsyncer.storage.base import normalize_meta_value
|
from vdirsyncer.storage.base import normalize_meta_value
|
||||||
from vdirsyncer.vobject import Item
|
from vdirsyncer.vobject import Item
|
||||||
|
|
||||||
from .. import EVENT_TEMPLATE, TASK_TEMPLATE, VCARD_TEMPLATE, \
|
|
||||||
assert_item_equals, normalize_item, printable_characters_strategy
|
|
||||||
|
|
||||||
|
|
||||||
def get_server_mixin(server_name):
|
def get_server_mixin(server_name):
|
||||||
from . import __name__ as base
|
from . import __name__ as base
|
||||||
x = __import__('{}.servers.{}'.format(base, server_name), fromlist=[''])
|
x = __import__(f'{base}.servers.{server_name}', fromlist=[''])
|
||||||
return x.ServerMixin
|
return x.ServerMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -183,7 +185,7 @@ class StorageTests:
|
||||||
def test_discover(self, requires_collections, get_storage_args, get_item):
|
def test_discover(self, requires_collections, get_storage_args, get_item):
|
||||||
collections = set()
|
collections = set()
|
||||||
for i in range(1, 5):
|
for i in range(1, 5):
|
||||||
collection = 'test{}'.format(i)
|
collection = f'test{i}'
|
||||||
s = self.storage_class(**get_storage_args(collection=collection))
|
s = self.storage_class(**get_storage_args(collection=collection))
|
||||||
assert not list(s.list())
|
assert not list(s.list())
|
||||||
s.upload(get_item())
|
s.upload(get_item())
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def slow_create_collection(request):
|
def slow_create_collection(request):
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,15 @@
|
||||||
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import requests
|
|
||||||
import requests.exceptions
|
import requests.exceptions
|
||||||
|
|
||||||
|
from .. import get_server_mixin
|
||||||
|
from .. import StorageTests
|
||||||
from tests import assert_item_equals
|
from tests import assert_item_equals
|
||||||
|
|
||||||
from vdirsyncer import exceptions
|
from vdirsyncer import exceptions
|
||||||
from vdirsyncer.vobject import Item
|
from vdirsyncer.vobject import Item
|
||||||
|
|
||||||
from .. import StorageTests, get_server_mixin
|
|
||||||
|
|
||||||
|
|
||||||
dav_server = os.environ.get('DAV_SERVER', 'skip')
|
dav_server = os.environ.get('DAV_SERVER', 'skip')
|
||||||
ServerMixin = get_server_mixin(dav_server)
|
ServerMixin = get_server_mixin(dav_server)
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,17 @@ import datetime
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import requests
|
|
||||||
import requests.exceptions
|
import requests.exceptions
|
||||||
|
|
||||||
from tests import EVENT_TEMPLATE, TASK_TEMPLATE, VCARD_TEMPLATE
|
from . import dav_server
|
||||||
|
from . import DAVStorageTests
|
||||||
|
from .. import format_item
|
||||||
|
from tests import EVENT_TEMPLATE
|
||||||
|
from tests import TASK_TEMPLATE
|
||||||
|
from tests import VCARD_TEMPLATE
|
||||||
from vdirsyncer import exceptions
|
from vdirsyncer import exceptions
|
||||||
from vdirsyncer.storage.dav import CalDAVStorage
|
from vdirsyncer.storage.dav import CalDAVStorage
|
||||||
|
|
||||||
from . import DAVStorageTests, dav_server
|
|
||||||
from .. import format_item
|
|
||||||
|
|
||||||
|
|
||||||
class TestCalDAVStorage(DAVStorageTests):
|
class TestCalDAVStorage(DAVStorageTests):
|
||||||
storage_class = CalDAVStorage
|
storage_class = CalDAVStorage
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from vdirsyncer.storage.dav import CardDAVStorage
|
|
||||||
|
|
||||||
from . import DAVStorageTests
|
from . import DAVStorageTests
|
||||||
|
from vdirsyncer.storage.dav import CardDAVStorage
|
||||||
|
|
||||||
|
|
||||||
class TestCardDAVStorage(DAVStorageTests):
|
class TestCardDAVStorage(DAVStorageTests):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from vdirsyncer.storage.dav import _BAD_XML_CHARS, _merge_xml, _parse_xml
|
from vdirsyncer.storage.dav import _BAD_XML_CHARS
|
||||||
|
from vdirsyncer.storage.dav import _merge_xml
|
||||||
|
from vdirsyncer.storage.dav import _parse_xml
|
||||||
|
|
||||||
|
|
||||||
def test_xml_utilities():
|
def test_xml_utilities():
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ https://docs.djangoproject.com/en/1.10/topics/settings/
|
||||||
For the full list of settings and their values, see
|
For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/1.10/ref/settings/
|
https://docs.djangoproject.com/en/1.10/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,10 @@ Including another URLconf
|
||||||
1. Import the include() function: from django.conf.urls import url, include
|
1. Import the include() function: from django.conf.urls import url, include
|
||||||
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include
|
||||||
|
from django.conf.urls import url
|
||||||
from rest_framework_nested import routers
|
|
||||||
|
|
||||||
from journal import views
|
from journal import views
|
||||||
|
from rest_framework_nested import routers
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.register(r'journals', views.JournalViewSet)
|
router.register(r'journals', views.JournalViewSet)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
For more information on this file, see
|
For more information on this file, see
|
||||||
https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
|
https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import shutil
|
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from vdirsyncer.storage.etesync import EtesyncContacts, EtesyncCalendars
|
|
||||||
|
|
||||||
from .. import StorageTests
|
from .. import StorageTests
|
||||||
|
from vdirsyncer.storage.etesync import EtesyncCalendars
|
||||||
|
from vdirsyncer.storage.etesync import EtesyncContacts
|
||||||
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.skipif(os.getenv('ETESYNC_TESTS', '') != 'true',
|
pytestmark = pytest.mark.skipif(os.getenv('ETESYNC_TESTS', '') != 'true',
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import os
|
import os
|
||||||
import pytest
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
try:
|
try:
|
||||||
caldav_args = {
|
caldav_args = {
|
||||||
# Those credentials are configured through the Travis UI
|
# Those credentials are configured through the Travis UI
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import shutil
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
testserver_repo = os.path.dirname(__file__)
|
testserver_repo = os.path.dirname(__file__)
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,10 @@ import subprocess
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from . import StorageTests
|
||||||
from vdirsyncer.storage.filesystem import FilesystemStorage
|
from vdirsyncer.storage.filesystem import FilesystemStorage
|
||||||
from vdirsyncer.vobject import Item
|
from vdirsyncer.vobject import Item
|
||||||
|
|
||||||
from . import StorageTests
|
|
||||||
|
|
||||||
|
|
||||||
class TestFilesystemStorage(StorageTests):
|
class TestFilesystemStorage(StorageTests):
|
||||||
storage_class = FilesystemStorage
|
storage_class = FilesystemStorage
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from requests import Response
|
from requests import Response
|
||||||
|
|
||||||
from tests import normalize_item
|
from tests import normalize_item
|
||||||
|
|
||||||
from vdirsyncer.exceptions import UserError
|
from vdirsyncer.exceptions import UserError
|
||||||
from vdirsyncer.storage.http import HttpStorage, prepare_auth
|
from vdirsyncer.storage.http import HttpStorage
|
||||||
|
from vdirsyncer.storage.http import prepare_auth
|
||||||
|
|
||||||
|
|
||||||
def test_list(monkeypatch):
|
def test_list(monkeypatch):
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from requests import Response
|
from requests import Response
|
||||||
|
|
||||||
import vdirsyncer.storage.http
|
import vdirsyncer.storage.http
|
||||||
|
from . import StorageTests
|
||||||
from vdirsyncer.storage.base import Storage
|
from vdirsyncer.storage.base import Storage
|
||||||
from vdirsyncer.storage.singlefile import SingleFileStorage
|
from vdirsyncer.storage.singlefile import SingleFileStorage
|
||||||
|
|
||||||
from . import StorageTests
|
|
||||||
|
|
||||||
|
|
||||||
class CombinedStorage(Storage):
|
class CombinedStorage(Storage):
|
||||||
'''A subclass of HttpStorage to make testing easier. It supports writes via
|
'''A subclass of HttpStorage to make testing easier. It supports writes via
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from vdirsyncer.storage.memory import MemoryStorage
|
|
||||||
|
|
||||||
from . import StorageTests
|
from . import StorageTests
|
||||||
|
from vdirsyncer.storage.memory import MemoryStorage
|
||||||
|
|
||||||
|
|
||||||
class TestMemoryStorage(StorageTests):
|
class TestMemoryStorage(StorageTests):
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from vdirsyncer.storage.singlefile import SingleFileStorage
|
|
||||||
|
|
||||||
from . import StorageTests
|
from . import StorageTests
|
||||||
|
from vdirsyncer.storage.singlefile import SingleFileStorage
|
||||||
|
|
||||||
|
|
||||||
class TestSingleFileStorage(StorageTests):
|
class TestSingleFileStorage(StorageTests):
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
from click.testing import CliRunner
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
import vdirsyncer.cli as cli
|
import vdirsyncer.cli as cli
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ from textwrap import dedent
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from vdirsyncer import cli, exceptions
|
from vdirsyncer import cli
|
||||||
|
from vdirsyncer import exceptions
|
||||||
from vdirsyncer.cli.config import Config
|
from vdirsyncer.cli.config import Config
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ def storage(tmpdir, runner):
|
||||||
def test_basic(storage, runner, collection):
|
def test_basic(storage, runner, collection):
|
||||||
if collection is not None:
|
if collection is not None:
|
||||||
storage = storage.mkdir(collection)
|
storage = storage.mkdir(collection)
|
||||||
collection_arg = 'foo/{}'.format(collection)
|
collection_arg = f'foo/{collection}'
|
||||||
else:
|
else:
|
||||||
collection_arg = 'foo'
|
collection_arg = 'foo'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ import sys
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
from hypothesis import example, given
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from hypothesis import example
|
||||||
|
from hypothesis import given
|
||||||
|
|
||||||
|
|
||||||
def test_simple_run(tmpdir, runner):
|
def test_simple_run(tmpdir, runner):
|
||||||
|
|
@ -123,7 +123,10 @@ def test_verbosity(tmpdir, runner):
|
||||||
runner.write_with_general('')
|
runner.write_with_general('')
|
||||||
result = runner.invoke(['--verbosity=HAHA', 'sync'])
|
result = runner.invoke(['--verbosity=HAHA', 'sync'])
|
||||||
assert result.exception
|
assert result.exception
|
||||||
assert 'invalid value for "--verbosity"' in result.output.lower()
|
assert (
|
||||||
|
'invalid value for "--verbosity"' in result.output.lower()
|
||||||
|
or "invalid value for '--verbosity'" in result.output.lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_collections_cache_invalidation(tmpdir, runner):
|
def test_collections_cache_invalidation(tmpdir, runner):
|
||||||
|
|
@ -461,7 +464,7 @@ def test_partial_sync(tmpdir, runner, partial_sync):
|
||||||
fileext = ".txt"
|
fileext = ".txt"
|
||||||
path = "{base}/bar"
|
path = "{base}/bar"
|
||||||
'''.format(
|
'''.format(
|
||||||
partial_sync=('partial_sync = "{}"\n'.format(partial_sync)
|
partial_sync=(f'partial_sync = "{partial_sync}"\n'
|
||||||
if partial_sync else ''),
|
if partial_sync else ''),
|
||||||
base=str(tmpdir)
|
base=str(tmpdir)
|
||||||
)))
|
)))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from vdirsyncer import exceptions
|
from vdirsyncer import exceptions
|
||||||
from vdirsyncer.cli.utils import handle_cli_error, \
|
from vdirsyncer.cli.utils import handle_cli_error
|
||||||
storage_instance_from_config, storage_names
|
from vdirsyncer.cli.utils import storage_instance_from_config
|
||||||
|
from vdirsyncer.cli.utils import storage_names
|
||||||
|
|
||||||
|
|
||||||
def test_handle_cli_error(capsys):
|
def test_handle_cli_error(capsys):
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import sys
|
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
import click_log
|
import click_log
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from vdirsyncer import http, utils
|
from vdirsyncer import http
|
||||||
|
from vdirsyncer import utils
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
|
import pytest
|
||||||
from hypothesis import given
|
from hypothesis import given
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from vdirsyncer import exceptions
|
from vdirsyncer import exceptions
|
||||||
from vdirsyncer.cli.fetchparams import STRATEGIES, expand_fetch_params
|
from vdirsyncer.cli.fetchparams import expand_fetch_params
|
||||||
|
from vdirsyncer.cli.fetchparams import STRATEGIES
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -47,7 +47,7 @@ def test_key_conflict(monkeypatch, mystrategy):
|
||||||
@given(s=st.text(), t=st.text(min_size=1))
|
@given(s=st.text(), t=st.text(min_size=1))
|
||||||
def test_fuzzing(s, t, mystrategy):
|
def test_fuzzing(s, t, mystrategy):
|
||||||
config = expand_fetch_params({
|
config = expand_fetch_params({
|
||||||
'{}.fetch'.format(s): ['mystrategy', t]
|
f'{s}.fetch': ['mystrategy', t]
|
||||||
})
|
})
|
||||||
|
|
||||||
assert config[s] == t
|
assert config[s] == t
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
from hypothesis import assume, given
|
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
|
import pytest
|
||||||
|
from hypothesis import assume
|
||||||
|
from hypothesis import given
|
||||||
|
|
||||||
from vdirsyncer.sync.status import SqliteStatus
|
from vdirsyncer.sync.status import SqliteStatus
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,22 @@
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
from hypothesis import assume
|
|
||||||
from hypothesis.stateful import Bundle, RuleBasedStateMachine, rule
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from hypothesis import assume
|
||||||
|
from hypothesis.stateful import Bundle
|
||||||
|
from hypothesis.stateful import rule
|
||||||
|
from hypothesis.stateful import RuleBasedStateMachine
|
||||||
|
|
||||||
from tests import blow_up, uid_strategy
|
from tests import blow_up
|
||||||
|
from tests import uid_strategy
|
||||||
from vdirsyncer.storage.memory import MemoryStorage, _random_string
|
from vdirsyncer.storage.memory import _random_string
|
||||||
|
from vdirsyncer.storage.memory import MemoryStorage
|
||||||
from vdirsyncer.sync import sync as _sync
|
from vdirsyncer.sync import sync as _sync
|
||||||
from vdirsyncer.sync.exceptions import BothReadOnly, IdentConflict, \
|
from vdirsyncer.sync.exceptions import BothReadOnly
|
||||||
PartialSync, StorageEmpty, SyncConflict
|
from vdirsyncer.sync.exceptions import IdentConflict
|
||||||
|
from vdirsyncer.sync.exceptions import PartialSync
|
||||||
|
from vdirsyncer.sync.exceptions import StorageEmpty
|
||||||
|
from vdirsyncer.sync.exceptions import SyncConflict
|
||||||
from vdirsyncer.sync.status import SqliteStatus
|
from vdirsyncer.sync.status import SqliteStatus
|
||||||
from vdirsyncer.vobject import Item
|
from vdirsyncer.vobject import Item
|
||||||
|
|
||||||
|
|
@ -253,7 +258,7 @@ def test_conflict_resolution_both_etags_new(winning_storage):
|
||||||
b.update(href_b, item_b, etag_b)
|
b.update(href_b, item_b, etag_b)
|
||||||
with pytest.raises(SyncConflict):
|
with pytest.raises(SyncConflict):
|
||||||
sync(a, b, status)
|
sync(a, b, status)
|
||||||
sync(a, b, status, conflict_resolution='{} wins'.format(winning_storage))
|
sync(a, b, status, conflict_resolution=f'{winning_storage} wins')
|
||||||
assert items(a) == items(b) == {
|
assert items(a) == items(b) == {
|
||||||
item_a.raw if winning_storage == 'a' else item_b.raw
|
item_a.raw if winning_storage == 'a' else item_b.raw
|
||||||
}
|
}
|
||||||
|
|
@ -563,7 +568,7 @@ class SyncMachine(RuleBasedStateMachine):
|
||||||
uid=uid_strategy,
|
uid=uid_strategy,
|
||||||
etag=st.text())
|
etag=st.text())
|
||||||
def upload(self, storage, uid, etag):
|
def upload(self, storage, uid, etag):
|
||||||
item = Item('UID:{}'.format(uid))
|
item = Item(f'UID:{uid}')
|
||||||
storage.items[uid] = (etag, item)
|
storage.items[uid] = (etag, item)
|
||||||
|
|
||||||
@rule(storage=Storage, href=st.text())
|
@rule(storage=Storage, href=st.text())
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
from hypothesis import example, given
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from hypothesis import example
|
||||||
|
from hypothesis import given
|
||||||
|
|
||||||
from tests import blow_up
|
from tests import blow_up
|
||||||
|
|
||||||
from vdirsyncer.exceptions import UserError
|
from vdirsyncer.exceptions import UserError
|
||||||
from vdirsyncer.metasync import MetaSyncConflict, logger, metasync
|
from vdirsyncer.metasync import logger
|
||||||
|
from vdirsyncer.metasync import metasync
|
||||||
|
from vdirsyncer.metasync import MetaSyncConflict
|
||||||
from vdirsyncer.storage.base import normalize_meta_value
|
from vdirsyncer.storage.base import normalize_meta_value
|
||||||
from vdirsyncer.storage.memory import MemoryStorage
|
from vdirsyncer.storage.memory import MemoryStorage
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
from hypothesis import HealthCheck, given, settings
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from hypothesis import given
|
||||||
|
from hypothesis import HealthCheck
|
||||||
|
from hypothesis import settings
|
||||||
|
|
||||||
from tests import uid_strategy
|
from tests import uid_strategy
|
||||||
|
from vdirsyncer.repair import IrreparableItem
|
||||||
from vdirsyncer.repair import IrreparableItem, repair_item, repair_storage
|
from vdirsyncer.repair import repair_item
|
||||||
|
from vdirsyncer.repair import repair_storage
|
||||||
from vdirsyncer.storage.memory import MemoryStorage
|
from vdirsyncer.storage.memory import MemoryStorage
|
||||||
from vdirsyncer.utils import href_safe
|
from vdirsyncer.utils import href_safe
|
||||||
from vdirsyncer.vobject import Item
|
from vdirsyncer.vobject import Item
|
||||||
|
|
@ -18,11 +20,11 @@ def test_repair_uids(uid):
|
||||||
s.items = {
|
s.items = {
|
||||||
'one': (
|
'one': (
|
||||||
'asdf',
|
'asdf',
|
||||||
Item('BEGIN:VCARD\nFN:Hans\nUID:{}\nEND:VCARD'.format(uid))
|
Item(f'BEGIN:VCARD\nFN:Hans\nUID:{uid}\nEND:VCARD')
|
||||||
),
|
),
|
||||||
'two': (
|
'two': (
|
||||||
'asdf',
|
'asdf',
|
||||||
Item('BEGIN:VCARD\nFN:Peppi\nUID:{}\nEND:VCARD'.format(uid))
|
Item(f'BEGIN:VCARD\nFN:Peppi\nUID:{uid}\nEND:VCARD')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,7 +42,7 @@ def test_repair_uids(uid):
|
||||||
@settings(suppress_health_check=HealthCheck.all())
|
@settings(suppress_health_check=HealthCheck.all())
|
||||||
def test_repair_unsafe_uids(uid):
|
def test_repair_unsafe_uids(uid):
|
||||||
s = MemoryStorage()
|
s = MemoryStorage()
|
||||||
item = Item('BEGIN:VCARD\nUID:{}\nEND:VCARD'.format(uid))
|
item = Item(f'BEGIN:VCARD\nUID:{uid}\nEND:VCARD')
|
||||||
href, etag = s.upload(item)
|
href, etag = s.upload(item)
|
||||||
assert s.get(href)[0].uid == uid
|
assert s.get(href)[0].uid == uid
|
||||||
assert not href_safe(uid)
|
assert not href_safe(uid)
|
||||||
|
|
@ -58,7 +60,7 @@ def test_repair_unsafe_uids(uid):
|
||||||
('perfectly-fine', 'b@dh0mbr3')
|
('perfectly-fine', 'b@dh0mbr3')
|
||||||
])
|
])
|
||||||
def test_repair_unsafe_href(uid, href):
|
def test_repair_unsafe_href(uid, href):
|
||||||
item = Item('BEGIN:VCARD\nUID:{}\nEND:VCARD'.format(uid))
|
item = Item(f'BEGIN:VCARD\nUID:{uid}\nEND:VCARD')
|
||||||
new_item = repair_item(href, item, set(), True)
|
new_item = repair_item(href, item, set(), True)
|
||||||
assert new_item.raw != item.raw
|
assert new_item.raw != item.raw
|
||||||
assert new_item.uid != item.uid
|
assert new_item.uid != item.uid
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,20 @@
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
from hypothesis import assume, given
|
|
||||||
from hypothesis.stateful import Bundle, RuleBasedStateMachine, rule
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from hypothesis import assume
|
||||||
from tests import BARE_EVENT_TEMPLATE, EVENT_TEMPLATE, \
|
from hypothesis import given
|
||||||
EVENT_WITH_TIMEZONE_TEMPLATE, VCARD_TEMPLATE, normalize_item, \
|
from hypothesis.stateful import Bundle
|
||||||
uid_strategy
|
from hypothesis.stateful import rule
|
||||||
|
from hypothesis.stateful import RuleBasedStateMachine
|
||||||
|
|
||||||
import vdirsyncer.vobject as vobject
|
import vdirsyncer.vobject as vobject
|
||||||
|
from tests import BARE_EVENT_TEMPLATE
|
||||||
|
from tests import EVENT_TEMPLATE
|
||||||
|
from tests import EVENT_WITH_TIMEZONE_TEMPLATE
|
||||||
|
from tests import normalize_item
|
||||||
|
from tests import uid_strategy
|
||||||
|
from tests import VCARD_TEMPLATE
|
||||||
|
|
||||||
|
|
||||||
_simple_split = [
|
_simple_split = [
|
||||||
|
|
@ -221,7 +225,7 @@ def test_replace_uid(template, uid):
|
||||||
item = vobject.Item(template.format(r=123, uid=123)).with_uid(uid)
|
item = vobject.Item(template.format(r=123, uid=123)).with_uid(uid)
|
||||||
assert item.uid == uid
|
assert item.uid == uid
|
||||||
if uid:
|
if uid:
|
||||||
assert item.raw.count('\nUID:{}'.format(uid)) == 1
|
assert item.raw.count(f'\nUID:{uid}') == 1
|
||||||
else:
|
else:
|
||||||
assert '\nUID:' not in item.raw
|
assert '\nUID:' not in item.raw
|
||||||
|
|
||||||
|
|
@ -317,7 +321,7 @@ class VobjectMachine(RuleBasedStateMachine):
|
||||||
params=st.lists(st.tuples(value_strategy, value_strategy)))
|
params=st.lists(st.tuples(value_strategy, value_strategy)))
|
||||||
def add_prop_raw(self, c, key, value, params):
|
def add_prop_raw(self, c, key, value, params):
|
||||||
params_str = ','.join(k + '=' + v for k, v in params)
|
params_str = ','.join(k + '=' + v for k, v in params)
|
||||||
c.props.insert(0, '{};{}:{}'.format(key, params_str, value))
|
c.props.insert(0, f'{key};{params_str}:{value}')
|
||||||
assert c[key] == value
|
assert c[key] == value
|
||||||
assert key in c
|
assert key in c
|
||||||
assert c.get(key) == value
|
assert c.get(key) == value
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ except ImportError: # pragma: no cover
|
||||||
|
|
||||||
def _check_python_version(): # pragma: no cover
|
def _check_python_version(): # pragma: no cover
|
||||||
import sys
|
import sys
|
||||||
if sys.version_info < (3, 4, 0):
|
if sys.version_info < (3, 7, 0):
|
||||||
print('vdirsyncer requires at least Python 3.5.')
|
print('vdirsyncer requires at least Python 3.7.')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
import click_log
|
import click_log
|
||||||
|
|
||||||
from .. import BUGTRACKER_HOME, __version__
|
from .. import __version__
|
||||||
|
from .. import BUGTRACKER_HOME
|
||||||
|
|
||||||
|
|
||||||
cli_logger = logging.getLogger(__name__)
|
cli_logger = logging.getLogger(__name__)
|
||||||
|
|
@ -65,7 +65,7 @@ def max_workers_callback(ctx, param, value):
|
||||||
if value == 0 and logging.getLogger('vdirsyncer').level == logging.DEBUG:
|
if value == 0 and logging.getLogger('vdirsyncer').level == logging.DEBUG:
|
||||||
value = 1
|
value = 1
|
||||||
|
|
||||||
cli_logger.debug('Using {} maximal workers.'.format(value))
|
cli_logger.debug(f'Using {value} maximal workers.')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -75,7 +75,7 @@ def max_workers_option(default=0):
|
||||||
help += 'The default is 0, which means "as many as necessary". ' \
|
help += 'The default is 0, which means "as many as necessary". ' \
|
||||||
'With -vdebug enabled, the default is 1.'
|
'With -vdebug enabled, the default is 1.'
|
||||||
else:
|
else:
|
||||||
help += 'The default is {}.'.format(default)
|
help += f'The default is {default}.'
|
||||||
|
|
||||||
return click.option(
|
return click.option(
|
||||||
'--max-workers', default=default, type=click.IntRange(min=0, max=None),
|
'--max-workers', default=default, type=click.IntRange(min=0, max=None),
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,12 @@ from itertools import chain
|
||||||
|
|
||||||
from click_threading import get_ui_worker
|
from click_threading import get_ui_worker
|
||||||
|
|
||||||
|
from .. import exceptions
|
||||||
|
from .. import PROJECT_HOME
|
||||||
|
from ..utils import cached_property
|
||||||
|
from ..utils import expand_path
|
||||||
from .fetchparams import expand_fetch_params
|
from .fetchparams import expand_fetch_params
|
||||||
from .utils import storage_class_from_config
|
from .utils import storage_class_from_config
|
||||||
from .. import PROJECT_HOME, exceptions
|
|
||||||
from ..utils import cached_property, expand_path
|
|
||||||
|
|
||||||
|
|
||||||
GENERAL_ALL = frozenset(['status_path'])
|
GENERAL_ALL = frozenset(['status_path'])
|
||||||
|
|
@ -101,7 +103,7 @@ class _ConfigReader:
|
||||||
def _parse_section(self, section_type, name, options):
|
def _parse_section(self, section_type, name, options):
|
||||||
validate_section_name(name, section_type)
|
validate_section_name(name, section_type)
|
||||||
if name in self._seen_names:
|
if name in self._seen_names:
|
||||||
raise ValueError('Name "{}" already used.'.format(name))
|
raise ValueError(f'Name "{name}" already used.')
|
||||||
self._seen_names.add(name)
|
self._seen_names.add(name)
|
||||||
|
|
||||||
if section_type == 'general':
|
if section_type == 'general':
|
||||||
|
|
@ -163,7 +165,7 @@ class Config:
|
||||||
try:
|
try:
|
||||||
self.pairs[name] = PairConfig(self, name, options)
|
self.pairs[name] = PairConfig(self, name, options)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise exceptions.UserError('Pair {}: {}'.format(name, e))
|
raise exceptions.UserError(f'Pair {name}: {e}')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_fileobject(cls, f):
|
def from_fileobject(cls, f):
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .utils import handle_collection_not_found, handle_storage_init_error, \
|
|
||||||
load_status, save_status, storage_class_from_config, \
|
|
||||||
storage_instance_from_config
|
|
||||||
|
|
||||||
from .. import exceptions
|
from .. import exceptions
|
||||||
from ..utils import cached_property
|
from ..utils import cached_property
|
||||||
|
from .utils import handle_collection_not_found
|
||||||
|
from .utils import handle_storage_init_error
|
||||||
|
from .utils import load_status
|
||||||
|
from .utils import save_status
|
||||||
|
from .utils import storage_class_from_config
|
||||||
|
from .utils import storage_instance_from_config
|
||||||
|
|
||||||
|
|
||||||
# Increase whenever upgrade potentially breaks discovery cache and collections
|
# Increase whenever upgrade potentially breaks discovery cache and collections
|
||||||
|
|
@ -211,7 +213,7 @@ def _print_collections(instance_name, get_discovered):
|
||||||
logger.warning('Failed to discover collections for {}, use `-vdebug` '
|
logger.warning('Failed to discover collections for {}, use `-vdebug` '
|
||||||
'to see the full traceback.'.format(instance_name))
|
'to see the full traceback.'.format(instance_name))
|
||||||
return
|
return
|
||||||
logger.info('{}:'.format(instance_name))
|
logger.info(f'{instance_name}:')
|
||||||
for args in discovered.values():
|
for args in discovered.values():
|
||||||
collection = args['collection']
|
collection = args['collection']
|
||||||
if collection is None:
|
if collection is None:
|
||||||
|
|
@ -226,7 +228,7 @@ def _print_collections(instance_name, get_discovered):
|
||||||
|
|
||||||
logger.info(' - {}{}'.format(
|
logger.info(' - {}{}'.format(
|
||||||
json.dumps(collection),
|
json.dumps(collection),
|
||||||
' ("{}")'.format(displayname)
|
f' ("{displayname}")'
|
||||||
if displayname and displayname != collection
|
if displayname and displayname != collection
|
||||||
else ''
|
else ''
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ import click
|
||||||
|
|
||||||
from . import AppContext
|
from . import AppContext
|
||||||
from .. import exceptions
|
from .. import exceptions
|
||||||
from ..utils import expand_path, synchronized
|
from ..utils import expand_path
|
||||||
|
from ..utils import synchronized
|
||||||
|
|
||||||
SUFFIX = '.fetch'
|
SUFFIX = '.fetch'
|
||||||
|
|
||||||
|
|
@ -19,7 +20,7 @@ def expand_fetch_params(config):
|
||||||
|
|
||||||
newkey = key[:-len(SUFFIX)]
|
newkey = key[:-len(SUFFIX)]
|
||||||
if newkey in config:
|
if newkey in config:
|
||||||
raise ValueError('Can\'t set {} and {}.'.format(key, newkey))
|
raise ValueError(f'Can\'t set {key} and {newkey}.')
|
||||||
config[newkey] = _fetch_value(config[key], key)
|
config[newkey] = _fetch_value(config[key], key)
|
||||||
del config[key]
|
del config[key]
|
||||||
|
|
||||||
|
|
@ -45,7 +46,7 @@ def _fetch_value(opts, key):
|
||||||
cache_key = tuple(opts)
|
cache_key = tuple(opts)
|
||||||
if cache_key in password_cache:
|
if cache_key in password_cache:
|
||||||
rv = password_cache[cache_key]
|
rv = password_cache[cache_key]
|
||||||
logger.debug('Found cached value for {!r}.'.format(opts))
|
logger.debug(f'Found cached value for {opts!r}.')
|
||||||
if isinstance(rv, BaseException):
|
if isinstance(rv, BaseException):
|
||||||
raise rv
|
raise rv
|
||||||
return rv
|
return rv
|
||||||
|
|
@ -54,7 +55,7 @@ def _fetch_value(opts, key):
|
||||||
try:
|
try:
|
||||||
strategy_fn = STRATEGIES[strategy]
|
strategy_fn = STRATEGIES[strategy]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise exceptions.UserError('Unknown strategy: {}'.format(strategy))
|
raise exceptions.UserError(f'Unknown strategy: {strategy}')
|
||||||
|
|
||||||
logger.debug('Fetching value for {} with {} strategy.'
|
logger.debug('Fetching value for {} with {} strategy.'
|
||||||
.format(key, strategy))
|
.format(key, strategy))
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,19 @@
|
||||||
import functools
|
import functools
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from .. import exceptions
|
||||||
|
from .. import sync
|
||||||
from .config import CollectionConfig
|
from .config import CollectionConfig
|
||||||
from .discover import collections_for_pair, storage_class_from_config, \
|
from .discover import collections_for_pair
|
||||||
storage_instance_from_config
|
from .discover import storage_class_from_config
|
||||||
from .utils import JobFailed, cli_logger, get_status_name, \
|
from .discover import storage_instance_from_config
|
||||||
handle_cli_error, load_status, manage_sync_status, save_status
|
from .utils import cli_logger
|
||||||
|
from .utils import get_status_name
|
||||||
from .. import exceptions, sync
|
from .utils import handle_cli_error
|
||||||
|
from .utils import JobFailed
|
||||||
|
from .utils import load_status
|
||||||
|
from .utils import manage_sync_status
|
||||||
|
from .utils import save_status
|
||||||
|
|
||||||
|
|
||||||
def prepare_pair(wq, pair_name, collections, config, callback, **kwargs):
|
def prepare_pair(wq, pair_name, collections, config, callback, **kwargs):
|
||||||
|
|
@ -45,7 +51,7 @@ def sync_collection(wq, collection, general, force_delete):
|
||||||
status_name = get_status_name(pair.name, collection.name)
|
status_name = get_status_name(pair.name, collection.name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cli_logger.info('Syncing {}'.format(status_name))
|
cli_logger.info(f'Syncing {status_name}')
|
||||||
|
|
||||||
a = storage_instance_from_config(collection.config_a)
|
a = storage_instance_from_config(collection.config_a)
|
||||||
b = storage_instance_from_config(collection.config_b)
|
b = storage_instance_from_config(collection.config_b)
|
||||||
|
|
@ -110,7 +116,7 @@ def repair_collection(config, collection, repair_unsafe_uid):
|
||||||
config['type'] = storage_type
|
config['type'] = storage_type
|
||||||
storage = storage_instance_from_config(config)
|
storage = storage_instance_from_config(config)
|
||||||
|
|
||||||
cli_logger.info('Repairing {}/{}'.format(storage_name, collection))
|
cli_logger.info(f'Repairing {storage_name}/{collection}')
|
||||||
cli_logger.warning('Make sure no other program is talking to the server.')
|
cli_logger.warning('Make sure no other program is talking to the server.')
|
||||||
repair_storage(storage, repair_unsafe_uid=repair_unsafe_uid)
|
repair_storage(storage, repair_unsafe_uid=repair_unsafe_uid)
|
||||||
|
|
||||||
|
|
@ -121,7 +127,7 @@ def metasync_collection(wq, collection, general):
|
||||||
status_name = get_status_name(pair.name, collection.name)
|
status_name = get_status_name(pair.name, collection.name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cli_logger.info('Metasyncing {}'.format(status_name))
|
cli_logger.info(f'Metasyncing {status_name}')
|
||||||
|
|
||||||
status = load_status(general['status_path'], pair.name,
|
status = load_status(general['status_path'], pair.name,
|
||||||
collection.name, data_type='metadata') or {}
|
collection.name, data_type='metadata') or {}
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,21 @@ import os
|
||||||
import queue
|
import queue
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import click
|
||||||
|
import click_threading
|
||||||
from atomicwrites import atomic_write
|
from atomicwrites import atomic_write
|
||||||
|
|
||||||
import click
|
|
||||||
|
|
||||||
import click_threading
|
|
||||||
|
|
||||||
from . import cli_logger
|
from . import cli_logger
|
||||||
from .. import BUGTRACKER_HOME, DOCS_HOME, exceptions
|
from .. import BUGTRACKER_HOME
|
||||||
from ..sync.exceptions import IdentConflict, PartialSync, StorageEmpty, \
|
from .. import DOCS_HOME
|
||||||
SyncConflict
|
from .. import exceptions
|
||||||
|
from ..sync.exceptions import IdentConflict
|
||||||
|
from ..sync.exceptions import PartialSync
|
||||||
|
from ..sync.exceptions import StorageEmpty
|
||||||
|
from ..sync.exceptions import SyncConflict
|
||||||
from ..sync.status import SqliteStatus
|
from ..sync.status import SqliteStatus
|
||||||
from ..utils import expand_path, get_storage_init_args
|
from ..utils import expand_path
|
||||||
|
from ..utils import get_storage_init_args
|
||||||
|
|
||||||
|
|
||||||
STATUS_PERMISSIONS = 0o600
|
STATUS_PERMISSIONS = 0o600
|
||||||
|
|
@ -144,11 +147,11 @@ def handle_cli_error(status_name=None, e=None):
|
||||||
import traceback
|
import traceback
|
||||||
tb = traceback.format_tb(tb)
|
tb = traceback.format_tb(tb)
|
||||||
if status_name:
|
if status_name:
|
||||||
msg = 'Unknown error occurred for {}'.format(status_name)
|
msg = f'Unknown error occurred for {status_name}'
|
||||||
else:
|
else:
|
||||||
msg = 'Unknown error occurred'
|
msg = 'Unknown error occurred'
|
||||||
|
|
||||||
msg += ': {}\nUse `-vdebug` to see the full traceback.'.format(e)
|
msg += f': {e}\nUse `-vdebug` to see the full traceback.'
|
||||||
|
|
||||||
cli_logger.error(msg)
|
cli_logger.error(msg)
|
||||||
cli_logger.debug(''.join(tb))
|
cli_logger.debug(''.join(tb))
|
||||||
|
|
@ -210,8 +213,7 @@ def manage_sync_status(base_path, pair_name, collection_name):
|
||||||
with open(path, 'rb') as f:
|
with open(path, 'rb') as f:
|
||||||
if f.read(1) == b'{':
|
if f.read(1) == b'{':
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
# json.load doesn't work on binary files for Python 3.5
|
legacy_status = dict(json.load(f))
|
||||||
legacy_status = dict(json.loads(f.read().decode('utf-8')))
|
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -247,7 +249,7 @@ def storage_class_from_config(config):
|
||||||
cls = storage_names[storage_name]
|
cls = storage_names[storage_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise exceptions.UserError(
|
raise exceptions.UserError(
|
||||||
'Unknown storage type: {}'.format(storage_name))
|
f'Unknown storage type: {storage_name}')
|
||||||
return cls, config
|
return cls, config
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -399,7 +401,7 @@ def handle_collection_not_found(config, collection, e=None):
|
||||||
storage_name = config.get('instance_name', None)
|
storage_name = config.get('instance_name', None)
|
||||||
|
|
||||||
cli_logger.warning('{}No collection {} found for storage {}.'
|
cli_logger.warning('{}No collection {} found for storage {}.'
|
||||||
.format('{}\n'.format(e) if e else '',
|
.format(f'{e}\n' if e else '',
|
||||||
json.dumps(collection), storage_name))
|
json.dumps(collection), storage_name))
|
||||||
|
|
||||||
if click.confirm('Should vdirsyncer attempt to create it?'):
|
if click.confirm('Should vdirsyncer attempt to create it?'):
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class Error(Exception):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
if getattr(self, key, object()) is not None: # pragma: no cover
|
if getattr(self, key, object()) is not None: # pragma: no cover
|
||||||
raise TypeError('Invalid argument: {}'.format(key))
|
raise TypeError(f'Invalid argument: {key}')
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
|
|
@ -25,7 +25,7 @@ class UserError(Error, ValueError):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Error.__str__(self)
|
msg = Error.__str__(self)
|
||||||
for problem in self.problems or ():
|
for problem in self.problems or ():
|
||||||
msg += '\n - {}'.format(problem)
|
msg += f'\n - {problem}'
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@ import logging
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from . import __version__
|
||||||
|
from . import DOCS_HOME
|
||||||
|
from . import exceptions
|
||||||
from .utils import expand_path
|
from .utils import expand_path
|
||||||
from . import DOCS_HOME, exceptions, __version__
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
USERAGENT = 'vdirsyncer/{}'.format(__version__)
|
USERAGENT = f'vdirsyncer/{__version__}'
|
||||||
|
|
||||||
|
|
||||||
def _detect_faulty_requests(): # pragma: no cover
|
def _detect_faulty_requests(): # pragma: no cover
|
||||||
|
|
@ -133,7 +135,7 @@ def request(method, url, session=None, latin1_fallback=True,
|
||||||
|
|
||||||
func = session.request
|
func = session.request
|
||||||
|
|
||||||
logger.debug('{} {}'.format(method, url))
|
logger.debug(f'{method} {url}')
|
||||||
logger.debug(kwargs.get('headers', {}))
|
logger.debug(kwargs.get('headers', {}))
|
||||||
logger.debug(kwargs.get('data', None))
|
logger.debug(kwargs.get('data', None))
|
||||||
logger.debug('Sending request...')
|
logger.debug('Sending request...')
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@ class MetaSyncConflict(MetaSyncError):
|
||||||
|
|
||||||
def metasync(storage_a, storage_b, status, keys, conflict_resolution=None):
|
def metasync(storage_a, storage_b, status, keys, conflict_resolution=None):
|
||||||
def _a_to_b():
|
def _a_to_b():
|
||||||
logger.info('Copying {} to {}'.format(key, storage_b))
|
logger.info(f'Copying {key} to {storage_b}')
|
||||||
storage_b.set_meta(key, a)
|
storage_b.set_meta(key, a)
|
||||||
status[key] = a
|
status[key] = a
|
||||||
|
|
||||||
def _b_to_a():
|
def _b_to_a():
|
||||||
logger.info('Copying {} to {}'.format(key, storage_a))
|
logger.info(f'Copying {key} to {storage_a}')
|
||||||
storage_a.set_meta(key, b)
|
storage_a.set_meta(key, b)
|
||||||
status[key] = b
|
status[key] = b
|
||||||
|
|
||||||
|
|
@ -45,10 +45,10 @@ def metasync(storage_a, storage_b, status, keys, conflict_resolution=None):
|
||||||
a = storage_a.get_meta(key)
|
a = storage_a.get_meta(key)
|
||||||
b = storage_b.get_meta(key)
|
b = storage_b.get_meta(key)
|
||||||
s = normalize_meta_value(status.get(key))
|
s = normalize_meta_value(status.get(key))
|
||||||
logger.debug('Key: {}'.format(key))
|
logger.debug(f'Key: {key}')
|
||||||
logger.debug('A: {}'.format(a))
|
logger.debug(f'A: {a}')
|
||||||
logger.debug('B: {}'.format(b))
|
logger.debug(f'B: {b}')
|
||||||
logger.debug('S: {}'.format(s))
|
logger.debug(f'S: {s}')
|
||||||
|
|
||||||
if a != s and b != s:
|
if a != s and b != s:
|
||||||
_resolve_conflict()
|
_resolve_conflict()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import logging
|
import logging
|
||||||
from os.path import basename
|
from os.path import basename
|
||||||
|
|
||||||
from .utils import generate_href, href_safe
|
from .utils import generate_href
|
||||||
|
from .utils import href_safe
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -25,7 +26,7 @@ def repair_storage(storage, repair_unsafe_uid):
|
||||||
'The PRODID property may indicate which software '
|
'The PRODID property may indicate which software '
|
||||||
'created this item.'
|
'created this item.'
|
||||||
.format(href))
|
.format(href))
|
||||||
logger.error('Item content: {!r}'.format(item.raw))
|
logger.error(f'Item content: {item.raw!r}')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
seen_uids.add(new_item.uid)
|
seen_uids.add(new_item.uid)
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ class Storage(metaclass=StorageMeta):
|
||||||
self.read_only = bool(read_only)
|
self.read_only = bool(read_only)
|
||||||
|
|
||||||
if collection and instance_name:
|
if collection and instance_name:
|
||||||
instance_name = '{}/{}'.format(instance_name, collection)
|
instance_name = f'{instance_name}/{collection}'
|
||||||
self.instance_name = instance_name
|
self.instance_name = instance_name
|
||||||
self.collection = collection
|
self.collection = collection
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,21 @@ import datetime
|
||||||
import logging
|
import logging
|
||||||
import urllib.parse as urlparse
|
import urllib.parse as urlparse
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
from inspect import getfullargspec
|
from inspect import getfullargspec
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from requests.exceptions import HTTPError
|
from requests.exceptions import HTTPError
|
||||||
|
|
||||||
from .base import Storage, normalize_meta_value
|
from .. import exceptions
|
||||||
from .. import exceptions, http, utils
|
from .. import http
|
||||||
from ..http import USERAGENT, prepare_auth, \
|
from .. import utils
|
||||||
prepare_client_cert, prepare_verify
|
from ..http import prepare_auth
|
||||||
|
from ..http import prepare_client_cert
|
||||||
|
from ..http import prepare_verify
|
||||||
|
from ..http import USERAGENT
|
||||||
from ..vobject import Item
|
from ..vobject import Item
|
||||||
|
from .base import normalize_meta_value
|
||||||
|
from .base import Storage
|
||||||
|
|
||||||
|
|
||||||
dav_logger = logging.getLogger(__name__)
|
dav_logger = logging.getLogger(__name__)
|
||||||
|
|
@ -34,7 +38,7 @@ del _generate_path_reserved_chars
|
||||||
def _contains_quoted_reserved_chars(x):
|
def _contains_quoted_reserved_chars(x):
|
||||||
for y in _path_reserved_chars:
|
for y in _path_reserved_chars:
|
||||||
if y in x:
|
if y in x:
|
||||||
dav_logger.debug('Unsafe character: {!r}'.format(y))
|
dav_logger.debug(f'Unsafe character: {y!r}')
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -52,7 +56,7 @@ def _assert_multistatus_success(r):
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
continue
|
continue
|
||||||
if st < 200 or st >= 400:
|
if st < 200 or st >= 400:
|
||||||
raise HTTPError('Server error: {}'.format(st))
|
raise HTTPError(f'Server error: {st}')
|
||||||
|
|
||||||
|
|
||||||
def _normalize_href(base, href):
|
def _normalize_href(base, href):
|
||||||
|
|
@ -78,7 +82,7 @@ def _normalize_href(base, href):
|
||||||
x = urlparse.quote(x, '/@%:')
|
x = urlparse.quote(x, '/@%:')
|
||||||
|
|
||||||
if orig_href == x:
|
if orig_href == x:
|
||||||
dav_logger.debug('Already normalized: {!r}'.format(x))
|
dav_logger.debug(f'Already normalized: {x!r}')
|
||||||
else:
|
else:
|
||||||
dav_logger.debug('Normalized URL from {!r} to {!r}'
|
dav_logger.debug('Normalized URL from {!r} to {!r}'
|
||||||
.format(orig_href, x))
|
.format(orig_href, x))
|
||||||
|
|
@ -120,7 +124,7 @@ def _merge_xml(items):
|
||||||
return None
|
return None
|
||||||
rv = items[0]
|
rv = items[0]
|
||||||
for item in items[1:]:
|
for item in items[1:]:
|
||||||
rv.extend(item.getiterator())
|
rv.extend(item.iter())
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -459,7 +463,7 @@ class DAVStorage(Storage):
|
||||||
for href in hrefs:
|
for href in hrefs:
|
||||||
if href != self._normalize_href(href):
|
if href != self._normalize_href(href):
|
||||||
raise exceptions.NotFoundError(href)
|
raise exceptions.NotFoundError(href)
|
||||||
href_xml.append('<D:href>{}</D:href>'.format(href))
|
href_xml.append(f'<D:href>{href}</D:href>')
|
||||||
if not href_xml:
|
if not href_xml:
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
|
|
@ -591,7 +595,7 @@ class DAVStorage(Storage):
|
||||||
props = _merge_xml(props)
|
props = _merge_xml(props)
|
||||||
|
|
||||||
if props.find('{DAV:}resourcetype/{DAV:}collection') is not None:
|
if props.find('{DAV:}resourcetype/{DAV:}collection') is not None:
|
||||||
dav_logger.debug('Skipping {!r}, is collection.'.format(href))
|
dav_logger.debug(f'Skipping {href!r}, is collection.')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
etag = getattr(props.find('{DAV:}getetag'), 'text', '')
|
etag = getattr(props.find('{DAV:}getetag'), 'text', '')
|
||||||
|
|
@ -641,7 +645,7 @@ class DAVStorage(Storage):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise exceptions.UnsupportedMetadataError()
|
raise exceptions.UnsupportedMetadataError()
|
||||||
|
|
||||||
xpath = '{{{}}}{}'.format(namespace, tagname)
|
xpath = f'{{{namespace}}}{tagname}'
|
||||||
data = '''<?xml version="1.0" encoding="utf-8" ?>
|
data = '''<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<D:propfind xmlns:D="DAV:">
|
<D:propfind xmlns:D="DAV:">
|
||||||
<D:prop>
|
<D:prop>
|
||||||
|
|
@ -674,7 +678,7 @@ class DAVStorage(Storage):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise exceptions.UnsupportedMetadataError()
|
raise exceptions.UnsupportedMetadataError()
|
||||||
|
|
||||||
lxml_selector = '{{{}}}{}'.format(namespace, tagname)
|
lxml_selector = f'{{{namespace}}}{tagname}'
|
||||||
element = etree.Element(lxml_selector)
|
element = etree.Element(lxml_selector)
|
||||||
element.text = normalize_meta_value(value)
|
element.text = normalize_meta_value(value)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
|
import binascii
|
||||||
import contextlib
|
import contextlib
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import binascii
|
|
||||||
|
|
||||||
import atomicwrites
|
import atomicwrites
|
||||||
import click
|
import click
|
||||||
|
|
@ -66,7 +66,7 @@ class _Session:
|
||||||
key = self._get_key()
|
key = self._get_key()
|
||||||
if not key:
|
if not key:
|
||||||
password = click.prompt('Enter key password', hide_input=True)
|
password = click.prompt('Enter key password', hide_input=True)
|
||||||
click.echo('Deriving key for {}'.format(self.email))
|
click.echo(f'Deriving key for {self.email}')
|
||||||
self.etesync.derive_key(password)
|
self.etesync.derive_key(password)
|
||||||
self._set_key(self.etesync.cipher_key)
|
self._set_key(self.etesync.cipher_key)
|
||||||
else:
|
else:
|
||||||
|
|
@ -134,7 +134,7 @@ class EtesyncStorage(Storage):
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.debug('Skipping collection: {!r}'.format(entry))
|
logger.debug(f'Skipping collection: {entry!r}')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_collection(cls, collection, email, secrets_dir, server_url=None,
|
def create_collection(cls, collection, email, secrets_dir, server_url=None,
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,14 @@ import subprocess
|
||||||
|
|
||||||
from atomicwrites import atomic_write
|
from atomicwrites import atomic_write
|
||||||
|
|
||||||
from .base import Storage, normalize_meta_value
|
|
||||||
from .. import exceptions
|
from .. import exceptions
|
||||||
from ..utils import checkdir, expand_path, generate_href, get_etag_from_file
|
from ..utils import checkdir
|
||||||
|
from ..utils import expand_path
|
||||||
|
from ..utils import generate_href
|
||||||
|
from ..utils import get_etag_from_file
|
||||||
from ..vobject import Item
|
from ..vobject import Item
|
||||||
|
from .base import normalize_meta_value
|
||||||
|
from .base import Storage
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@ import logging
|
||||||
import os
|
import os
|
||||||
import urllib.parse as urlparse
|
import urllib.parse as urlparse
|
||||||
|
|
||||||
from atomicwrites import atomic_write
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
from atomicwrites import atomic_write
|
||||||
from click_threading import get_ui_worker
|
from click_threading import get_ui_worker
|
||||||
|
|
||||||
from . import base, dav
|
from . import base
|
||||||
|
from . import dav
|
||||||
from .. import exceptions
|
from .. import exceptions
|
||||||
from ..utils import checkdir, expand_path, open_graphical_browser
|
from ..utils import checkdir
|
||||||
|
from ..utils import expand_path
|
||||||
|
from ..utils import open_graphical_browser
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -80,7 +81,7 @@ class GoogleSession(dav.DAVSession):
|
||||||
# access_type and approval_prompt are Google specific
|
# access_type and approval_prompt are Google specific
|
||||||
# extra parameters.
|
# extra parameters.
|
||||||
access_type='offline', approval_prompt='force')
|
access_type='offline', approval_prompt='force')
|
||||||
click.echo('Opening {} ...'.format(authorization_url))
|
click.echo(f'Opening {authorization_url} ...')
|
||||||
try:
|
try:
|
||||||
open_graphical_browser(authorization_url)
|
open_graphical_browser(authorization_url)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
import urllib.parse as urlparse
|
import urllib.parse as urlparse
|
||||||
|
|
||||||
from .base import Storage
|
|
||||||
from .. import exceptions
|
from .. import exceptions
|
||||||
from ..http import USERAGENT, prepare_auth, \
|
from ..http import prepare_auth
|
||||||
prepare_client_cert, prepare_verify, request
|
from ..http import prepare_client_cert
|
||||||
from ..vobject import Item, split_collection
|
from ..http import prepare_verify
|
||||||
|
from ..http import request
|
||||||
|
from ..http import USERAGENT
|
||||||
|
from ..vobject import Item
|
||||||
|
from ..vobject import split_collection
|
||||||
|
from .base import Storage
|
||||||
|
|
||||||
|
|
||||||
class HttpStorage(Storage):
|
class HttpStorage(Storage):
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from .base import Storage, normalize_meta_value
|
|
||||||
|
|
||||||
from .. import exceptions
|
from .. import exceptions
|
||||||
|
from .base import normalize_meta_value
|
||||||
|
from .base import Storage
|
||||||
|
|
||||||
|
|
||||||
def _random_string():
|
def _random_string():
|
||||||
return '{:.9f}'.format(random.random())
|
return f'{random.random():.9f}'
|
||||||
|
|
||||||
|
|
||||||
class MemoryStorage(Storage):
|
class MemoryStorage(Storage):
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,14 @@ import os
|
||||||
|
|
||||||
from atomicwrites import atomic_write
|
from atomicwrites import atomic_write
|
||||||
|
|
||||||
from .base import Storage
|
|
||||||
from .. import exceptions
|
from .. import exceptions
|
||||||
from ..utils import checkfile, expand_path, get_etag_from_file
|
from ..utils import checkfile
|
||||||
from ..vobject import Item, join_collection, split_collection
|
from ..utils import expand_path
|
||||||
|
from ..utils import get_etag_from_file
|
||||||
|
from ..vobject import Item
|
||||||
|
from ..vobject import join_collection
|
||||||
|
from ..vobject import split_collection
|
||||||
|
from .base import Storage
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,13 @@ import logging
|
||||||
|
|
||||||
from ..exceptions import UserError
|
from ..exceptions import UserError
|
||||||
from ..utils import uniq
|
from ..utils import uniq
|
||||||
|
from .exceptions import BothReadOnly
|
||||||
from .status import SubStatus, ItemMetadata
|
from .exceptions import IdentAlreadyExists
|
||||||
from .exceptions import BothReadOnly, IdentAlreadyExists, PartialSync, \
|
from .exceptions import PartialSync
|
||||||
StorageEmpty, SyncConflict
|
from .exceptions import StorageEmpty
|
||||||
|
from .exceptions import SyncConflict
|
||||||
|
from .status import ItemMetadata
|
||||||
|
from .status import SubStatus
|
||||||
|
|
||||||
sync_logger = logging.getLogger(__name__)
|
sync_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import abc
|
import abc
|
||||||
import contextlib
|
import contextlib
|
||||||
import sys
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import sys
|
||||||
|
|
||||||
from .exceptions import IdentAlreadyExists
|
from .exceptions import IdentAlreadyExists
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import functools
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from inspect import getfullargspec
|
from inspect import getfullargspec
|
||||||
|
|
||||||
from . import exceptions
|
from . import exceptions
|
||||||
|
|
@ -73,7 +72,7 @@ def get_etag_from_file(f):
|
||||||
mtime = getattr(stat, 'st_mtime_ns', None)
|
mtime = getattr(stat, 'st_mtime_ns', None)
|
||||||
if mtime is None:
|
if mtime is None:
|
||||||
mtime = stat.st_mtime
|
mtime = stat.st_mtime
|
||||||
return '{:.9f};{}'.format(mtime, stat.st_ino)
|
return f'{mtime:.9f};{stat.st_ino}'
|
||||||
|
|
||||||
|
|
||||||
def get_storage_init_specs(cls, stop_at=object):
|
def get_storage_init_specs(cls, stop_at=object):
|
||||||
|
|
@ -125,7 +124,7 @@ def checkdir(path, create=False, mode=0o750):
|
||||||
|
|
||||||
if not os.path.isdir(path):
|
if not os.path.isdir(path):
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
raise OSError('{} is not a directory.'.format(path))
|
raise OSError(f'{path} is not a directory.')
|
||||||
if create:
|
if create:
|
||||||
os.makedirs(path, mode)
|
os.makedirs(path, mode)
|
||||||
else:
|
else:
|
||||||
|
|
@ -143,7 +142,7 @@ def checkfile(path, create=False):
|
||||||
checkdir(os.path.dirname(path), create=create)
|
checkdir(os.path.dirname(path), create=create)
|
||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
raise OSError('{} is not a file.'.format(path))
|
raise OSError(f'{path} is not a file.')
|
||||||
if create:
|
if create:
|
||||||
with open(path, 'wb'):
|
with open(path, 'wb'):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
from itertools import chain, tee
|
from itertools import chain
|
||||||
|
from itertools import tee
|
||||||
|
|
||||||
from .utils import cached_property, uniq
|
from .utils import cached_property
|
||||||
|
from .utils import uniq
|
||||||
|
|
||||||
|
|
||||||
IGNORE_PROPS = (
|
IGNORE_PROPS = (
|
||||||
|
|
@ -205,14 +207,14 @@ def join_collection(items, wrappers=_default_join_wrappers):
|
||||||
|
|
||||||
if wrapper_type is not None:
|
if wrapper_type is not None:
|
||||||
lines = chain(*(
|
lines = chain(*(
|
||||||
['BEGIN:{}'.format(wrapper_type)],
|
[f'BEGIN:{wrapper_type}'],
|
||||||
# XXX: wrapper_props is a list of lines (with line-wrapping), so
|
# XXX: wrapper_props is a list of lines (with line-wrapping), so
|
||||||
# filtering out duplicate lines will almost certainly break
|
# filtering out duplicate lines will almost certainly break
|
||||||
# multiline-values. Since the only props we usually need to
|
# multiline-values. Since the only props we usually need to
|
||||||
# support are PRODID and VERSION, I don't care.
|
# support are PRODID and VERSION, I don't care.
|
||||||
uniq(wrapper_props),
|
uniq(wrapper_props),
|
||||||
lines,
|
lines,
|
||||||
['END:{}'.format(wrapper_type)]
|
[f'END:{wrapper_type}']
|
||||||
))
|
))
|
||||||
return ''.join(line + '\r\n' for line in lines)
|
return ''.join(line + '\r\n' for line in lines)
|
||||||
|
|
||||||
|
|
@ -299,14 +301,14 @@ class _Component:
|
||||||
return rv[0]
|
return rv[0]
|
||||||
|
|
||||||
def dump_lines(self):
|
def dump_lines(self):
|
||||||
yield 'BEGIN:{}'.format(self.name)
|
yield f'BEGIN:{self.name}'
|
||||||
yield from self.props
|
yield from self.props
|
||||||
for c in self.subcomponents:
|
for c in self.subcomponents:
|
||||||
yield from c.dump_lines()
|
yield from c.dump_lines()
|
||||||
yield 'END:{}'.format(self.name)
|
yield f'END:{self.name}'
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
prefix = ('{}:'.format(key), '{};'.format(key))
|
prefix = (f'{key}:', f'{key};')
|
||||||
new_lines = []
|
new_lines = []
|
||||||
lineiter = iter(self.props)
|
lineiter = iter(self.props)
|
||||||
while True:
|
while True:
|
||||||
|
|
@ -329,7 +331,7 @@ class _Component:
|
||||||
assert isinstance(val, str)
|
assert isinstance(val, str)
|
||||||
assert '\n' not in val
|
assert '\n' not in val
|
||||||
del self[key]
|
del self[key]
|
||||||
line = '{}:{}'.format(key, val)
|
line = f'{key}:{val}'
|
||||||
self.props.append(line)
|
self.props.append(line)
|
||||||
|
|
||||||
def __contains__(self, obj):
|
def __contains__(self, obj):
|
||||||
|
|
@ -342,8 +344,8 @@ class _Component:
|
||||||
raise ValueError(obj)
|
raise ValueError(obj)
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
prefix_without_params = '{}:'.format(key)
|
prefix_without_params = f'{key}:'
|
||||||
prefix_with_params = '{};'.format(key)
|
prefix_with_params = f'{key};'
|
||||||
iterlines = iter(self.props)
|
iterlines = iter(self.props)
|
||||||
for line in iterlines:
|
for line in iterlines:
|
||||||
if line.startswith(prefix_without_params):
|
if line.startswith(prefix_without_params):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue