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