Merge pull request #830 from pimutils/next

Keep moving forward
This commit is contained in:
Hugo Barrera 2020-06-10 19:48:15 +00:00 committed by GitHub
commit 68ff37e677
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 286 additions and 303 deletions

View file

@ -14,6 +14,10 @@ repos:
hooks:
- id: flake8
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
hooks:
- id: update-travis

View file

@ -19,74 +19,22 @@
"include": [
{
"env": "BUILD=style",
"python": "3.6"
"python": "3.7"
},
{
"env": "BUILD=test REQUIREMENTS=release",
"python": "3.5"
},
{
"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"
"python": "3.7"
},
{
"env": "BUILD=test-storage DAV_SERVER=radicale REQUIREMENTS=release ",
"python": "3.6"
"python": "3.7"
},
{
"env": "BUILD=test-storage DAV_SERVER=xandikos REQUIREMENTS=release ",
"python": "3.6"
"python": "3.7"
},
{
"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"
},
{
@ -127,7 +75,7 @@
},
{
"env": "BUILD=test ETESYNC_TESTS=true REQUIREMENTS=latest",
"python": "3.6"
"python": "3.7"
}
]
},

View file

@ -30,11 +30,6 @@ PYTEST_ARGS =
TEST_EXTRA_PACKAGES =
ifeq ($(COVERAGE), true)
TEST_EXTRA_PACKAGES += pytest-cov
PYTEST_ARGS += --cov-config .coveragerc --cov vdirsyncer
endif
ifeq ($(ETESYNC_TESTS), true)
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
@ -106,9 +101,6 @@ docs:
linkcheck:
sphinx-build -W -b linkcheck ./docs/ ./docs/_build/linkcheck/
release:
python setup.py sdist bdist_wheel upload
release-deb:
sh scripts/release-deb.sh debian jessie
sh scripts/release-deb.sh debian stretch

View file

@ -65,7 +65,7 @@ def github_issue_role(name, rawtext, text, lineno, inliner,
if issue_num <= 0:
raise ValueError()
except ValueError:
msg = inliner.reporter.error('Invalid GitHub issue: {}'.format(text),
msg = inliner.reporter.error(f'Invalid GitHub issue: {text}',
line=lineno)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]

View file

@ -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
following things are installed:
- Python 3.5+ and pip.
- Python 3.7+ and pip.
- ``libxml`` and ``libxslt``
- ``zlib``
- Linux or OS X. **Windows is not supported**, see :gh:`535`.

View file

@ -1,10 +1,8 @@
#!/usr/bin/env python
import itertools
import json
python_versions = ("3.5", "3.6", "3.7", "3.8")
latest_python = "3.6"
python_versions = ["3.7", "3.8"]
cfg = {}
@ -34,7 +32,7 @@ matrix = []
cfg['matrix'] = {'include': matrix, 'fast_finish': True}
matrix.append({
'python': latest_python,
'python': python_versions[0],
'env': 'BUILD=style'
})
@ -51,7 +49,7 @@ for python, requirements in itertools.product(
'env': f"BUILD=test REQUIREMENTS={requirements}",
})
if python == latest_python and requirements == "release":
if python == python_versions[0] and requirements == "release":
dav_servers += ("fastmail",)
for dav_server in dav_servers:
@ -61,8 +59,6 @@ for python, requirements in itertools.product(
f"DAV_SERVER={dav_server} "
f"REQUIREMENTS={requirements} ")
}
if python == '3.5':
job['dist'] = 'trusty'
if dav_server in ("davical", "icloud"):
job['if'] = 'NOT (type IN (pull_request))'
@ -70,7 +66,7 @@ for python, requirements in itertools.product(
matrix.append(job)
matrix.append({
'python': latest_python,
'python': python_versions[0],
'env': ("BUILD=test "
"ETESYNC_TESTS=true "
"REQUIREMENTS=latest")

View file

@ -3,7 +3,12 @@ universal = 1
[tool:pytest]
norecursedirs = tests/storage/servers/*
addopts = --tb=short
addopts =
--tb=short
--cov-config .coveragerc
--cov=vdirsyncer
--cov-report=term-missing
--no-cov-on-fail
[flake8]
# E731: Use a def instead of lambda expr

View file

@ -4,14 +4,14 @@ Vdirsyncer synchronizes calendars and contacts.
Please refer to https://vdirsyncer.pimutils.org/en/stable/packaging.html for
how to package vdirsyncer.
'''
from setuptools import Command, find_packages, setup
from setuptools import Command
from setuptools import find_packages
from setuptools import setup
requirements = [
# https://github.com/mitsuhiko/click/issues/200
'click>=5.0,<6.0',
'click>=5.0',
'click-log>=0.3.0, <0.4.0',
# https://github.com/pimutils/vdirsyncer/issues/478
@ -87,8 +87,6 @@ setup(
'License :: OSI Approved :: BSD License',
'Operating System :: POSIX',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Topic :: Internet',

View file

@ -1,4 +1,5 @@
hypothesis>=5.0.0
pytest
pytest-cov
pytest-localserver
pytest-subtesthack

View file

@ -1,14 +1,11 @@
'''
Test suite for vdirsyncer.
'''
import hypothesis.strategies as st
import urllib3.exceptions
from vdirsyncer.vobject import normalize_item
import urllib3
import urllib3.exceptions
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

View file

@ -5,10 +5,10 @@ import logging
import os
import click_log
from hypothesis import HealthCheck, Verbosity, settings
import pytest
from hypothesis import HealthCheck
from hypothesis import settings
from hypothesis import Verbosity
@pytest.fixture(autouse=True)

View file

@ -1,25 +1,27 @@
import random
import uuid
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 pytest
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.storage.base import normalize_meta_value
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):
from . import __name__ as base
x = __import__('{}.servers.{}'.format(base, server_name), fromlist=[''])
x = __import__(f'{base}.servers.{server_name}', fromlist=[''])
return x.ServerMixin
@ -183,7 +185,7 @@ class StorageTests:
def test_discover(self, requires_collections, get_storage_args, get_item):
collections = set()
for i in range(1, 5):
collection = 'test{}'.format(i)
collection = f'test{i}'
s = self.storage_class(**get_storage_args(collection=collection))
assert not list(s.list())
s.upload(get_item())

View file

@ -1,7 +1,7 @@
import pytest
import uuid
import pytest
@pytest.fixture
def slow_create_collection(request):

View file

@ -1,19 +1,15 @@
import os
import uuid
import os
import pytest
import requests
import requests.exceptions
from .. import get_server_mixin
from .. import StorageTests
from tests import assert_item_equals
from vdirsyncer import exceptions
from vdirsyncer.vobject import Item
from .. import StorageTests, get_server_mixin
dav_server = os.environ.get('DAV_SERVER', 'skip')
ServerMixin = get_server_mixin(dav_server)

View file

@ -2,18 +2,17 @@ import datetime
from textwrap import dedent
import pytest
import requests
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.storage.dav import CalDAVStorage
from . import DAVStorageTests, dav_server
from .. import format_item
class TestCalDAVStorage(DAVStorageTests):
storage_class = CalDAVStorage

View file

@ -1,8 +1,7 @@
import pytest
from vdirsyncer.storage.dav import CardDAVStorage
from . import DAVStorageTests
from vdirsyncer.storage.dav import CardDAVStorage
class TestCardDAVStorage(DAVStorageTests):

View file

@ -1,6 +1,8 @@
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():

View file

@ -9,7 +9,6 @@ https://docs.djangoproject.com/en/1.10/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.10/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)

View file

@ -13,11 +13,10 @@ Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import include, url
from rest_framework_nested import routers
from django.conf.urls import include
from django.conf.urls import url
from journal import views
from rest_framework_nested import routers
router = routers.DefaultRouter()
router.register(r'journals', views.JournalViewSet)

View file

@ -6,7 +6,6 @@ It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application

View file

@ -1,14 +1,13 @@
import shutil
import os
import shutil
import sys
import pytest
import requests
from vdirsyncer.storage.etesync import EtesyncContacts, EtesyncCalendars
from .. import StorageTests
from vdirsyncer.storage.etesync import EtesyncCalendars
from vdirsyncer.storage.etesync import EtesyncContacts
pytestmark = pytest.mark.skipif(os.getenv('ETESYNC_TESTS', '') != 'true',

View file

@ -1,7 +1,8 @@
import os
import pytest
import uuid
import pytest
try:
caldav_args = {
# Those credentials are configured through the Travis UI

View file

@ -1,10 +1,9 @@
import os
import shutil
import subprocess
import time
import shutil
import pytest
import requests
testserver_repo = os.path.dirname(__file__)

View file

@ -2,11 +2,10 @@ import subprocess
import pytest
from . import StorageTests
from vdirsyncer.storage.filesystem import FilesystemStorage
from vdirsyncer.vobject import Item
from . import StorageTests
class TestFilesystemStorage(StorageTests):
storage_class = FilesystemStorage

View file

@ -1,11 +1,10 @@
import pytest
from requests import Response
from tests import normalize_item
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):

View file

@ -1,13 +1,11 @@
import pytest
from requests import Response
import vdirsyncer.storage.http
from . import StorageTests
from vdirsyncer.storage.base import Storage
from vdirsyncer.storage.singlefile import SingleFileStorage
from . import StorageTests
class CombinedStorage(Storage):
'''A subclass of HttpStorage to make testing easier. It supports writes via

View file

@ -1,8 +1,7 @@
import pytest
from vdirsyncer.storage.memory import MemoryStorage
from . import StorageTests
from vdirsyncer.storage.memory import MemoryStorage
class TestMemoryStorage(StorageTests):

View file

@ -1,8 +1,7 @@
import pytest
from vdirsyncer.storage.singlefile import SingleFileStorage
from . import StorageTests
from vdirsyncer.storage.singlefile import SingleFileStorage
class TestSingleFileStorage(StorageTests):

View file

@ -1,8 +1,7 @@
from textwrap import dedent
from click.testing import CliRunner
import pytest
from click.testing import CliRunner
import vdirsyncer.cli as cli

View file

@ -3,7 +3,8 @@ from textwrap import dedent
import pytest
from vdirsyncer import cli, exceptions
from vdirsyncer import cli
from vdirsyncer import exceptions
from vdirsyncer.cli.config import Config

View file

@ -19,7 +19,7 @@ def storage(tmpdir, runner):
def test_basic(storage, runner, collection):
if collection is not None:
storage = storage.mkdir(collection)
collection_arg = 'foo/{}'.format(collection)
collection_arg = f'foo/{collection}'
else:
collection_arg = 'foo'

View file

@ -3,9 +3,9 @@ import sys
from textwrap import dedent
import hypothesis.strategies as st
from hypothesis import example, given
import pytest
from hypothesis import example
from hypothesis import given
def test_simple_run(tmpdir, runner):
@ -123,7 +123,10 @@ def test_verbosity(tmpdir, runner):
runner.write_with_general('')
result = runner.invoke(['--verbosity=HAHA', 'sync'])
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):
@ -461,7 +464,7 @@ def test_partial_sync(tmpdir, runner, partial_sync):
fileext = ".txt"
path = "{base}/bar"
'''.format(
partial_sync=('partial_sync = "{}"\n'.format(partial_sync)
partial_sync=(f'partial_sync = "{partial_sync}"\n'
if partial_sync else ''),
base=str(tmpdir)
)))

View file

@ -1,6 +1,7 @@
from vdirsyncer import exceptions
from vdirsyncer.cli.utils import handle_cli_error, \
storage_instance_from_config, storage_names
from vdirsyncer.cli.utils import handle_cli_error
from vdirsyncer.cli.utils import storage_instance_from_config
from vdirsyncer.cli.utils import storage_names
def test_handle_cli_error(capsys):

View file

@ -1,11 +1,12 @@
import sys
import logging
import sys
import click_log
import pytest
import requests
from vdirsyncer import http, utils
from vdirsyncer import http
from vdirsyncer import utils
@pytest.fixture(autouse=True)

View file

@ -1,10 +1,10 @@
import hypothesis.strategies as st
import pytest
from hypothesis import given
import pytest
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
@ -47,7 +47,7 @@ def test_key_conflict(monkeypatch, mystrategy):
@given(s=st.text(), t=st.text(min_size=1))
def test_fuzzing(s, t, mystrategy):
config = expand_fetch_params({
'{}.fetch'.format(s): ['mystrategy', t]
f'{s}.fetch': ['mystrategy', t]
})
assert config[s] == t

View file

@ -1,7 +1,7 @@
import pytest
from hypothesis import assume, given
import hypothesis.strategies as st
import pytest
from hypothesis import assume
from hypothesis import given
from vdirsyncer.sync.status import SqliteStatus

View file

@ -1,17 +1,22 @@
from copy import deepcopy
import hypothesis.strategies as st
from hypothesis import assume
from hypothesis.stateful import Bundle, RuleBasedStateMachine, rule
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 vdirsyncer.storage.memory import MemoryStorage, _random_string
from tests import blow_up
from tests import uid_strategy
from vdirsyncer.storage.memory import _random_string
from vdirsyncer.storage.memory import MemoryStorage
from vdirsyncer.sync import sync as _sync
from vdirsyncer.sync.exceptions import BothReadOnly, IdentConflict, \
PartialSync, StorageEmpty, SyncConflict
from vdirsyncer.sync.exceptions import BothReadOnly
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.vobject import Item
@ -253,7 +258,7 @@ def test_conflict_resolution_both_etags_new(winning_storage):
b.update(href_b, item_b, etag_b)
with pytest.raises(SyncConflict):
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) == {
item_a.raw if winning_storage == 'a' else item_b.raw
}
@ -563,7 +568,7 @@ class SyncMachine(RuleBasedStateMachine):
uid=uid_strategy,
etag=st.text())
def upload(self, storage, uid, etag):
item = Item('UID:{}'.format(uid))
item = Item(f'UID:{uid}')
storage.items[uid] = (etag, item)
@rule(storage=Storage, href=st.text())

View file

@ -1,12 +1,13 @@
import hypothesis.strategies as st
from hypothesis import example, given
import pytest
from hypothesis import example
from hypothesis import given
from tests import blow_up
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.memory import MemoryStorage

View file

@ -1,10 +1,12 @@
from hypothesis import HealthCheck, given, settings
import pytest
from hypothesis import given
from hypothesis import HealthCheck
from hypothesis import settings
from tests import uid_strategy
from vdirsyncer.repair import IrreparableItem, repair_item, repair_storage
from vdirsyncer.repair import IrreparableItem
from vdirsyncer.repair import repair_item
from vdirsyncer.repair import repair_storage
from vdirsyncer.storage.memory import MemoryStorage
from vdirsyncer.utils import href_safe
from vdirsyncer.vobject import Item
@ -18,11 +20,11 @@ def test_repair_uids(uid):
s.items = {
'one': (
'asdf',
Item('BEGIN:VCARD\nFN:Hans\nUID:{}\nEND:VCARD'.format(uid))
Item(f'BEGIN:VCARD\nFN:Hans\nUID:{uid}\nEND:VCARD')
),
'two': (
'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())
def test_repair_unsafe_uids(uid):
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)
assert s.get(href)[0].uid == uid
assert not href_safe(uid)
@ -58,7 +60,7 @@ def test_repair_unsafe_uids(uid):
('perfectly-fine', 'b@dh0mbr3')
])
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)
assert new_item.raw != item.raw
assert new_item.uid != item.uid

View file

@ -1,16 +1,20 @@
from textwrap import dedent
import hypothesis.strategies as st
from hypothesis import assume, given
from hypothesis.stateful import Bundle, RuleBasedStateMachine, rule
import pytest
from tests import BARE_EVENT_TEMPLATE, EVENT_TEMPLATE, \
EVENT_WITH_TIMEZONE_TEMPLATE, VCARD_TEMPLATE, normalize_item, \
uid_strategy
from hypothesis import assume
from hypothesis import given
from hypothesis.stateful import Bundle
from hypothesis.stateful import rule
from hypothesis.stateful import RuleBasedStateMachine
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 = [
@ -221,7 +225,7 @@ def test_replace_uid(template, uid):
item = vobject.Item(template.format(r=123, uid=123)).with_uid(uid)
assert item.uid == uid
if uid:
assert item.raw.count('\nUID:{}'.format(uid)) == 1
assert item.raw.count(f'\nUID:{uid}') == 1
else:
assert '\nUID:' not in item.raw
@ -317,7 +321,7 @@ class VobjectMachine(RuleBasedStateMachine):
params=st.lists(st.tuples(value_strategy, value_strategy)))
def add_prop_raw(self, c, key, value, 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 key in c
assert c.get(key) == value

View file

@ -19,8 +19,8 @@ except ImportError: # pragma: no cover
def _check_python_version(): # pragma: no cover
import sys
if sys.version_info < (3, 4, 0):
print('vdirsyncer requires at least Python 3.5.')
if sys.version_info < (3, 7, 0):
print('vdirsyncer requires at least Python 3.7.')
sys.exit(1)

View file

@ -3,10 +3,10 @@ import logging
import sys
import click
import click_log
from .. import BUGTRACKER_HOME, __version__
from .. import __version__
from .. import BUGTRACKER_HOME
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:
value = 1
cli_logger.debug('Using {} maximal workers.'.format(value))
cli_logger.debug(f'Using {value} maximal workers.')
return value
@ -75,7 +75,7 @@ def max_workers_option(default=0):
help += 'The default is 0, which means "as many as necessary". ' \
'With -vdebug enabled, the default is 1.'
else:
help += 'The default is {}.'.format(default)
help += f'The default is {default}.'
return click.option(
'--max-workers', default=default, type=click.IntRange(min=0, max=None),

View file

@ -6,10 +6,12 @@ from itertools import chain
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 .utils import storage_class_from_config
from .. import PROJECT_HOME, exceptions
from ..utils import cached_property, expand_path
GENERAL_ALL = frozenset(['status_path'])
@ -101,7 +103,7 @@ class _ConfigReader:
def _parse_section(self, section_type, name, options):
validate_section_name(name, section_type)
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)
if section_type == 'general':
@ -163,7 +165,7 @@ class Config:
try:
self.pairs[name] = PairConfig(self, name, options)
except ValueError as e:
raise exceptions.UserError('Pair {}: {}'.format(name, e))
raise exceptions.UserError(f'Pair {name}: {e}')
@classmethod
def from_fileobject(cls, f):

View file

@ -3,12 +3,14 @@ import json
import logging
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 ..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
@ -211,7 +213,7 @@ def _print_collections(instance_name, get_discovered):
logger.warning('Failed to discover collections for {}, use `-vdebug` '
'to see the full traceback.'.format(instance_name))
return
logger.info('{}:'.format(instance_name))
logger.info(f'{instance_name}:')
for args in discovered.values():
collection = args['collection']
if collection is None:
@ -226,7 +228,7 @@ def _print_collections(instance_name, get_discovered):
logger.info(' - {}{}'.format(
json.dumps(collection),
' ("{}")'.format(displayname)
f' ("{displayname}")'
if displayname and displayname != collection
else ''
))

View file

@ -4,7 +4,8 @@ import click
from . import AppContext
from .. import exceptions
from ..utils import expand_path, synchronized
from ..utils import expand_path
from ..utils import synchronized
SUFFIX = '.fetch'
@ -19,7 +20,7 @@ def expand_fetch_params(config):
newkey = key[:-len(SUFFIX)]
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)
del config[key]
@ -45,7 +46,7 @@ def _fetch_value(opts, key):
cache_key = tuple(opts)
if cache_key in password_cache:
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):
raise rv
return rv
@ -54,7 +55,7 @@ def _fetch_value(opts, key):
try:
strategy_fn = STRATEGIES[strategy]
except KeyError:
raise exceptions.UserError('Unknown strategy: {}'.format(strategy))
raise exceptions.UserError(f'Unknown strategy: {strategy}')
logger.debug('Fetching value for {} with {} strategy.'
.format(key, strategy))

View file

@ -1,13 +1,19 @@
import functools
import json
from .. import exceptions
from .. import sync
from .config import CollectionConfig
from .discover import collections_for_pair, storage_class_from_config, \
storage_instance_from_config
from .utils import JobFailed, cli_logger, get_status_name, \
handle_cli_error, load_status, manage_sync_status, save_status
from .. import exceptions, sync
from .discover import collections_for_pair
from .discover import storage_class_from_config
from .discover import storage_instance_from_config
from .utils import cli_logger
from .utils import get_status_name
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):
@ -45,7 +51,7 @@ def sync_collection(wq, collection, general, force_delete):
status_name = get_status_name(pair.name, collection.name)
try:
cli_logger.info('Syncing {}'.format(status_name))
cli_logger.info(f'Syncing {status_name}')
a = storage_instance_from_config(collection.config_a)
b = storage_instance_from_config(collection.config_b)
@ -110,7 +116,7 @@ def repair_collection(config, collection, repair_unsafe_uid):
config['type'] = storage_type
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.')
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)
try:
cli_logger.info('Metasyncing {}'.format(status_name))
cli_logger.info(f'Metasyncing {status_name}')
status = load_status(general['status_path'], pair.name,
collection.name, data_type='metadata') or {}

View file

@ -7,18 +7,21 @@ import os
import queue
import sys
import click
import click_threading
from atomicwrites import atomic_write
import click
import click_threading
from . import cli_logger
from .. import BUGTRACKER_HOME, DOCS_HOME, exceptions
from ..sync.exceptions import IdentConflict, PartialSync, StorageEmpty, \
SyncConflict
from .. import BUGTRACKER_HOME
from .. import DOCS_HOME
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 ..utils import expand_path, get_storage_init_args
from ..utils import expand_path
from ..utils import get_storage_init_args
STATUS_PERMISSIONS = 0o600
@ -144,11 +147,11 @@ def handle_cli_error(status_name=None, e=None):
import traceback
tb = traceback.format_tb(tb)
if status_name:
msg = 'Unknown error occurred for {}'.format(status_name)
msg = f'Unknown error occurred for {status_name}'
else:
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.debug(''.join(tb))
@ -210,8 +213,7 @@ def manage_sync_status(base_path, pair_name, collection_name):
with open(path, 'rb') as f:
if f.read(1) == b'{':
f.seek(0)
# json.load doesn't work on binary files for Python 3.5
legacy_status = dict(json.loads(f.read().decode('utf-8')))
legacy_status = dict(json.load(f))
except (OSError, ValueError):
pass
@ -247,7 +249,7 @@ def storage_class_from_config(config):
cls = storage_names[storage_name]
except KeyError:
raise exceptions.UserError(
'Unknown storage type: {}'.format(storage_name))
f'Unknown storage type: {storage_name}')
return cls, config
@ -399,7 +401,7 @@ def handle_collection_not_found(config, collection, e=None):
storage_name = config.get('instance_name', None)
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))
if click.confirm('Should vdirsyncer attempt to create it?'):

View file

@ -10,7 +10,7 @@ class Error(Exception):
def __init__(self, *args, **kwargs):
for key, value in kwargs.items():
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)
super().__init__(*args)
@ -25,7 +25,7 @@ class UserError(Error, ValueError):
def __str__(self):
msg = Error.__str__(self)
for problem in self.problems or ():
msg += '\n - {}'.format(problem)
msg += f'\n - {problem}'
return msg

View file

@ -2,12 +2,14 @@ import logging
import requests
from . import __version__
from . import DOCS_HOME
from . import exceptions
from .utils import expand_path
from . import DOCS_HOME, exceptions, __version__
logger = logging.getLogger(__name__)
USERAGENT = 'vdirsyncer/{}'.format(__version__)
USERAGENT = f'vdirsyncer/{__version__}'
def _detect_faulty_requests(): # pragma: no cover
@ -133,7 +135,7 @@ def request(method, url, session=None, latin1_fallback=True,
func = session.request
logger.debug('{} {}'.format(method, url))
logger.debug(f'{method} {url}')
logger.debug(kwargs.get('headers', {}))
logger.debug(kwargs.get('data', None))
logger.debug('Sending request...')

View file

@ -16,12 +16,12 @@ class MetaSyncConflict(MetaSyncError):
def metasync(storage_a, storage_b, status, keys, conflict_resolution=None):
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)
status[key] = 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)
status[key] = b
@ -45,10 +45,10 @@ def metasync(storage_a, storage_b, status, keys, conflict_resolution=None):
a = storage_a.get_meta(key)
b = storage_b.get_meta(key)
s = normalize_meta_value(status.get(key))
logger.debug('Key: {}'.format(key))
logger.debug('A: {}'.format(a))
logger.debug('B: {}'.format(b))
logger.debug('S: {}'.format(s))
logger.debug(f'Key: {key}')
logger.debug(f'A: {a}')
logger.debug(f'B: {b}')
logger.debug(f'S: {s}')
if a != s and b != s:
_resolve_conflict()

View file

@ -1,7 +1,8 @@
import logging
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__)
@ -25,7 +26,7 @@ def repair_storage(storage, repair_unsafe_uid):
'The PRODID property may indicate which software '
'created this item.'
.format(href))
logger.error('Item content: {!r}'.format(item.raw))
logger.error(f'Item content: {item.raw!r}')
continue
seen_uids.add(new_item.uid)

View file

@ -71,7 +71,7 @@ class Storage(metaclass=StorageMeta):
self.read_only = bool(read_only)
if collection and instance_name:
instance_name = '{}/{}'.format(instance_name, collection)
instance_name = f'{instance_name}/{collection}'
self.instance_name = instance_name
self.collection = collection

View file

@ -2,17 +2,21 @@ import datetime
import logging
import urllib.parse as urlparse
import xml.etree.ElementTree as etree
from inspect import getfullargspec
import requests
from requests.exceptions import HTTPError
from .base import Storage, normalize_meta_value
from .. import exceptions, http, utils
from ..http import USERAGENT, prepare_auth, \
prepare_client_cert, prepare_verify
from .. import exceptions
from .. import http
from .. import utils
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 .base import normalize_meta_value
from .base import Storage
dav_logger = logging.getLogger(__name__)
@ -34,7 +38,7 @@ del _generate_path_reserved_chars
def _contains_quoted_reserved_chars(x):
for y in _path_reserved_chars:
if y in x:
dav_logger.debug('Unsafe character: {!r}'.format(y))
dav_logger.debug(f'Unsafe character: {y!r}')
return True
return False
@ -52,7 +56,7 @@ def _assert_multistatus_success(r):
except (ValueError, IndexError):
continue
if st < 200 or st >= 400:
raise HTTPError('Server error: {}'.format(st))
raise HTTPError(f'Server error: {st}')
def _normalize_href(base, href):
@ -78,7 +82,7 @@ def _normalize_href(base, href):
x = urlparse.quote(x, '/@%:')
if orig_href == x:
dav_logger.debug('Already normalized: {!r}'.format(x))
dav_logger.debug(f'Already normalized: {x!r}')
else:
dav_logger.debug('Normalized URL from {!r} to {!r}'
.format(orig_href, x))
@ -120,7 +124,7 @@ def _merge_xml(items):
return None
rv = items[0]
for item in items[1:]:
rv.extend(item.getiterator())
rv.extend(item.iter())
return rv
@ -459,7 +463,7 @@ class DAVStorage(Storage):
for href in hrefs:
if href != self._normalize_href(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:
return ()
@ -591,7 +595,7 @@ class DAVStorage(Storage):
props = _merge_xml(props)
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
etag = getattr(props.find('{DAV:}getetag'), 'text', '')
@ -641,7 +645,7 @@ class DAVStorage(Storage):
except KeyError:
raise exceptions.UnsupportedMetadataError()
xpath = '{{{}}}{}'.format(namespace, tagname)
xpath = f'{{{namespace}}}{tagname}'
data = '''<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:">
<D:prop>
@ -674,7 +678,7 @@ class DAVStorage(Storage):
except KeyError:
raise exceptions.UnsupportedMetadataError()
lxml_selector = '{{{}}}{}'.format(namespace, tagname)
lxml_selector = f'{{{namespace}}}{tagname}'
element = etree.Element(lxml_selector)
element.text = normalize_meta_value(value)

View file

@ -1,8 +1,8 @@
import binascii
import contextlib
import functools
import logging
import os
import binascii
import atomicwrites
import click
@ -66,7 +66,7 @@ class _Session:
key = self._get_key()
if not key:
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._set_key(self.etesync.cipher_key)
else:
@ -134,7 +134,7 @@ class EtesyncStorage(Storage):
**kwargs
)
else:
logger.debug('Skipping collection: {!r}'.format(entry))
logger.debug(f'Skipping collection: {entry!r}')
@classmethod
def create_collection(cls, collection, email, secrets_dir, server_url=None,

View file

@ -5,10 +5,14 @@ import subprocess
from atomicwrites import atomic_write
from .base import Storage, normalize_meta_value
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 .base import normalize_meta_value
from .base import Storage
logger = logging.getLogger(__name__)

View file

@ -3,15 +3,16 @@ import logging
import os
import urllib.parse as urlparse
from atomicwrites import atomic_write
import click
from atomicwrites import atomic_write
from click_threading import get_ui_worker
from . import base, dav
from . import base
from . import dav
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__)
@ -80,7 +81,7 @@ class GoogleSession(dav.DAVSession):
# access_type and approval_prompt are Google specific
# extra parameters.
access_type='offline', approval_prompt='force')
click.echo('Opening {} ...'.format(authorization_url))
click.echo(f'Opening {authorization_url} ...')
try:
open_graphical_browser(authorization_url)
except Exception as e:

View file

@ -1,10 +1,14 @@
import urllib.parse as urlparse
from .base import Storage
from .. import exceptions
from ..http import USERAGENT, prepare_auth, \
prepare_client_cert, prepare_verify, request
from ..vobject import Item, split_collection
from ..http import prepare_auth
from ..http import prepare_client_cert
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):

View file

@ -1,12 +1,12 @@
import random
from .base import Storage, normalize_meta_value
from .. import exceptions
from .base import normalize_meta_value
from .base import Storage
def _random_string():
return '{:.9f}'.format(random.random())
return f'{random.random():.9f}'
class MemoryStorage(Storage):

View file

@ -7,10 +7,14 @@ import os
from atomicwrites import atomic_write
from .base import Storage
from .. import exceptions
from ..utils import checkfile, expand_path, get_etag_from_file
from ..vobject import Item, join_collection, split_collection
from ..utils import checkfile
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__)

View file

@ -15,10 +15,13 @@ import logging
from ..exceptions import UserError
from ..utils import uniq
from .status import SubStatus, ItemMetadata
from .exceptions import BothReadOnly, IdentAlreadyExists, PartialSync, \
StorageEmpty, SyncConflict
from .exceptions import BothReadOnly
from .exceptions import IdentAlreadyExists
from .exceptions import PartialSync
from .exceptions import StorageEmpty
from .exceptions import SyncConflict
from .status import ItemMetadata
from .status import SubStatus
sync_logger = logging.getLogger(__name__)

View file

@ -1,7 +1,7 @@
import abc
import contextlib
import sys
import sqlite3
import sys
from .exceptions import IdentAlreadyExists

View file

@ -2,7 +2,6 @@ import functools
import os
import sys
import uuid
from inspect import getfullargspec
from . import exceptions
@ -73,7 +72,7 @@ def get_etag_from_file(f):
mtime = getattr(stat, 'st_mtime_ns', None)
if mtime is None:
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):
@ -125,7 +124,7 @@ def checkdir(path, create=False, mode=0o750):
if not os.path.isdir(path):
if os.path.exists(path):
raise OSError('{} is not a directory.'.format(path))
raise OSError(f'{path} is not a directory.')
if create:
os.makedirs(path, mode)
else:
@ -143,7 +142,7 @@ def checkfile(path, create=False):
checkdir(os.path.dirname(path), create=create)
if not os.path.isfile(path):
if os.path.exists(path):
raise OSError('{} is not a file.'.format(path))
raise OSError(f'{path} is not a file.')
if create:
with open(path, 'wb'):
pass

View file

@ -1,7 +1,9 @@
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 = (
@ -205,14 +207,14 @@ def join_collection(items, wrappers=_default_join_wrappers):
if wrapper_type is not None:
lines = chain(*(
['BEGIN:{}'.format(wrapper_type)],
[f'BEGIN:{wrapper_type}'],
# XXX: wrapper_props is a list of lines (with line-wrapping), so
# filtering out duplicate lines will almost certainly break
# multiline-values. Since the only props we usually need to
# support are PRODID and VERSION, I don't care.
uniq(wrapper_props),
lines,
['END:{}'.format(wrapper_type)]
[f'END:{wrapper_type}']
))
return ''.join(line + '\r\n' for line in lines)
@ -299,14 +301,14 @@ class _Component:
return rv[0]
def dump_lines(self):
yield 'BEGIN:{}'.format(self.name)
yield f'BEGIN:{self.name}'
yield from self.props
for c in self.subcomponents:
yield from c.dump_lines()
yield 'END:{}'.format(self.name)
yield f'END:{self.name}'
def __delitem__(self, key):
prefix = ('{}:'.format(key), '{};'.format(key))
prefix = (f'{key}:', f'{key};')
new_lines = []
lineiter = iter(self.props)
while True:
@ -329,7 +331,7 @@ class _Component:
assert isinstance(val, str)
assert '\n' not in val
del self[key]
line = '{}:{}'.format(key, val)
line = f'{key}:{val}'
self.props.append(line)
def __contains__(self, obj):
@ -342,8 +344,8 @@ class _Component:
raise ValueError(obj)
def __getitem__(self, key):
prefix_without_params = '{}:'.format(key)
prefix_with_params = '{};'.format(key)
prefix_without_params = f'{key}:'
prefix_with_params = f'{key};'
iterlines = iter(self.props)
for line in iterlines:
if line.startswith(prefix_without_params):