mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
Be Python 3 compatible
Not that anybody actually uses Python 3, but this helps very much with finding obscure bugs.
This commit is contained in:
parent
cef68b5d09
commit
e66b43c839
14 changed files with 77 additions and 55 deletions
|
|
@ -1,5 +1,7 @@
|
||||||
language: python
|
language: python
|
||||||
python: "2.7"
|
python:
|
||||||
|
- "2.7"
|
||||||
|
- "3.3"
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- IS_TRAVIS=true
|
- IS_TRAVIS=true
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
* Make sure you have the latest version by executing ``pip install --user
|
* Make sure you have the latest version by executing ``pip install --user
|
||||||
--upgrade vdirsyncer``.
|
--upgrade vdirsyncer``.
|
||||||
|
|
||||||
* Include your configuration, the commands you're executing, and their
|
* Include the Python version, your configuration, the commands you're
|
||||||
output.
|
executing, and their output.
|
||||||
|
|
||||||
* Use ``--verbosity=DEBUG`` when including output from vdirsyncer.
|
* Use ``--verbosity=DEBUG`` when including output from vdirsyncer.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,11 @@ informations on problems with ownCloud.
|
||||||
How to use
|
How to use
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
vdirsyncer requires Python >= 2.7 or Python >= 3.3.
|
||||||
|
|
||||||
As all Python packages, vdirsyncer can be installed with ``pip``::
|
As all Python packages, vdirsyncer can be installed with ``pip``::
|
||||||
|
|
||||||
pip install --user vdirsyncer # use the pip for Python 2
|
pip install --user vdirsyncer
|
||||||
|
|
||||||
Then copy ``example.cfg`` to ``~/.vdirsyncer/config`` and edit it. You can use the
|
Then copy ``example.cfg`` to ``~/.vdirsyncer/config`` and edit it. You can use the
|
||||||
`VDIRSYNCER_CONFIG` environment variable to change the path vdirsyncer will
|
`VDIRSYNCER_CONFIG` environment variable to change the path vdirsyncer will
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
from vdirsyncer.storage.base import Item
|
from vdirsyncer.storage.base import Item
|
||||||
import vdirsyncer.exceptions as exceptions
|
import vdirsyncer.exceptions as exceptions
|
||||||
|
from vdirsyncer.utils import text_type
|
||||||
from .. import assert_item_equals
|
from .. import assert_item_equals
|
||||||
import random
|
import random
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -33,7 +34,7 @@ class StorageTests(object):
|
||||||
def test_generic(self):
|
def test_generic(self):
|
||||||
items = map(self._create_bogus_item, range(1, 10))
|
items = map(self._create_bogus_item, range(1, 10))
|
||||||
for i, item in enumerate(items):
|
for i, item in enumerate(items):
|
||||||
assert item.uid == unicode(i + 1), item.raw
|
assert item.uid == text_type(i + 1), item.raw
|
||||||
s = self._get_storage()
|
s = self._get_storage()
|
||||||
hrefs = []
|
hrefs = []
|
||||||
for item in items:
|
for item in items:
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import urlparse
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from vdirsyncer.utils import urlparse
|
||||||
|
|
||||||
from werkzeug.test import Client
|
from werkzeug.test import Client
|
||||||
from werkzeug.wrappers import BaseResponse as WerkzeugResponse
|
from werkzeug.wrappers import BaseResponse as WerkzeugResponse
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ class TestFilesystemStorage(StorageTests):
|
||||||
s = self.storage_class(str(tmpdir), '.txt')
|
s = self.storage_class(str(tmpdir), '.txt')
|
||||||
|
|
||||||
class BrokenItem(object):
|
class BrokenItem(object):
|
||||||
raw = b'Ц, Ш, Л, ж, Д, З, Ю'
|
raw = u'Ц, Ш, Л, ж, Д, З, Ю'.encode('utf-8')
|
||||||
uid = 'jeezus'
|
uid = 'jeezus'
|
||||||
with pytest.raises(UnicodeError):
|
with pytest.raises(TypeError):
|
||||||
s.upload(BrokenItem)
|
s.upload(BrokenItem)
|
||||||
assert not tmpdir.listdir()
|
assert not tmpdir.listdir()
|
||||||
|
|
|
||||||
|
|
@ -18,29 +18,25 @@ class TestHttpStorage(object):
|
||||||
collection_url = 'http://127.0.0.1/calendar/collection.ics'
|
collection_url = 'http://127.0.0.1/calendar/collection.ics'
|
||||||
|
|
||||||
items = [
|
items = [
|
||||||
dedent(b'''
|
(u'BEGIN:VEVENT\n'
|
||||||
BEGIN:VEVENT
|
u'SUMMARY:Eine Kurzinfo\n'
|
||||||
SUMMARY:Eine Kurzinfo
|
u'DESCRIPTION:Beschreibung des Termines\n'
|
||||||
DESCRIPTION:Beschreibung des Termines
|
u'END:VEVENT'),
|
||||||
END:VEVENT
|
(u'BEGIN:VEVENT\n'
|
||||||
''').strip(),
|
u'SUMMARY:Eine zweite Kurzinfo\n'
|
||||||
dedent(b'''
|
u'DESCRIPTION:Beschreibung des anderen Termines\n'
|
||||||
BEGIN:VEVENT
|
u'BEGIN:VALARM\n'
|
||||||
SUMMARY:Eine zweite Kurzinfo
|
u'ACTION:AUDIO\n'
|
||||||
DESCRIPTION:Beschreibung des anderen Termines
|
u'TRIGGER:19980403T120000\n'
|
||||||
BEGIN:VALARM
|
u'ATTACH;FMTTYPE=audio/basic:http://host.com/pub/ssbanner.aud\n'
|
||||||
ACTION:AUDIO
|
u'REPEAT:4\n'
|
||||||
TRIGGER:19980403T120000
|
u'DURATION:PT1H\n'
|
||||||
ATTACH;FMTTYPE=audio/basic:http://host.com/pub/ssbanner.aud
|
u'END:VALARM\n'
|
||||||
REPEAT:4
|
u'END:VEVENT')
|
||||||
DURATION:PT1H
|
|
||||||
END:VALARM
|
|
||||||
END:VEVENT
|
|
||||||
''').strip()
|
|
||||||
]
|
]
|
||||||
|
|
||||||
responses = [
|
responses = [
|
||||||
'\n'.join([b'BEGIN:VCALENDAR'] + items + [b'END:VCALENDAR'])
|
u'\n'.join([u'BEGIN:VCALENDAR'] + items + [u'END:VCALENDAR'])
|
||||||
] * 2
|
] * 2
|
||||||
|
|
||||||
def get(method, url, *a, **kw):
|
def get(method, url, *a, **kw):
|
||||||
|
|
@ -49,7 +45,8 @@ class TestHttpStorage(object):
|
||||||
r = Response()
|
r = Response()
|
||||||
r.status_code = 200
|
r.status_code = 200
|
||||||
assert responses
|
assert responses
|
||||||
r._content = responses.pop()
|
r._content = responses.pop().encode('utf-8')
|
||||||
|
r.encoding = 'utf-8'
|
||||||
return r
|
return r
|
||||||
|
|
||||||
monkeypatch.setattr('requests.request', get)
|
monkeypatch.setattr('requests.request', get)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import ConfigParser
|
|
||||||
from vdirsyncer.sync import sync
|
from vdirsyncer.sync import sync
|
||||||
from vdirsyncer.utils import expand_path, split_dict, parse_options
|
from vdirsyncer.utils import expand_path, split_dict, parse_options
|
||||||
from vdirsyncer.storage import storage_names
|
from vdirsyncer.storage import storage_names
|
||||||
|
|
@ -18,11 +17,17 @@ import vdirsyncer.log as log
|
||||||
import argvard
|
import argvard
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ConfigParser import RawConfigParser
|
||||||
|
except ImportError:
|
||||||
|
from configparser import RawConfigParser
|
||||||
|
|
||||||
|
|
||||||
cli_logger = log.get('cli')
|
cli_logger = log.get('cli')
|
||||||
|
|
||||||
|
|
||||||
def load_config(fname, pair_options=('collections', 'conflict_resolution')):
|
def load_config(fname, pair_options=('collections', 'conflict_resolution')):
|
||||||
c = ConfigParser.RawConfigParser()
|
c = RawConfigParser()
|
||||||
c.read(fname)
|
c.read(fname)
|
||||||
|
|
||||||
get_options = lambda s: dict(parse_options(c.items(s)))
|
get_options = lambda s: dict(parse_options(c.items(s)))
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from .. import exceptions
|
from .. import exceptions
|
||||||
|
from .. import utils
|
||||||
|
|
||||||
|
|
||||||
class Item(object):
|
class Item(object):
|
||||||
|
|
@ -15,7 +16,7 @@ class Item(object):
|
||||||
'''should-be-immutable wrapper class for VCALENDAR and VCARD'''
|
'''should-be-immutable wrapper class for VCALENDAR and VCARD'''
|
||||||
|
|
||||||
def __init__(self, raw):
|
def __init__(self, raw):
|
||||||
assert type(raw) is unicode
|
assert isinstance(raw, utils.text_type)
|
||||||
raw = raw.splitlines()
|
raw = raw.splitlines()
|
||||||
self.uid = None
|
self.uid = None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,9 @@ from .base import Storage, Item
|
||||||
from .http import prepare_auth, prepare_verify, USERAGENT
|
from .http import prepare_auth, prepare_verify, USERAGENT
|
||||||
from .. import exceptions
|
from .. import exceptions
|
||||||
from .. import log
|
from .. import log
|
||||||
from ..utils import request, get_password
|
from ..utils import request, get_password, urlparse
|
||||||
import requests
|
import requests
|
||||||
import datetime
|
import datetime
|
||||||
import urlparse
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -166,7 +165,7 @@ class DavStorage(Storage):
|
||||||
hrefs_left = set(hrefs)
|
hrefs_left = set(hrefs)
|
||||||
for element in root.iter('{DAV:}response'):
|
for element in root.iter('{DAV:}response'):
|
||||||
href = self._normalize_href(
|
href = self._normalize_href(
|
||||||
element.find('{DAV:}href').text.decode(response.encoding))
|
element.find('{DAV:}href').text)
|
||||||
raw = element \
|
raw = element \
|
||||||
.find('{DAV:}propstat') \
|
.find('{DAV:}propstat') \
|
||||||
.find('{DAV:}prop') \
|
.find('{DAV:}prop') \
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
import os
|
import os
|
||||||
from vdirsyncer.storage.base import Storage, Item
|
from vdirsyncer.storage.base import Storage, Item
|
||||||
import vdirsyncer.exceptions as exceptions
|
import vdirsyncer.exceptions as exceptions
|
||||||
from vdirsyncer.utils import expand_path
|
from vdirsyncer.utils import expand_path, text_type
|
||||||
import vdirsyncer.log as log
|
import vdirsyncer.log as log
|
||||||
logger = log.get('storage.filesystem')
|
logger = log.get('storage.filesystem')
|
||||||
|
|
||||||
|
|
@ -76,7 +76,7 @@ class FilesystemStorage(Storage):
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
raise IOError('{} is not a directory.')
|
raise IOError('{} is not a directory.')
|
||||||
if create:
|
if create:
|
||||||
os.makedirs(path, 0750)
|
os.makedirs(path, 0o750)
|
||||||
else:
|
else:
|
||||||
raise IOError('Directory {} does not exist. Use create = '
|
raise IOError('Directory {} does not exist. Use create = '
|
||||||
'True in your configuration to automatically '
|
'True in your configuration to automatically '
|
||||||
|
|
@ -125,6 +125,10 @@ class FilesystemStorage(Storage):
|
||||||
fpath = self._get_filepath(href)
|
fpath = self._get_filepath(href)
|
||||||
if os.path.exists(fpath):
|
if os.path.exists(fpath):
|
||||||
raise exceptions.AlreadyExistingError(item.uid)
|
raise exceptions.AlreadyExistingError(item.uid)
|
||||||
|
|
||||||
|
if not isinstance(item.raw, text_type):
|
||||||
|
raise TypeError('item.raw must be a unicode string.')
|
||||||
|
|
||||||
with safe_write(fpath, 'wb+') as f:
|
with safe_write(fpath, 'wb+') as f:
|
||||||
f.write(item.raw.encode(self.encoding))
|
f.write(item.raw.encode(self.encoding))
|
||||||
return href, f.get_etag()
|
return href, f.get_etag()
|
||||||
|
|
@ -140,6 +144,9 @@ class FilesystemStorage(Storage):
|
||||||
if etag != actual_etag:
|
if etag != actual_etag:
|
||||||
raise exceptions.WrongEtagError(etag, actual_etag)
|
raise exceptions.WrongEtagError(etag, actual_etag)
|
||||||
|
|
||||||
|
if not isinstance(item.raw, text_type):
|
||||||
|
raise TypeError('item.raw must be a unicode string.')
|
||||||
|
|
||||||
with safe_write(fpath, 'wb') as f:
|
with safe_write(fpath, 'wb') as f:
|
||||||
f.write(item.raw.encode(self.encoding))
|
f.write(item.raw.encode(self.encoding))
|
||||||
return f.get_etag()
|
return f.get_etag()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
'''
|
||||||
vdirsyncer.storage.http
|
vdirsyncer.storage.http
|
||||||
|
|
@ -8,10 +7,10 @@
|
||||||
:license: MIT, see LICENSE for more details.
|
:license: MIT, see LICENSE for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import urlparse
|
|
||||||
import hashlib
|
import hashlib
|
||||||
from .base import Storage, Item
|
from .base import Storage, Item
|
||||||
from vdirsyncer.utils import expand_path, get_password, request
|
from vdirsyncer.utils import expand_path, get_password, request, urlparse, \
|
||||||
|
text_type
|
||||||
|
|
||||||
USERAGENT = 'vdirsyncer'
|
USERAGENT = 'vdirsyncer'
|
||||||
|
|
||||||
|
|
@ -59,7 +58,7 @@ def prepare_auth(auth, username, password):
|
||||||
|
|
||||||
|
|
||||||
def prepare_verify(verify):
|
def prepare_verify(verify):
|
||||||
if isinstance(verify, (str, unicode)):
|
if isinstance(verify, (text_type, bytes)):
|
||||||
return expand_path(verify)
|
return expand_path(verify)
|
||||||
return verify
|
return verify
|
||||||
|
|
||||||
|
|
@ -110,8 +109,8 @@ class HttpStorage(Storage):
|
||||||
self._items[uid] = item
|
self._items[uid] = item
|
||||||
|
|
||||||
for uid, item in self._items.items():
|
for uid, item in self._items.items():
|
||||||
yield uid, hashlib.sha256(item.raw).hexdigest()
|
yield uid, hashlib.sha256(item.raw.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
def get(self, href):
|
def get(self, href):
|
||||||
x = self._items[href]
|
x = self._items[href]
|
||||||
return x, hashlib.sha256(x.raw).hexdigest()
|
return x, hashlib.sha256(x.raw.encode('utf-8')).hexdigest()
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import itertools
|
||||||
|
|
||||||
import vdirsyncer.exceptions as exceptions
|
import vdirsyncer.exceptions as exceptions
|
||||||
import vdirsyncer.log
|
import vdirsyncer.log
|
||||||
|
from .utils import iteritems, itervalues
|
||||||
sync_logger = vdirsyncer.log.get('sync')
|
sync_logger = vdirsyncer.log.get('sync')
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -58,18 +59,18 @@ def sync(storage_a, storage_b, status, conflict_resolution=None):
|
||||||
'''
|
'''
|
||||||
a_href_to_status = dict(
|
a_href_to_status = dict(
|
||||||
(href_a, (uid, etag_a))
|
(href_a, (uid, etag_a))
|
||||||
for uid, (href_a, etag_a, href_b, etag_b) in status.iteritems()
|
for uid, (href_a, etag_a, href_b, etag_b) in iteritems(status)
|
||||||
)
|
)
|
||||||
b_href_to_status = dict(
|
b_href_to_status = dict(
|
||||||
(href_b, (uid, etag_b))
|
(href_b, (uid, etag_b))
|
||||||
for uid, (href_a, etag_a, href_b, etag_b) in status.iteritems()
|
for uid, (href_a, etag_a, href_b, etag_b) in iteritems(status)
|
||||||
)
|
)
|
||||||
# href => {'etag': etag, 'item': optional item, 'uid': uid}
|
# href => {'etag': etag, 'item': optional item, 'uid': uid}
|
||||||
list_a = dict(prepare_list(storage_a, a_href_to_status))
|
list_a = dict(prepare_list(storage_a, a_href_to_status))
|
||||||
list_b = dict(prepare_list(storage_b, b_href_to_status))
|
list_b = dict(prepare_list(storage_b, b_href_to_status))
|
||||||
|
|
||||||
a_uid_to_href = dict((x['uid'], href) for href, x in list_a.iteritems())
|
a_uid_to_href = dict((x['uid'], href) for href, x in iteritems(list_a))
|
||||||
b_uid_to_href = dict((x['uid'], href) for href, x in list_b.iteritems())
|
b_uid_to_href = dict((x['uid'], href) for href, x in iteritems(list_b))
|
||||||
del a_href_to_status, b_href_to_status
|
del a_href_to_status, b_href_to_status
|
||||||
|
|
||||||
storages = {
|
storages = {
|
||||||
|
|
@ -175,8 +176,8 @@ def get_actions(storages, status):
|
||||||
storage_a, list_a, a_uid_to_href = storages['a']
|
storage_a, list_a, a_uid_to_href = storages['a']
|
||||||
storage_b, list_b, b_uid_to_href = storages['b']
|
storage_b, list_b, b_uid_to_href = storages['b']
|
||||||
|
|
||||||
uids_a = (x['uid'] for x in list_a.itervalues())
|
uids_a = (x['uid'] for x in itervalues(list_a))
|
||||||
uids_b = (x['uid'] for x in list_b.itervalues())
|
uids_b = (x['uid'] for x in itervalues(list_b))
|
||||||
handled = set()
|
handled = set()
|
||||||
for uid in itertools.chain(uids_a, uids_b, status):
|
for uid in itertools.chain(uids_a, uids_b, status):
|
||||||
if uid in handled:
|
if uid in handled:
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,24 @@
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import vdirsyncer.log
|
import vdirsyncer.log
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
try: # pragma: no cover
|
PY2 = sys.version_info[0] == 2
|
||||||
import urllib.parse as urlparse
|
|
||||||
except ImportError: # pragma: no cover
|
|
||||||
|
if PY2:
|
||||||
import urlparse
|
import urlparse
|
||||||
|
text_type = unicode
|
||||||
|
iteritems = lambda x: x.iteritems()
|
||||||
|
itervalues = lambda x: x.itervalues()
|
||||||
|
else:
|
||||||
|
import urllib.parse as urlparse
|
||||||
|
text_type = str
|
||||||
|
iteritems = lambda x: x.items()
|
||||||
|
itervalues = lambda x: x.values()
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -23,9 +34,6 @@ except ImportError:
|
||||||
keyring = None
|
keyring = None
|
||||||
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
password_key_prefix = 'vdirsyncer:'
|
password_key_prefix = 'vdirsyncer:'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue