Remove python2 leftover code

This commit is contained in:
Hugo Osvaldo Barrera 2020-06-08 18:34:57 +02:00
parent eece9a6bde
commit 308289febf
58 changed files with 253 additions and 344 deletions

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import datetime import datetime
import os import os
@ -12,8 +10,8 @@ templates_path = ['_templates']
source_suffix = '.rst' source_suffix = '.rst'
master_doc = 'index' master_doc = 'index'
project = u'vdirsyncer' project = 'vdirsyncer'
copyright = (u'2014-{}, Markus Unterwaditzer & contributors' copyright = ('2014-{}, Markus Unterwaditzer & contributors'
.format(datetime.date.today().strftime('%Y'))) .format(datetime.date.today().strftime('%Y')))
release = setuptools_scm.get_version(root='..', relative_to=__file__) release = setuptools_scm.get_version(root='..', relative_to=__file__)
@ -44,18 +42,18 @@ htmlhelp_basename = 'vdirsyncerdoc'
latex_elements = {} latex_elements = {}
latex_documents = [ latex_documents = [
('index', 'vdirsyncer.tex', u'vdirsyncer Documentation', ('index', 'vdirsyncer.tex', 'vdirsyncer Documentation',
u'Markus Unterwaditzer', 'manual'), 'Markus Unterwaditzer', 'manual'),
] ]
man_pages = [ man_pages = [
('index', 'vdirsyncer', u'vdirsyncer Documentation', ('index', 'vdirsyncer', 'vdirsyncer Documentation',
[u'Markus Unterwaditzer'], 1) ['Markus Unterwaditzer'], 1)
] ]
texinfo_documents = [ texinfo_documents = [
('index', 'vdirsyncer', u'vdirsyncer Documentation', ('index', 'vdirsyncer', 'vdirsyncer Documentation',
u'Markus Unterwaditzer', 'vdirsyncer', 'Markus Unterwaditzer', 'vdirsyncer',
'Synchronize calendars and contacts.', 'Miscellaneous'), 'Synchronize calendars and contacts.', 'Miscellaneous'),
] ]

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
''' '''
Vdirsyncer synchronizes calendars and contacts. Vdirsyncer synchronizes calendars and contacts.

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
''' '''
Test suite for vdirsyncer. Test suite for vdirsyncer.
''' '''
@ -21,7 +20,7 @@ def assert_item_equals(a, b):
assert normalize_item(a) == normalize_item(b) assert normalize_item(a) == normalize_item(b)
VCARD_TEMPLATE = u'''BEGIN:VCARD VCARD_TEMPLATE = '''BEGIN:VCARD
VERSION:3.0 VERSION:3.0
FN:Cyrus Daboo FN:Cyrus Daboo
N:Daboo;Cyrus;;; N:Daboo;Cyrus;;;
@ -37,7 +36,7 @@ X-SOMETHING:{r}
UID:{uid} UID:{uid}
END:VCARD''' END:VCARD'''
TASK_TEMPLATE = u'''BEGIN:VCALENDAR TASK_TEMPLATE = '''BEGIN:VCALENDAR
VERSION:2.0 VERSION:2.0
PRODID:-//dmfs.org//mimedir.icalendar//EN PRODID:-//dmfs.org//mimedir.icalendar//EN
BEGIN:VTODO BEGIN:VTODO
@ -52,7 +51,7 @@ END:VTODO
END:VCALENDAR''' END:VCALENDAR'''
BARE_EVENT_TEMPLATE = u'''BEGIN:VEVENT BARE_EVENT_TEMPLATE = '''BEGIN:VEVENT
DTSTART:19970714T170000Z DTSTART:19970714T170000Z
DTEND:19970715T035959Z DTEND:19970715T035959Z
SUMMARY:Bastille Day Party SUMMARY:Bastille Day Party
@ -61,10 +60,10 @@ UID:{uid}
END:VEVENT''' END:VEVENT'''
EVENT_TEMPLATE = u'''BEGIN:VCALENDAR EVENT_TEMPLATE = '''BEGIN:VCALENDAR
VERSION:2.0 VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN PRODID:-//hacksw/handcal//NONSGML v1.0//EN
''' + BARE_EVENT_TEMPLATE + u''' ''' + BARE_EVENT_TEMPLATE + '''
END:VCALENDAR''' END:VCALENDAR'''
EVENT_WITH_TIMEZONE_TEMPLATE = '''BEGIN:VCALENDAR EVENT_WITH_TIMEZONE_TEMPLATE = '''BEGIN:VCALENDAR
@ -90,7 +89,7 @@ END:VTIMEZONE
END:VCALENDAR''' END:VCALENDAR'''
SIMPLE_TEMPLATE = u'''BEGIN:FOO SIMPLE_TEMPLATE = '''BEGIN:FOO
UID:{uid} UID:{uid}
X-SOMETHING:{r} X-SOMETHING:{r}
HAHA:YES HAHA:YES

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
''' '''
General-purpose fixtures for vdirsyncer's testsuite. General-purpose fixtures for vdirsyncer's testsuite.
''' '''

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import random import random
import uuid import uuid
@ -31,7 +29,7 @@ def format_item(item_template, uid=None):
return Item(item_template.format(r=r, uid=uid or r)) return Item(item_template.format(r=r, uid=uid or r))
class StorageTests(object): class StorageTests:
storage_class = None storage_class = None
supports_collections = True supports_collections = True
supports_metadata = True supports_metadata = True
@ -173,10 +171,10 @@ class StorageTests(object):
_, etag = s.get(href) _, etag = s.get(href)
info[href] = etag info[href] = etag
assert dict( assert {
(href, etag) for href, item, etag href: etag for href, item, etag
in s.get_multi(href for href, etag in info.items()) in s.get_multi(href for href, etag in info.items())
) == info } == info
def test_repr(self, s, get_storage_args): def test_repr(self, s, get_storage_args):
assert self.storage_class.__name__ in repr(s) assert self.storage_class.__name__ in repr(s)
@ -191,10 +189,10 @@ class StorageTests(object):
s.upload(get_item()) s.upload(get_item())
collections.add(s.collection) collections.add(s.collection)
actual = set( actual = {
c['collection'] for c in c['collection'] for c in
self.storage_class.discover(**get_storage_args(collection=None)) self.storage_class.discover(**get_storage_args(collection=None))
) }
assert actual >= collections assert actual >= collections
@ -212,7 +210,7 @@ class StorageTests(object):
) )
href = s.upload(get_item())[0] 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, def test_discover_collection_arg(self, requires_collections,
get_storage_args): get_storage_args):
@ -255,7 +253,7 @@ class StorageTests(object):
monkeypatch.setattr('vdirsyncer.utils.generate_href', lambda x: x) 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' collection = 'test @ foo ät bar'
s = self.storage_class(**get_storage_args(collection=collection)) s = self.storage_class(**get_storage_args(collection=collection))
@ -286,12 +284,12 @@ class StorageTests(object):
try: try:
s.set_meta('color', None) s.set_meta('color', None)
assert not s.get_meta('color') assert not s.get_meta('color')
s.set_meta('color', u'#ff0000') s.set_meta('color', '#ff0000')
assert s.get_meta('color') == u'#ff0000' assert s.get_meta('color') == '#ff0000'
except exceptions.UnsupportedMetadataError: except exceptions.UnsupportedMetadataError:
pass pass
for x in (u'hello world', u'hello wörld'): for x in ('hello world', 'hello wörld'):
s.set_meta('displayname', x) s.set_meta('displayname', x)
rv = s.get_meta('displayname') rv = s.get_meta('displayname')
assert rv == x assert rv == x
@ -315,7 +313,7 @@ class StorageTests(object):
pytest.skip('This storage instance doesn\'t support iCalendar.') pytest.skip('This storage instance doesn\'t support iCalendar.')
uid = str(uuid.uuid4()) uid = str(uuid.uuid4())
item = Item(textwrap.dedent(u''' item = Item(textwrap.dedent('''
BEGIN:VCALENDAR BEGIN:VCALENDAR
VERSION:2.0 VERSION:2.0
BEGIN:VEVENT BEGIN:VEVENT

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import pytest import pytest
import uuid import uuid

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import uuid import uuid
import os import os
@ -27,7 +25,7 @@ class DAVStorageTests(ServerMixin, StorageTests):
@pytest.mark.skipif(dav_server == 'radicale', @pytest.mark.skipif(dav_server == 'radicale',
reason='Radicale is very tolerant.') reason='Radicale is very tolerant.')
def test_dav_broken_item(self, s): def test_dav_broken_item(self, s):
item = Item(u'HAHA:YES') item = Item('HAHA:YES')
with pytest.raises((exceptions.Error, requests.exceptions.HTTPError)): with pytest.raises((exceptions.Error, requests.exceptions.HTTPError)):
s.upload(item) s.upload(item)
assert not list(s.list()) assert not list(s.list())
@ -50,7 +48,7 @@ class DAVStorageTests(ServerMixin, StorageTests):
monkeypatch.setattr(s, '_get_href', monkeypatch.setattr(s, '_get_href',
lambda item: item.ident + s.fileext) 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) href, etag = s.upload(item)
item2, etag2 = s.get(href) item2, etag2 = s.get(href)
assert_item_equals(item, item2) assert_item_equals(item, item2)

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import datetime import datetime
from textwrap import dedent from textwrap import dedent
@ -64,7 +62,7 @@ class TestCalDAVStorage(DAVStorageTests):
s = self.storage_class(start_date=start_date, end_date=end_date, s = self.storage_class(start_date=start_date, end_date=end_date,
**get_storage_args()) **get_storage_args())
too_old_item = format_item(dedent(u''' too_old_item = format_item(dedent('''
BEGIN:VCALENDAR BEGIN:VCALENDAR
VERSION:2.0 VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN PRODID:-//hacksw/handcal//NONSGML v1.0//EN
@ -78,7 +76,7 @@ class TestCalDAVStorage(DAVStorageTests):
END:VCALENDAR END:VCALENDAR
''').strip()) ''').strip())
too_new_item = format_item(dedent(u''' too_new_item = format_item(dedent('''
BEGIN:VCALENDAR BEGIN:VCALENDAR
VERSION:2.0 VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN PRODID:-//hacksw/handcal//NONSGML v1.0//EN
@ -92,7 +90,7 @@ class TestCalDAVStorage(DAVStorageTests):
END:VCALENDAR END:VCALENDAR
''').strip()) ''').strip())
good_item = format_item(dedent(u''' good_item = format_item(dedent('''
BEGIN:VCALENDAR BEGIN:VCALENDAR
VERSION:2.0 VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN PRODID:-//hacksw/handcal//NONSGML v1.0//EN

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import pytest import pytest
from vdirsyncer.storage.dav import CardDAVStorage from vdirsyncer.storage.dav import CardDAVStorage

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import shutil import shutil
import os import os
import sys import sys

View file

@ -1 +0,0 @@
# -*- coding: utf-8 -*-

View file

@ -14,7 +14,7 @@ except KeyError as e:
@pytest.mark.flaky(reruns=5) @pytest.mark.flaky(reruns=5)
class ServerMixin(object): class ServerMixin:
@pytest.fixture @pytest.fixture
def davical_args(self): def davical_args(self):
if self.storage_class.fileext == '.ics': if self.storage_class.fileext == '.ics':

View file

@ -3,7 +3,7 @@ import os
import pytest import pytest
class ServerMixin(object): class ServerMixin:
@pytest.fixture @pytest.fixture
def get_storage_args(self, slow_create_collection): def get_storage_args(self, slow_create_collection):

View file

@ -3,7 +3,7 @@ import os
import pytest import pytest
class ServerMixin(object): class ServerMixin:
@pytest.fixture @pytest.fixture
def get_storage_args(self, item_type, slow_create_collection): def get_storage_args(self, item_type, slow_create_collection):

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import os import os
import subprocess import subprocess
import time import time
@ -28,7 +26,7 @@ def wait():
return False return False
class ServerMixin(object): class ServerMixin:
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def setup_mysteryshack_server(self, xprocess): def setup_mysteryshack_server(self, xprocess):
def preparefunc(cwd): def preparefunc(cwd):

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import logging import logging
import pytest import pytest
@ -14,7 +12,7 @@ import wsgi_intercept.requests_intercept
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class ServerMixin(object): class ServerMixin:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def setup(self, request, tmpdir): def setup(self, request, tmpdir):

View file

@ -1,7 +1,7 @@
import pytest import pytest
class ServerMixin(object): class ServerMixin:
@pytest.fixture @pytest.fixture
def get_storage_args(self): def get_storage_args(self):

View file

@ -6,7 +6,7 @@ import wsgi_intercept
import wsgi_intercept.requests_intercept import wsgi_intercept.requests_intercept
class ServerMixin(object): class ServerMixin:
@pytest.fixture @pytest.fixture
def get_storage_args(self, request, tmpdir, slow_create_collection): def get_storage_args(self, request, tmpdir, slow_create_collection):
tmpdir.mkdir('xandikos') tmpdir.mkdir('xandikos')

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import subprocess import subprocess
import pytest import pytest
@ -32,8 +30,8 @@ class TestFilesystemStorage(StorageTests):
def test_broken_data(self, tmpdir): def test_broken_data(self, tmpdir):
s = self.storage_class(str(tmpdir), '.txt') s = self.storage_class(str(tmpdir), '.txt')
class BrokenItem(object): class BrokenItem:
raw = u'Ц, Ш, Л, ж, Д, З, Ю'.encode('utf-8') raw = 'Ц, Ш, Л, ж, Д, З, Ю'.encode()
uid = 'jeezus' uid = 'jeezus'
ident = uid ident = uid
with pytest.raises(TypeError): with pytest.raises(TypeError):
@ -42,13 +40,13 @@ class TestFilesystemStorage(StorageTests):
def test_ident_with_slash(self, tmpdir): def test_ident_with_slash(self, tmpdir):
s = self.storage_class(str(tmpdir), '.txt') 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() item_file, = tmpdir.listdir()
assert '/' not in item_file.basename and item_file.isfile() assert '/' not in item_file.basename and item_file.isfile()
def test_too_long_uid(self, tmpdir): def test_too_long_uid(self, tmpdir):
s = self.storage_class(str(tmpdir), '.txt') s = self.storage_class(str(tmpdir), '.txt')
item = Item(u'UID:' + u'hue' * 600) item = Item('UID:' + 'hue' * 600)
href, etag = s.upload(item) href, etag = s.upload(item)
assert item.uid not in href assert item.uid not in href
@ -60,7 +58,7 @@ class TestFilesystemStorage(StorageTests):
monkeypatch.setattr(subprocess, 'call', check_call_mock) monkeypatch.setattr(subprocess, 'call', check_call_mock)
s = self.storage_class(str(tmpdir), '.txt', post_hook=None) 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): def test_post_hook_active(self, tmpdir, monkeypatch):
@ -75,7 +73,7 @@ class TestFilesystemStorage(StorageTests):
monkeypatch.setattr(subprocess, 'call', check_call_mock) monkeypatch.setattr(subprocess, 'call', check_call_mock)
s = self.storage_class(str(tmpdir), '.txt', post_hook=exe) 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 assert calls
def test_ignore_git_dirs(self, tmpdir): def test_ignore_git_dirs(self, tmpdir):

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import pytest import pytest
from requests import Response from requests import Response
@ -14,25 +12,25 @@ def test_list(monkeypatch):
collection_url = 'http://127.0.0.1/calendar/collection.ics' collection_url = 'http://127.0.0.1/calendar/collection.ics'
items = [ items = [
(u'BEGIN:VEVENT\n' ('BEGIN:VEVENT\n'
u'SUMMARY:Eine Kurzinfo\n' 'SUMMARY:Eine Kurzinfo\n'
u'DESCRIPTION:Beschreibung des Termines\n' 'DESCRIPTION:Beschreibung des Termines\n'
u'END:VEVENT'), 'END:VEVENT'),
(u'BEGIN:VEVENT\n' ('BEGIN:VEVENT\n'
u'SUMMARY:Eine zweite Küèrzinfo\n' 'SUMMARY:Eine zweite Küèrzinfo\n'
u'DESCRIPTION:Beschreibung des anderen Termines\n' 'DESCRIPTION:Beschreibung des anderen Termines\n'
u'BEGIN:VALARM\n' 'BEGIN:VALARM\n'
u'ACTION:AUDIO\n' 'ACTION:AUDIO\n'
u'TRIGGER:19980403T120000\n' 'TRIGGER:19980403T120000\n'
u'ATTACH;FMTTYPE=audio/basic:http://host.com/pub/ssbanner.aud\n' 'ATTACH;FMTTYPE=audio/basic:http://host.com/pub/ssbanner.aud\n'
u'REPEAT:4\n' 'REPEAT:4\n'
u'DURATION:PT1H\n' 'DURATION:PT1H\n'
u'END:VALARM\n' 'END:VALARM\n'
u'END:VEVENT') 'END:VEVENT')
] ]
responses = [ responses = [
u'\n'.join([u'BEGIN:VCALENDAR'] + items + [u'END:VCALENDAR']) '\n'.join(['BEGIN:VCALENDAR'] + items + ['END:VCALENDAR'])
] * 2 ] * 2
def get(self, method, url, *a, **kw): def get(self, method, url, *a, **kw):
@ -58,8 +56,8 @@ def test_list(monkeypatch):
assert etag2 == etag assert etag2 == etag
found_items[normalize_item(item)] = href found_items[normalize_item(item)] = href
expected = set(normalize_item(u'BEGIN:VCALENDAR\n' + x + '\nEND:VCALENDAR') expected = {normalize_item('BEGIN:VCALENDAR\n' + x + '\nEND:VCALENDAR')
for x in items) for x in items}
assert set(found_items) == expected assert set(found_items) == expected

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import pytest import pytest
from requests import Response from requests import Response
@ -21,7 +19,7 @@ class CombinedStorage(Storage):
if kwargs.get('collection', None) is not None: if kwargs.get('collection', None) is not None:
raise ValueError() raise ValueError()
super(CombinedStorage, self).__init__(**kwargs) super().__init__(**kwargs)
self.url = url self.url = url
self.path = path self.path = path
self._reader = vdirsyncer.storage.http.HttpStorage(url=url) self._reader = vdirsyncer.storage.http.HttpStorage(url=url)

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import pytest import pytest
from vdirsyncer.storage.memory import MemoryStorage from vdirsyncer.storage.memory import MemoryStorage

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import pytest import pytest
from vdirsyncer.storage.singlefile import SingleFileStorage from vdirsyncer.storage.singlefile import SingleFileStorage

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
from textwrap import dedent from textwrap import dedent
from click.testing import CliRunner from click.testing import CliRunner
@ -9,7 +7,7 @@ import pytest
import vdirsyncer.cli as cli import vdirsyncer.cli as cli
class _CustomRunner(object): class _CustomRunner:
def __init__(self, tmpdir): def __init__(self, tmpdir):
self.tmpdir = tmpdir self.tmpdir = tmpdir
self.cfg = tmpdir.join('config') self.cfg = tmpdir.join('config')

View file

@ -23,7 +23,7 @@ def read_config(tmpdir, monkeypatch):
def test_read_config(read_config): def test_read_config(read_config):
errors, c = read_config(u''' errors, c = read_config('''
[general] [general]
status_path = "/tmp/status/" status_path = "/tmp/status/"
@ -59,7 +59,7 @@ def test_read_config(read_config):
def test_missing_collections_param(read_config): def test_missing_collections_param(read_config):
with pytest.raises(exceptions.UserError) as excinfo: with pytest.raises(exceptions.UserError) as excinfo:
read_config(u''' read_config('''
[general] [general]
status_path = "/tmp/status/" status_path = "/tmp/status/"
@ -79,7 +79,7 @@ def test_missing_collections_param(read_config):
def test_invalid_section_type(read_config): def test_invalid_section_type(read_config):
with pytest.raises(exceptions.UserError) as excinfo: with pytest.raises(exceptions.UserError) as excinfo:
read_config(u''' read_config('''
[general] [general]
status_path = "/tmp/status/" status_path = "/tmp/status/"
@ -92,7 +92,7 @@ def test_invalid_section_type(read_config):
def test_missing_general_section(read_config): def test_missing_general_section(read_config):
with pytest.raises(exceptions.UserError) as excinfo: with pytest.raises(exceptions.UserError) as excinfo:
read_config(u''' read_config('''
[pair my_pair] [pair my_pair]
a = "my_a" a = "my_a"
b = "my_b" b = "my_b"
@ -114,7 +114,7 @@ def test_missing_general_section(read_config):
def test_wrong_general_section(read_config): def test_wrong_general_section(read_config):
with pytest.raises(exceptions.UserError) as excinfo: with pytest.raises(exceptions.UserError) as excinfo:
read_config(u''' read_config('''
[general] [general]
wrong = true wrong = true
''') ''')
@ -128,7 +128,7 @@ def test_wrong_general_section(read_config):
def test_invalid_storage_name(read_config): def test_invalid_storage_name(read_config):
with pytest.raises(exceptions.UserError) as excinfo: with pytest.raises(exceptions.UserError) as excinfo:
read_config(u''' read_config('''
[general] [general]
status_path = "{base}/status/" status_path = "{base}/status/"
@ -140,7 +140,7 @@ def test_invalid_storage_name(read_config):
def test_invalid_collections_arg(read_config): def test_invalid_collections_arg(read_config):
with pytest.raises(exceptions.UserError) as excinfo: with pytest.raises(exceptions.UserError) as excinfo:
read_config(u''' read_config('''
[general] [general]
status_path = "/tmp/status/" status_path = "/tmp/status/"
@ -165,7 +165,7 @@ def test_invalid_collections_arg(read_config):
def test_duplicate_sections(read_config): def test_duplicate_sections(read_config):
with pytest.raises(exceptions.UserError) as excinfo: with pytest.raises(exceptions.UserError) as excinfo:
read_config(u''' read_config('''
[general] [general]
status_path = "/tmp/status/" status_path = "/tmp/status/"

View file

@ -1,5 +1,3 @@
# encoding: utf-8
from textwrap import dedent from textwrap import dedent
import pytest import pytest

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import json import json
import sys import sys
from textwrap import dedent from textwrap import dedent
@ -257,17 +255,17 @@ def test_multiple_pairs(tmpdir, runner):
result = runner.invoke(['discover']) result = runner.invoke(['discover'])
assert not result.exception assert not result.exception
assert set(result.output.splitlines()) > set([ assert set(result.output.splitlines()) > {
'Discovering collections for pair bambaz', 'Discovering collections for pair bambaz',
'Discovering collections for pair foobar' 'Discovering collections for pair foobar'
]) }
result = runner.invoke(['sync']) result = runner.invoke(['sync'])
assert not result.exception assert not result.exception
assert set(result.output.splitlines()) == set([ assert set(result.output.splitlines()) == {
'Syncing bambaz', 'Syncing bambaz',
'Syncing foobar', 'Syncing foobar',
]) }
# XXX: https://github.com/pimutils/vdirsyncer/issues/617 # XXX: https://github.com/pimutils/vdirsyncer/issues/617
@ -277,17 +275,17 @@ def test_multiple_pairs(tmpdir, runner):
st.text( st.text(
st.characters( st.characters(
blacklist_characters=set( 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 # Surrogates can't be encoded to utf-8 in Python
blacklist_categories=set(['Cs']) blacklist_categories={'Cs'}
), ),
min_size=1, min_size=1,
max_size=50 max_size=50
), ),
min_size=1 min_size=1
)) ))
@example(collections=[u'persönlich']) @example(collections=['persönlich'])
@example(collections={'a', 'A'}) @example(collections={'a', 'A'})
@example(collections={'\ufffe'}) @example(collections={'\ufffe'})
def test_create_collections(subtest, collections): def test_create_collections(subtest, collections):
@ -322,8 +320,8 @@ def test_create_collections(subtest, collections):
) )
assert not result.exception, result.output assert not result.exception, result.output
assert set(x.basename for x in tmpdir.join('foo').listdir()) == \ assert {x.basename for x in tmpdir.join('foo').listdir()} == \
set(x.basename for x in tmpdir.join('bar').listdir()) {x.basename for x in tmpdir.join('bar').listdir()}
def test_ident_conflict(tmpdir, runner): def test_ident_conflict(tmpdir, runner):

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import sys import sys
import logging import logging
@ -20,7 +18,7 @@ def test_get_storage_init_args():
from vdirsyncer.storage.memory import MemoryStorage from vdirsyncer.storage.memory import MemoryStorage
all, required = utils.get_storage_init_args(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 assert not required

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import pytest import pytest
from vdirsyncer.cli.discover import expand_collections from vdirsyncer.cli.discover import expand_collections

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import hypothesis.strategies as st import hypothesis.strategies as st
from hypothesis import given from hypothesis import given
@ -23,7 +21,7 @@ def mystrategy(monkeypatch):
def value_cache(monkeypatch): def value_cache(monkeypatch):
_cache = {} _cache = {}
class FakeContext(object): class FakeContext:
fetched_params = _cache fetched_params = _cache
def find_object(self, _): def find_object(self, _):

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
from copy import deepcopy from copy import deepcopy
import hypothesis.strategies as st import hypothesis.strategies as st
@ -32,7 +30,7 @@ def empty_storage(x):
def items(s): 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(): def test_irrelevant_status():
@ -49,7 +47,7 @@ def test_missing_status():
a = MemoryStorage() a = MemoryStorage()
b = MemoryStorage() b = MemoryStorage()
status = {} status = {}
item = Item(u'asdf') item = Item('asdf')
a.upload(item) a.upload(item)
b.upload(item) b.upload(item)
sync(a, b, status) sync(a, b, status)
@ -62,8 +60,8 @@ def test_missing_status_and_different_items():
b = MemoryStorage() b = MemoryStorage()
status = {} status = {}
item1 = Item(u'UID:1\nhaha') item1 = Item('UID:1\nhaha')
item2 = Item(u'UID:1\nhoho') item2 = Item('UID:1\nhoho')
a.upload(item1) a.upload(item1)
b.upload(item2) b.upload(item2)
with pytest.raises(SyncConflict): with pytest.raises(SyncConflict):
@ -79,8 +77,8 @@ def test_read_only_and_prefetch():
b.read_only = True b.read_only = True
status = {} status = {}
item1 = Item(u'UID:1\nhaha') item1 = Item('UID:1\nhaha')
item2 = Item(u'UID:2\nhoho') item2 = Item('UID:2\nhoho')
a.upload(item1) a.upload(item1)
a.upload(item2) a.upload(item2)
@ -152,22 +150,22 @@ def test_upload_and_update():
b = MemoryStorage(fileext='.b') b = MemoryStorage(fileext='.b')
status = {} status = {}
item = Item(u'UID:1') # new item 1 in a item = Item('UID:1') # new item 1 in a
a.upload(item) a.upload(item)
sync(a, b, status) sync(a, b, status)
assert items(b) == items(a) == {item.raw} 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]) b.update('1.b', item, b.get('1.b')[1])
sync(a, b, status) sync(a, b, status)
assert items(b) == items(a) == {item.raw} 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) b.upload(item2)
sync(a, b, status) sync(a, b, status)
assert items(b) == items(a) == {item.raw, item2.raw} 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]) a.update('2.a', item2, a.get('2.a')[1])
sync(a, b, status) sync(a, b, status)
assert items(b) == items(a) == {item.raw, item2.raw} assert items(b) == items(a) == {item.raw, item2.raw}
@ -178,9 +176,9 @@ def test_deletion():
b = MemoryStorage(fileext='.b') b = MemoryStorage(fileext='.b')
status = {} status = {}
item = Item(u'UID:1') item = Item('UID:1')
a.upload(item) a.upload(item)
item2 = Item(u'UID:2') item2 = Item('UID:2')
a.upload(item2) a.upload(item2)
sync(a, b, status) sync(a, b, status)
b.delete('1.b', b.get('1.b')[1]) b.delete('1.b', b.get('1.b')[1])
@ -215,7 +213,7 @@ def test_insert_hash():
def test_already_synced(): def test_already_synced():
a = MemoryStorage(fileext='.a') a = MemoryStorage(fileext='.a')
b = MemoryStorage(fileext='.b') b = MemoryStorage(fileext='.b')
item = Item(u'UID:1') item = Item('UID:1')
a.upload(item) a.upload(item)
b.upload(item) b.upload(item)
status = { status = {
@ -243,14 +241,14 @@ def test_already_synced():
def test_conflict_resolution_both_etags_new(winning_storage): def test_conflict_resolution_both_etags_new(winning_storage):
a = MemoryStorage() a = MemoryStorage()
b = MemoryStorage() b = MemoryStorage()
item = Item(u'UID:1') item = Item('UID:1')
href_a, etag_a = a.upload(item) href_a, etag_a = a.upload(item)
href_b, etag_b = b.upload(item) href_b, etag_b = b.upload(item)
status = {} status = {}
sync(a, b, status) sync(a, b, status)
assert status assert status
item_a = Item(u'UID:1\nitem a') item_a = Item('UID:1\nitem a')
item_b = Item(u'UID:1\nitem b') item_b = Item('UID:1\nitem b')
a.update(href_a, item_a, etag_a) a.update(href_a, item_a, etag_a)
b.update(href_b, item_b, etag_b) b.update(href_b, item_b, etag_b)
with pytest.raises(SyncConflict): with pytest.raises(SyncConflict):
@ -264,13 +262,13 @@ def test_conflict_resolution_both_etags_new(winning_storage):
def test_updated_and_deleted(): def test_updated_and_deleted():
a = MemoryStorage() a = MemoryStorage()
b = MemoryStorage() b = MemoryStorage()
href_a, etag_a = a.upload(Item(u'UID:1')) href_a, etag_a = a.upload(Item('UID:1'))
status = {} status = {}
sync(a, b, status, force_delete=True) sync(a, b, status, force_delete=True)
(href_b, etag_b), = b.list() (href_b, etag_b), = b.list()
b.delete(href_b, etag_b) b.delete(href_b, etag_b)
updated = Item(u'UID:1\nupdated') updated = Item('UID:1\nupdated')
a.update(href_a, updated, etag_a) a.update(href_a, updated, etag_a)
sync(a, b, status, force_delete=True) sync(a, b, status, force_delete=True)
@ -280,8 +278,8 @@ def test_updated_and_deleted():
def test_conflict_resolution_invalid_mode(): def test_conflict_resolution_invalid_mode():
a = MemoryStorage() a = MemoryStorage()
b = MemoryStorage() b = MemoryStorage()
item_a = Item(u'UID:1\nitem a') item_a = Item('UID:1\nitem a')
item_b = Item(u'UID:1\nitem b') item_b = Item('UID:1\nitem b')
a.upload(item_a) a.upload(item_a)
b.upload(item_b) b.upload(item_b)
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -291,7 +289,7 @@ def test_conflict_resolution_invalid_mode():
def test_conflict_resolution_new_etags_without_changes(): def test_conflict_resolution_new_etags_without_changes():
a = MemoryStorage() a = MemoryStorage()
b = MemoryStorage() b = MemoryStorage()
item = Item(u'UID:1') item = Item('UID:1')
href_a, etag_a = a.upload(item) href_a, etag_a = a.upload(item)
href_b, etag_b = b.upload(item) href_b, etag_b = b.upload(item)
status = {'1': (href_a, 'BOGUS_a', href_b, 'BOGUS_b')} status = {'1': (href_a, 'BOGUS_a', href_b, 'BOGUS_b')}
@ -326,7 +324,7 @@ def test_uses_get_multi(monkeypatch):
a = MemoryStorage() a = MemoryStorage()
b = MemoryStorage() b = MemoryStorage()
item = Item(u'UID:1') item = Item('UID:1')
expected_href, etag = a.upload(item) expected_href, etag = a.upload(item)
sync(a, b, {}) sync(a, b, {})
@ -336,8 +334,8 @@ def test_uses_get_multi(monkeypatch):
def test_empty_storage_dataloss(): def test_empty_storage_dataloss():
a = MemoryStorage() a = MemoryStorage()
b = MemoryStorage() b = MemoryStorage()
a.upload(Item(u'UID:1')) a.upload(Item('UID:1'))
a.upload(Item(u'UID:2')) a.upload(Item('UID:2'))
status = {} status = {}
sync(a, b, status) sync(a, b, status)
with pytest.raises(StorageEmpty): with pytest.raises(StorageEmpty):
@ -350,22 +348,22 @@ def test_empty_storage_dataloss():
def test_no_uids(): def test_no_uids():
a = MemoryStorage() a = MemoryStorage()
b = MemoryStorage() b = MemoryStorage()
a.upload(Item(u'ASDF')) a.upload(Item('ASDF'))
b.upload(Item(u'FOOBAR')) b.upload(Item('FOOBAR'))
status = {} status = {}
sync(a, b, 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(): def test_changed_uids():
a = MemoryStorage() a = MemoryStorage()
b = MemoryStorage() b = MemoryStorage()
href_a, etag_a = a.upload(Item(u'UID:A-ONE')) href_a, etag_a = a.upload(Item('UID:A-ONE'))
href_b, etag_b = b.upload(Item(u'UID:B-ONE')) href_b, etag_b = b.upload(Item('UID:B-ONE'))
status = {} status = {}
sync(a, b, 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) sync(a, b, status)
@ -383,8 +381,8 @@ def test_partial_sync_revert():
a = MemoryStorage(instance_name='a') a = MemoryStorage(instance_name='a')
b = MemoryStorage(instance_name='b') b = MemoryStorage(instance_name='b')
status = {} status = {}
a.upload(Item(u'UID:1')) a.upload(Item('UID:1'))
b.upload(Item(u'UID:2')) b.upload(Item('UID:2'))
b.read_only = True b.read_only = True
sync(a, b, status, partial_sync='revert') sync(a, b, status, partial_sync='revert')
@ -418,13 +416,13 @@ def test_ident_conflict(sync_inbetween):
a = MemoryStorage() a = MemoryStorage()
b = MemoryStorage() b = MemoryStorage()
status = {} status = {}
href_a, etag_a = a.upload(Item(u'UID:aaa')) href_a, etag_a = a.upload(Item('UID:aaa'))
href_b, etag_b = a.upload(Item(u'UID:bbb')) href_b, etag_b = a.upload(Item('UID:bbb'))
if sync_inbetween: if sync_inbetween:
sync(a, b, status) sync(a, b, status)
a.update(href_a, Item(u'UID:xxx'), etag_a) a.update(href_a, Item('UID:xxx'), etag_a)
a.update(href_b, Item(u'UID:xxx'), etag_b) a.update(href_b, Item('UID:xxx'), etag_b)
with pytest.raises(IdentConflict): with pytest.raises(IdentConflict):
sync(a, b, status) sync(a, b, status)
@ -441,7 +439,7 @@ def test_moved_href():
a = MemoryStorage() a = MemoryStorage()
b = MemoryStorage() b = MemoryStorage()
status = {} status = {}
href, etag = a.upload(Item(u'UID:haha')) href, etag = a.upload(Item('UID:haha'))
sync(a, b, status) sync(a, b, status)
b.items['lol'] = b.items.pop('haha') b.items['lol'] = b.items.pop('haha')
@ -476,26 +474,26 @@ def test_bogus_etag_change():
a = MemoryStorage() a = MemoryStorage()
b = MemoryStorage() b = MemoryStorage()
status = {} status = {}
href_a, etag_a = a.upload(Item(u'UID:ASDASD')) href_a, etag_a = a.upload(Item('UID:ASDASD'))
sync(a, b, status) sync(a, b, status)
assert len(status) == len(list(a.list())) == len(list(b.list())) == 1 assert len(status) == len(list(a.list())) == len(list(b.list())) == 1
(href_b, etag_b), = b.list() (href_b, etag_b), = b.list()
a.update(href_a, Item(u'UID:ASDASD'), etag_a) a.update(href_a, Item('UID:ASDASD'), etag_a)
b.update(href_b, Item(u'UID:ASDASD\nACTUALCHANGE:YES'), etag_b) b.update(href_b, Item('UID:ASDASD\nACTUALCHANGE:YES'), etag_b)
b.delete = b.update = b.upload = blow_up b.delete = b.update = b.upload = blow_up
sync(a, b, status) sync(a, b, status)
assert len(status) == 1 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(): def test_unicode_hrefs():
a = MemoryStorage() a = MemoryStorage()
b = MemoryStorage() b = MemoryStorage()
status = {} status = {}
href, etag = a.upload(Item(u'UID:äää')) href, etag = a.upload(Item('UID:äää'))
sync(a, b, status) sync(a, b, status)
@ -565,7 +563,7 @@ class SyncMachine(RuleBasedStateMachine):
uid=uid_strategy, uid=uid_strategy,
etag=st.text()) etag=st.text())
def upload(self, storage, uid, etag): def upload(self, storage, uid, etag):
item = Item(u'UID:{}'.format(uid)) item = Item('UID:{}'.format(uid))
storage.items[uid] = (etag, item) storage.items[uid] = (etag, item)
@rule(storage=Storage, href=st.text()) @rule(storage=Storage, href=st.text())

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import hypothesis.strategies as st import hypothesis.strategies as st
from hypothesis import example, given from hypothesis import example, given
@ -130,7 +128,7 @@ metadata = st.dictionaries(keys, values)
status=metadata, keys=st.sets(keys), status=metadata, keys=st.sets(keys),
conflict_resolution=st.just('a wins') | st.just('b wins') 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') conflict_resolution='a wins')
@example(a={'0': '0'}, b={'0': '1'}, status={'0': '0'}, keys={'0'}, @example(a={'0': '0'}, b={'0': '1'}, status={'0': '0'}, keys={'0'},
conflict_resolution='a wins') conflict_resolution='a wins')
@ -144,9 +142,9 @@ def test_fuzzing(a, b, status, keys, conflict_resolution):
b = _get_storage(b, 'B') b = _get_storage(b, 'B')
winning_storage = (a if conflict_resolution == 'a wins' else b) winning_storage = (a if conflict_resolution == 'a wins' else b)
expected_values = dict((key, winning_storage.get_meta(key)) expected_values = {key: winning_storage.get_meta(key)
for key in keys for key in keys
if key not in status) if key not in status}
metasync(a, b, status, metasync(a, b, status,
keys=keys, conflict_resolution=conflict_resolution) keys=keys, conflict_resolution=conflict_resolution)

View file

@ -18,11 +18,11 @@ def test_repair_uids(uid):
s.items = { s.items = {
'one': ( 'one': (
'asdf', 'asdf',
Item(u'BEGIN:VCARD\nFN:Hans\nUID:{}\nEND:VCARD'.format(uid)) Item('BEGIN:VCARD\nFN:Hans\nUID:{}\nEND:VCARD'.format(uid))
), ),
'two': ( 'two': (
'asdf', 'asdf',
Item(u'BEGIN:VCARD\nFN:Peppi\nUID:{}\nEND:VCARD'.format(uid)) Item('BEGIN:VCARD\nFN:Peppi\nUID:{}\nEND:VCARD'.format(uid))
) )
} }
@ -40,7 +40,7 @@ def test_repair_uids(uid):
@settings(suppress_health_check=HealthCheck.all()) @settings(suppress_health_check=HealthCheck.all())
def test_repair_unsafe_uids(uid): def test_repair_unsafe_uids(uid):
s = MemoryStorage() s = MemoryStorage()
item = Item(u'BEGIN:VCARD\nUID:{}\nEND:VCARD'.format(uid)) item = Item('BEGIN:VCARD\nUID:{}\nEND:VCARD'.format(uid))
href, etag = s.upload(item) href, etag = s.upload(item)
assert s.get(href)[0].uid == uid assert s.get(href)[0].uid == uid
assert not href_safe(uid) assert not href_safe(uid)

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
from textwrap import dedent from textwrap import dedent
import hypothesis.strategies as st import hypothesis.strategies as st
@ -21,10 +19,10 @@ _simple_split = [
VCARD_TEMPLATE.format(r=678, uid=678) VCARD_TEMPLATE.format(r=678, uid=678)
] ]
_simple_joined = u'\r\n'.join( _simple_joined = '\r\n'.join(
[u'BEGIN:VADDRESSBOOK'] ['BEGIN:VADDRESSBOOK']
+ _simple_split + _simple_split
+ [u'END:VADDRESSBOOK\r\n'] + ['END:VADDRESSBOOK\r\n']
) )
@ -39,10 +37,10 @@ def test_split_collection_simple(benchmark):
def test_split_collection_multiple_wrappers(benchmark): def test_split_collection_multiple_wrappers(benchmark):
joined = u'\r\n'.join( joined = '\r\n'.join(
u'BEGIN:VADDRESSBOOK\r\n' 'BEGIN:VADDRESSBOOK\r\n'
+ x + x
+ u'\r\nEND:VADDRESSBOOK\r\n' + '\r\nEND:VADDRESSBOOK\r\n'
for x in _simple_split for x in _simple_split
) )
given = benchmark(lambda: list(vobject.split_collection(joined))) given = benchmark(lambda: list(vobject.split_collection(joined)))
@ -105,29 +103,29 @@ def test_split_collection_timezones():
] ]
timezone = ( timezone = (
u'BEGIN:VTIMEZONE\r\n' 'BEGIN:VTIMEZONE\r\n'
u'TZID:/mozilla.org/20070129_1/Asia/Tokyo\r\n' 'TZID:/mozilla.org/20070129_1/Asia/Tokyo\r\n'
u'X-LIC-LOCATION:Asia/Tokyo\r\n' 'X-LIC-LOCATION:Asia/Tokyo\r\n'
u'BEGIN:STANDARD\r\n' 'BEGIN:STANDARD\r\n'
u'TZOFFSETFROM:+0900\r\n' 'TZOFFSETFROM:+0900\r\n'
u'TZOFFSETTO:+0900\r\n' 'TZOFFSETTO:+0900\r\n'
u'TZNAME:JST\r\n' 'TZNAME:JST\r\n'
u'DTSTART:19700101T000000\r\n' 'DTSTART:19700101T000000\r\n'
u'END:STANDARD\r\n' 'END:STANDARD\r\n'
u'END:VTIMEZONE' 'END:VTIMEZONE'
) )
full = u'\r\n'.join( full = '\r\n'.join(
[u'BEGIN:VCALENDAR'] ['BEGIN:VCALENDAR']
+ items + items
+ [timezone, u'END:VCALENDAR'] + [timezone, 'END:VCALENDAR']
) )
given = {normalize_item(item) given = {normalize_item(item)
for item in vobject.split_collection(full)} for item in vobject.split_collection(full)}
expected = { expected = {
normalize_item(u'\r\n'.join(( normalize_item('\r\n'.join((
u'BEGIN:VCALENDAR', item, timezone, u'END:VCALENDAR' 'BEGIN:VCALENDAR', item, timezone, 'END:VCALENDAR'
))) )))
for item in items for item in items
} }
@ -148,20 +146,20 @@ def test_split_contacts():
def test_hash_item(): def test_hash_item():
a = EVENT_TEMPLATE.format(r=1, uid=1) a = EVENT_TEMPLATE.format(r=1, uid=1)
b = u'\n'.join(line for line in a.splitlines() b = '\n'.join(line for line in a.splitlines()
if u'PRODID' not in line) if 'PRODID' not in line)
assert vobject.hash_item(a) == vobject.hash_item(b) assert vobject.hash_item(a) == vobject.hash_item(b)
def test_multiline_uid(benchmark): def test_multiline_uid(benchmark):
a = (u'BEGIN:FOO\r\n' a = ('BEGIN:FOO\r\n'
u'UID:123456789abcd\r\n' 'UID:123456789abcd\r\n'
u' efgh\r\n' ' efgh\r\n'
u'END:FOO\r\n') 'END:FOO\r\n')
assert benchmark(lambda: vobject.Item(a).uid) == u'123456789abcdefgh' assert benchmark(lambda: vobject.Item(a).uid) == '123456789abcdefgh'
complex_uid_item = dedent(u''' complex_uid_item = dedent('''
BEGIN:VCALENDAR BEGIN:VCALENDAR
BEGIN:VTIMEZONE BEGIN:VTIMEZONE
TZID:Europe/Rome TZID:Europe/Rome
@ -202,9 +200,9 @@ complex_uid_item = dedent(u'''
def test_multiline_uid_complex(benchmark): def test_multiline_uid_complex(benchmark):
assert benchmark(lambda: vobject.Item(complex_uid_item).uid) == ( assert benchmark(lambda: vobject.Item(complex_uid_item).uid) == (
u'040000008200E00074C5B7101A82E008000000005' '040000008200E00074C5B7101A82E008000000005'
u'0AAABEEF50DCF001000000062548482FA830A46B9' '0AAABEEF50DCF001000000062548482FA830A46B9'
u'EA62114AC9F0EF' 'EA62114AC9F0EF'
) )

View file

@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
''' '''
Vdirsyncer synchronizes calendars and contacts. Vdirsyncer synchronizes calendars and contacts.
''' '''
from __future__ import print_function
PROJECT_HOME = 'https://github.com/pimutils/vdirsyncer' PROJECT_HOME = 'https://github.com/pimutils/vdirsyncer'
BUGTRACKER_HOME = PROJECT_HOME + '/issues' BUGTRACKER_HOME = PROJECT_HOME + '/issues'

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import functools import functools
import logging import logging
import sys import sys
@ -15,7 +13,7 @@ cli_logger = logging.getLogger(__name__)
click_log.basic_config('vdirsyncer') click_log.basic_config('vdirsyncer')
class AppContext(object): class AppContext:
def __init__(self): def __init__(self):
self.config = None self.config = None
self.fetched_params = {} self.fetched_params = {}

View file

@ -33,17 +33,17 @@ def _validate_general_section(general_config):
problems = [] problems = []
if invalid: if invalid:
problems.append(u'general section doesn\'t take the parameters: {}' problems.append('general section doesn\'t take the parameters: {}'
.format(u', '.join(invalid))) .format(', '.join(invalid)))
if missing: if missing:
problems.append(u'general section is missing the parameters: {}' problems.append('general section is missing the parameters: {}'
.format(u', '.join(missing))) .format(', '.join(missing)))
if problems: if problems:
raise exceptions.UserError( raise exceptions.UserError(
u'Invalid general section. Copy the example ' 'Invalid general section. Copy the example '
u'config from the repository and edit it: {}' 'config from the repository and edit it: {}'
.format(PROJECT_HOME), problems=problems) .format(PROJECT_HOME), problems=problems)
@ -151,7 +151,7 @@ def _parse_options(items, section=None):
.format(section, key, e)) .format(section, key, e))
class Config(object): class Config:
def __init__(self, general, pairs, storages): def __init__(self, general, pairs, storages):
self.general = general self.general = general
self.storages = storages self.storages = storages
@ -209,7 +209,7 @@ class Config(object):
raise exceptions.PairNotFound(e, pair_name=pair_name) raise exceptions.PairNotFound(e, pair_name=pair_name)
class PairConfig(object): class PairConfig:
def __init__(self, full_config, name, options): def __init__(self, full_config, name, options):
self._config = full_config self._config = full_config
self.name = name self.name = name
@ -299,7 +299,7 @@ class PairConfig(object):
return partial_sync return partial_sync
class CollectionConfig(object): class CollectionConfig:
def __init__(self, pair, name, config_a, config_b): def __init__(self, pair, name, config_a, config_b):
self.pair = pair self.pair = pair
self._config = pair._config self._config = pair._config

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import hashlib import hashlib
import json import json
import logging import logging
@ -224,7 +222,7 @@ def _print_collections(instance_name, get_discovered):
storage = storage_instance_from_config(args, create=False) storage = storage_instance_from_config(args, create=False)
displayname = storage.get_meta('displayname') displayname = storage.get_meta('displayname')
except Exception: except Exception:
displayname = u'' displayname = ''
logger.info(' - {}{}'.format( logger.info(' - {}{}'.format(
json.dumps(collection), json.dumps(collection),

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import logging import logging
import click import click

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import functools import functools
import json import json

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import contextlib import contextlib
import errno import errno
import importlib import importlib
@ -27,7 +25,7 @@ STATUS_PERMISSIONS = 0o600
STATUS_DIR_PERMISSIONS = 0o700 STATUS_DIR_PERMISSIONS = 0o700
class _StorageIndex(object): class _StorageIndex:
def __init__(self): def __init__(self):
self._storages = dict( self._storages = dict(
caldav='vdirsyncer.storage.dav.CalDAVStorage', caldav='vdirsyncer.storage.dav.CalDAVStorage',
@ -288,24 +286,24 @@ def handle_storage_init_error(cls, config):
if missing: if missing:
problems.append( problems.append(
u'{} storage requires the parameters: {}' '{} storage requires the parameters: {}'
.format(cls.storage_name, u', '.join(missing))) .format(cls.storage_name, ', '.join(missing)))
if invalid: if invalid:
problems.append( problems.append(
u'{} storage doesn\'t take the parameters: {}' '{} storage doesn\'t take the parameters: {}'
.format(cls.storage_name, u', '.join(invalid))) .format(cls.storage_name, ', '.join(invalid)))
if not problems: if not problems:
raise e raise e
raise exceptions.UserError( raise exceptions.UserError(
u'Failed to initialize {}'.format(config['instance_name']), 'Failed to initialize {}'.format(config['instance_name']),
problems=problems problems=problems
) )
class WorkerQueue(object): class WorkerQueue:
''' '''
A simple worker-queue setup. A simple worker-queue setup.

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
''' '''
Contains exception classes used by vdirsyncer. Not all exceptions are here, Contains exception classes used by vdirsyncer. Not all exceptions are here,
only the most commonly used ones. only the most commonly used ones.
@ -14,7 +13,7 @@ class Error(Exception):
raise TypeError('Invalid argument: {}'.format(key)) raise TypeError('Invalid argument: {}'.format(key))
setattr(self, key, value) setattr(self, key, value)
super(Error, self).__init__(*args) super().__init__(*args)
class UserError(Error, ValueError): class UserError(Error, ValueError):
@ -26,7 +25,7 @@ class UserError(Error, ValueError):
def __str__(self): def __str__(self):
msg = Error.__str__(self) msg = Error.__str__(self)
for problem in self.problems or (): for problem in self.problems or ():
msg += u'\n - {}'.format(problem) msg += '\n - {}'.format(problem)
return msg return msg

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import logging import logging
import requests import requests
@ -134,7 +133,7 @@ def request(method, url, session=None, latin1_fallback=True,
func = session.request func = session.request
logger.debug(u'{} {}'.format(method, url)) logger.debug('{} {}'.format(method, url))
logger.debug(kwargs.get('headers', {})) logger.debug(kwargs.get('headers', {}))
logger.debug(kwargs.get('data', None)) logger.debug(kwargs.get('data', None))
logger.debug('Sending request...') logger.debug('Sending request...')

View file

@ -16,12 +16,12 @@ class MetaSyncConflict(MetaSyncError):
def metasync(storage_a, storage_b, status, keys, conflict_resolution=None): def metasync(storage_a, storage_b, status, keys, conflict_resolution=None):
def _a_to_b(): def _a_to_b():
logger.info(u'Copying {} to {}'.format(key, storage_b)) logger.info('Copying {} to {}'.format(key, storage_b))
storage_b.set_meta(key, a) storage_b.set_meta(key, a)
status[key] = a status[key] = a
def _b_to_a(): def _b_to_a():
logger.info(u'Copying {} to {}'.format(key, storage_a)) logger.info('Copying {} to {}'.format(key, storage_a))
storage_a.set_meta(key, b) storage_a.set_meta(key, b)
status[key] = b status[key] = b
@ -45,10 +45,10 @@ def metasync(storage_a, storage_b, status, keys, conflict_resolution=None):
a = storage_a.get_meta(key) a = storage_a.get_meta(key)
b = storage_b.get_meta(key) b = storage_b.get_meta(key)
s = normalize_meta_value(status.get(key)) s = normalize_meta_value(status.get(key))
logger.debug(u'Key: {}'.format(key)) logger.debug('Key: {}'.format(key))
logger.debug(u'A: {}'.format(a)) logger.debug('A: {}'.format(a))
logger.debug(u'B: {}'.format(b)) logger.debug('B: {}'.format(b))
logger.debug(u'S: {}'.format(s)) logger.debug('S: {}'.format(s))
if a != s and b != s: if a != s and b != s:
_resolve_conflict() _resolve_conflict()

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import logging import logging
from os.path import basename from os.path import basename
@ -17,7 +15,7 @@ def repair_storage(storage, repair_unsafe_uid):
all_hrefs = list(storage.list()) all_hrefs = list(storage.list())
for i, (href, _) in enumerate(all_hrefs): for i, (href, _) in enumerate(all_hrefs):
item, etag = storage.get(href) item, etag = storage.get(href)
logger.info(u'[{}/{}] Processing {}' logger.info('[{}/{}] Processing {}'
.format(i, len(all_hrefs), href)) .format(i, len(all_hrefs), href))
try: try:

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
''' '''
There are storage classes which control the access to one vdir-collection and There are storage classes which control the access to one vdir-collection and
offer basic CRUD-ish methods for modifying those collections. The exact offer basic CRUD-ish methods for modifying those collections. The exact

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import contextlib import contextlib
import functools import functools
@ -20,7 +18,7 @@ class StorageMeta(type):
def __init__(cls, name, bases, d): def __init__(cls, name, bases, d):
for method in ('update', 'upload', 'delete'): for method in ('update', 'upload', 'delete'):
setattr(cls, method, mutating_storage_method(getattr(cls, method))) 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): class Storage(metaclass=StorageMeta):
@ -116,7 +114,7 @@ class Storage(metaclass=StorageMeta):
return '<{}(**{})>'.format( return '<{}(**{})>'.format(
self.__class__.__name__, 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): def list(self):

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import datetime import datetime
import logging import logging
import urllib.parse as urlparse import urllib.parse as urlparse
@ -140,7 +138,7 @@ def _fuzzy_matches_mimetype(strict, weak):
return False return False
class Discover(object): class Discover:
_namespace = None _namespace = None
_resourcetype = None _resourcetype = None
_homeset_xml = None _homeset_xml = None
@ -349,7 +347,7 @@ class CardDiscover(Discover):
_well_known_uri = '/.well-known/carddav' _well_known_uri = '/.well-known/carddav'
class DAVSession(object): class DAVSession:
''' '''
A helper class to connect to DAV servers. A helper class to connect to DAV servers.
''' '''
@ -423,7 +421,7 @@ class DAVStorage(Storage):
self.session, kwargs = \ self.session, kwargs = \
self.session_class.init_and_remaining_args(**kwargs) self.session_class.init_and_remaining_args(**kwargs)
super(DAVStorage, self).__init__(**kwargs) super().__init__(**kwargs)
import inspect import inspect
__init__.__signature__ = inspect.signature(session_class.__init__) __init__.__signature__ = inspect.signature(session_class.__init__)
@ -483,7 +481,7 @@ class DAVStorage(Storage):
.format(href)) .format(href))
continue continue
raw = raw.text or u'' raw = raw.text or ''
if isinstance(raw, bytes): if isinstance(raw, bytes):
raw = raw.decode(response.encoding) raw = raw.decode(response.encoding)
@ -617,7 +615,7 @@ class DAVStorage(Storage):
headers = self.session.get_default_headers() headers = self.session.get_default_headers()
headers['Depth'] = '1' headers['Depth'] = '1'
data = '''<?xml version="1.0" encoding="utf-8" ?> data = b'''<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:"> <D:propfind xmlns:D="DAV:">
<D:prop> <D:prop>
<D:resourcetype/> <D:resourcetype/>
@ -625,7 +623,7 @@ class DAVStorage(Storage):
<D:getetag/> <D:getetag/>
</D:prop> </D:prop>
</D:propfind> </D:propfind>
'''.encode('utf-8') '''
# We use a PROPFIND request instead of addressbook-query due to issues # We use a PROPFIND request instead of addressbook-query due to issues
# with Zimbra. See https://github.com/pimutils/vdirsyncer/issues/83 # with Zimbra. See https://github.com/pimutils/vdirsyncer/issues/83
@ -643,7 +641,7 @@ class DAVStorage(Storage):
except KeyError: except KeyError:
raise exceptions.UnsupportedMetadataError() raise exceptions.UnsupportedMetadataError()
xpath = '{%s}%s' % (namespace, tagname) xpath = '{{{}}}{}'.format(namespace, tagname)
data = '''<?xml version="1.0" encoding="utf-8" ?> data = '''<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:"> <D:propfind xmlns:D="DAV:">
<D:prop> <D:prop>
@ -668,7 +666,7 @@ class DAVStorage(Storage):
text = normalize_meta_value(getattr(prop, 'text', None)) text = normalize_meta_value(getattr(prop, 'text', None))
if text: if text:
return text return text
return u'' return ''
def set_meta(self, key, value): def set_meta(self, key, value):
try: try:
@ -676,7 +674,7 @@ class DAVStorage(Storage):
except KeyError: except KeyError:
raise exceptions.UnsupportedMetadataError() raise exceptions.UnsupportedMetadataError()
lxml_selector = '{%s}%s' % (namespace, tagname) lxml_selector = '{{{}}}{}'.format(namespace, tagname)
element = etree.Element(lxml_selector) element = etree.Element(lxml_selector)
element.text = normalize_meta_value(value) element.text = normalize_meta_value(value)
@ -730,7 +728,7 @@ class CalDAVStorage(DAVStorage):
def __init__(self, start_date=None, end_date=None, def __init__(self, start_date=None, end_date=None,
item_types=(), **kwargs): item_types=(), **kwargs):
super(CalDAVStorage, self).__init__(**kwargs) super().__init__(**kwargs)
if not isinstance(item_types, (list, tuple)): if not isinstance(item_types, (list, tuple)):
raise exceptions.UserError('item_types must be a list.') raise exceptions.UserError('item_types must be a list.')
@ -774,9 +772,8 @@ class CalDAVStorage(DAVStorage):
timefilter=timefilter) timefilter=timefilter)
else: else:
if start is not None and end is not None: if start is not None and end is not None:
for x in CalDAVStorage._get_list_filters(('VTODO', 'VEVENT'), yield from CalDAVStorage._get_list_filters(('VTODO', 'VEVENT'),
start, end): start, end)
yield x
def list(self): def list(self):
caldavfilters = list(self._get_list_filters( caldavfilters = list(self._get_list_filters(
@ -792,8 +789,7 @@ class CalDAVStorage(DAVStorage):
# instead? # instead?
# #
# See https://github.com/dmfs/tasks/issues/118 for backstory. # See https://github.com/dmfs/tasks/issues/118 for backstory.
for x in DAVStorage.list(self): yield from DAVStorage.list(self)
yield x
data = '''<?xml version="1.0" encoding="utf-8" ?> data = '''<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:D="DAV:" <C:calendar-query xmlns:D="DAV:"

View file

@ -110,7 +110,7 @@ class EtesyncStorage(Storage):
raise ValueError('Collection argument required') raise ValueError('Collection argument required')
self._session = _Session(email, secrets_dir, server_url, db_path) self._session = _Session(email, secrets_dir, server_url, db_path)
super(EtesyncStorage, self).__init__(**kwargs) super().__init__(**kwargs)
self._journal = self._session.etesync.get(self.collection) self._journal = self._session.etesync.get(self.collection)
def _sync_journal(self): def _sync_journal(self):

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import errno import errno
import logging import logging
import os import os
@ -22,7 +20,7 @@ class FilesystemStorage(Storage):
def __init__(self, path, fileext, encoding='utf-8', post_hook=None, def __init__(self, path, fileext, encoding='utf-8', post_hook=None,
**kwargs): **kwargs):
super(FilesystemStorage, self).__init__(**kwargs) super().__init__(**kwargs)
path = expand_path(path) path = expand_path(path)
checkdir(path, create=False) checkdir(path, create=False)
self.path = path self.path = path
@ -174,7 +172,7 @@ class FilesystemStorage(Storage):
return normalize_meta_value(f.read().decode(self.encoding)) return normalize_meta_value(f.read().decode(self.encoding))
except OSError as e: except OSError as e:
if e.errno == errno.ENOENT: if e.errno == errno.ENOENT:
return u'' return ''
else: else:
raise raise

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import json import json
import logging import logging
import os import os
@ -123,7 +121,7 @@ class GoogleCalendarStorage(dav.CalDAVStorage):
if not kwargs.get('collection'): if not kwargs.get('collection'):
raise exceptions.CollectionRequired() raise exceptions.CollectionRequired()
super(GoogleCalendarStorage, self).__init__( super().__init__(
token_file=token_file, client_id=client_id, token_file=token_file, client_id=client_id,
client_secret=client_secret, start_date=start_date, client_secret=client_secret, start_date=start_date,
end_date=end_date, item_types=item_types, end_date=end_date, item_types=item_types,
@ -158,7 +156,7 @@ class GoogleContactsStorage(dav.CardDAVStorage):
if not kwargs.get('collection'): if not kwargs.get('collection'):
raise exceptions.CollectionRequired() raise exceptions.CollectionRequired()
super(GoogleContactsStorage, self).__init__( super().__init__(
token_file=token_file, client_id=client_id, token_file=token_file, client_id=client_id,
client_secret=client_secret, client_secret=client_secret,
**kwargs **kwargs

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import urllib.parse as urlparse import urllib.parse as urlparse
from .base import Storage from .base import Storage
@ -21,7 +19,7 @@ class HttpStorage(Storage):
def __init__(self, url, username='', password='', verify=True, auth=None, def __init__(self, url, username='', password='', verify=True, auth=None,
useragent=USERAGENT, verify_fingerprint=None, auth_cert=None, useragent=USERAGENT, verify_fingerprint=None, auth_cert=None,
**kwargs): **kwargs):
super(HttpStorage, self).__init__(**kwargs) super().__init__(**kwargs)
self._settings = { self._settings = {
'auth': prepare_auth(auth, username, password), 'auth': prepare_auth(auth, username, password),

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import random import random
from .base import Storage, normalize_meta_value from .base import Storage, normalize_meta_value
@ -26,7 +24,7 @@ class MemoryStorage(Storage):
self.items = {} # href => (etag, item) self.items = {} # href => (etag, item)
self.metadata = {} self.metadata = {}
self.fileext = fileext self.fileext = fileext
super(MemoryStorage, self).__init__(**kwargs) super().__init__(**kwargs)
def _get_href(self, item): def _get_href(self, item):
return item.ident + self.fileext return item.ident + self.fileext

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import collections import collections
import contextlib import contextlib
import functools import functools
@ -41,7 +39,7 @@ class SingleFileStorage(Storage):
_last_etag = None _last_etag = None
def __init__(self, path, encoding='utf-8', **kwargs): def __init__(self, path, encoding='utf-8', **kwargs):
super(SingleFileStorage, self).__init__(**kwargs) super().__init__(**kwargs)
path = os.path.abspath(expand_path(path)) path = os.path.abspath(expand_path(path))
checkfile(path, create=False) checkfile(path, create=False)

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
''' '''
The `sync` function in `vdirsyncer.sync` can be called on two instances of 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 `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__) sync_logger = logging.getLogger(__name__)
class _StorageInfo(object): class _StorageInfo:
'''A wrapper class that holds prefetched items, the status and other '''A wrapper class that holds prefetched items, the status and other
things.''' things.'''
def __init__(self, storage, status): def __init__(self, storage, status):
@ -199,7 +198,7 @@ class Upload(Action):
if self.dest.storage.read_only: if self.dest.storage.read_only:
href = etag = None href = etag = None
else: else:
sync_logger.info(u'Copying (uploading) item {} to {}' sync_logger.info('Copying (uploading) item {} to {}'
.format(self.ident, self.dest.storage)) .format(self.ident, self.dest.storage))
href, etag = self.dest.storage.upload(self.item) href, etag = self.dest.storage.upload(self.item)
assert href is not None assert href is not None
@ -221,7 +220,7 @@ class Update(Action):
if self.dest.storage.read_only: if self.dest.storage.read_only:
meta = ItemMetadata(hash=self.item.hash) meta = ItemMetadata(hash=self.item.hash)
else: else:
sync_logger.info(u'Copying (updating) item {} to {}' sync_logger.info('Copying (updating) item {} to {}'
.format(self.ident, self.dest.storage)) .format(self.ident, self.dest.storage))
meta = self.dest.status.get_new(self.ident) meta = self.dest.status.get_new(self.ident)
meta.etag = \ meta.etag = \
@ -238,7 +237,7 @@ class Delete(Action):
def _run_impl(self, a, b): def _run_impl(self, a, b):
meta = self.dest.status.get_new(self.ident) meta = self.dest.status.get_new(self.ident)
if not self.dest.storage.read_only: 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)) .format(self.ident, self.dest.storage))
self.dest.storage.delete(meta.href, meta.etag) self.dest.storage.delete(meta.href, meta.etag)
@ -251,14 +250,14 @@ class ResolveConflict(Action):
def run(self, a, b, conflict_resolution, partial_sync): def run(self, a, b, conflict_resolution, partial_sync):
with self.auto_rollback(a, b): 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)) .format(self.ident))
meta_a = a.status.get_new(self.ident) meta_a = a.status.get_new(self.ident)
meta_b = b.status.get_new(self.ident) meta_b = b.status.get_new(self.ident)
if meta_a.hash == meta_b.hash: 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: elif conflict_resolution is None:
raise SyncConflict(ident=self.ident, href_a=meta_a.href, raise SyncConflict(ident=self.ident, href_a=meta_a.href,
href_b=meta_b.href) href_b=meta_b.href)

View file

@ -313,7 +313,7 @@ class SqliteStatus(_StatusBase):
return self._get_by_href_impl(*a, **kw) return self._get_by_href_impl(*a, **kw)
class SubStatus(object): class SubStatus:
def __init__(self, parent, side): def __init__(self, parent, side):
self.parent = parent self.parent = parent
assert side in 'ab' assert side in 'ab'

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import functools import functools
import os import os
import sys import sys
@ -154,7 +152,7 @@ def checkfile(path, create=False):
.format(path)) .format(path))
class cached_property(object): class cached_property:
'''A read-only @property that is only evaluated once. Only usable on class '''A read-only @property that is only evaluated once. Only usable on class
instances' methods. instances' methods.
''' '''
@ -212,8 +210,7 @@ def open_graphical_browser(url, new=0, autoraise=True):
emulator. emulator.
''' '''
import webbrowser import webbrowser
cli_names = set(['www-browser', 'links', 'links2', 'elinks', 'lynx', cli_names = {'www-browser', 'links', 'links2', 'elinks', 'lynx', 'w3m'}
'w3m'])
if webbrowser._tryorder is None: # Python 3.7 if webbrowser._tryorder is None: # Python 3.7
webbrowser.register_standard_browsers() webbrowser.register_standard_browsers()

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import hashlib import hashlib
from itertools import chain, tee from itertools import chain, tee
@ -34,7 +32,7 @@ IGNORE_PROPS = (
) )
class Item(object): class Item:
'''Immutable wrapper class for VCALENDAR (VEVENT, VTODO) and '''Immutable wrapper class for VCALENDAR (VEVENT, VTODO) and
VCARD''' VCARD'''
@ -117,7 +115,7 @@ def normalize_item(item, ignore_props=IGNORE_PROPS):
del x[prop] del x[prop]
x.props.sort() 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): def _strip_timezones(item):
@ -146,16 +144,16 @@ def split_collection(text):
for item in chain(items.values(), ungrouped_items): for item in chain(items.values(), ungrouped_items):
item.subcomponents.extend(inline) 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): def _split_collection_impl(item, main, inline, items, ungrouped_items):
if item.name == u'VTIMEZONE': if item.name == 'VTIMEZONE':
inline.append(item) inline.append(item)
elif item.name == u'VCARD': elif item.name == 'VCARD':
ungrouped_items.append(item) ungrouped_items.append(item)
elif item.name in (u'VTODO', u'VEVENT', u'VJOURNAL'): elif item.name in ('VTODO', 'VEVENT', 'VJOURNAL'):
uid = item.get(u'UID', u'') uid = item.get('UID', '')
wrapper = _Component(main.name, main.props[:], []) wrapper = _Component(main.name, main.props[:], [])
if uid.strip(): if uid.strip():
@ -164,7 +162,7 @@ def _split_collection_impl(item, main, inline, items, ungrouped_items):
ungrouped_items.append(wrapper) ungrouped_items.append(wrapper)
wrapper.subcomponents.append(item) wrapper.subcomponents.append(item)
elif item.name in (u'VCALENDAR', u'VADDRESSBOOK'): elif item.name in ('VCALENDAR', 'VADDRESSBOOK'):
if item.name == 'VCALENDAR': if item.name == 'VCALENDAR':
del item['METHOD'] del item['METHOD']
for subitem in item.subcomponents: for subitem in item.subcomponents:
@ -176,10 +174,10 @@ def _split_collection_impl(item, main, inline, items, ungrouped_items):
_default_join_wrappers = { _default_join_wrappers = {
u'VCALENDAR': u'VCALENDAR', 'VCALENDAR': 'VCALENDAR',
u'VEVENT': u'VCALENDAR', 'VEVENT': 'VCALENDAR',
u'VTODO': u'VCALENDAR', 'VTODO': 'VCALENDAR',
u'VCARD': u'VADDRESSBOOK' 'VCARD': 'VADDRESSBOOK'
} }
@ -207,16 +205,16 @@ def join_collection(items, wrappers=_default_join_wrappers):
if wrapper_type is not None: if wrapper_type is not None:
lines = chain(*( lines = chain(*(
[u'BEGIN:{}'.format(wrapper_type)], ['BEGIN:{}'.format(wrapper_type)],
# XXX: wrapper_props is a list of lines (with line-wrapping), so # XXX: wrapper_props is a list of lines (with line-wrapping), so
# filtering out duplicate lines will almost certainly break # filtering out duplicate lines will almost certainly break
# multiline-values. Since the only props we usually need to # multiline-values. Since the only props we usually need to
# support are PRODID and VERSION, I don't care. # support are PRODID and VERSION, I don't care.
uniq(wrapper_props), uniq(wrapper_props),
lines, lines,
[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): def _get_item_type(components, wrappers):
@ -237,7 +235,7 @@ def _get_item_type(components, wrappers):
raise ValueError('Not sure how to join components.') raise ValueError('Not sure how to join components.')
class _Component(object): class _Component:
''' '''
Raw outline of the components. Raw outline of the components.
@ -277,10 +275,10 @@ class _Component(object):
rv = [] rv = []
try: try:
for _i, line in enumerate(lines): for _i, line in enumerate(lines):
if line.startswith(u'BEGIN:'): if line.startswith('BEGIN:'):
c_name = line[len(u'BEGIN:'):].strip().upper() c_name = line[len('BEGIN:'):].strip().upper()
stack.append(cls(c_name, [], [])) stack.append(cls(c_name, [], []))
elif line.startswith(u'END:'): elif line.startswith('END:'):
component = stack.pop() component = stack.pop()
if stack: if stack:
stack[-1].subcomponents.append(component) stack[-1].subcomponents.append(component)
@ -301,16 +299,14 @@ class _Component(object):
return rv[0] return rv[0]
def dump_lines(self): def dump_lines(self):
yield u'BEGIN:{}'.format(self.name) yield 'BEGIN:{}'.format(self.name)
for line in self.props: yield from self.props
yield line
for c in self.subcomponents: for c in self.subcomponents:
for line in c.dump_lines(): yield from c.dump_lines()
yield line yield 'END:{}'.format(self.name)
yield u'END:{}'.format(self.name)
def __delitem__(self, key): def __delitem__(self, key):
prefix = (u'{}:'.format(key), u'{};'.format(key)) prefix = ('{}:'.format(key), '{};'.format(key))
new_lines = [] new_lines = []
lineiter = iter(self.props) lineiter = iter(self.props)
while True: while True:
@ -323,7 +319,7 @@ class _Component(object):
break break
for line in lineiter: for line in lineiter:
if not line.startswith((u' ', u'\t')): if not line.startswith((' ', '\t')):
new_lines.append(line) new_lines.append(line)
break break
@ -331,9 +327,9 @@ class _Component(object):
def __setitem__(self, key, val): def __setitem__(self, key, val):
assert isinstance(val, str) assert isinstance(val, str)
assert u'\n' not in val assert '\n' not in val
del self[key] del self[key]
line = u'{}:{}'.format(key, val) line = '{}:{}'.format(key, val)
self.props.append(line) self.props.append(line)
def __contains__(self, obj): def __contains__(self, obj):
@ -360,7 +356,7 @@ class _Component(object):
raise KeyError() raise KeyError()
for line in iterlines: for line in iterlines:
if line.startswith((u' ', u'\t')): if line.startswith((' ', '\t')):
rv += line[1:] rv += line[1:]
else: else:
break break