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