diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..fc1c641 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.4.0 + hooks: + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - id: end-of-file-fixer + - id: check-toml + - id: check-added-large-files + - id: debug-statements + - repo: https://gitlab.com/pycqa/flake8 + rev: "master" # pick a git hash / tag to point to + hooks: + - id: flake8 + additional_dependencies: [flake8-import-order, flake8-bugbear] diff --git a/.travis.yml b/.travis.yml index f81bc77..04b6d49 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ "branches": { "only": [ "auto", + "next", "master", "/^.*-maintenance$/" ] @@ -13,9 +14,6 @@ }, "install": [ ". scripts/travis-install.sh", - "pip install -U pip setuptools", - "pip install wheel", - "make -e install-dev", "make -e install-$BUILD" ], "language": "python", diff --git a/Makefile b/Makefile index 9f69944..7efe354 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,7 @@ install-servers: (cd $(TESTSERVER_BASE)$$server && sh install.sh); \ done -install-test: install-servers +install-test: install-servers install-dev pip install -Ur test-requirements.txt set -xe && if [ "$$REQUIREMENTS" = "devel" ]; then \ pip install -U --force-reinstall \ @@ -81,9 +81,9 @@ install-test: install-servers fi [ -z "$(TEST_EXTRA_PACKAGES)" ] || pip install $(TEST_EXTRA_PACKAGES) -install-style: install-docs - pip install -U flake8==3.5.0 flake8-import-order 'flake8-bugbear>=17.3.0' autopep8 - +install-style: install-docs install-dev + pip install -U flake8 flake8-import-order flake8-bugbear autopep8 + style: flake8 ! git grep -i syncroniz */* @@ -114,6 +114,7 @@ release-deb: sh scripts/release-deb.sh ubuntu zesty install-dev: + pip install -U pip setuptools wheel pip install -e . [ "$(ETESYNC_TESTS)" = "false" ] || pip install -Ue .[etesync] set -xe && if [ "$(REQUIREMENTS)" = "devel" ]; then \ diff --git a/README.rst b/README.rst index fb8c82d..10ba6d1 100644 --- a/README.rst +++ b/README.rst @@ -2,6 +2,26 @@ vdirsyncer ========== +.. image:: https://travis-ci.org/pimutils/vdirsyncer.svg?branch=master + :target: https://travis-ci.org/pimutils/vdirsyncer + :alt: CI status + +.. image:: https://codecov.io/github/pimutils/vdirsyncer/coverage.svg?branch=master + :target: https://codecov.io/github/pimutils/vdirsyncer?branch=master + :alt: Codecov coverage report + +.. image:: https://readthedocs.org/projects/vdirsyncer/badge/ + :target: https://vdirsyncer.rtfd.org/ + :alt: documentation + +.. image:: https://img.shields.io/pypi/v/vdirsyncer.svg + :target: https://pypi.python.org/pypi/vdirsyncer + :alt: version on pypi + +.. image:: https://img.shields.io/pypi/l/vdirsyncer.svg + :target: https://github.com/pimutils/vdirsyncer/blob/master/LICENCE + :alt: licence: BSD + - `Documentation `_ - `Source code `_ @@ -20,15 +40,6 @@ It aims to be for calendars and contacts what `OfflineIMAP .. _programs: https://vdirsyncer.pimutils.org/en/latest/tutorials/ -.. image:: https://travis-ci.org/pimutils/vdirsyncer.svg?branch=master - :target: https://travis-ci.org/pimutils/vdirsyncer - -.. image:: https://codecov.io/github/pimutils/vdirsyncer/coverage.svg?branch=master - :target: https://codecov.io/github/pimutils/vdirsyncer?branch=master - -.. image:: https://badge.waffle.io/pimutils/vdirsyncer.svg?label=ready&title=Ready - :target: https://waffle.io/pimutils/vdirsyncer - Links of interest ================= diff --git a/docs/changelog.rst b/docs/changelog.rst index 1743454..565b052 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1 +1 @@ -.. include:: ../CHANGELOG.rst +.. include:: ../CHANGELOG.rst diff --git a/docs/conf.py b/docs/conf.py index bbcd7e0..385d2e8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- - import datetime import os -import setuptools_scm +from pkg_resources import get_distribution extensions = ['sphinx.ext.autodoc'] @@ -12,11 +10,11 @@ templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' -project = u'vdirsyncer' -copyright = (u'2014-{}, Markus Unterwaditzer & contributors' +project = 'vdirsyncer' +copyright = ('2014-{}, Markus Unterwaditzer & contributors' .format(datetime.date.today().strftime('%Y'))) -release = setuptools_scm.get_version(root='..', relative_to=__file__) +release = get_distribution('vdirsyncer').version version = '.'.join(release.split('.')[:2]) # The short X.Y version. rst_epilog = '.. |vdirsyncer_version| replace:: %s' % release @@ -44,18 +42,18 @@ htmlhelp_basename = 'vdirsyncerdoc' latex_elements = {} latex_documents = [ - ('index', 'vdirsyncer.tex', u'vdirsyncer Documentation', - u'Markus Unterwaditzer', 'manual'), + ('index', 'vdirsyncer.tex', 'vdirsyncer Documentation', + 'Markus Unterwaditzer', 'manual'), ] man_pages = [ - ('index', 'vdirsyncer', u'vdirsyncer Documentation', - [u'Markus Unterwaditzer'], 1) + ('index', 'vdirsyncer', 'vdirsyncer Documentation', + ['Markus Unterwaditzer'], 1) ] texinfo_documents = [ - ('index', 'vdirsyncer', u'vdirsyncer Documentation', - u'Markus Unterwaditzer', 'vdirsyncer', + ('index', 'vdirsyncer', 'vdirsyncer Documentation', + 'Markus Unterwaditzer', 'vdirsyncer', 'Synchronize calendars and contacts.', 'Miscellaneous'), ] diff --git a/docs/config.rst b/docs/config.rst index c54b0cd..425f740 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -286,7 +286,7 @@ Service to hardcode those into opensource software [googleterms]_: 3. In the sidebar, select "Credentials" and create a new "OAuth Client ID". The application type is "Other". - + You'll be prompted to create a OAuth consent screen first. Fill out that form however you like. @@ -368,7 +368,7 @@ password. Neither are stored. secrets_dir = ... #server_path = ... #db_path = ... - + :param email: The email address of your account. :param secrets_dir: A directory where vdirsyncer can store the encryption key and authentication token. @@ -386,7 +386,7 @@ password. Neither are stored. secrets_dir = ... #server_path = ... #db_path = ... - + :param email: The email address of your account. :param secrets_dir: A directory where vdirsyncer can store the encryption key and authentication token. diff --git a/docs/keyring.rst b/docs/keyring.rst index 2447f96..b3fc442 100644 --- a/docs/keyring.rst +++ b/docs/keyring.rst @@ -60,7 +60,7 @@ passwords from the OS's password store. Installation:: Basic usage:: password.fetch = ["command", "keyring", "get", "example.com", "foouser"] - + .. _keyring: https://github.com/jaraco/keyring/ Password Prompt diff --git a/docs/tutorials/baikal.rst b/docs/tutorials/baikal.rst index 815750d..f721fc7 100644 --- a/docs/tutorials/baikal.rst +++ b/docs/tutorials/baikal.rst @@ -7,4 +7,4 @@ Vdirsyncer is continuously tested against the latest version of Baikal_. - Baikal up to ``0.2.7`` also uses an old version of SabreDAV, with the same issue as ownCloud, see :gh:`160`. This issue is fixed in later versions. -.. _Baikal: http://baikal-server.com/ +.. _Baikal: http://sabre.io/baikal/ diff --git a/docs/tutorials/claws-mail.rst b/docs/tutorials/claws-mail.rst index 337b863..fba9e98 100644 --- a/docs/tutorials/claws-mail.rst +++ b/docs/tutorials/claws-mail.rst @@ -69,7 +69,7 @@ Now we discover and sync our contacts:: Claws Mail ---------- -Open Claws-Mail. Got to **Tools** => **Addressbook**. +Open Claws-Mail. Go to **Tools** => **Addressbook**. Click on **Addressbook** => **New vCard**. Choose a name for the book. @@ -77,7 +77,7 @@ Then search for the for the vCard in the folder **~/.contacts/**. Click ok, and you we will see your contacts. .. note:: - + Claws-Mail shows only contacts that have a mail address. Crontab diff --git a/docs/tutorials/fastmail.rst b/docs/tutorials/fastmail.rst index d066de3..5f133ef 100644 --- a/docs/tutorials/fastmail.rst +++ b/docs/tutorials/fastmail.rst @@ -10,13 +10,13 @@ the settings to use:: [storage cal] type = "caldav" - url = "https://caldav.messagingengine.com/" + url = "https://caldav.fastmail.com/" username = ... password = ... [storage card] type = "carddav" - url = "https://carddav.messagingengine.com/" + url = "https://carddav.fastmail.com/" username = ... password = ... diff --git a/docs/when.rst b/docs/when.rst index b69aed4..a7ae997 100644 --- a/docs/when.rst +++ b/docs/when.rst @@ -39,7 +39,7 @@ program chosen: * Like with ``todo.txt``, Dropbox and friends are obviously agnostic/unaware of the files' contents. If a file has changed on both sides, Dropbox just copies both versions to both sides. - + This is a good idea if the user is directly interfacing with the file system and is able to resolve conflicts themselves. Here it might lead to erroneous behavior with e.g. ``khal``, since there are now two events with diff --git a/scripts/dpkg.Dockerfile b/scripts/dpkg.Dockerfile index 78c8aab..09d9f24 100644 --- a/scripts/dpkg.Dockerfile +++ b/scripts/dpkg.Dockerfile @@ -9,7 +9,7 @@ ARG distrover RUN apt-get update RUN apt-get install -y build-essential fakeroot debhelper git RUN apt-get install -y python3-all python3-pip -RUN apt-get install -y ruby ruby-dev +RUN apt-get install -y ruby ruby-dev RUN apt-get install -y python-all python-pip RUN gem install fpm diff --git a/scripts/make_travisconf.py b/scripts/make_travisconf.py index 15f872c..afca282 100644 --- a/scripts/make_travisconf.py +++ b/scripts/make_travisconf.py @@ -17,14 +17,11 @@ cfg['git'] = { } cfg['branches'] = { - 'only': ['auto', 'master', '/^.*-maintenance$/'] + 'only': ['auto', 'next', 'master', '/^.*-maintenance$/'] } cfg['install'] = """ . scripts/travis-install.sh -pip install -U pip setuptools -pip install wheel -make -e install-dev make -e install-$BUILD """.strip().splitlines() diff --git a/setup.cfg b/setup.cfg index 4635543..ec67785 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,6 +9,8 @@ addopts = --tb=short # E731: Use a def instead of lambda expr # E743: Ambiguous function definition ignore = E731, E743 +# E503: Line break occurred before a binary operator +extend-ignore = W503 select = C,E,F,W,B,B9 exclude = .eggs, tests/storage/servers/owncloud/, tests/storage/servers/nextcloud/, tests/storage/servers/baikal/, build/ application-package-names = tests,vdirsyncer diff --git a/setup.py b/setup.py index 552165f..c9b986c 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ''' Vdirsyncer synchronizes calendars and contacts. @@ -18,14 +17,7 @@ requirements = [ # https://github.com/pimutils/vdirsyncer/issues/478 'click-threading>=0.2', - # !=2.9.0: https://github.com/kennethreitz/requests/issues/2930 - # - # >=2.4.1: https://github.com/shazow/urllib3/pull/444 - # Without the above pull request, `verify=False` also disables fingerprint - # validation. This is *not* what we want, and it's not possible to - # replicate vdirsyncer's current behavior (verifying fingerprints without - # verifying against CAs) with older versions of urllib3. - 'requests >=2.4.1, !=2.9.0', + 'requests >=2.20.0', # https://github.com/sigmavirus24/requests-toolbelt/pull/28 # And https://github.com/sigmavirus24/requests-toolbelt/issues/54 diff --git a/test-requirements.txt b/test-requirements.txt index d05994c..feb39a6 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,4 @@ -hypothesis>=3.1,<4.0 +hypothesis>=5.0.0 pytest pytest-localserver pytest-subtesthack diff --git a/tests/__init__.py b/tests/__init__.py index 9103d74..6948552 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ''' Test suite for vdirsyncer. ''' @@ -21,7 +20,7 @@ def assert_item_equals(a, b): assert normalize_item(a) == normalize_item(b) -VCARD_TEMPLATE = u'''BEGIN:VCARD +VCARD_TEMPLATE = '''BEGIN:VCARD VERSION:3.0 FN:Cyrus Daboo N:Daboo;Cyrus;;; @@ -37,7 +36,7 @@ X-SOMETHING:{r} UID:{uid} END:VCARD''' -TASK_TEMPLATE = u'''BEGIN:VCALENDAR +TASK_TEMPLATE = '''BEGIN:VCALENDAR VERSION:2.0 PRODID:-//dmfs.org//mimedir.icalendar//EN BEGIN:VTODO @@ -52,7 +51,7 @@ END:VTODO END:VCALENDAR''' -BARE_EVENT_TEMPLATE = u'''BEGIN:VEVENT +BARE_EVENT_TEMPLATE = '''BEGIN:VEVENT DTSTART:19970714T170000Z DTEND:19970715T035959Z SUMMARY:Bastille Day Party @@ -61,10 +60,10 @@ UID:{uid} END:VEVENT''' -EVENT_TEMPLATE = u'''BEGIN:VCALENDAR +EVENT_TEMPLATE = '''BEGIN:VCALENDAR VERSION:2.0 PRODID:-//hacksw/handcal//NONSGML v1.0//EN -''' + BARE_EVENT_TEMPLATE + u''' +''' + BARE_EVENT_TEMPLATE + ''' END:VCALENDAR''' EVENT_WITH_TIMEZONE_TEMPLATE = '''BEGIN:VCALENDAR @@ -90,7 +89,7 @@ END:VTIMEZONE END:VCALENDAR''' -SIMPLE_TEMPLATE = u'''BEGIN:FOO +SIMPLE_TEMPLATE = '''BEGIN:FOO UID:{uid} X-SOMETHING:{r} HAHA:YES diff --git a/tests/conftest.py b/tests/conftest.py index eb8ffac..afd5aff 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ''' General-purpose fixtures for vdirsyncer's testsuite. ''' @@ -34,8 +33,7 @@ settings.register_profile("ci", settings( )) settings.register_profile("deterministic", settings( derandomize=True, - perform_health_check=False, - suppress_health_check=[HealthCheck.too_slow], + suppress_health_check=HealthCheck.all(), )) if os.environ.get('DETERMINISTIC_TESTS', 'false').lower() == 'true': diff --git a/tests/storage/__init__.py b/tests/storage/__init__.py index ecb8b36..9add313 100644 --- a/tests/storage/__init__.py +++ b/tests/storage/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import random import uuid @@ -31,7 +29,7 @@ def format_item(item_template, uid=None): return Item(item_template.format(r=r, uid=uid or r)) -class StorageTests(object): +class StorageTests: storage_class = None supports_collections = True supports_metadata = True @@ -173,10 +171,10 @@ class StorageTests(object): _, etag = s.get(href) info[href] = etag - assert dict( - (href, etag) for href, item, etag + assert { + href: etag for href, item, etag in s.get_multi(href for href, etag in info.items()) - ) == info + } == info def test_repr(self, s, get_storage_args): assert self.storage_class.__name__ in repr(s) @@ -191,10 +189,10 @@ class StorageTests(object): s.upload(get_item()) collections.add(s.collection) - actual = set( + actual = { c['collection'] for c in self.storage_class.discover(**get_storage_args(collection=None)) - ) + } assert actual >= collections @@ -212,7 +210,7 @@ class StorageTests(object): ) href = s.upload(get_item())[0] - assert href in set(href for href, etag in s.list()) + assert href in {href for href, etag in s.list()} def test_discover_collection_arg(self, requires_collections, get_storage_args): @@ -255,7 +253,7 @@ class StorageTests(object): monkeypatch.setattr('vdirsyncer.utils.generate_href', lambda x: x) - uid = u'test @ foo ät bar град сатану' + uid = 'test @ foo ät bar град сатану' collection = 'test @ foo ät bar' s = self.storage_class(**get_storage_args(collection=collection)) @@ -286,12 +284,12 @@ class StorageTests(object): try: s.set_meta('color', None) assert not s.get_meta('color') - s.set_meta('color', u'#ff0000') - assert s.get_meta('color') == u'#ff0000' + s.set_meta('color', '#ff0000') + assert s.get_meta('color') == '#ff0000' except exceptions.UnsupportedMetadataError: pass - for x in (u'hello world', u'hello wörld'): + for x in ('hello world', 'hello wörld'): s.set_meta('displayname', x) rv = s.get_meta('displayname') assert rv == x @@ -315,7 +313,7 @@ class StorageTests(object): pytest.skip('This storage instance doesn\'t support iCalendar.') uid = str(uuid.uuid4()) - item = Item(textwrap.dedent(u''' + item = Item(textwrap.dedent(''' BEGIN:VCALENDAR VERSION:2.0 BEGIN:VEVENT diff --git a/tests/storage/conftest.py b/tests/storage/conftest.py index 90333ad..dcb42f6 100644 --- a/tests/storage/conftest.py +++ b/tests/storage/conftest.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import pytest import uuid diff --git a/tests/storage/dav/__init__.py b/tests/storage/dav/__init__.py index 77109e8..8886f73 100644 --- a/tests/storage/dav/__init__.py +++ b/tests/storage/dav/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import uuid import os @@ -27,7 +25,7 @@ class DAVStorageTests(ServerMixin, StorageTests): @pytest.mark.skipif(dav_server == 'radicale', reason='Radicale is very tolerant.') def test_dav_broken_item(self, s): - item = Item(u'HAHA:YES') + item = Item('HAHA:YES') with pytest.raises((exceptions.Error, requests.exceptions.HTTPError)): s.upload(item) assert not list(s.list()) @@ -50,7 +48,7 @@ class DAVStorageTests(ServerMixin, StorageTests): monkeypatch.setattr(s, '_get_href', lambda item: item.ident + s.fileext) - item = get_item(uid=u'град сатану' + str(uuid.uuid4())) + item = get_item(uid='град сатану' + str(uuid.uuid4())) href, etag = s.upload(item) item2, etag2 = s.get(href) assert_item_equals(item, item2) diff --git a/tests/storage/dav/test_caldav.py b/tests/storage/dav/test_caldav.py index 3063bbd..1d3f2fa 100644 --- a/tests/storage/dav/test_caldav.py +++ b/tests/storage/dav/test_caldav.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import datetime from textwrap import dedent @@ -64,7 +62,7 @@ class TestCalDAVStorage(DAVStorageTests): s = self.storage_class(start_date=start_date, end_date=end_date, **get_storage_args()) - too_old_item = format_item(dedent(u''' + too_old_item = format_item(dedent(''' BEGIN:VCALENDAR VERSION:2.0 PRODID:-//hacksw/handcal//NONSGML v1.0//EN @@ -78,7 +76,7 @@ class TestCalDAVStorage(DAVStorageTests): END:VCALENDAR ''').strip()) - too_new_item = format_item(dedent(u''' + too_new_item = format_item(dedent(''' BEGIN:VCALENDAR VERSION:2.0 PRODID:-//hacksw/handcal//NONSGML v1.0//EN @@ -92,7 +90,7 @@ class TestCalDAVStorage(DAVStorageTests): END:VCALENDAR ''').strip()) - good_item = format_item(dedent(u''' + good_item = format_item(dedent(''' BEGIN:VCALENDAR VERSION:2.0 PRODID:-//hacksw/handcal//NONSGML v1.0//EN @@ -140,13 +138,13 @@ class TestCalDAVStorage(DAVStorageTests): task = s.upload(format_item(TASK_TEMPLATE))[0] s.item_types = ('VTODO', 'VEVENT') - def l(): - return set(href for href, etag in s.list()) + def hrefs(): + return {href for href, etag in s.list()} - assert l() == {event, task} + assert hrefs() == {event, task} s.item_types = ('VTODO',) - assert l() == {task} + assert hrefs() == {task} s.item_types = ('VEVENT',) - assert l() == {event} + assert hrefs() == {event} s.item_types = () - assert l() == {event, task} + assert hrefs() == {event, task} diff --git a/tests/storage/dav/test_carddav.py b/tests/storage/dav/test_carddav.py index 7aedaac..e15e047 100644 --- a/tests/storage/dav/test_carddav.py +++ b/tests/storage/dav/test_carddav.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import pytest from vdirsyncer.storage.dav import CardDAVStorage diff --git a/tests/storage/etesync/test_main.py b/tests/storage/etesync/test_main.py index 9add484..2045646 100644 --- a/tests/storage/etesync/test_main.py +++ b/tests/storage/etesync/test_main.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import shutil import os import sys diff --git a/tests/storage/servers/__init__.py b/tests/storage/servers/__init__.py index 40a96af..e69de29 100644 --- a/tests/storage/servers/__init__.py +++ b/tests/storage/servers/__init__.py @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/tests/storage/servers/davical/__init__.py b/tests/storage/servers/davical/__init__.py index 25eaee1..a402ba2 100644 --- a/tests/storage/servers/davical/__init__.py +++ b/tests/storage/servers/davical/__init__.py @@ -14,7 +14,7 @@ except KeyError as e: @pytest.mark.flaky(reruns=5) -class ServerMixin(object): +class ServerMixin: @pytest.fixture def davical_args(self): if self.storage_class.fileext == '.ics': diff --git a/tests/storage/servers/fastmail/__init__.py b/tests/storage/servers/fastmail/__init__.py index a421f92..6471ef3 100644 --- a/tests/storage/servers/fastmail/__init__.py +++ b/tests/storage/servers/fastmail/__init__.py @@ -3,7 +3,7 @@ import os import pytest -class ServerMixin(object): +class ServerMixin: @pytest.fixture def get_storage_args(self, slow_create_collection): @@ -14,9 +14,9 @@ class ServerMixin(object): } if self.storage_class.fileext == '.ics': - args['url'] = 'https://caldav.messagingengine.com/' + args['url'] = 'https://caldav.fastmail.com/' elif self.storage_class.fileext == '.vcf': - args['url'] = 'https://carddav.messagingengine.com/' + args['url'] = 'https://carddav.fastmail.com/' else: raise RuntimeError() diff --git a/tests/storage/servers/icloud/__init__.py b/tests/storage/servers/icloud/__init__.py index f814490..04a0049 100644 --- a/tests/storage/servers/icloud/__init__.py +++ b/tests/storage/servers/icloud/__init__.py @@ -3,7 +3,7 @@ import os import pytest -class ServerMixin(object): +class ServerMixin: @pytest.fixture def get_storage_args(self, item_type, slow_create_collection): diff --git a/tests/storage/servers/mysteryshack/__init__.py b/tests/storage/servers/mysteryshack/__init__.py index b5e7478..617c154 100644 --- a/tests/storage/servers/mysteryshack/__init__.py +++ b/tests/storage/servers/mysteryshack/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import os import subprocess import time @@ -28,7 +26,7 @@ def wait(): return False -class ServerMixin(object): +class ServerMixin: @pytest.fixture(scope='session') def setup_mysteryshack_server(self, xprocess): def preparefunc(cwd): diff --git a/tests/storage/servers/radicale/__init__.py b/tests/storage/servers/radicale/__init__.py index 66b1407..6e4ae29 100644 --- a/tests/storage/servers/radicale/__init__.py +++ b/tests/storage/servers/radicale/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import logging import pytest @@ -14,7 +12,7 @@ import wsgi_intercept.requests_intercept logger = logging.getLogger(__name__) -class ServerMixin(object): +class ServerMixin: @pytest.fixture(autouse=True) def setup(self, request, tmpdir): diff --git a/tests/storage/servers/skip/__init__.py b/tests/storage/servers/skip/__init__.py index 0c8b392..081a41e 100644 --- a/tests/storage/servers/skip/__init__.py +++ b/tests/storage/servers/skip/__init__.py @@ -1,7 +1,7 @@ import pytest -class ServerMixin(object): +class ServerMixin: @pytest.fixture def get_storage_args(self): diff --git a/tests/storage/servers/xandikos/__init__.py b/tests/storage/servers/xandikos/__init__.py index 570d87f..899564b 100644 --- a/tests/storage/servers/xandikos/__init__.py +++ b/tests/storage/servers/xandikos/__init__.py @@ -6,7 +6,7 @@ import wsgi_intercept import wsgi_intercept.requests_intercept -class ServerMixin(object): +class ServerMixin: @pytest.fixture def get_storage_args(self, request, tmpdir, slow_create_collection): tmpdir.mkdir('xandikos') diff --git a/tests/storage/test_filesystem.py b/tests/storage/test_filesystem.py index 39a4880..e8a3d71 100644 --- a/tests/storage/test_filesystem.py +++ b/tests/storage/test_filesystem.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import subprocess import pytest @@ -32,8 +30,8 @@ class TestFilesystemStorage(StorageTests): def test_broken_data(self, tmpdir): s = self.storage_class(str(tmpdir), '.txt') - class BrokenItem(object): - raw = u'Ц, Ш, Л, ж, Д, З, Ю'.encode('utf-8') + class BrokenItem: + raw = 'Ц, Ш, Л, ж, Д, З, Ю'.encode() uid = 'jeezus' ident = uid with pytest.raises(TypeError): @@ -42,13 +40,13 @@ class TestFilesystemStorage(StorageTests): def test_ident_with_slash(self, tmpdir): s = self.storage_class(str(tmpdir), '.txt') - s.upload(Item(u'UID:a/b/c')) + s.upload(Item('UID:a/b/c')) item_file, = tmpdir.listdir() assert '/' not in item_file.basename and item_file.isfile() def test_too_long_uid(self, tmpdir): s = self.storage_class(str(tmpdir), '.txt') - item = Item(u'UID:' + u'hue' * 600) + item = Item('UID:' + 'hue' * 600) href, etag = s.upload(item) assert item.uid not in href @@ -60,27 +58,27 @@ class TestFilesystemStorage(StorageTests): monkeypatch.setattr(subprocess, 'call', check_call_mock) s = self.storage_class(str(tmpdir), '.txt', post_hook=None) - s.upload(Item(u'UID:a/b/c')) + s.upload(Item('UID:a/b/c')) def test_post_hook_active(self, tmpdir, monkeypatch): calls = [] exe = 'foo' - def check_call_mock(l, *args, **kwargs): + def check_call_mock(call, *args, **kwargs): calls.append(True) - assert len(l) == 2 - assert l[0] == exe + assert len(call) == 2 + assert call[0] == exe monkeypatch.setattr(subprocess, 'call', check_call_mock) s = self.storage_class(str(tmpdir), '.txt', post_hook=exe) - s.upload(Item(u'UID:a/b/c')) + s.upload(Item('UID:a/b/c')) assert calls def test_ignore_git_dirs(self, tmpdir): tmpdir.mkdir('.git').mkdir('foo') tmpdir.mkdir('a') tmpdir.mkdir('b') - assert set(c['collection'] for c - in self.storage_class.discover(str(tmpdir))) == {'a', 'b'} + assert {c['collection'] for c + in self.storage_class.discover(str(tmpdir))} == {'a', 'b'} diff --git a/tests/storage/test_http.py b/tests/storage/test_http.py index da14d5d..5e63a8f 100644 --- a/tests/storage/test_http.py +++ b/tests/storage/test_http.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import pytest from requests import Response @@ -14,25 +12,25 @@ def test_list(monkeypatch): collection_url = 'http://127.0.0.1/calendar/collection.ics' items = [ - (u'BEGIN:VEVENT\n' - u'SUMMARY:Eine Kurzinfo\n' - u'DESCRIPTION:Beschreibung des Termines\n' - u'END:VEVENT'), - (u'BEGIN:VEVENT\n' - u'SUMMARY:Eine zweite Küèrzinfo\n' - u'DESCRIPTION:Beschreibung des anderen Termines\n' - u'BEGIN:VALARM\n' - u'ACTION:AUDIO\n' - u'TRIGGER:19980403T120000\n' - u'ATTACH;FMTTYPE=audio/basic:http://host.com/pub/ssbanner.aud\n' - u'REPEAT:4\n' - u'DURATION:PT1H\n' - u'END:VALARM\n' - u'END:VEVENT') + ('BEGIN:VEVENT\n' + 'SUMMARY:Eine Kurzinfo\n' + 'DESCRIPTION:Beschreibung des Termines\n' + 'END:VEVENT'), + ('BEGIN:VEVENT\n' + 'SUMMARY:Eine zweite Küèrzinfo\n' + 'DESCRIPTION:Beschreibung des anderen Termines\n' + 'BEGIN:VALARM\n' + 'ACTION:AUDIO\n' + 'TRIGGER:19980403T120000\n' + 'ATTACH;FMTTYPE=audio/basic:http://host.com/pub/ssbanner.aud\n' + 'REPEAT:4\n' + 'DURATION:PT1H\n' + 'END:VALARM\n' + 'END:VEVENT') ] responses = [ - u'\n'.join([u'BEGIN:VCALENDAR'] + items + [u'END:VCALENDAR']) + '\n'.join(['BEGIN:VCALENDAR'] + items + ['END:VCALENDAR']) ] * 2 def get(self, method, url, *a, **kw): @@ -58,8 +56,8 @@ def test_list(monkeypatch): assert etag2 == etag found_items[normalize_item(item)] = href - expected = set(normalize_item(u'BEGIN:VCALENDAR\n' + x + '\nEND:VCALENDAR') - for x in items) + expected = {normalize_item('BEGIN:VCALENDAR\n' + x + '\nEND:VCALENDAR') + for x in items} assert set(found_items) == expected diff --git a/tests/storage/test_http_with_singlefile.py b/tests/storage/test_http_with_singlefile.py index 48c4587..6480a52 100644 --- a/tests/storage/test_http_with_singlefile.py +++ b/tests/storage/test_http_with_singlefile.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import pytest from requests import Response @@ -21,7 +19,7 @@ class CombinedStorage(Storage): if kwargs.get('collection', None) is not None: raise ValueError() - super(CombinedStorage, self).__init__(**kwargs) + super().__init__(**kwargs) self.url = url self.path = path self._reader = vdirsyncer.storage.http.HttpStorage(url=url) diff --git a/tests/storage/test_memory.py b/tests/storage/test_memory.py index 77b2cdb..417db1b 100644 --- a/tests/storage/test_memory.py +++ b/tests/storage/test_memory.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import pytest from vdirsyncer.storage.memory import MemoryStorage diff --git a/tests/storage/test_singlefile.py b/tests/storage/test_singlefile.py index 87eae1f..54ef020 100644 --- a/tests/storage/test_singlefile.py +++ b/tests/storage/test_singlefile.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import pytest from vdirsyncer.storage.singlefile import SingleFileStorage diff --git a/tests/system/cli/conftest.py b/tests/system/cli/conftest.py index e6876af..43e4b79 100644 --- a/tests/system/cli/conftest.py +++ b/tests/system/cli/conftest.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from textwrap import dedent from click.testing import CliRunner @@ -9,7 +7,7 @@ import pytest import vdirsyncer.cli as cli -class _CustomRunner(object): +class _CustomRunner: def __init__(self, tmpdir): self.tmpdir = tmpdir self.cfg = tmpdir.join('config') diff --git a/tests/system/cli/test_config.py b/tests/system/cli/test_config.py index 98de91a..734e1ee 100644 --- a/tests/system/cli/test_config.py +++ b/tests/system/cli/test_config.py @@ -23,7 +23,7 @@ def read_config(tmpdir, monkeypatch): def test_read_config(read_config): - errors, c = read_config(u''' + errors, c = read_config(''' [general] status_path = "/tmp/status/" @@ -59,7 +59,7 @@ def test_read_config(read_config): def test_missing_collections_param(read_config): with pytest.raises(exceptions.UserError) as excinfo: - read_config(u''' + read_config(''' [general] status_path = "/tmp/status/" @@ -79,7 +79,7 @@ def test_missing_collections_param(read_config): def test_invalid_section_type(read_config): with pytest.raises(exceptions.UserError) as excinfo: - read_config(u''' + read_config(''' [general] status_path = "/tmp/status/" @@ -92,7 +92,7 @@ def test_invalid_section_type(read_config): def test_missing_general_section(read_config): with pytest.raises(exceptions.UserError) as excinfo: - read_config(u''' + read_config(''' [pair my_pair] a = "my_a" b = "my_b" @@ -114,7 +114,7 @@ def test_missing_general_section(read_config): def test_wrong_general_section(read_config): with pytest.raises(exceptions.UserError) as excinfo: - read_config(u''' + read_config(''' [general] wrong = true ''') @@ -128,7 +128,7 @@ def test_wrong_general_section(read_config): def test_invalid_storage_name(read_config): with pytest.raises(exceptions.UserError) as excinfo: - read_config(u''' + read_config(''' [general] status_path = "{base}/status/" @@ -140,7 +140,7 @@ def test_invalid_storage_name(read_config): def test_invalid_collections_arg(read_config): with pytest.raises(exceptions.UserError) as excinfo: - read_config(u''' + read_config(''' [general] status_path = "/tmp/status/" @@ -165,7 +165,7 @@ def test_invalid_collections_arg(read_config): def test_duplicate_sections(read_config): with pytest.raises(exceptions.UserError) as excinfo: - read_config(u''' + read_config(''' [general] status_path = "/tmp/status/" diff --git a/tests/system/cli/test_repair.py b/tests/system/cli/test_repair.py index 7573801..cad41f6 100644 --- a/tests/system/cli/test_repair.py +++ b/tests/system/cli/test_repair.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - from textwrap import dedent import pytest diff --git a/tests/system/cli/test_sync.py b/tests/system/cli/test_sync.py index e8ddca9..97e21e8 100644 --- a/tests/system/cli/test_sync.py +++ b/tests/system/cli/test_sync.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import json import sys from textwrap import dedent @@ -257,17 +255,17 @@ def test_multiple_pairs(tmpdir, runner): result = runner.invoke(['discover']) assert not result.exception - assert set(result.output.splitlines()) > set([ + assert set(result.output.splitlines()) > { 'Discovering collections for pair bambaz', 'Discovering collections for pair foobar' - ]) + } result = runner.invoke(['sync']) assert not result.exception - assert set(result.output.splitlines()) == set([ + assert set(result.output.splitlines()) == { 'Syncing bambaz', 'Syncing foobar', - ]) + } # XXX: https://github.com/pimutils/vdirsyncer/issues/617 @@ -277,17 +275,17 @@ def test_multiple_pairs(tmpdir, runner): st.text( st.characters( blacklist_characters=set( - u'./\x00' # Invalid chars on POSIX filesystems + './\x00' # Invalid chars on POSIX filesystems ), # Surrogates can't be encoded to utf-8 in Python - blacklist_categories=set(['Cs']) + blacklist_categories={'Cs'} ), min_size=1, max_size=50 ), min_size=1 )) -@example(collections=[u'persönlich']) +@example(collections=['persönlich']) @example(collections={'a', 'A'}) @example(collections={'\ufffe'}) def test_create_collections(subtest, collections): @@ -322,8 +320,8 @@ def test_create_collections(subtest, collections): ) assert not result.exception, result.output - assert set(x.basename for x in tmpdir.join('foo').listdir()) == \ - set(x.basename for x in tmpdir.join('bar').listdir()) + assert {x.basename for x in tmpdir.join('foo').listdir()} == \ + {x.basename for x in tmpdir.join('bar').listdir()} def test_ident_conflict(tmpdir, runner): diff --git a/tests/system/utils/test_main.py b/tests/system/utils/test_main.py index d25b0d0..10bfde9 100644 --- a/tests/system/utils/test_main.py +++ b/tests/system/utils/test_main.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import sys import logging @@ -20,7 +18,7 @@ def test_get_storage_init_args(): from vdirsyncer.storage.memory import MemoryStorage all, required = utils.get_storage_init_args(MemoryStorage) - assert all == set(['fileext', 'collection', 'read_only', 'instance_name']) + assert all == {'fileext', 'collection', 'read_only', 'instance_name'} assert not required diff --git a/tests/unit/cli/test_discover.py b/tests/unit/cli/test_discover.py index 6f71e85..bc6bd96 100644 --- a/tests/unit/cli/test_discover.py +++ b/tests/unit/cli/test_discover.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import pytest from vdirsyncer.cli.discover import expand_collections diff --git a/tests/unit/cli/test_fetchparams.py b/tests/unit/cli/test_fetchparams.py index a156985..2d28507 100644 --- a/tests/unit/cli/test_fetchparams.py +++ b/tests/unit/cli/test_fetchparams.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import hypothesis.strategies as st from hypothesis import given @@ -23,7 +21,7 @@ def mystrategy(monkeypatch): def value_cache(monkeypatch): _cache = {} - class FakeContext(object): + class FakeContext: fetched_params = _cache def find_object(self, _): diff --git a/tests/unit/sync/test_sync.py b/tests/unit/sync/test_sync.py index ca96f71..4e9651f 100644 --- a/tests/unit/sync/test_sync.py +++ b/tests/unit/sync/test_sync.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from copy import deepcopy import hypothesis.strategies as st @@ -32,7 +30,7 @@ def empty_storage(x): def items(s): - return set(x[1].raw for x in s.items.values()) + return {x[1].raw for x in s.items.values()} def test_irrelevant_status(): @@ -49,7 +47,7 @@ def test_missing_status(): a = MemoryStorage() b = MemoryStorage() status = {} - item = Item(u'asdf') + item = Item('asdf') a.upload(item) b.upload(item) sync(a, b, status) @@ -62,8 +60,8 @@ def test_missing_status_and_different_items(): b = MemoryStorage() status = {} - item1 = Item(u'UID:1\nhaha') - item2 = Item(u'UID:1\nhoho') + item1 = Item('UID:1\nhaha') + item2 = Item('UID:1\nhoho') a.upload(item1) b.upload(item2) with pytest.raises(SyncConflict): @@ -79,8 +77,8 @@ def test_read_only_and_prefetch(): b.read_only = True status = {} - item1 = Item(u'UID:1\nhaha') - item2 = Item(u'UID:2\nhoho') + item1 = Item('UID:1\nhaha') + item2 = Item('UID:2\nhoho') a.upload(item1) a.upload(item2) @@ -152,22 +150,22 @@ def test_upload_and_update(): b = MemoryStorage(fileext='.b') status = {} - item = Item(u'UID:1') # new item 1 in a + item = Item('UID:1') # new item 1 in a a.upload(item) sync(a, b, status) assert items(b) == items(a) == {item.raw} - item = Item(u'UID:1\nASDF:YES') # update of item 1 in b + item = Item('UID:1\nASDF:YES') # update of item 1 in b b.update('1.b', item, b.get('1.b')[1]) sync(a, b, status) assert items(b) == items(a) == {item.raw} - item2 = Item(u'UID:2') # new item 2 in b + item2 = Item('UID:2') # new item 2 in b b.upload(item2) sync(a, b, status) assert items(b) == items(a) == {item.raw, item2.raw} - item2 = Item(u'UID:2\nASDF:YES') # update of item 2 in a + item2 = Item('UID:2\nASDF:YES') # update of item 2 in a a.update('2.a', item2, a.get('2.a')[1]) sync(a, b, status) assert items(b) == items(a) == {item.raw, item2.raw} @@ -178,9 +176,9 @@ def test_deletion(): b = MemoryStorage(fileext='.b') status = {} - item = Item(u'UID:1') + item = Item('UID:1') a.upload(item) - item2 = Item(u'UID:2') + item2 = Item('UID:2') a.upload(item2) sync(a, b, status) b.delete('1.b', b.get('1.b')[1]) @@ -215,7 +213,7 @@ def test_insert_hash(): def test_already_synced(): a = MemoryStorage(fileext='.a') b = MemoryStorage(fileext='.b') - item = Item(u'UID:1') + item = Item('UID:1') a.upload(item) b.upload(item) status = { @@ -243,14 +241,14 @@ def test_already_synced(): def test_conflict_resolution_both_etags_new(winning_storage): a = MemoryStorage() b = MemoryStorage() - item = Item(u'UID:1') + item = Item('UID:1') href_a, etag_a = a.upload(item) href_b, etag_b = b.upload(item) status = {} sync(a, b, status) assert status - item_a = Item(u'UID:1\nitem a') - item_b = Item(u'UID:1\nitem b') + item_a = Item('UID:1\nitem a') + item_b = Item('UID:1\nitem b') a.update(href_a, item_a, etag_a) b.update(href_b, item_b, etag_b) with pytest.raises(SyncConflict): @@ -264,13 +262,13 @@ def test_conflict_resolution_both_etags_new(winning_storage): def test_updated_and_deleted(): a = MemoryStorage() b = MemoryStorage() - href_a, etag_a = a.upload(Item(u'UID:1')) + href_a, etag_a = a.upload(Item('UID:1')) status = {} sync(a, b, status, force_delete=True) (href_b, etag_b), = b.list() b.delete(href_b, etag_b) - updated = Item(u'UID:1\nupdated') + updated = Item('UID:1\nupdated') a.update(href_a, updated, etag_a) sync(a, b, status, force_delete=True) @@ -280,8 +278,8 @@ def test_updated_and_deleted(): def test_conflict_resolution_invalid_mode(): a = MemoryStorage() b = MemoryStorage() - item_a = Item(u'UID:1\nitem a') - item_b = Item(u'UID:1\nitem b') + item_a = Item('UID:1\nitem a') + item_b = Item('UID:1\nitem b') a.upload(item_a) b.upload(item_b) with pytest.raises(ValueError): @@ -291,7 +289,7 @@ def test_conflict_resolution_invalid_mode(): def test_conflict_resolution_new_etags_without_changes(): a = MemoryStorage() b = MemoryStorage() - item = Item(u'UID:1') + item = Item('UID:1') href_a, etag_a = a.upload(item) href_b, etag_b = b.upload(item) status = {'1': (href_a, 'BOGUS_a', href_b, 'BOGUS_b')} @@ -326,7 +324,7 @@ def test_uses_get_multi(monkeypatch): a = MemoryStorage() b = MemoryStorage() - item = Item(u'UID:1') + item = Item('UID:1') expected_href, etag = a.upload(item) sync(a, b, {}) @@ -336,8 +334,8 @@ def test_uses_get_multi(monkeypatch): def test_empty_storage_dataloss(): a = MemoryStorage() b = MemoryStorage() - a.upload(Item(u'UID:1')) - a.upload(Item(u'UID:2')) + a.upload(Item('UID:1')) + a.upload(Item('UID:2')) status = {} sync(a, b, status) with pytest.raises(StorageEmpty): @@ -350,22 +348,22 @@ def test_empty_storage_dataloss(): def test_no_uids(): a = MemoryStorage() b = MemoryStorage() - a.upload(Item(u'ASDF')) - b.upload(Item(u'FOOBAR')) + a.upload(Item('ASDF')) + b.upload(Item('FOOBAR')) status = {} sync(a, b, status) - assert items(a) == items(b) == {u'ASDF', u'FOOBAR'} + assert items(a) == items(b) == {'ASDF', 'FOOBAR'} def test_changed_uids(): a = MemoryStorage() b = MemoryStorage() - href_a, etag_a = a.upload(Item(u'UID:A-ONE')) - href_b, etag_b = b.upload(Item(u'UID:B-ONE')) + href_a, etag_a = a.upload(Item('UID:A-ONE')) + href_b, etag_b = b.upload(Item('UID:B-ONE')) status = {} sync(a, b, status) - a.update(href_a, Item(u'UID:A-TWO'), etag_a) + a.update(href_a, Item('UID:A-TWO'), etag_a) sync(a, b, status) @@ -383,8 +381,8 @@ def test_partial_sync_revert(): a = MemoryStorage(instance_name='a') b = MemoryStorage(instance_name='b') status = {} - a.upload(Item(u'UID:1')) - b.upload(Item(u'UID:2')) + a.upload(Item('UID:1')) + b.upload(Item('UID:2')) b.read_only = True sync(a, b, status, partial_sync='revert') @@ -418,13 +416,13 @@ def test_ident_conflict(sync_inbetween): a = MemoryStorage() b = MemoryStorage() status = {} - href_a, etag_a = a.upload(Item(u'UID:aaa')) - href_b, etag_b = a.upload(Item(u'UID:bbb')) + href_a, etag_a = a.upload(Item('UID:aaa')) + href_b, etag_b = a.upload(Item('UID:bbb')) if sync_inbetween: sync(a, b, status) - a.update(href_a, Item(u'UID:xxx'), etag_a) - a.update(href_b, Item(u'UID:xxx'), etag_b) + a.update(href_a, Item('UID:xxx'), etag_a) + a.update(href_b, Item('UID:xxx'), etag_b) with pytest.raises(IdentConflict): sync(a, b, status) @@ -441,7 +439,7 @@ def test_moved_href(): a = MemoryStorage() b = MemoryStorage() status = {} - href, etag = a.upload(Item(u'UID:haha')) + href, etag = a.upload(Item('UID:haha')) sync(a, b, status) b.items['lol'] = b.items.pop('haha') @@ -476,26 +474,26 @@ def test_bogus_etag_change(): a = MemoryStorage() b = MemoryStorage() status = {} - href_a, etag_a = a.upload(Item(u'UID:ASDASD')) + href_a, etag_a = a.upload(Item('UID:ASDASD')) sync(a, b, status) assert len(status) == len(list(a.list())) == len(list(b.list())) == 1 (href_b, etag_b), = b.list() - a.update(href_a, Item(u'UID:ASDASD'), etag_a) - b.update(href_b, Item(u'UID:ASDASD\nACTUALCHANGE:YES'), etag_b) + a.update(href_a, Item('UID:ASDASD'), etag_a) + b.update(href_b, Item('UID:ASDASD\nACTUALCHANGE:YES'), etag_b) b.delete = b.update = b.upload = blow_up sync(a, b, status) assert len(status) == 1 - assert items(a) == items(b) == {u'UID:ASDASD\nACTUALCHANGE:YES'} + assert items(a) == items(b) == {'UID:ASDASD\nACTUALCHANGE:YES'} def test_unicode_hrefs(): a = MemoryStorage() b = MemoryStorage() status = {} - href, etag = a.upload(Item(u'UID:äää')) + href, etag = a.upload(Item('UID:äää')) sync(a, b, status) @@ -565,7 +563,7 @@ class SyncMachine(RuleBasedStateMachine): uid=uid_strategy, etag=st.text()) def upload(self, storage, uid, etag): - item = Item(u'UID:{}'.format(uid)) + item = Item('UID:{}'.format(uid)) storage.items[uid] = (etag, item) @rule(storage=Storage, href=st.text()) diff --git a/tests/unit/test_metasync.py b/tests/unit/test_metasync.py index 0b1ab95..ae6d94c 100644 --- a/tests/unit/test_metasync.py +++ b/tests/unit/test_metasync.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import hypothesis.strategies as st from hypothesis import example, given @@ -130,7 +128,7 @@ metadata = st.dictionaries(keys, values) status=metadata, keys=st.sets(keys), conflict_resolution=st.just('a wins') | st.just('b wins') ) -@example(a={u'0': u'0'}, b={}, status={u'0': u'0'}, keys={u'0'}, +@example(a={'0': '0'}, b={}, status={'0': '0'}, keys={'0'}, conflict_resolution='a wins') @example(a={'0': '0'}, b={'0': '1'}, status={'0': '0'}, keys={'0'}, conflict_resolution='a wins') @@ -144,9 +142,9 @@ def test_fuzzing(a, b, status, keys, conflict_resolution): b = _get_storage(b, 'B') winning_storage = (a if conflict_resolution == 'a wins' else b) - expected_values = dict((key, winning_storage.get_meta(key)) - for key in keys - if key not in status) + expected_values = {key: winning_storage.get_meta(key) + for key in keys + if key not in status} metasync(a, b, status, keys=keys, conflict_resolution=conflict_resolution) diff --git a/tests/unit/test_repair.py b/tests/unit/test_repair.py index 0ba726e..5467c54 100644 --- a/tests/unit/test_repair.py +++ b/tests/unit/test_repair.py @@ -1,4 +1,4 @@ -from hypothesis import given, settings +from hypothesis import HealthCheck, given, settings import pytest @@ -11,17 +11,18 @@ from vdirsyncer.vobject import Item @given(uid=uid_strategy) -@settings(perform_health_check=False) # Using the random module for UIDs +# Using the random module for UIDs: +@settings(suppress_health_check=HealthCheck.all()) def test_repair_uids(uid): s = MemoryStorage() s.items = { 'one': ( 'asdf', - Item(u'BEGIN:VCARD\nFN:Hans\nUID:{}\nEND:VCARD'.format(uid)) + Item('BEGIN:VCARD\nFN:Hans\nUID:{}\nEND:VCARD'.format(uid)) ), 'two': ( 'asdf', - Item(u'BEGIN:VCARD\nFN:Peppi\nUID:{}\nEND:VCARD'.format(uid)) + Item('BEGIN:VCARD\nFN:Peppi\nUID:{}\nEND:VCARD'.format(uid)) ) } @@ -35,10 +36,11 @@ def test_repair_uids(uid): @given(uid=uid_strategy.filter(lambda x: not href_safe(x))) -@settings(perform_health_check=False) # Using the random module for UIDs +# Using the random module for UIDs: +@settings(suppress_health_check=HealthCheck.all()) def test_repair_unsafe_uids(uid): s = MemoryStorage() - item = Item(u'BEGIN:VCARD\nUID:{}\nEND:VCARD'.format(uid)) + item = Item('BEGIN:VCARD\nUID:{}\nEND:VCARD'.format(uid)) href, etag = s.upload(item) assert s.get(href)[0].uid == uid assert not href_safe(uid) diff --git a/tests/unit/utils/test_vobject.py b/tests/unit/utils/test_vobject.py index cfb52d1..84d87a8 100644 --- a/tests/unit/utils/test_vobject.py +++ b/tests/unit/utils/test_vobject.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from textwrap import dedent import hypothesis.strategies as st @@ -21,10 +19,10 @@ _simple_split = [ VCARD_TEMPLATE.format(r=678, uid=678) ] -_simple_joined = u'\r\n'.join( - [u'BEGIN:VADDRESSBOOK'] + - _simple_split + - [u'END:VADDRESSBOOK\r\n'] +_simple_joined = '\r\n'.join( + ['BEGIN:VADDRESSBOOK'] + + _simple_split + + ['END:VADDRESSBOOK\r\n'] ) @@ -39,10 +37,10 @@ def test_split_collection_simple(benchmark): def test_split_collection_multiple_wrappers(benchmark): - joined = u'\r\n'.join( - u'BEGIN:VADDRESSBOOK\r\n' + - x + - u'\r\nEND:VADDRESSBOOK\r\n' + joined = '\r\n'.join( + 'BEGIN:VADDRESSBOOK\r\n' + + x + + '\r\nEND:VADDRESSBOOK\r\n' for x in _simple_split ) given = benchmark(lambda: list(vobject.split_collection(joined))) @@ -105,32 +103,32 @@ def test_split_collection_timezones(): ] timezone = ( - u'BEGIN:VTIMEZONE\r\n' - u'TZID:/mozilla.org/20070129_1/Asia/Tokyo\r\n' - u'X-LIC-LOCATION:Asia/Tokyo\r\n' - u'BEGIN:STANDARD\r\n' - u'TZOFFSETFROM:+0900\r\n' - u'TZOFFSETTO:+0900\r\n' - u'TZNAME:JST\r\n' - u'DTSTART:19700101T000000\r\n' - u'END:STANDARD\r\n' - u'END:VTIMEZONE' + 'BEGIN:VTIMEZONE\r\n' + 'TZID:/mozilla.org/20070129_1/Asia/Tokyo\r\n' + 'X-LIC-LOCATION:Asia/Tokyo\r\n' + 'BEGIN:STANDARD\r\n' + 'TZOFFSETFROM:+0900\r\n' + 'TZOFFSETTO:+0900\r\n' + 'TZNAME:JST\r\n' + 'DTSTART:19700101T000000\r\n' + 'END:STANDARD\r\n' + 'END:VTIMEZONE' ) - full = u'\r\n'.join( - [u'BEGIN:VCALENDAR'] + - items + - [timezone, u'END:VCALENDAR'] + full = '\r\n'.join( + ['BEGIN:VCALENDAR'] + + items + + [timezone, 'END:VCALENDAR'] ) - given = set(normalize_item(item) - for item in vobject.split_collection(full)) - expected = set( - normalize_item(u'\r\n'.join(( - u'BEGIN:VCALENDAR', item, timezone, u'END:VCALENDAR' + given = {normalize_item(item) + for item in vobject.split_collection(full)} + expected = { + normalize_item('\r\n'.join(( + 'BEGIN:VCALENDAR', item, timezone, 'END:VCALENDAR' ))) for item in items - ) + } assert given == expected @@ -148,20 +146,20 @@ def test_split_contacts(): def test_hash_item(): a = EVENT_TEMPLATE.format(r=1, uid=1) - b = u'\n'.join(line for line in a.splitlines() - if u'PRODID' not in line) + b = '\n'.join(line for line in a.splitlines() + if 'PRODID' not in line) assert vobject.hash_item(a) == vobject.hash_item(b) def test_multiline_uid(benchmark): - a = (u'BEGIN:FOO\r\n' - u'UID:123456789abcd\r\n' - u' efgh\r\n' - u'END:FOO\r\n') - assert benchmark(lambda: vobject.Item(a).uid) == u'123456789abcdefgh' + a = ('BEGIN:FOO\r\n' + 'UID:123456789abcd\r\n' + ' efgh\r\n' + 'END:FOO\r\n') + assert benchmark(lambda: vobject.Item(a).uid) == '123456789abcdefgh' -complex_uid_item = dedent(u''' +complex_uid_item = dedent(''' BEGIN:VCALENDAR BEGIN:VTIMEZONE TZID:Europe/Rome @@ -202,9 +200,9 @@ complex_uid_item = dedent(u''' def test_multiline_uid_complex(benchmark): assert benchmark(lambda: vobject.Item(complex_uid_item).uid) == ( - u'040000008200E00074C5B7101A82E008000000005' - u'0AAABEEF50DCF001000000062548482FA830A46B9' - u'EA62114AC9F0EF' + '040000008200E00074C5B7101A82E008000000005' + '0AAABEEF50DCF001000000062548482FA830A46B9' + 'EA62114AC9F0EF' ) diff --git a/vdirsyncer/__init__.py b/vdirsyncer/__init__.py index 45eaaa3..84dbb69 100644 --- a/vdirsyncer/__init__.py +++ b/vdirsyncer/__init__.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- ''' Vdirsyncer synchronizes calendars and contacts. ''' -from __future__ import print_function PROJECT_HOME = 'https://github.com/pimutils/vdirsyncer' BUGTRACKER_HOME = PROJECT_HOME + '/issues' diff --git a/vdirsyncer/cli/__init__.py b/vdirsyncer/cli/__init__.py index b87ccbd..116759a 100644 --- a/vdirsyncer/cli/__init__.py +++ b/vdirsyncer/cli/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import functools import logging import sys @@ -15,7 +13,7 @@ cli_logger = logging.getLogger(__name__) click_log.basic_config('vdirsyncer') -class AppContext(object): +class AppContext: def __init__(self): self.config = None self.fetched_params = {} diff --git a/vdirsyncer/cli/config.py b/vdirsyncer/cli/config.py index 87296d9..643e506 100644 --- a/vdirsyncer/cli/config.py +++ b/vdirsyncer/cli/config.py @@ -33,17 +33,17 @@ def _validate_general_section(general_config): problems = [] if invalid: - problems.append(u'general section doesn\'t take the parameters: {}' - .format(u', '.join(invalid))) + problems.append('general section doesn\'t take the parameters: {}' + .format(', '.join(invalid))) if missing: - problems.append(u'general section is missing the parameters: {}' - .format(u', '.join(missing))) + problems.append('general section is missing the parameters: {}' + .format(', '.join(missing))) if problems: raise exceptions.UserError( - u'Invalid general section. Copy the example ' - u'config from the repository and edit it: {}' + 'Invalid general section. Copy the example ' + 'config from the repository and edit it: {}' .format(PROJECT_HOME), problems=problems) @@ -64,7 +64,7 @@ def _validate_collections_param(collections): e = ValueError( 'Expected list of format ' '["config_name", "storage_a_name", "storage_b_name"]' - .format(len(collection))) + ) if len(collection) != 3: raise e @@ -151,7 +151,7 @@ def _parse_options(items, section=None): .format(section, key, e)) -class Config(object): +class Config: def __init__(self, general, pairs, storages): self.general = general self.storages = storages @@ -209,7 +209,7 @@ class Config(object): raise exceptions.PairNotFound(e, pair_name=pair_name) -class PairConfig(object): +class PairConfig: def __init__(self, full_config, name, options): self._config = full_config self.name = name @@ -299,7 +299,7 @@ class PairConfig(object): return partial_sync -class CollectionConfig(object): +class CollectionConfig: def __init__(self, pair, name, config_a, config_b): self.pair = pair self._config = pair._config diff --git a/vdirsyncer/cli/discover.py b/vdirsyncer/cli/discover.py index 06aecfb..30addc3 100644 --- a/vdirsyncer/cli/discover.py +++ b/vdirsyncer/cli/discover.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import hashlib import json import logging @@ -224,7 +222,7 @@ def _print_collections(instance_name, get_discovered): storage = storage_instance_from_config(args, create=False) displayname = storage.get_meta('displayname') except Exception: - displayname = u'' + displayname = '' logger.info(' - {}{}'.format( json.dumps(collection), diff --git a/vdirsyncer/cli/fetchparams.py b/vdirsyncer/cli/fetchparams.py index 29e61ae..346a14f 100644 --- a/vdirsyncer/cli/fetchparams.py +++ b/vdirsyncer/cli/fetchparams.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import logging import click diff --git a/vdirsyncer/cli/tasks.py b/vdirsyncer/cli/tasks.py index 8735b59..da7adc5 100644 --- a/vdirsyncer/cli/tasks.py +++ b/vdirsyncer/cli/tasks.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import functools import json diff --git a/vdirsyncer/cli/utils.py b/vdirsyncer/cli/utils.py index 27589d4..a76f50e 100644 --- a/vdirsyncer/cli/utils.py +++ b/vdirsyncer/cli/utils.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import contextlib import errno import importlib @@ -27,7 +25,7 @@ STATUS_PERMISSIONS = 0o600 STATUS_DIR_PERMISSIONS = 0o700 -class _StorageIndex(object): +class _StorageIndex: def __init__(self): self._storages = dict( caldav='vdirsyncer.storage.dav.CalDAVStorage', @@ -288,24 +286,24 @@ def handle_storage_init_error(cls, config): if missing: problems.append( - u'{} storage requires the parameters: {}' - .format(cls.storage_name, u', '.join(missing))) + '{} storage requires the parameters: {}' + .format(cls.storage_name, ', '.join(missing))) if invalid: problems.append( - u'{} storage doesn\'t take the parameters: {}' - .format(cls.storage_name, u', '.join(invalid))) + '{} storage doesn\'t take the parameters: {}' + .format(cls.storage_name, ', '.join(invalid))) if not problems: raise e raise exceptions.UserError( - u'Failed to initialize {}'.format(config['instance_name']), + 'Failed to initialize {}'.format(config['instance_name']), problems=problems ) -class WorkerQueue(object): +class WorkerQueue: ''' A simple worker-queue setup. diff --git a/vdirsyncer/exceptions.py b/vdirsyncer/exceptions.py index 722e7de..5644b54 100644 --- a/vdirsyncer/exceptions.py +++ b/vdirsyncer/exceptions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ''' Contains exception classes used by vdirsyncer. Not all exceptions are here, only the most commonly used ones. @@ -14,7 +13,7 @@ class Error(Exception): raise TypeError('Invalid argument: {}'.format(key)) setattr(self, key, value) - super(Error, self).__init__(*args) + super().__init__(*args) class UserError(Error, ValueError): @@ -26,7 +25,7 @@ class UserError(Error, ValueError): def __str__(self): msg = Error.__str__(self) for problem in self.problems or (): - msg += u'\n - {}'.format(problem) + msg += '\n - {}'.format(problem) return msg diff --git a/vdirsyncer/http.py b/vdirsyncer/http.py index cb13ae3..5cb6204 100644 --- a/vdirsyncer/http.py +++ b/vdirsyncer/http.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import logging import requests @@ -134,7 +133,7 @@ def request(method, url, session=None, latin1_fallback=True, func = session.request - logger.debug(u'{} {}'.format(method, url)) + logger.debug('{} {}'.format(method, url)) logger.debug(kwargs.get('headers', {})) logger.debug(kwargs.get('data', None)) logger.debug('Sending request...') diff --git a/vdirsyncer/metasync.py b/vdirsyncer/metasync.py index ca85c70..875f255 100644 --- a/vdirsyncer/metasync.py +++ b/vdirsyncer/metasync.py @@ -16,12 +16,12 @@ class MetaSyncConflict(MetaSyncError): def metasync(storage_a, storage_b, status, keys, conflict_resolution=None): def _a_to_b(): - logger.info(u'Copying {} to {}'.format(key, storage_b)) + logger.info('Copying {} to {}'.format(key, storage_b)) storage_b.set_meta(key, a) status[key] = a def _b_to_a(): - logger.info(u'Copying {} to {}'.format(key, storage_a)) + logger.info('Copying {} to {}'.format(key, 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(u'Key: {}'.format(key)) - logger.debug(u'A: {}'.format(a)) - logger.debug(u'B: {}'.format(b)) - logger.debug(u'S: {}'.format(s)) + logger.debug('Key: {}'.format(key)) + logger.debug('A: {}'.format(a)) + logger.debug('B: {}'.format(b)) + logger.debug('S: {}'.format(s)) if a != s and b != s: _resolve_conflict() diff --git a/vdirsyncer/repair.py b/vdirsyncer/repair.py index 0a775ee..6c8b257 100644 --- a/vdirsyncer/repair.py +++ b/vdirsyncer/repair.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import logging from os.path import basename @@ -17,7 +15,7 @@ def repair_storage(storage, repair_unsafe_uid): all_hrefs = list(storage.list()) for i, (href, _) in enumerate(all_hrefs): item, etag = storage.get(href) - logger.info(u'[{}/{}] Processing {}' + logger.info('[{}/{}] Processing {}' .format(i, len(all_hrefs), href)) try: diff --git a/vdirsyncer/storage/__init__.py b/vdirsyncer/storage/__init__.py index 5dd1829..5547ab9 100644 --- a/vdirsyncer/storage/__init__.py +++ b/vdirsyncer/storage/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ''' There are storage classes which control the access to one vdir-collection and offer basic CRUD-ish methods for modifying those collections. The exact diff --git a/vdirsyncer/storage/base.py b/vdirsyncer/storage/base.py index 89fe919..7dfcbdd 100644 --- a/vdirsyncer/storage/base.py +++ b/vdirsyncer/storage/base.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import contextlib import functools @@ -20,7 +18,7 @@ class StorageMeta(type): def __init__(cls, name, bases, d): for method in ('update', 'upload', 'delete'): setattr(cls, method, mutating_storage_method(getattr(cls, method))) - return super(StorageMeta, cls).__init__(name, bases, d) + return super().__init__(name, bases, d) class Storage(metaclass=StorageMeta): @@ -116,7 +114,7 @@ class Storage(metaclass=StorageMeta): return '<{}(**{})>'.format( self.__class__.__name__, - dict((x, getattr(self, x)) for x in self._repr_attributes) + {x: getattr(self, x) for x in self._repr_attributes} ) def list(self): diff --git a/vdirsyncer/storage/dav.py b/vdirsyncer/storage/dav.py index 47ccf8b..a3becb7 100644 --- a/vdirsyncer/storage/dav.py +++ b/vdirsyncer/storage/dav.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import datetime import logging import urllib.parse as urlparse @@ -140,7 +138,7 @@ def _fuzzy_matches_mimetype(strict, weak): return False -class Discover(object): +class Discover: _namespace = None _resourcetype = None _homeset_xml = None @@ -349,7 +347,7 @@ class CardDiscover(Discover): _well_known_uri = '/.well-known/carddav' -class DAVSession(object): +class DAVSession: ''' A helper class to connect to DAV servers. ''' @@ -423,7 +421,7 @@ class DAVStorage(Storage): self.session, kwargs = \ self.session_class.init_and_remaining_args(**kwargs) - super(DAVStorage, self).__init__(**kwargs) + super().__init__(**kwargs) import inspect __init__.__signature__ = inspect.signature(session_class.__init__) @@ -483,7 +481,7 @@ class DAVStorage(Storage): .format(href)) continue - raw = raw.text or u'' + raw = raw.text or '' if isinstance(raw, bytes): raw = raw.decode(response.encoding) @@ -617,7 +615,7 @@ class DAVStorage(Storage): headers = self.session.get_default_headers() headers['Depth'] = '1' - data = ''' + data = b''' @@ -625,7 +623,7 @@ class DAVStorage(Storage): - '''.encode('utf-8') + ''' # We use a PROPFIND request instead of addressbook-query due to issues # with Zimbra. See https://github.com/pimutils/vdirsyncer/issues/83 @@ -643,7 +641,7 @@ class DAVStorage(Storage): except KeyError: raise exceptions.UnsupportedMetadataError() - xpath = '{%s}%s' % (namespace, tagname) + xpath = '{{{}}}{}'.format(namespace, tagname) data = ''' @@ -668,7 +666,7 @@ class DAVStorage(Storage): text = normalize_meta_value(getattr(prop, 'text', None)) if text: return text - return u'' + return '' def set_meta(self, key, value): try: @@ -676,7 +674,7 @@ class DAVStorage(Storage): except KeyError: raise exceptions.UnsupportedMetadataError() - lxml_selector = '{%s}%s' % (namespace, tagname) + lxml_selector = '{{{}}}{}'.format(namespace, tagname) element = etree.Element(lxml_selector) element.text = normalize_meta_value(value) @@ -730,7 +728,7 @@ class CalDAVStorage(DAVStorage): def __init__(self, start_date=None, end_date=None, item_types=(), **kwargs): - super(CalDAVStorage, self).__init__(**kwargs) + super().__init__(**kwargs) if not isinstance(item_types, (list, tuple)): raise exceptions.UserError('item_types must be a list.') @@ -774,9 +772,8 @@ class CalDAVStorage(DAVStorage): timefilter=timefilter) else: if start is not None and end is not None: - for x in CalDAVStorage._get_list_filters(('VTODO', 'VEVENT'), - start, end): - yield x + yield from CalDAVStorage._get_list_filters(('VTODO', 'VEVENT'), + start, end) def list(self): caldavfilters = list(self._get_list_filters( @@ -792,8 +789,7 @@ class CalDAVStorage(DAVStorage): # instead? # # See https://github.com/dmfs/tasks/issues/118 for backstory. - for x in DAVStorage.list(self): - yield x + yield from DAVStorage.list(self) data = ''' (etag, item) self.metadata = {} self.fileext = fileext - super(MemoryStorage, self).__init__(**kwargs) + super().__init__(**kwargs) def _get_href(self, item): return item.ident + self.fileext diff --git a/vdirsyncer/storage/singlefile.py b/vdirsyncer/storage/singlefile.py index 1e13f20..5a56aef 100644 --- a/vdirsyncer/storage/singlefile.py +++ b/vdirsyncer/storage/singlefile.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import collections import contextlib import functools @@ -41,7 +39,7 @@ class SingleFileStorage(Storage): _last_etag = None def __init__(self, path, encoding='utf-8', **kwargs): - super(SingleFileStorage, self).__init__(**kwargs) + super().__init__(**kwargs) path = os.path.abspath(expand_path(path)) checkfile(path, create=False) @@ -70,9 +68,10 @@ class SingleFileStorage(Storage): args['path'] = subpath collection_end = ( - placeholder_pos + - 2 + # length of '%s' - len(subpath) - len(path) + placeholder_pos + + 2 # length of '%s' + + len(subpath) + - len(path) ) collection = subpath[placeholder_pos:collection_end] args['collection'] = collection @@ -162,10 +161,11 @@ class SingleFileStorage(Storage): def _write(self): if self._last_etag is not None and \ self._last_etag != get_etag_from_file(self.path): - raise exceptions.PreconditionFailed( - 'Some other program modified the file {r!}. Re-run the ' + raise exceptions.PreconditionFailed(( + 'Some other program modified the file {!r}. Re-run the ' 'synchronization and make sure absolutely no other program is ' - 'writing into the same file.'.format(self.path)) + 'writing into the same file.' + ).format(self.path)) text = join_collection( item.raw for item, etag in self._items.values() ) diff --git a/vdirsyncer/sync/__init__.py b/vdirsyncer/sync/__init__.py index e899868..bdb945d 100644 --- a/vdirsyncer/sync/__init__.py +++ b/vdirsyncer/sync/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- ''' The `sync` function in `vdirsyncer.sync` can be called on two instances of `Storage` to synchronize them. Apart from the defined errors, this is the only @@ -24,7 +23,7 @@ from .exceptions import BothReadOnly, IdentAlreadyExists, PartialSync, \ sync_logger = logging.getLogger(__name__) -class _StorageInfo(object): +class _StorageInfo: '''A wrapper class that holds prefetched items, the status and other things.''' def __init__(self, storage, status): @@ -74,9 +73,9 @@ class _StorageInfo(object): new_meta = self.status.get_new(ident) return ( - new_meta.etag != old_meta.etag and # etag changed + new_meta.etag != old_meta.etag # etag changed # item actually changed - (old_meta.hash is None or new_meta.hash != old_meta.hash) + and (old_meta.hash is None or new_meta.hash != old_meta.hash) ) def set_item_cache(self, ident, item): @@ -199,7 +198,7 @@ class Upload(Action): if self.dest.storage.read_only: href = etag = None else: - sync_logger.info(u'Copying (uploading) item {} to {}' + sync_logger.info('Copying (uploading) item {} to {}' .format(self.ident, self.dest.storage)) href, etag = self.dest.storage.upload(self.item) assert href is not None @@ -221,7 +220,7 @@ class Update(Action): if self.dest.storage.read_only: meta = ItemMetadata(hash=self.item.hash) else: - sync_logger.info(u'Copying (updating) item {} to {}' + sync_logger.info('Copying (updating) item {} to {}' .format(self.ident, self.dest.storage)) meta = self.dest.status.get_new(self.ident) meta.etag = \ @@ -238,7 +237,7 @@ class Delete(Action): def _run_impl(self, a, b): meta = self.dest.status.get_new(self.ident) if not self.dest.storage.read_only: - sync_logger.info(u'Deleting item {} from {}' + sync_logger.info('Deleting item {} from {}' .format(self.ident, self.dest.storage)) self.dest.storage.delete(meta.href, meta.etag) @@ -251,14 +250,14 @@ class ResolveConflict(Action): def run(self, a, b, conflict_resolution, partial_sync): with self.auto_rollback(a, b): - sync_logger.info(u'Doing conflict resolution for item {}...' + sync_logger.info('Doing conflict resolution for item {}...' .format(self.ident)) meta_a = a.status.get_new(self.ident) meta_b = b.status.get_new(self.ident) if meta_a.hash == meta_b.hash: - sync_logger.info(u'...same content on both sides.') + sync_logger.info('...same content on both sides.') elif conflict_resolution is None: raise SyncConflict(ident=self.ident, href_a=meta_a.href, href_b=meta_b.href) diff --git a/vdirsyncer/sync/status.py b/vdirsyncer/sync/status.py index 4278c45..2544680 100644 --- a/vdirsyncer/sync/status.py +++ b/vdirsyncer/sync/status.py @@ -313,7 +313,7 @@ class SqliteStatus(_StatusBase): return self._get_by_href_impl(*a, **kw) -class SubStatus(object): +class SubStatus: def __init__(self, parent, side): self.parent = parent assert side in 'ab' diff --git a/vdirsyncer/utils.py b/vdirsyncer/utils.py index f3b5d43..5559ec3 100644 --- a/vdirsyncer/utils.py +++ b/vdirsyncer/utils.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import functools import os import sys @@ -154,7 +152,7 @@ def checkfile(path, create=False): .format(path)) -class cached_property(object): +class cached_property: '''A read-only @property that is only evaluated once. Only usable on class instances' methods. ''' @@ -212,8 +210,7 @@ def open_graphical_browser(url, new=0, autoraise=True): emulator. ''' import webbrowser - cli_names = set(['www-browser', 'links', 'links2', 'elinks', 'lynx', - 'w3m']) + cli_names = {'www-browser', 'links', 'links2', 'elinks', 'lynx', 'w3m'} if webbrowser._tryorder is None: # Python 3.7 webbrowser.register_standard_browsers() diff --git a/vdirsyncer/vobject.py b/vdirsyncer/vobject.py index d97a057..521e97c 100644 --- a/vdirsyncer/vobject.py +++ b/vdirsyncer/vobject.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import hashlib from itertools import chain, tee @@ -34,7 +32,7 @@ IGNORE_PROPS = ( ) -class Item(object): +class Item: '''Immutable wrapper class for VCALENDAR (VEVENT, VTODO) and VCARD''' @@ -117,7 +115,7 @@ def normalize_item(item, ignore_props=IGNORE_PROPS): del x[prop] x.props.sort() - return u'\r\n'.join(filter(bool, (line.strip() for line in x.props))) + return '\r\n'.join(filter(bool, (line.strip() for line in x.props))) def _strip_timezones(item): @@ -146,16 +144,16 @@ def split_collection(text): for item in chain(items.values(), ungrouped_items): item.subcomponents.extend(inline) - yield u'\r\n'.join(item.dump_lines()) + yield '\r\n'.join(item.dump_lines()) def _split_collection_impl(item, main, inline, items, ungrouped_items): - if item.name == u'VTIMEZONE': + if item.name == 'VTIMEZONE': inline.append(item) - elif item.name == u'VCARD': + elif item.name == 'VCARD': ungrouped_items.append(item) - elif item.name in (u'VTODO', u'VEVENT', u'VJOURNAL'): - uid = item.get(u'UID', u'') + elif item.name in ('VTODO', 'VEVENT', 'VJOURNAL'): + uid = item.get('UID', '') wrapper = _Component(main.name, main.props[:], []) if uid.strip(): @@ -164,7 +162,7 @@ def _split_collection_impl(item, main, inline, items, ungrouped_items): ungrouped_items.append(wrapper) wrapper.subcomponents.append(item) - elif item.name in (u'VCALENDAR', u'VADDRESSBOOK'): + elif item.name in ('VCALENDAR', 'VADDRESSBOOK'): if item.name == 'VCALENDAR': del item['METHOD'] for subitem in item.subcomponents: @@ -176,10 +174,10 @@ def _split_collection_impl(item, main, inline, items, ungrouped_items): _default_join_wrappers = { - u'VCALENDAR': u'VCALENDAR', - u'VEVENT': u'VCALENDAR', - u'VTODO': u'VCALENDAR', - u'VCARD': u'VADDRESSBOOK' + 'VCALENDAR': 'VCALENDAR', + 'VEVENT': 'VCALENDAR', + 'VTODO': 'VCALENDAR', + 'VCARD': 'VADDRESSBOOK' } @@ -207,16 +205,16 @@ def join_collection(items, wrappers=_default_join_wrappers): if wrapper_type is not None: lines = chain(*( - [u'BEGIN:{}'.format(wrapper_type)], + ['BEGIN:{}'.format(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, - [u'END:{}'.format(wrapper_type)] + ['END:{}'.format(wrapper_type)] )) - return u''.join(line + u'\r\n' for line in lines) + return ''.join(line + '\r\n' for line in lines) def _get_item_type(components, wrappers): @@ -237,7 +235,7 @@ def _get_item_type(components, wrappers): raise ValueError('Not sure how to join components.') -class _Component(object): +class _Component: ''' Raw outline of the components. @@ -277,10 +275,10 @@ class _Component(object): rv = [] try: for _i, line in enumerate(lines): - if line.startswith(u'BEGIN:'): - c_name = line[len(u'BEGIN:'):].strip().upper() + if line.startswith('BEGIN:'): + c_name = line[len('BEGIN:'):].strip().upper() stack.append(cls(c_name, [], [])) - elif line.startswith(u'END:'): + elif line.startswith('END:'): component = stack.pop() if stack: stack[-1].subcomponents.append(component) @@ -301,16 +299,14 @@ class _Component(object): return rv[0] def dump_lines(self): - yield u'BEGIN:{}'.format(self.name) - for line in self.props: - yield line + yield 'BEGIN:{}'.format(self.name) + yield from self.props for c in self.subcomponents: - for line in c.dump_lines(): - yield line - yield u'END:{}'.format(self.name) + yield from c.dump_lines() + yield 'END:{}'.format(self.name) def __delitem__(self, key): - prefix = (u'{}:'.format(key), u'{};'.format(key)) + prefix = ('{}:'.format(key), '{};'.format(key)) new_lines = [] lineiter = iter(self.props) while True: @@ -323,7 +319,7 @@ class _Component(object): break for line in lineiter: - if not line.startswith((u' ', u'\t')): + if not line.startswith((' ', '\t')): new_lines.append(line) break @@ -331,9 +327,9 @@ class _Component(object): def __setitem__(self, key, val): assert isinstance(val, str) - assert u'\n' not in val + assert '\n' not in val del self[key] - line = u'{}:{}'.format(key, val) + line = '{}:{}'.format(key, val) self.props.append(line) def __contains__(self, obj): @@ -360,7 +356,7 @@ class _Component(object): raise KeyError() for line in iterlines: - if line.startswith((u' ', u'\t')): + if line.startswith((' ', '\t')): rv += line[1:] else: break @@ -375,8 +371,8 @@ class _Component(object): def __eq__(self, other): return ( - isinstance(other, type(self)) and - self.name == other.name and - self.props == other.props and - self.subcomponents == other.subcomponents + isinstance(other, type(self)) + and self.name == other.name + and self.props == other.props + and self.subcomponents == other.subcomponents )