Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-vdirsyncer for
openSUSE:Factory checked in at 2025-12-16 15:56:33
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-vdirsyncer (Old)
and /work/SRC/openSUSE:Factory/.python-vdirsyncer.new.1939 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-vdirsyncer"
Tue Dec 16 15:56:33 2025 rev:22 rq:1323041 version:0.20.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-vdirsyncer/python-vdirsyncer.changes
2025-07-22 12:21:25.430934796 +0200
+++
/work/SRC/openSUSE:Factory/.python-vdirsyncer.new.1939/python-vdirsyncer.changes
2025-12-16 16:02:44.303240390 +0100
@@ -1,0 +2,13 @@
+Tue Dec 16 04:25:20 UTC 2025 - Steve Kowalik <[email protected]>
+
+- Update to 0.20.0:
+ * Remove dependency on abandoned atomicwrites library.
+ * Implement filter_hook for the HTTP storage.
+ * Drop support for Python 3.7.
+ * Add support for Python 3.12 and Python 3.13.
+ * Properly close the status database after using. This especially affects
+ tests, where we were leaking a large amount of file descriptors.
+ * Extend supported versions of aiostream to include 0.7.x.
+- Drop patch support-new-pytest-asyncio.patch, no longer required.
+
+-------------------------------------------------------------------
Old:
----
support-new-pytest-asyncio.patch
vdirsyncer-0.19.3.tar.gz
New:
----
vdirsyncer-0.20.0.tar.gz
----------(Old B)----------
Old: * Extend supported versions of aiostream to include 0.7.x.
- Drop patch support-new-pytest-asyncio.patch, no longer required.
----------(Old E)----------
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-vdirsyncer.spec ++++++
--- /var/tmp/diff_new_pack.L9QhLH/_old 2025-12-16 16:02:46.011312581 +0100
+++ /var/tmp/diff_new_pack.L9QhLH/_new 2025-12-16 16:02:46.015312750 +0100
@@ -1,7 +1,7 @@
#
# spec file for package python-vdirsyncer
#
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2025 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -17,7 +17,7 @@
Name: python-vdirsyncer
-Version: 0.19.3
+Version: 0.20.0
Release: 0
Summary: CalDAV and CardDAV synchronization module
License: BSD-3-Clause
@@ -25,9 +25,7 @@
Source0:
https://files.pythonhosted.org/packages/source/v/vdirsyncer/vdirsyncer-%{version}.tar.gz
Source1: vdirsyncer.service
Source2: vdirsyncer.timer
-# PATCH-FIX-OPENSUSE Support pytest-asyncio 1.0 changes.
-Patch0: support-new-pytest-asyncio.patch
-BuildRequires: %{python_module atomicwrites}
+BuildRequires: %{python_module base >= 3.8}
BuildRequires: %{python_module pip}
BuildRequires: %{python_module setuptools_scm}
BuildRequires: %{python_module wheel}
@@ -38,7 +36,6 @@
BuildRequires: pkgconfig(systemd)
Requires: python-aiohttp
Requires: python-aiostream
-Requires: python-atomicwrites >= 0.1.7
Requires: python-click >= 5.0
Requires: python-click-log >= 0.3
Requires: python-requests >= 2.20.0
++++++ vdirsyncer-0.19.3.tar.gz -> vdirsyncer-0.20.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/.builds/archlinux-py313.yml
new/vdirsyncer-0.20.0/.builds/archlinux-py313.yml
--- old/vdirsyncer-0.19.3/.builds/archlinux-py313.yml 1970-01-01
01:00:00.000000000 +0100
+++ new/vdirsyncer-0.20.0/.builds/archlinux-py313.yml 2025-08-28
22:57:38.000000000 +0200
@@ -0,0 +1,49 @@
+# Run tests using the packaged dependencies on ArchLinux.
+
+image: archlinux
+packages:
+ - docker
+ - docker-compose
+ # Build dependencies:
+ - python-wheel
+ - python-build
+ - python-installer
+ - python-setuptools-scm
+ # Runtime dependencies:
+ - python-click
+ - python-click-log
+ - python-click-threading
+ - python-requests
+ - python-requests-toolbelt
+ - python-aiohttp-oauthlib
+ # Test dependencies:
+ - python-hypothesis
+ - python-pytest-cov
+ - python-pytest-httpserver
+ - python-trustme
+ - python-pytest-asyncio
+ - python-aiohttp
+ - python-aiostream
+ - python-aioresponses
+sources:
+ - https://github.com/pimutils/vdirsyncer
+environment:
+ BUILD: test
+ CI: true
+ CODECOV_TOKEN: b834a3c5-28fa-4808-9bdb-182210069c79
+ DAV_SERVER: radicale xandikos
+ REQUIREMENTS: release
+ # TODO: ETESYNC_TESTS
+tasks:
+ - check-python:
+ python --version | grep 'Python 3.13'
+ - docker: |
+ sudo systemctl start docker
+ - setup: |
+ cd vdirsyncer
+ python -m build --wheel --skip-dependency-check --no-isolation
+ sudo python -m installer dist/*.whl
+ - test: |
+ cd vdirsyncer
+ make -e ci-test
+ make -e ci-test-storage
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/.builds/tests-archlinux.yml
new/vdirsyncer-0.20.0/.builds/tests-archlinux.yml
--- old/vdirsyncer-0.19.3/.builds/tests-archlinux.yml 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/.builds/tests-archlinux.yml 1970-01-01
01:00:00.000000000 +0100
@@ -1,48 +0,0 @@
-# Run tests using the packaged dependencies on ArchLinux.
-
-image: archlinux
-packages:
- - docker
- - docker-compose
- # Build dependencies:
- - python-wheel
- - python-build
- - python-installer
- - python-setuptools-scm
- # Runtime dependencies:
- - python-atomicwrites
- - python-click
- - python-click-log
- - python-click-threading
- - python-requests
- - python-requests-toolbelt
- - python-aiohttp-oauthlib
- # Test dependencies:
- - python-hypothesis
- - python-pytest-cov
- - python-pytest-httpserver
- - python-trustme
- - python-pytest-asyncio
- - python-aiohttp
- - python-aiostream
- - python-aioresponses
-sources:
- - https://github.com/pimutils/vdirsyncer
-environment:
- BUILD: test
- CI: true
- CODECOV_TOKEN: b834a3c5-28fa-4808-9bdb-182210069c79
- DAV_SERVER: radicale xandikos
- REQUIREMENTS: release
- # TODO: ETESYNC_TESTS
-tasks:
- - docker: |
- sudo systemctl start docker
- - setup: |
- cd vdirsyncer
- python -m build --wheel --skip-dependency-check --no-isolation
- sudo python -m installer dist/*.whl
- - test: |
- cd vdirsyncer
- make -e ci-test
- make -e ci-test-storage
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/.builds/tests-minimal.yml
new/vdirsyncer-0.20.0/.builds/tests-minimal.yml
--- old/vdirsyncer-0.19.3/.builds/tests-minimal.yml 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/.builds/tests-minimal.yml 2025-08-28
22:57:38.000000000 +0200
@@ -3,7 +3,7 @@
# TODO: It might make more sense to test with an older Ubuntu or Fedora version
# here, and consider that our "oldest suppported environment".
-image: alpine/3.17 # python 3.10
+image: alpine/3.19 # python 3.11
packages:
- docker
- docker-cli
@@ -18,7 +18,6 @@
CODECOV_TOKEN: b834a3c5-28fa-4808-9bdb-182210069c79
DAV_SERVER: radicale xandikos
REQUIREMENTS: minimal
- # TODO: ETESYNC_TESTS
tasks:
- venv: |
python3 -m venv $HOME/venv
@@ -28,6 +27,8 @@
sudo service docker start
- setup: |
cd vdirsyncer
+ # Hack, no idea why it's needed
+ sudo ln -s /usr/include/python3.11/cpython/longintrepr.h
/usr/include/python3.11/longintrepr.h
make -e install-dev
- test: |
cd vdirsyncer
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/.envrc new/vdirsyncer-0.20.0/.envrc
--- old/vdirsyncer-0.19.3/.envrc 1970-01-01 01:00:00.000000000 +0100
+++ new/vdirsyncer-0.20.0/.envrc 2025-08-28 22:57:38.000000000 +0200
@@ -0,0 +1 @@
+layout python3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/.gitlab-ci.yml
new/vdirsyncer-0.20.0/.gitlab-ci.yml
--- old/vdirsyncer-0.19.3/.gitlab-ci.yml 2024-09-11 17:26:58.000000000
+0200
+++ new/vdirsyncer-0.20.0/.gitlab-ci.yml 1970-01-01 01:00:00.000000000
+0100
@@ -1,6 +0,0 @@
-python37:
- image: python:3.7
- before_script:
- - make -e install-dev
- script:
- - make -e ci-test
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/.pre-commit-config.yaml
new/vdirsyncer-0.20.0/.pre-commit-config.yaml
--- old/vdirsyncer-0.19.3/.pre-commit-config.yaml 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/.pre-commit-config.yaml 2025-08-28
22:57:38.000000000 +0200
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.5.0
+ rev: v5.0.0
hooks:
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
@@ -8,12 +8,8 @@
- id: check-toml
- id: check-added-large-files
- id: debug-statements
- - repo: https://github.com/psf/black
- rev: "24.2.0"
- hooks:
- - id: black
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: "v1.8.0"
+ rev: "v1.15.0"
hooks:
- id: mypy
files: vdirsyncer/.*
@@ -21,12 +17,12 @@
- types-setuptools
- types-docutils
- types-requests
- - types-atomicwrites
- repo: https://github.com/charliermarsh/ruff-pre-commit
- rev: 'v0.2.2'
+ rev: 'v0.11.4'
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
+ - id: ruff-format
- repo: local
hooks:
- id: typos-syncroniz
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/.readthedocs.yaml
new/vdirsyncer-0.20.0/.readthedocs.yaml
--- old/vdirsyncer-0.19.3/.readthedocs.yaml 1970-01-01 01:00:00.000000000
+0100
+++ new/vdirsyncer-0.20.0/.readthedocs.yaml 2025-08-28 22:57:38.000000000
+0200
@@ -0,0 +1,15 @@
+version: 2
+
+sphinx:
+ configuration: docs/conf.py
+
+build:
+ os: "ubuntu-22.04"
+ tools:
+ python: "3.9"
+
+python:
+ install:
+ - method: pip
+ path: .
+ - requirements: docs-requirements.txt
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/CHANGELOG.rst
new/vdirsyncer-0.20.0/CHANGELOG.rst
--- old/vdirsyncer-0.19.3/CHANGELOG.rst 2024-09-11 17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/CHANGELOG.rst 2025-08-28 22:57:38.000000000 +0200
@@ -9,6 +9,17 @@
may want to subscribe to `GitHub's tag feed
<https://github.com/pimutils/vdirsyncer/tags.atom>`_.
+Version 0.20.0
+==============
+
+- Remove dependency on abandoned ``atomicwrites`` library.
+- Implement ``filter_hook`` for the HTTP storage.
+- Drop support for Python 3.7.
+- Add support for Python 3.12 and Python 3.13.
+- Properly close the status database after using. This especially affects
tests,
+ where we were leaking a large amount of file descriptors.
+- Extend supported versions of ``aiostream`` to include 0.7.x.
+
Version 0.19.3
==============
@@ -18,6 +29,7 @@
- Require matching ``BEGIN`` and ``END`` lines in vobjects. :gh:`1103`
- A Docker environment for Vdirsyncer has been added `Vdirsyncer DOCKERIZED
<https://github.com/Bleala/Vdirsyncer-DOCKERIZED>`_.
- Implement digest auth. :gh:`1137`
+- Add ``filter_hook`` parameter to :storage:`http`. :gh:`1136`
Version 0.19.2
==============
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/Makefile
new/vdirsyncer-0.20.0/Makefile
--- old/vdirsyncer-0.19.3/Makefile 2024-09-11 17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/Makefile 2025-08-28 22:57:38.000000000 +0200
@@ -49,10 +49,11 @@
install-dev:
pip install -U pip setuptools wheel
- pip install -e .
- pip install -Ur test-requirements.txt -r docs-requirements.txt
pre-commit
+ pip install -e '.[test]'
+ pip install -U -r docs-requirements.txt pre-commit
set -xe && if [ "$(REQUIREMENTS)" = "minimal" ]; then \
- pip install -U --force-reinstall $$(python setup.py --quiet
minimal_requirements); \
+ pip install pyproject-dependencies && \
+ pip install -U --force-reinstall $$(pyproject-dependencies . |
sed 's/>/=/'); \
fi
.PHONY: docs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/PKG-INFO
new/vdirsyncer-0.20.0/PKG-INFO
--- old/vdirsyncer-0.19.3/PKG-INFO 2024-09-11 17:27:00.646295000 +0200
+++ new/vdirsyncer-0.20.0/PKG-INFO 2025-08-28 22:57:40.347712000 +0200
@@ -1,33 +1,42 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: vdirsyncer
-Version: 0.19.3
+Version: 0.20.0
Summary: Synchronize calendars and contacts
-Home-page: https://github.com/pimutils/vdirsyncer
-Author: Markus Unterwaditzer
-Author-email: [email protected]
-License: BSD
+Author-email: Markus Unterwaditzer <[email protected]>
+License-Expression: BSD-3-Clause
+Keywords: todo,task,icalendar,cli
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
-Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.7
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
Classifier: Topic :: Internet
+Classifier: Topic :: Office/Business :: Scheduling
Classifier: Topic :: Utilities
+Requires-Python: >=3.8
+Description-Content-Type: text/x-rst
License-File: LICENSE
-License-File: AUTHORS.rst
Requires-Dist: click<9.0,>=5.0
Requires-Dist: click-log<0.5.0,>=0.3.0
Requires-Dist: requests>=2.20.0
-Requires-Dist: atomicwrites>=0.1.7
-Requires-Dist: aiohttp<4.0.0,>=3.8.0
-Requires-Dist: aiostream<0.5.0,>=0.4.3
+Requires-Dist: aiohttp<4.0.0,>=3.8.2
+Requires-Dist: aiostream<0.8.0,>=0.4.3
Provides-Extra: google
Requires-Dist: aiohttp-oauthlib; extra == "google"
+Provides-Extra: test
+Requires-Dist: hypothesis<7.0.0,>=6.72.0; extra == "test"
+Requires-Dist: pytest; extra == "test"
+Requires-Dist: pytest-cov; extra == "test"
+Requires-Dist: pytest-httpserver; extra == "test"
+Requires-Dist: trustme; extra == "test"
+Requires-Dist: pytest-asyncio; extra == "test"
+Requires-Dist: aioresponses; extra == "test"
+Dynamic: license-file
==========
vdirsyncer
@@ -71,7 +80,7 @@
between two servers directly.
It aims to be for calendars and contacts what `OfflineIMAP
-<http://offlineimap.org/>`_ is for emails.
+<https://www.offlineimap.org/>`_ is for emails.
.. _programs: https://vdirsyncer.pimutils.org/en/latest/tutorials/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/README.rst
new/vdirsyncer-0.20.0/README.rst
--- old/vdirsyncer-0.19.3/README.rst 2024-09-11 17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/README.rst 2025-08-28 22:57:38.000000000 +0200
@@ -40,7 +40,7 @@
between two servers directly.
It aims to be for calendars and contacts what `OfflineIMAP
-<http://offlineimap.org/>`_ is for emails.
+<https://www.offlineimap.org/>`_ is for emails.
.. _programs: https://vdirsyncer.pimutils.org/en/latest/tutorials/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/vdirsyncer-0.19.3/contrib/conflict_resolution/resolve_interactively.py
new/vdirsyncer-0.20.0/contrib/conflict_resolution/resolve_interactively.py
--- old/vdirsyncer-0.19.3/contrib/conflict_resolution/resolve_interactively.py
2024-09-11 17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/contrib/conflict_resolution/resolve_interactively.py
2025-08-28 22:57:38.000000000 +0200
@@ -16,6 +16,7 @@
SPDX-FileCopyrightText: 2021 Intevation GmbH <https://intevation.de>
Author: <[email protected]>
"""
+
from __future__ import annotations
import re
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/docs/conf.py
new/vdirsyncer-0.20.0/docs/conf.py
--- old/vdirsyncer-0.19.3/docs/conf.py 2024-09-11 17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/docs/conf.py 2025-08-28 22:57:38.000000000 +0200
@@ -37,9 +37,7 @@
html_theme = "default"
if not on_rtd:
print("-" * 74)
- print(
- "Warning: sphinx-rtd-theme not installed, building with default "
"theme."
- )
+ print("Warning: sphinx-rtd-theme not installed, building with default
theme.")
print("-" * 74)
html_static_path = ["_static"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/docs/config.rst
new/vdirsyncer-0.20.0/docs/config.rst
--- old/vdirsyncer-0.19.3/docs/config.rst 2024-09-11 17:26:58.000000000
+0200
+++ new/vdirsyncer-0.20.0/docs/config.rst 2025-08-28 22:57:38.000000000
+0200
@@ -484,6 +484,7 @@
[storage holidays_remote]
type = "http"
url = https://example.com/holidays_from_hicksville.ics
+ #filter_hook = null
Too many WebCAL providers generate UIDs of all ``VEVENT``-components
on-the-fly, i.e. all UIDs change every time the calendar is downloaded.
@@ -508,3 +509,8 @@
:param auth_cert: Optional. Either a path to a certificate with a client
certificate and the key or a list of paths to the files with them.
:param useragent: Default ``vdirsyncer``.
+ :param filter_hook: Optional. A filter command to call for each fetched
+ item, passed in raw form to stdin and returned via stdout.
+ If nothing is returned by the filter command, the item is skipped.
+ This can be used to alter fields as needed when dealing with providers
+ generating malformed events.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/docs/contributing.rst
new/vdirsyncer-0.20.0/docs/contributing.rst
--- old/vdirsyncer-0.19.3/docs/contributing.rst 2024-09-11 17:26:58.000000000
+0200
+++ new/vdirsyncer-0.20.0/docs/contributing.rst 2025-08-28 22:57:38.000000000
+0200
@@ -81,7 +81,7 @@
# Install development dependencies, including:
# - vdirsyncer from the repo into the virtualenv
- # - stylecheckers (ruff) and code formatters (black)
+ # - style checks and formatting (ruff)
make install-dev
# Install git commit hook for some extra linting and checking
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/docs/installation.rst
new/vdirsyncer-0.20.0/docs/installation.rst
--- old/vdirsyncer-0.19.3/docs/installation.rst 2024-09-11 17:26:58.000000000
+0200
+++ new/vdirsyncer-0.20.0/docs/installation.rst 2025-08-28 22:57:38.000000000
+0200
@@ -42,7 +42,7 @@
use Python's package manager "pip". First, you'll have to check that the
following things are installed:
-- Python 3.7 to 3.11 and pip.
+- Python 3.8 to 3.13 and pip.
- ``libxml`` and ``libxslt``
- ``zlib``
- Linux or macOS. **Windows is not supported**, see :gh:`535`.
@@ -84,7 +84,7 @@
The dirty, easy way
~~~~~~~~~~~~~~~~~~~
-If pipx is not available on your distirbution, the easiest way to install
+If pipx is not available on your distribution, the easiest way to install
vdirsyncer at this point would be to run::
pip install --ignore-installed vdirsyncer
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/docs/packaging.rst
new/vdirsyncer-0.20.0/docs/packaging.rst
--- old/vdirsyncer-0.19.3/docs/packaging.rst 2024-09-11 17:26:58.000000000
+0200
+++ new/vdirsyncer-0.20.0/docs/packaging.rst 2025-08-28 22:57:38.000000000
+0200
@@ -46,8 +46,9 @@
make install-dev
You probably don't want this since it will use pip to download the
-dependencies. Alternatively you can find the testing dependencies in
-``test-requirements.txt``, again with lower-bound version requirements.
+dependencies. Alternatively test dependencies are listed as ``test`` optional
+dependencies in ``pyproject.toml``, again with lower-bound version
+requirements.
You also have to have vdirsyncer fully installed at this point. Merely
``cd``-ing into the tarball will not be sufficient.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/docs/when.rst
new/vdirsyncer-0.20.0/docs/when.rst
--- old/vdirsyncer-0.19.3/docs/when.rst 2024-09-11 17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/docs/when.rst 2025-08-28 22:57:38.000000000 +0200
@@ -50,7 +50,6 @@
* Such a setup doesn't work at all with smartphones. Vdirsyncer, on the other
hand, synchronizes with CardDAV/CalDAV servers, which can be accessed with
- e.g. DAVx⁵_ or the apps by dmfs_.
+ e.g. DAVx⁵_ or other apps bundled with smartphones.
.. _DAVx⁵: https://www.davx5.com/
-.. _dmfs: https://dmfs.org/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/pyproject.toml
new/vdirsyncer-0.20.0/pyproject.toml
--- old/vdirsyncer-0.19.3/pyproject.toml 2024-09-11 17:26:58.000000000
+0200
+++ new/vdirsyncer-0.20.0/pyproject.toml 2025-08-28 22:57:38.000000000
+0200
@@ -1,18 +1,75 @@
-[tool.ruff]
-select = [
+# Vdirsyncer synchronizes calendars and contacts.
+#
+# Please refer to https://vdirsyncer.pimutils.org/en/stable/packaging.html for
+# how to package vdirsyncer.
+
+[build-system]
+requires = ["setuptools>=64", "setuptools_scm>=8"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "vdirsyncer"
+authors = [
+ {name = "Markus Unterwaditzer", email = "[email protected]"},
+]
+description = "Synchronize calendars and contacts"
+readme = "README.rst"
+requires-python = ">=3.8"
+keywords = ["todo", "task", "icalendar", "cli"]
+license = "BSD-3-Clause"
+license-files = ["LICENSE"]
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "Environment :: Console",
+ "Operating System :: POSIX",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Topic :: Internet",
+ "Topic :: Office/Business :: Scheduling",
+ "Topic :: Utilities",
+]
+dependencies = [
+ "click>=5.0,<9.0",
+ "click-log>=0.3.0,<0.5.0",
+ "requests>=2.20.0",
+ "aiohttp>=3.8.2,<4.0.0",
+ "aiostream>=0.4.3,<0.8.0",
+]
+dynamic = ["version"]
+
+[project.optional-dependencies]
+google = ["aiohttp-oauthlib"]
+test = [
+ "hypothesis>=6.72.0,<7.0.0",
+ "pytest",
+ "pytest-cov",
+ "pytest-httpserver",
+ "trustme",
+ "pytest-asyncio",
+ "aioresponses",
+]
+
+[project.scripts]
+vdirsyncer = "vdirsyncer.cli:app"
+
+[tool.lint.ruff]
+extend-select = [
"E",
- "F",
"W",
"B0",
"I",
"UP",
"C4",
- # "TID",
+ "TID",
"RSE"
]
-target-version = "py37"
-[tool.ruff.isort]
+[tool.ruff.lint.isort]
force-single-line = true
required-imports = ["from __future__ import annotations"]
@@ -26,6 +83,7 @@
--color=yes
"""
# filterwarnings=error
+asyncio_default_fixture_loop_scope = "function"
[tool.mypy]
ignore_missing_imports = true
@@ -34,3 +92,10 @@
exclude_lines = [
"if TYPE_CHECKING:",
]
+
+[tool.setuptools.packages.find]
+include = ["vdirsyncer*"]
+
+[tool.setuptools_scm]
+write_to = "vdirsyncer/version.py"
+version_scheme = "no-guess-dev"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/setup.py
new/vdirsyncer-0.20.0/setup.py
--- old/vdirsyncer-0.19.3/setup.py 2024-09-11 17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/setup.py 1970-01-01 01:00:00.000000000 +0100
@@ -1,82 +0,0 @@
-"""
-Vdirsyncer synchronizes calendars and contacts.
-
-Please refer to https://vdirsyncer.pimutils.org/en/stable/packaging.html for
-how to package vdirsyncer.
-"""
-
-from __future__ import annotations
-
-from setuptools import Command
-from setuptools import find_packages
-from setuptools import setup
-
-requirements = [
- # https://github.com/mitsuhiko/click/issues/200
- "click>=5.0,<9.0",
- "click-log>=0.3.0, <0.5.0",
- "requests >=2.20.0",
- #
https://github.com/untitaker/python-atomicwrites/commit/4d12f23227b6a944ab1d99c507a69fdbc7c9ed6d
# noqa
- "atomicwrites>=0.1.7",
- "aiohttp>=3.8.0,<4.0.0",
- "aiostream>=0.4.3,<0.5.0",
-]
-
-
-class PrintRequirements(Command):
- description = "Prints minimal requirements"
- user_options: list = []
-
- def initialize_options(self):
- pass
-
- def finalize_options(self):
- pass
-
- def run(self):
- for requirement in requirements:
- print(requirement.replace(">", "=").replace(" ", ""))
-
-
-with open("README.rst") as f:
- long_description = f.read()
-
-
-setup(
- # General metadata
- name="vdirsyncer",
- author="Markus Unterwaditzer",
- author_email="[email protected]",
- url="https://github.com/pimutils/vdirsyncer",
- description="Synchronize calendars and contacts",
- license="BSD",
- long_description=long_description,
- # Runtime dependencies
- install_requires=requirements,
- # Optional dependencies
- extras_require={
- "google": ["aiohttp-oauthlib"],
- },
- # Build dependencies
- setup_requires=["setuptools_scm != 1.12.0"],
- # Other
- packages=find_packages(exclude=["tests.*", "tests"]),
- include_package_data=True,
- cmdclass={"minimal_requirements": PrintRequirements},
- use_scm_version={"write_to": "vdirsyncer/version.py"},
- entry_points={"console_scripts": ["vdirsyncer = vdirsyncer.cli:app"]},
- classifiers=[
- "Development Status :: 4 - Beta",
- "Environment :: Console",
- "License :: OSI Approved :: BSD License",
- "Operating System :: POSIX",
- "Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.7",
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- "Topic :: Internet",
- "Topic :: Utilities",
- ],
-)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/test-requirements.txt
new/vdirsyncer-0.20.0/test-requirements.txt
--- old/vdirsyncer-0.19.3/test-requirements.txt 2024-09-11 17:26:58.000000000
+0200
+++ new/vdirsyncer-0.20.0/test-requirements.txt 1970-01-01 01:00:00.000000000
+0100
@@ -1,7 +0,0 @@
-hypothesis>=5.0.0,<7.0.0
-pytest
-pytest-cov
-pytest-httpserver
-trustme
-pytest-asyncio
-aioresponses
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/tests/__init__.py
new/vdirsyncer-0.20.0/tests/__init__.py
--- old/vdirsyncer-0.19.3/tests/__init__.py 2024-09-11 17:26:58.000000000
+0200
+++ new/vdirsyncer-0.20.0/tests/__init__.py 2025-08-28 22:57:38.000000000
+0200
@@ -103,10 +103,8 @@
HAHA:YES
END:FOO"""
-printable_characters_strategy = st.text(
- st.characters(blacklist_categories=("Cc", "Cs"))
-)
+printable_characters_strategy =
st.text(st.characters(exclude_categories=("Cc", "Cs")))
uid_strategy = st.text(
- st.characters(blacklist_categories=("Zs", "Zl", "Zp", "Cc", "Cs")),
min_size=1
+ st.characters(exclude_categories=("Zs", "Zl", "Zp", "Cc", "Cs")),
min_size=1
).filter(lambda x: x.strip() == x)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/tests/conftest.py
new/vdirsyncer-0.20.0/tests/conftest.py
--- old/vdirsyncer-0.19.3/tests/conftest.py 2024-09-11 17:26:58.000000000
+0200
+++ new/vdirsyncer-0.20.0/tests/conftest.py 2025-08-28 22:57:38.000000000
+0200
@@ -45,7 +45,7 @@
"deterministic",
settings(
derandomize=True,
- suppress_health_check=HealthCheck.all(),
+ suppress_health_check=list(HealthCheck),
),
)
settings.register_profile("dev",
settings(suppress_health_check=[HealthCheck.too_slow]))
@@ -59,12 +59,12 @@
@pytest_asyncio.fixture
-async def aio_session(event_loop):
+async def aio_session():
async with aiohttp.ClientSession() as session:
yield session
@pytest_asyncio.fixture
-async def aio_connector(event_loop):
+async def aio_connector():
async with aiohttp.TCPConnector(limit_per_host=16) as conn:
yield conn
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/tests/system/cli/test_sync.py
new/vdirsyncer-0.20.0/tests/system/cli/test_sync.py
--- old/vdirsyncer-0.19.3/tests/system/cli/test_sync.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/tests/system/cli/test_sync.py 2025-08-28
22:57:38.000000000 +0200
@@ -90,9 +90,7 @@
result = runner.invoke(["sync"])
lines = result.output.splitlines()
assert lines[0] == "Syncing my_pair"
- assert lines[1].startswith(
- "error: my_pair: " 'Storage "my_b" was completely emptied.'
- )
+ assert lines[1].startswith('error: my_pair: Storage "my_b" was completely
emptied.')
assert result.exception
@@ -553,9 +551,7 @@
type = "filesystem"
path = "{path}"
fileext.fetch = ["command", "sh", "{script}"]
- """.format(
- path=str(tmpdir.mkdir("bogus")), script=str(fetch_script)
- )
+ """.format(path=str(tmpdir.mkdir("bogus")), script=str(fetch_script))
)
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/tests/unit/sync/test_status.py
new/vdirsyncer-0.20.0/tests/unit/sync/test_status.py
--- old/vdirsyncer-0.19.3/tests/unit/sync/test_status.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/tests/unit/sync/test_status.py 2025-08-28
22:57:38.000000000 +0200
@@ -34,3 +34,5 @@
assert meta2_a.to_status() == meta_a
assert meta2_b.to_status() == meta_b
assert ident_a == ident_b == ident
+
+ status.close()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/tests/unit/sync/test_sync.py
new/vdirsyncer-0.20.0/tests/unit/sync/test_sync.py
--- old/vdirsyncer-0.19.3/tests/unit/sync/test_sync.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/tests/unit/sync/test_sync.py 2025-08-28
22:57:38.000000000 +0200
@@ -1,6 +1,7 @@
from __future__ import annotations
import asyncio
+import contextlib
from copy import deepcopy
import aiostream
@@ -25,13 +26,12 @@
from vdirsyncer.vobject import Item
-async def sync(a, b, status, *args, **kwargs):
- new_status = SqliteStatus(":memory:")
- new_status.load_legacy_status(status)
- rv = await _sync(a, b, new_status, *args, **kwargs)
- status.clear()
- status.update(new_status.to_legacy_status())
- return rv
+async def sync(a, b, status, *args, **kwargs) -> None:
+ with contextlib.closing(SqliteStatus(":memory:")) as new_status:
+ new_status.load_legacy_status(status)
+ await _sync(a, b, new_status, *args, **kwargs)
+ status.clear()
+ status.update(new_status.to_legacy_status())
def empty_storage(x):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/tests/unit/test_metasync.py
new/vdirsyncer-0.20.0/tests/unit/test_metasync.py
--- old/vdirsyncer-0.19.3/tests/unit/test_metasync.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/tests/unit/test_metasync.py 2025-08-28
22:57:38.000000000 +0200
@@ -1,5 +1,7 @@
from __future__ import annotations
+import asyncio
+
import hypothesis.strategies as st
import pytest
import pytest_asyncio
@@ -56,23 +58,19 @@
@pytest_asyncio.fixture
[email protected]
-async def conflict_state(request, event_loop):
+async def conflict_state(request):
a = MemoryStorage()
b = MemoryStorage()
status = {}
await a.set_meta("foo", "bar")
await b.set_meta("foo", "baz")
- def cleanup():
- async def do_cleanup():
- assert await a.get_meta("foo") == "bar"
- assert await b.get_meta("foo") == "baz"
- assert not status
-
- event_loop.run_until_complete(do_cleanup())
+ async def do_cleanup():
+ assert await a.get_meta("foo") == "bar"
+ assert await b.get_meta("foo") == "baz"
+ assert not status
- request.addfinalizer(cleanup)
+ request.addfinalizer(lambda: asyncio.run(do_cleanup()))
return a, b, status
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/tests/unit/test_repair.py
new/vdirsyncer-0.20.0/tests/unit/test_repair.py
--- old/vdirsyncer-0.19.3/tests/unit/test_repair.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/tests/unit/test_repair.py 2025-08-28
22:57:38.000000000 +0200
@@ -17,7 +17,7 @@
@given(uid=uid_strategy)
# Using the random module for UIDs:
-@settings(suppress_health_check=HealthCheck.all())
+@settings(suppress_health_check=list(HealthCheck))
@pytest.mark.asyncio
async def test_repair_uids(uid):
s = MemoryStorage()
@@ -40,7 +40,7 @@
@given(uid=uid_strategy.filter(lambda x: not href_safe(x)))
# Using the random module for UIDs:
-@settings(suppress_health_check=HealthCheck.all())
+@settings(suppress_health_check=list(HealthCheck))
@pytest.mark.asyncio
async def test_repair_unsafe_uids(uid):
s = MemoryStorage()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/tests/unit/utils/test_vobject.py
new/vdirsyncer-0.20.0/tests/unit/utils/test_vobject.py
--- old/vdirsyncer-0.19.3/tests/unit/utils/test_vobject.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/tests/unit/utils/test_vobject.py 2025-08-28
22:57:38.000000000 +0200
@@ -154,7 +154,7 @@
def test_multiline_uid(benchmark):
- a = "BEGIN:FOO\r\n" "UID:123456789abcd\r\n" " efgh\r\n" "END:FOO\r\n"
+ a = "BEGIN:FOO\r\nUID:123456789abcd\r\n efgh\r\nEND:FOO\r\n"
assert benchmark(lambda: vobject.Item(a).uid) == "123456789abcdefgh"
@@ -299,7 +299,7 @@
value_strategy = st.text(
st.characters(
- blacklist_categories=("Zs", "Zl", "Zp", "Cc", "Cs"),
blacklist_characters=":="
+ exclude_categories=("Zs", "Zl", "Zp", "Cc", "Cs"),
exclude_characters=":="
),
min_size=1,
).filter(lambda x: x.strip() == x)
@@ -365,6 +365,16 @@
TestVobjectMachine = VobjectMachine.TestCase
+def test_dupe_consecutive_keys():
+ state = VobjectMachine()
+ unparsed_0 = state.get_unparsed_lines(encoded=False, joined=False)
+ parsed_0 = state.parse(unparsed=unparsed_0)
+ state.add_prop_raw(c=parsed_0, key="0", params=[], value="0")
+ state.add_prop_raw(c=parsed_0, key="0", params=[], value="0")
+ state.add_prop(c=parsed_0, key="0", value="1")
+ state.teardown()
+
+
def test_component_contains():
item = vobject._Component.parse(["BEGIN:FOO", "FOO:YES", "END:FOO"])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/__init__.py
new/vdirsyncer-0.20.0/vdirsyncer/__init__.py
--- old/vdirsyncer-0.19.3/vdirsyncer/__init__.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/__init__.py 2025-08-28
22:57:38.000000000 +0200
@@ -21,8 +21,8 @@
def _check_python_version():
import sys
- if sys.version_info < (3, 7, 0): # noqa: UP036
- print("vdirsyncer requires at least Python 3.7.")
+ if sys.version_info < (3, 8, 0): # noqa: UP036
+ print("vdirsyncer requires at least Python 3.8.")
sys.exit(1)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/cli/config.py
new/vdirsyncer-0.20.0/vdirsyncer/cli/config.py
--- old/vdirsyncer-0.19.3/vdirsyncer/cli/config.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/cli/config.py 2025-08-28
22:57:38.000000000 +0200
@@ -8,6 +8,7 @@
from typing import IO
from typing import Any
from typing import Generator
+from typing import Tuple
from .. import PROJECT_HOME
from .. import exceptions
@@ -231,7 +232,7 @@
self.name_b: str = options.pop("b")
self._partial_sync: str | None = options.pop("partial_sync", None)
- self.metadata = options.pop("metadata", None) or ()
+ self.metadata: str | Tuple[()] = options.pop("metadata", ())
self.conflict_resolution = self._process_conflict_resolution_param(
options.pop("conflict_resolution", None)
@@ -359,7 +360,7 @@
new_b = f.read()
if new_a != new_b:
- raise exceptions.UserError("The two files are not completely "
"equal.")
+ raise exceptions.UserError("The two files are not completely
equal.")
return Item(new_a)
finally:
shutil.rmtree(dir)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/cli/discover.py
new/vdirsyncer-0.20.0/vdirsyncer/cli/discover.py
--- old/vdirsyncer-0.19.3/vdirsyncer/cli/discover.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/cli/discover.py 2025-08-28
22:57:38.000000000 +0200
@@ -72,8 +72,7 @@
)
else:
raise exceptions.UserError(
- f"Please run `vdirsyncer discover {pair.name}` "
- " before synchronization."
+ f"Please run `vdirsyncer discover {pair.name}` before
synchronization."
)
logger.info(f"Discovering collections for pair {pair.name}")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/cli/utils.py
new/vdirsyncer-0.20.0/vdirsyncer/cli/utils.py
--- old/vdirsyncer-0.19.3/vdirsyncer/cli/utils.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/cli/utils.py 2025-08-28
22:57:38.000000000 +0200
@@ -10,7 +10,6 @@
import aiohttp
import click
-from atomicwrites import atomic_write
from .. import BUGTRACKER_HOME
from .. import DOCS_HOME
@@ -21,6 +20,7 @@
from ..sync.exceptions import StorageEmpty
from ..sync.exceptions import SyncConflict
from ..sync.status import SqliteStatus
+from ..utils import atomic_write
from ..utils import expand_path
from ..utils import get_storage_init_args
from . import cli_logger
@@ -232,7 +232,8 @@
prepare_status_path(path)
status = SqliteStatus(path)
- yield status
+ with contextlib.closing(status):
+ yield status
def save_status(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/http.py
new/vdirsyncer-0.20.0/vdirsyncer/http.py
--- old/vdirsyncer-0.19.3/vdirsyncer/http.py 2024-09-11 17:26:58.000000000
+0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/http.py 2025-08-28 22:57:38.000000000
+0200
@@ -1,8 +1,11 @@
from __future__ import annotations
import logging
+import os
+import platform
import re
-from abc import ABC, abstractmethod
+from abc import ABC
+from abc import abstractmethod
from base64 import b64encode
from ssl import create_default_context
@@ -17,6 +20,13 @@
logger = logging.getLogger(__name__)
USERAGENT = f"vdirsyncer/{__version__}"
+# 'hack' to prevent aiohttp from loading the netrc config,
+# but still allow it to read PROXY_* env vars.
+# Otherwise, if our host is defined in the netrc config,
+# aiohttp will overwrite our Authorization header.
+# https://github.com/pimutils/vdirsyncer/issues/1138
+os.environ["NETRC"] = "NUL" if platform.system() == "Windows" else "/dev/null"
+
class AuthMethod(ABC):
def __init__(self, username, password):
@@ -34,7 +44,11 @@
def __eq__(self, other):
if not isinstance(other, AuthMethod):
return False
- return self.__class__ == other.__class__ and self.username ==
other.username and self.password == other.password
+ return (
+ self.__class__ == other.__class__
+ and self.username == other.username
+ and self.password == other.password
+ )
class BasicAuthMethod(AuthMethod):
@@ -43,20 +57,19 @@
def get_auth_header(self, _method, _url):
auth_str = f"{self.username}:{self.password}"
- return "Basic " + b64encode(auth_str.encode('utf-8')).decode("utf-8")
+ return "Basic " + b64encode(auth_str.encode("utf-8")).decode("utf-8")
class DigestAuthMethod(AuthMethod):
# make class var to 'cache' the state, which is more efficient because
otherwise
# each request would first require another 'initialization' request.
- _auth_helpers = {}
+ _auth_helpers: dict[tuple[str, str], requests.auth.HTTPDigestAuth] = {}
- def __init__(self, username, password):
+ def __init__(self, username: str, password: str):
super().__init__(username, password)
self._auth_helper = self._auth_helpers.get(
- (username, password),
- requests.auth.HTTPDigestAuth(username, password)
+ (username, password), requests.auth.HTTPDigestAuth(username,
password)
)
self._auth_helpers[(username, password)] = self._auth_helper
@@ -78,7 +91,7 @@
if not self.auth_helper_vars.chal:
# Need to do init request first
- return ''
+ return ""
return self._auth_helper.build_digest_header(method, url)
@@ -90,10 +103,12 @@
elif auth == "digest":
return DigestAuthMethod(username, password)
elif auth == "guess":
- raise exceptions.UserError(f"'Guess' authentication is not
supported in this version of vdirsyncer. \n"
- f"Please explicitly specify either
'basic' or 'digest' auth instead. \n"
- f"See the following issue for more
information: "
-
f"https://github.com/pimutils/vdirsyncer/issues/1015")
+ raise exceptions.UserError(
+ "'Guess' authentication is not supported in this version of
vdirsyncer. \n"
+ "Please explicitly specify either 'basic' or 'digest' auth
instead. \n"
+ "See the following issue for more information: "
+ "https://github.com/pimutils/vdirsyncer/issues/1015"
+ )
else:
raise exceptions.UserError(f"Unknown authentication method:
{auth}")
elif auth:
@@ -208,6 +223,10 @@
logger.debug(response.headers)
logger.debug(response.content)
+ if logger.getEffectiveLevel() <= logging.DEBUG and response.status >= 400:
+ # https://github.com/pimutils/vdirsyncer/issues/1186
+ logger.debug(await response.text())
+
if response.status == 412:
raise exceptions.PreconditionFailed(response.reason)
if response.status in (404, 410):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/repair.py
new/vdirsyncer-0.20.0/vdirsyncer/repair.py
--- old/vdirsyncer-0.19.3/vdirsyncer/repair.py 2024-09-11 17:26:58.000000000
+0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/repair.py 2025-08-28 22:57:38.000000000
+0200
@@ -56,9 +56,7 @@
new_item = item.with_uid(generate_href())
elif not href_safe(item.uid) or not href_safe(basename(href)):
if not repair_unsafe_uid:
- logger.warning(
- "UID may cause problems, add " "--repair-unsafe-uid to repair."
- )
+ logger.warning("UID may cause problems, add --repair-unsafe-uid to
repair.")
else:
logger.warning("UID or href is unsafe, assigning random UID.")
new_item = item.with_uid(generate_href())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/storage/dav.py
new/vdirsyncer-0.20.0/vdirsyncer/storage/dav.py
--- old/vdirsyncer-0.19.3/vdirsyncer/storage/dav.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/storage/dav.py 2025-08-28
22:57:38.000000000 +0200
@@ -261,7 +261,7 @@
href = response.find("{DAV:}href")
if href is None:
- raise InvalidXMLResponse("Missing href tag for collection "
"props.")
+ raise InvalidXMLResponse("Missing href tag for collection
props.")
href = urlparse.urljoin(str(r.url), href.text)
if href not in done:
done.add(href)
@@ -310,9 +310,7 @@
</mkcol>
""".format(
etree.tostring(etree.Element(self._resourcetype),
encoding="unicode")
- ).encode(
- "utf-8"
- )
+ ).encode("utf-8")
response = await self.session.request(
"MKCOL",
@@ -740,9 +738,7 @@
""".format(
etree.tostring(element, encoding="unicode"),
action=action,
- ).encode(
- "utf-8"
- )
+ ).encode("utf-8")
await self.session.request(
"PROPPATCH",
@@ -796,7 +792,7 @@
self.item_types = tuple(item_types)
if (start_date is None) != (end_date is None):
raise exceptions.UserError(
- "If start_date is given, " "end_date has to be given too."
+ "If start_date is given, end_date has to be given too."
)
elif start_date is not None and end_date is not None:
namespace = dict(datetime.__dict__)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/storage/filesystem.py
new/vdirsyncer-0.20.0/vdirsyncer/storage/filesystem.py
--- old/vdirsyncer-0.19.3/vdirsyncer/storage/filesystem.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/storage/filesystem.py 2025-08-28
22:57:38.000000000 +0200
@@ -5,9 +5,8 @@
import os
import subprocess
-from atomicwrites import atomic_write
-
from .. import exceptions
+from ..utils import atomic_write
from ..utils import checkdir
from ..utils import expand_path
from ..utils import generate_href
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/storage/google.py
new/vdirsyncer-0.20.0/vdirsyncer/storage/google.py
--- old/vdirsyncer-0.19.3/vdirsyncer/storage/google.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/storage/google.py 2025-08-28
22:57:38.000000000 +0200
@@ -11,9 +11,9 @@
import aiohttp
import click
-from atomicwrites import atomic_write
from .. import exceptions
+from ..utils import atomic_write
from ..utils import checkdir
from ..utils import expand_path
from ..utils import open_graphical_browser
@@ -98,6 +98,7 @@
token_updater=self._save_token,
connector=self.connector,
connector_owner=False,
+ trust_env=True,
)
async def _init_token(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/storage/http.py
new/vdirsyncer-0.20.0/vdirsyncer/storage/http.py
--- old/vdirsyncer-0.19.3/vdirsyncer/storage/http.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/storage/http.py 2025-08-28
22:57:38.000000000 +0200
@@ -1,5 +1,7 @@
from __future__ import annotations
+import logging
+import subprocess
import urllib.parse as urlparse
import aiohttp
@@ -14,6 +16,8 @@
from ..vobject import split_collection
from .base import Storage
+logger = logging.getLogger(__name__)
+
class HttpStorage(Storage):
storage_name = "http"
@@ -34,6 +38,7 @@
useragent=USERAGENT,
verify_fingerprint=None,
auth_cert=None,
+ filter_hook=None,
*,
connector,
**kwargs,
@@ -56,6 +61,7 @@
self.useragent = useragent
assert connector is not None
self.connector = connector
+ self._filter_hook = filter_hook
collection = kwargs.get("collection")
if collection is not None:
@@ -66,6 +72,19 @@
def _default_headers(self):
return {"User-Agent": self.useragent}
+ def _run_filter_hook(self, raw_item):
+ try:
+ result = subprocess.run(
+ [self._filter_hook],
+ input=raw_item,
+ capture_output=True,
+ encoding="utf-8",
+ )
+ return result.stdout
+ except OSError as e:
+ logger.warning(f"Error executing external command: {str(e)}")
+ return raw_item
+
async def list(self):
async with aiohttp.ClientSession(
connector=self.connector,
@@ -82,8 +101,13 @@
)
self._items = {}
- for item in split_collection((await r.read()).decode("utf-8")):
- item = Item(item)
+ for raw_item in split_collection((await r.read()).decode("utf-8")):
+ if self._filter_hook:
+ raw_item = self._run_filter_hook(raw_item)
+ if not raw_item:
+ continue
+
+ item = Item(raw_item)
if self._ignore_uids:
item = item.with_uid(item.hash)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/storage/singlefile.py
new/vdirsyncer-0.20.0/vdirsyncer/storage/singlefile.py
--- old/vdirsyncer-0.19.3/vdirsyncer/storage/singlefile.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/storage/singlefile.py 2025-08-28
22:57:38.000000000 +0200
@@ -8,9 +8,8 @@
import os
from typing import Iterable
-from atomicwrites import atomic_write
-
from .. import exceptions
+from ..utils import atomic_write
from ..utils import checkfile
from ..utils import expand_path
from ..utils import get_etag_from_file
@@ -95,7 +94,7 @@
path = path % (collection,)
except TypeError:
raise ValueError(
- "Exactly one %s required in path " "if collection is not
null."
+ "Exactly one %s required in path if collection is not
null."
)
checkfile(path, create=True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/sync/status.py
new/vdirsyncer-0.20.0/vdirsyncer/sync/status.py
--- old/vdirsyncer-0.19.3/vdirsyncer/sync/status.py 2024-09-11
17:26:58.000000000 +0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/sync/status.py 2025-08-28
22:57:38.000000000 +0200
@@ -169,6 +169,11 @@
); """
)
+ def close(self):
+ if self._c:
+ self._c.close()
+ self._c = None
+
def _is_latest_version(self):
try:
return bool(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/utils.py
new/vdirsyncer-0.20.0/vdirsyncer/utils.py
--- old/vdirsyncer-0.19.3/vdirsyncer/utils.py 2024-09-11 17:26:58.000000000
+0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/utils.py 2025-08-28 22:57:38.000000000
+0200
@@ -1,8 +1,10 @@
from __future__ import annotations
+import contextlib
import functools
import os
import sys
+import tempfile
import uuid
from inspect import getfullargspec
from typing import Callable
@@ -125,12 +127,13 @@
raise exceptions.CollectionNotFound(f"Directory {path} does not
exist.")
-def checkfile(path, create=False):
- """
- Check whether ``path`` is a file.
+def checkfile(path, create=False) -> None:
+ """Check whether ``path`` is a file.
:param create: Whether to create the file's parent directories if they do
not exist.
+ :raises CollectionNotFound: if path does not exist.
+ :raises OSError: if path exists but is not a file.
"""
checkdir(os.path.dirname(path), create=create)
if not os.path.isfile(path):
@@ -208,7 +211,7 @@
cli_names = {"www-browser", "links", "links2", "elinks", "lynx", "w3m"}
- if webbrowser._tryorder is None: # Python 3.7
+ if webbrowser._tryorder is None: # Python 3.8
webbrowser.register_standard_browsers()
for name in webbrowser._tryorder:
@@ -219,4 +222,28 @@
if browser.open(url, new, autoraise):
return
- raise RuntimeError("No graphical browser found. Please open the URL "
"manually.")
+ raise RuntimeError("No graphical browser found. Please open the URL
manually.")
+
+
[email protected]
+def atomic_write(dest, mode="wb", overwrite=False):
+ if "w" not in mode:
+ raise RuntimeError("`atomic_write` requires write access")
+
+ fd, src = tempfile.mkstemp(prefix=os.path.basename(dest),
dir=os.path.dirname(dest))
+ file = os.fdopen(fd, mode=mode)
+
+ try:
+ yield file
+ except Exception:
+ os.unlink(src)
+ raise
+ else:
+ file.flush()
+ file.close()
+
+ if overwrite:
+ os.rename(src, dest)
+ else:
+ os.link(src, dest)
+ os.unlink(src)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/version.py
new/vdirsyncer-0.20.0/vdirsyncer/version.py
--- old/vdirsyncer-0.19.3/vdirsyncer/version.py 2024-09-11 17:27:00.000000000
+0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/version.py 2025-08-28 22:57:40.000000000
+0200
@@ -1,16 +1,34 @@
-# file generated by setuptools_scm
+# file generated by setuptools-scm
# don't change, don't track in version control
+
+__all__ = [
+ "__version__",
+ "__version_tuple__",
+ "version",
+ "version_tuple",
+ "__commit_id__",
+ "commit_id",
+]
+
TYPE_CHECKING = False
if TYPE_CHECKING:
- from typing import Tuple, Union
+ from typing import Tuple
+ from typing import Union
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
+ COMMIT_ID = Union[str, None]
else:
VERSION_TUPLE = object
+ COMMIT_ID = object
version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
+commit_id: COMMIT_ID
+__commit_id__: COMMIT_ID
+
+__version__ = version = '0.20.0'
+__version_tuple__ = version_tuple = (0, 20, 0)
-__version__ = version = '0.19.3'
-__version_tuple__ = version_tuple = (0, 19, 3)
+__commit_id__ = commit_id = 'g8803d5a08'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer/vobject.py
new/vdirsyncer-0.20.0/vdirsyncer/vobject.py
--- old/vdirsyncer-0.19.3/vdirsyncer/vobject.py 2024-09-11 17:26:58.000000000
+0200
+++ new/vdirsyncer-0.20.0/vdirsyncer/vobject.py 2025-08-28 22:57:38.000000000
+0200
@@ -329,7 +329,7 @@
break
for line in lineiter:
- if not line.startswith((" ", "\t")):
+ if not line.startswith((" ", "\t", *prefix)):
new_lines.append(line)
break
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer.egg-info/PKG-INFO
new/vdirsyncer-0.20.0/vdirsyncer.egg-info/PKG-INFO
--- old/vdirsyncer-0.19.3/vdirsyncer.egg-info/PKG-INFO 2024-09-11
17:27:00.000000000 +0200
+++ new/vdirsyncer-0.20.0/vdirsyncer.egg-info/PKG-INFO 2025-08-28
22:57:40.000000000 +0200
@@ -1,33 +1,42 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
Name: vdirsyncer
-Version: 0.19.3
+Version: 0.20.0
Summary: Synchronize calendars and contacts
-Home-page: https://github.com/pimutils/vdirsyncer
-Author: Markus Unterwaditzer
-Author-email: [email protected]
-License: BSD
+Author-email: Markus Unterwaditzer <[email protected]>
+License-Expression: BSD-3-Clause
+Keywords: todo,task,icalendar,cli
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
-Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.7
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
Classifier: Topic :: Internet
+Classifier: Topic :: Office/Business :: Scheduling
Classifier: Topic :: Utilities
+Requires-Python: >=3.8
+Description-Content-Type: text/x-rst
License-File: LICENSE
-License-File: AUTHORS.rst
Requires-Dist: click<9.0,>=5.0
Requires-Dist: click-log<0.5.0,>=0.3.0
Requires-Dist: requests>=2.20.0
-Requires-Dist: atomicwrites>=0.1.7
-Requires-Dist: aiohttp<4.0.0,>=3.8.0
-Requires-Dist: aiostream<0.5.0,>=0.4.3
+Requires-Dist: aiohttp<4.0.0,>=3.8.2
+Requires-Dist: aiostream<0.8.0,>=0.4.3
Provides-Extra: google
Requires-Dist: aiohttp-oauthlib; extra == "google"
+Provides-Extra: test
+Requires-Dist: hypothesis<7.0.0,>=6.72.0; extra == "test"
+Requires-Dist: pytest; extra == "test"
+Requires-Dist: pytest-cov; extra == "test"
+Requires-Dist: pytest-httpserver; extra == "test"
+Requires-Dist: trustme; extra == "test"
+Requires-Dist: pytest-asyncio; extra == "test"
+Requires-Dist: aioresponses; extra == "test"
+Dynamic: license-file
==========
vdirsyncer
@@ -71,7 +80,7 @@
between two servers directly.
It aims to be for calendars and contacts what `OfflineIMAP
-<http://offlineimap.org/>`_ is for emails.
+<https://www.offlineimap.org/>`_ is for emails.
.. _programs: https://vdirsyncer.pimutils.org/en/latest/tutorials/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer.egg-info/SOURCES.txt
new/vdirsyncer-0.20.0/vdirsyncer.egg-info/SOURCES.txt
--- old/vdirsyncer-0.19.3/vdirsyncer.egg-info/SOURCES.txt 2024-09-11
17:27:00.000000000 +0200
+++ new/vdirsyncer-0.20.0/vdirsyncer.egg-info/SOURCES.txt 2025-08-28
22:57:40.000000000 +0200
@@ -1,8 +1,9 @@
.codecov.yml
.coveragerc
+.envrc
.gitignore
-.gitlab-ci.yml
.pre-commit-config.yaml
+.readthedocs.yaml
AUTHORS.rst
CHANGELOG.rst
CODE_OF_CONDUCT.rst
@@ -16,9 +17,7 @@
docs-requirements.txt
publish-release.yaml
pyproject.toml
-setup.py
-test-requirements.txt
-.builds/tests-archlinux.yml
+.builds/archlinux-py313.yml
.builds/tests-minimal.yml
.builds/tests-pypi.yml
contrib/vdirsyncer.service
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/vdirsyncer-0.19.3/vdirsyncer.egg-info/requires.txt
new/vdirsyncer-0.20.0/vdirsyncer.egg-info/requires.txt
--- old/vdirsyncer-0.19.3/vdirsyncer.egg-info/requires.txt 2024-09-11
17:27:00.000000000 +0200
+++ new/vdirsyncer-0.20.0/vdirsyncer.egg-info/requires.txt 2025-08-28
22:57:40.000000000 +0200
@@ -1,9 +1,17 @@
click<9.0,>=5.0
click-log<0.5.0,>=0.3.0
requests>=2.20.0
-atomicwrites>=0.1.7
-aiohttp<4.0.0,>=3.8.0
-aiostream<0.5.0,>=0.4.3
+aiohttp<4.0.0,>=3.8.2
+aiostream<0.8.0,>=0.4.3
[google]
aiohttp-oauthlib
+
+[test]
+hypothesis<7.0.0,>=6.72.0
+pytest
+pytest-cov
+pytest-httpserver
+trustme
+pytest-asyncio
+aioresponses