NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #5345 most downloaded on PyPI
CalDAV (RFC4791) client library
Last release 18 days ago
16 Sep 2026
Release timing varies
gaps range from 1 weeks to 5 months
Some releases are documented
notes for 29 of the last 60 stable releases
1 version withdrawn
withdrawn after publishing
16 years old
73 releases · first in 2010
The two main things in this release:
The two main things in this release:
The two main things in this release:
ioggstream/bedework:latest which has been locked towards Bedework 3.10.3 since 2018. Now there is a script for building a bedework container in the docker test servers. The old Bedework docker image has been kept, but renamed into bedework3. Workarounds for various server quirks have been implemented.bedework compatibility profile is renamed bedework_3_10_3, and bedework_5_0_0 is added. features: bedework or base: bedework in a config now raises a ValueError naming both.compatibility_hints: the write-delay server-peculiarity is turned around into the synchronous-write server-feature. While the caldav-server-tester does probe it, the delay (relevant for tests and the caldav server tester) should still be hand-configured.compatibility_hints: new caldav-server-tester probes caused the Xandikos, SOGo, Radicale, OX, Zimbra, Bedework and Cyrus profiles to be regraded. With features: <server> configured, is_supported() may give a different answer than in 3.3.0.make_calendar() raised MkcalendarError when the server answered MKCALENDAR with 207 Multi-Status instead of 201 Created, even though the calendar was created. A multistatus reporting no failure is now accepted (seen on Bedework 5).
An ETag delivered percent-encoded (%22...%22) in a PUT response is now decoded; previously every second save() of an object raised ETagMismatchError (seen on Bedework 5).
Expanded searches lost all but the last occurrence when the server answered with one DAV:response per recurrence instance, all under the same href. The instances are now merged (seen on Bedework 5).
A bare icalendar.Event/Todo/Journal handed to caldav is wrapped in a VCALENDAR - that wrapper no longer gets a random RFC 7986 UID of its own (icalendar.Calendar.new() adds one). Servers taking the calendar-level UID to be the identity of the calendar object resource (i.e. Stalwart) saw a brand new UID on every save and rejected it with 412 no-uid-conflict.
One column per quarter.
v3.3.1a - niquest-less release mirroring v3.3.1
v3.3.1a - niquest-less release mirroring v3.3.1
3.3.0 is mostly a maintenance and QA release. The major news here is that we've done an AI-based (Claude Fable) review of all the code, this has resul
3.3.0 is mostly a maintenance and QA release. The major news here is that we've done an AI-based (Claude Fable) review of all the code, this has resulted in quite a lot of hammering on the code to get all the issues found smoothened out.
3.3.0a1 is a variant without any http-library in the dependencies - allowing projects that bring their own http-library to pin a caldav-version that does not bring in niquests.
3.3.0 is mostly a maintenance and QA release. The major news here is that we've done an AI-based (Claude Fable) review of all the code, this has resulted in quite a lot of hammering on the code to get all the issues found smoothened out.
3.3.0a1 is a variant without any http-library in the dependencies - allowing projects that bring their own http-library to pin a caldav-version that does not bring in niquests.
The JMAP support has been declared experimental and compatibility_hints.py has been declared unstable, hence the two changes below are deemed allowable in a minor release:
jmap/client.py and jmap/async_client.py get_objects_by_sync_token(): the newState from CalendarEvent/changes was discarded into _, so callers could not chain sync calls without a separate get_sync_token() round-trip — a race window where intervening changes would be silently missed. The method now returns a 4-tuple (added, modified, deleted, new_sync_token) instead of a 3-tuple.compatibility_hints: There has been quite a lot of work refining, calibrating, extending and fixing the compatibility database (and surrounding code). This file is declared unstable for the 3.x release series. The details (wall-of-text) have been omitted. Changes in behaviour may be observed if you have configured your server with features= ...
synologyroburcaldav[niquests] is now a valid install target. Perhaps 4.x will come without any http library in the dependency-list.pip install niquests, or a dependency on caldav[niquests] rather than plain caldav - plus a link to the HTTP Library Configuration documentation. Previously it surfaced as a bare ModuleNotFoundError: No module named 'requests' from somewhere in the import chain. Covers the sync and async CalDAV clients, discovery, caldav.testing and the JMAP clients; the async JMAP client is niquests-only and now says so.JMAPClient and AsyncJMAPClient can be used as (async) context managers, and the session can also be released explicitly with client.close() / await client.aclose().compatibility_hints.pycaldav.config.extract_conn_params_from_section is now public API (renamed from _extract_conn_params_from_section), so that downstream tools like plann can map plann-style config sections (caldav_url, caldav_user, features, etc.) to DAVClient parameters without duplicating the logic.CalendarObjectResource.load(multiget_fallback=True) and Calendar.search(compatibility_workarounds=True). Explicitly set them to False to get more directly to the actual server results.calendar-multiget REPORT instead of one GET per object — a 200-event search made 200 requests before.Calendar._create() now discovers and adopts a server-assigned canonical URL after creating a calendar, if the new compatibility feature create-calendar.stable-url is configured as not supported.compatibility_hints.py - the derivation logic was always tricky. Now a feature with a default value set is considered to be an independent feature - a feature without a default value is considered to be just a node in the hierarchy, and will derivate its default value from the children. I consider it to be a bugfix, but if you have a hand-written compatibility matrix for your server, things may break.Disclaimer: This section is mostly AI-generated - some of it rewritten by hand, some of it shortened down significantly, but for the most I've just asked the AI to do a better job organizing it and making it short.
search.py: the documented operator='==' exact-match guarantee was never enforced.search.py: add_property_filter('category', '', operator='undef') queried the nonexistent CATEGORY, and is-not-defined on that matches everything - so it returned every object.search.py: the search.combined-is-logical-and: unsupported workaround (triggered on e.g. Nextcloud) was faulty, causing wrong search results.search()'s generator driver now feeds exceptions raised while executing a request back into the search logic, so the server-compatibility fallbacks and per-object load error handling actually take effect (previously dead code). Applies to both the sync and async code paths.calendarobjectresource.py _complete_recurring_safe(): completing a recurring task would drop the caller-supplied completion_timestampcalendarobjectresource.py _get_duration(): a VTODO with a timed DTSTART and no DUE/DURATION got duration = timedelta(days=1) instead of timedelta(0), shifting the next due date by one day when completing a recurring task.lib/vcal.py fix(): truncated iCalendar data (no END: line) triggered a bare assert. Now logs a warning and returns the data unchanged instead.lib/vcal.py create_ical(): when both alarm_* props and ical_fragment were supplied, the fragment was injected before the first END:V line — which is END:VALARM, placing e.g. an RRULE inside the alarm component. The regex now targets END:V(EVENT|TODO|JOURNAL) specifically.lib/vcal.py fix(): the backslash-unescape step used ('\"') as a regex group, which matches only the literal two-character sequence '". A backslash before a lone ' or lone " was silently left in place. Fixed by using the character class ['\"].lib/vcal.py fix(): the trailing-whitespace fixup (re.sub(" *$", "", fixed)) lacked re.MULTILINE.calendarobjectresource.py change_attendee_status(): calling the method on an event with no ATTENDEE raised a bare KeyError('ATTENDEE') instead of NotFoundError.calendarobjectresource.py add_attendee(): "MAILTO:" was not accepted, the scheme check used str.startswith("mailto:") which is case-sensitive. RFC 3986 §3.1 specifies URI schemes are case-insensitive.datastate.py RawDataState.get_component_type(): tested for the string "BEGIN:FREEBUSY", but real iCalendar data uses "BEGIN:VFREEBUSY". Any FreeBusy object holding raw data returned component_type=None, causing problems further down. The same typo appeared in the base-class get_uid() and get_component_type() fallback parsers which looked for comp.name == "FREEBUSY" instead of "VFREEBUSY".calendarobjectresource.py _set_data(): the raw-string branch cleared the legacy _data/_vobject_instance/_icalendar_instance attributes but never reset self._state, causing data consistency problems in some circumstances.vcal.fix(): the COMPLETED date-to-datetime regex consumed the trailing newline, merging the following iCal property into the COMPLETED value on every inbound object from a server that stores COMPLETED as a plain date (e.g. SOGo). Fixed by using a lookahead (?=\s) instead of consuming \s.caldav.lib.error.ResponseError naming the URL and quoting what arrived, instead of letting the icalendar library raise a ValueError. from inside the icalendar library.NotFoundError also when the server reports the 404 inside a 207 Multi-Status.collection.py: the ownCloud @-quoting heuristic for a relative calendar-home-set lived in three copies — Principal.calendar_home_set, its async twin, and _sanitize_calendar_home_set_url() used by the PROPFIND extractor — and they had drifted: only the extractor's copy skipped a URL that already contains %40, so a home-set the server delivered part-encoded went through quote() a second time and came back with %2540. All three are now the one helper.URL.canonical(): two related bugs — (a) for URLs with an auth part, the canonical form could leak user:pass@ - which again could cause __eq__/__hash__ comparisons to fail. (b) for URLS with no auth part, unauth() returned self and canonical() then overwrote url_raw/url_parsed in place — a bare == or hash() call silently mutated the URL object, potentially re-encoding special characters (e.g. + → %2B) and causing subsequent requests to target the wrong resource._post_put: a 302 response to PUT always raised IndexError instead of following the redirect.davclient.py and async_davclient.py: rate-limit retry raised TypeError: unsupported operand type(s) for +=: 'NoneType' and 'float' on the second 429 response when the server provides no usable Retry-After value (compute_sleep_seconds returns None). The sleep_seconds += rate_limit_time_slept / 2 line executed before the sleep_seconds is None guard. Now re-raise RateLimitError first, then update the sleep estimate.davclient.py DAVClient.__init__(): (a) DAVClient(url='https://user@host/', password='secret') did not work. (b) URL-embedded credentials silently overrode explicit username/password kwargs - now explicit kwargs win. An explicit username discards the URL credentials wholesale rather than merging them field by field — otherwise DAVClient(url='https://bob:hunter2@cal.example.com/', username='alice') would ship alice's login with bob's password. Overriding only the password keeps the URL's username, which stays a coherent pair.async_davclient.py: HTML-on-401 diagnostic hint checked self.headers (the client's own request headers) for Content-Type: text/html instead of r.headers, so the intended "server returned an HTML login page, consider setting auth_type" message could never fire.lib/auth.py extract_auth_types(): a WWW-Authenticate header ending with a trailing comma (seen in the wild) raised IndexError in the set comprehension because h.split() on an empty segment fails. Added an if h.strip() guard.config.py resolve_features(): returning a named profile (features='xandikos') returned the module-level dict object directly without copying. Similarly testing.py XandikosServer/RadicaleServer used a shallow .copy(). This was causing mutation-problems.config.py get_connection_params(): explicit keyword arguments (e.g. get_davclient(password='secret')) were only respected when url or features was also present. When an env-var or config-file source was found instead, explicit params were silently dropped. Now things are merged. A keyword argument whose value is None counts as "not supplied" rather than "unset it", it's needed to explicitly use an empty string e.g. to override a password from environment with an empty password.config.py expand_config_section(): requesting a section name that is absent from the config raised KeyError instead of returning [], causing plain caldav.get_calendars() to crash with KeyError: 'default' on configs with no default section.config.py expand_config_section(): disable: true was silently ignored for sections fetched by explicit name or via a contains list — the check used the string literal "section" as the config key instead of the section variable. Only the glob "*" path honoured disable.features but no caldav_url were rejected, even though the URL can be derived from the auto-connect.url compatibility hints. Explicitly passed parameters already worked this way; now get_davclient(config_section=...) and friends behave consistently.config.py read_config(): only FileNotFoundError was caught while probing the optional config-file locations. With HOME unset (e.g. a Nix build sandbox) the default path expands to //.config//caldav/calendar.conf, which open() rejects with IsADirectoryError; that OSError propagated out of an optional probe, breaking get_davclient() and even crashing pytest's terminal summary renderer. Any OSError (not found, is-a-directory, permission denied) is now treated as "no config here".caldav.async_davclient reached the sync HTTP library through caldav.requests, so the httpx selection succeeded and the import then failed anyway, claiming no supported library was installed.async_davclient.py AsyncDAVClient.propfind(): a raw XML body passed as props did not work and caused arbitrary behaviour. This is allowable in the sync DAVClient.propfind(), but it's legacy and serves backward-compatible purposes. Rather than fixing the async code, a TypeError is now raised. XML body should be passed in the body-parameter.async_davclient.py _async_request(): when the auth probe got no 401 challenge (an HTML login page, say), the probe response was returned as the real one and the connection error lost.collection.py freebusy_request(): on an async client a Principal attendee raised AttributeError - get_vcal_address() returns a coroutine, and it was never awaited.async_davclient.py aio.get_calendars(calendar_name=...): the loop iterated a coroutine rather than the calendars, so a lookup by name always came back empty.async_davclient.py get_calendars(): lacked the GMX principal-URL fallback present in the sync client.collection.py Principal.calendar(cal_id=...): for async clients a bare (non-URL) cal_id/name raised TypeError: argument of type 'coroutine' is not a container or iterable.base_client.py get_calendars(calendar_urls=...): a calendar explicitly requested by URL was silently omitted from the result when its displayname property is the empty string "".base_client.py, collection.py and async_davclient.py: a calendar whose display name could not be read was silently skipped by a bare except Exception: pass during a lookup by name. The lookup still continues past such a calendar, but it now emits a log.warning unless the server is known not to support propfind.displayname, so an unexpected failure is visible instead of looking like "no such calendar".jmap/client.py and jmap/async_client.py update_event(): to honour RFC 8620 PatchObject merge semantics, the update null-injects every optional property absent from the new iCalendar so removed properties are actually cleared server-side. This doidn't work out for Stalwart, as it rejects properties it does not support. Workaround applied.jmap/client.py and jmap/async_client.py create_task(): a JMAP server response that returned an empty created dict raised a bare KeyError instead of the documented JMAPMethodError.jmap/objects/calendar.py JMAPCalendar.search(): datetime arguments for start/end were formatted with a tz-offset for tz-aware non-UTC datetimes. Naive datetimes were copied out verbatim. JMAP requires UTCDate format (YYYY-MM-DDTHH:MM:SSZ). Fixed by converting to UTC and using strftime.jmap/convert/jscal_to_ical.py: a recurrenceOverrides entry that does not include a "start" key (the common case — title-only change, description update, etc.) produced a child VEVENT with DTSTART copied from the master event's start time rather than from the override key. This effectively relocated every non-rescheduled override to the master's first occurrence, breaking all override display. Default is now the override key itself.jmap/convert/jscal_to_ical.py: EXDATE and RECURRENCE-ID values were always emitted as floating (timezone-less) DATE-TIME regardless of the event's timeZone or showWithoutTime flag. Per RFC 5545 §3.8.5.1 the value type must match DTSTART; a floating EXDATE on a TZID-anchored event does not match any instance, so excluded occurrences reappear. Override keys are now parsed with the event timezone applied (TZID-anchored events) or as date objects (all-day events).jmap/convert/_utils.py _format_local_dt(): UTC datetimes produced a Z-suffixed string. RFC 8984 §1.4 defines LocalDateTime (the type required for recurrenceOverrides keys and recurrenceRules.until) as a bare YYYY-MM-DDThh:mm:ss without any suffix; Z-suffixed override keys cannot match LocalDateTime occurrence keys, causing mismatches on strict servers. The function now returns a bare local representation — converted into the event's own timezone first, since a LocalDateTime is local to the event: dropping the offset from a UTC value instead of converting it would shift EXDATE/RECURRENCE-ID/UNTIL by the UTC offset on every TZID-anchored event, and emit a floating UNTIL against a TZID DTSTART, which RFC 5545 §3.3.10 forbids.jmap/convert/ical_to_jscal.py and jmap/convert/jscal_to_ical.py: the STATUS property was silently dropped in both conversion directions. STATUS:CANCELLED round-tripped as status: confirmed (JSCalendar default), so cancelled meetings appeared active. Mappings CONFIRMED ↔ confirmed, TENTATIVE ↔ tentative, CANCELLED ↔ cancelled are now implemented.jmap/client.py and jmap/async_client.py update_event(): RFC 8620 §3.3 specifies that absent keys in a PatchObject preserve the server value. update_event sent the full converted JSCalendar dict as the patch; properties the caller removed (e.g. LOCATION, VALARM) were absent from the patch and therefore silently persisted on the server. update_event now explicitly sets all optional top-level JSCalendar properties to null when they are absent from the conversion result, ensuring the server removes them..gitignore — global gitignore on developers laptop caused caldav-3.2.1.tar.gz to be shipped with .claude/settings.json and 1755 files under venv/, and the current tree would have added 443 files under .prompts/. A new package tox environment (run in CI, and now part of the release procedure) builds both artifacts and fails if anything git does not track turns up in them.lib/error.py PYTHON_CALDAV_COMMDUMP: when this debug env-var is set, a logging.warning() is now emitted at import time to remind the operator that request/response bodies and headers (including credentials and calendar PII) are being written to uniquely-named files under /tmp that may accumulate indefinitely.response.py: the XML parser is now constructed with resolve_entities=False, no_network=True, so a malicious or MITM server cannot inject arbitrary text into parsed property values through inline DOCTYPE entity definitions. On the lxml versions most people have this was already the effective behaviour (no_network defaults to True, and resolve_entities defaults to 'internal' from lxml 5.0), but lxml is an unpinned dependency and the parser should not be relying on another project's defaults for this.discovery.py discover_service(): require_tls=True was not enforced on the well-known URI redirect target — a same-domain Location: http://... passed the domain-validation check and was returned as ServiceInfo(tls=False), allowing a misconfigured or MITM server to silently downgrade the connection to plaintext. Fixed by checking well_known_info.tls against require_tls before returning the result.see #690
The changeset in 3.2.1 is predominently added async integration tests. Those tests should now be replicating all the logic in the good old sync integr
The changeset in 3.2.1 is predominently added async integration tests. Those tests should now be replicating all the logic in the good old sync integration tests under test_caldav.py. Some few more bugs were found while adding those tests.
There are two "feature commits" adding new parameters to existing functions. Those are minor additions and was required while fixing things (test breakage plus observed crash due to weird real-world-data), hence I define this to be a patch-release rather than a minor-release.
The changeset in 3.2.1 is predominantly added async integration tests. Those tests should now be replicating all the logic in the good old sync integration tests under test_caldav.py. Some few more bugs were found while adding those tests.
There are two "feature commits" adding new parameters to existing functions. Those are minor additions and was required while fixing things (test breakage plus observed crash due to weird real-world-data), hence I define this to be a patch-release rather than a minor-release.
Calendar.delete() has had a "wipe-mode" since v2.2.0, deleting items from the calendar if it's not possible to delete the calendar itself. Now a tristate wipe parameter has been added, wipe=True to wipe rather than delete the calendar, wipe=False to not wipe, and default behaviour (wipe=None) is still "wipe if needed". (Useful for NextCloud tests, where events stuck on calendars in the "trashbin" pollutes the namespace preventing the same event to be added to a new calendar).save() only_this_recurrence parameter is now a tristate:
True (default) - unchanged, if the object is a recurrence it will be merged with the master event, making sure the saved recurrence is stored as an exception to the RRULE. If the master object does not exist, then it will raise NotFoundError.None (new) - same as True, except that if the master object does not exist, the recurrence will just be sent directly to the server as-is.False - unchanged, the recurrence will be sent directly to the server as-is.
None is used in the add_object(). This change was needed to avoid a crash when trying to add a recurrence-object without a master to the server. (So much ado for a very weird edge case).HTTPDigestAuth.handle_401() calls r.connection.send() which returns a coroutine in async context, causing AttributeError: 'coroutine' object has no attribute 'history'. Fixed by using AsyncHTTPDigestAuth for the async client. See https://github.com/jawah/niquests/issues/387Calendar.freebusy_request() was not async-aware._async_complete() raised NotImplementedError for handle_rrule=True._async_put() did not await the retry coroutine returned by _post_put()._async_get_object_by_uid() was missing include_completed=True, unlike the sync version._async_search_with_comptypes() did not skip component types unsupported by the server, unlike the sync version.DisplayName is now omitted from the MKCALENDAR request body when create-calendar.set-displayname is unsupported.niquests (preferred) → httpxyz → httpx. See https://github.com/python-caldav/caldav/issues/611These changes are not part of the shipped library, but make up the bulk of the 3.2.1 changeset.
tests/test_async_integration.py grew by ~1700 lines) to mirror the sync suite in test_caldav.py. Part of git bug bug show e44ee06 aka https://github.com/python-caldav/caldav/issues/667testCheckCompatibility no longer takes ~8 minutes on servers with a search-cache delay (e.g. Bedework) — two bugs causing repeated cache-delay waits were fixed, and some redundant Bedework compatibility-matrix entries were cleaned up.test_caldav.py, test_caldav_unit.py).tests/test_servers/: registered Baikal's URL_ENV_VAR so the async-httpx CI job can reach it, and added a get_available_servers() helper used by the async integration tests.Emulate Docker CLI using podman banner string.uid=…,gid=… tmpfs mount options that Podman does not support (CCS / CalendarServer runs as root inside the container, so the ownership hint wasn't needed).testuser@example.org), weak passwords are rejected, and a workaround was added for Stalwart advertising https:// even when reached over http:// in local dev.mailto: email addresses for the scheduling test users (so iTIP delivery works), and disabled the CalDAV trashbin (calendarRetentionObligation=0) so HTTP DELETE hard-deletes objects and re-using a UID doesn't hit a UNIQUE constraint violation.imapd.conf with virtdomains: off (the default virtdomains: userid caused 403s on iTIP delivery due to userid/ACL mismatch), unpinned from the March 2026 digest now that :latest is stable again, and pointed the health-check at the CalDAV port.async (niquests fallback) job to async (niquests) to reflect that niquests is the default install, not a fallback.async (niquests), async (httpxyz fallback) and async (httpx fallback).scheme://hostname:port instead of silently rewriting them, which the 30-day cache had been masking — placeholder URLs were replaced with concrete examples (http://proxy.example.com:8080) in davclient.py and async_davclient.py, and .lycheeignore was updated accordingly.The two most significant news in v3.2 are relatively well-tested support for scheduling (RFC6638) and better-tested support for async. Care should sti
The two most significant news in v3.2 are relatively well-tested support for scheduling (RFC6638) and better-tested support for async. Care should still be taken, those features are backed by many tests, but lacks testing for how well they support real-world use-case scenarios. While async support was added in version 3.0, it was not well-enough tested. Still only a fraction of all the integration tests for sync usage has been duplicated in the async integration test, I expect to release 3.2.1 with symmetric async integration tests before 2025-07.
The two most significant news in v3.2 are relatively well-tested support for scheduling (RFC6638) and better-tested support for async. Care should still be taken, those features are backed by many tests, but lacks testing for how well they support real-world use-case scenarios. While async support was added in version 3.0, it was not well-enough tested. Still only a fraction of all the integration tests for sync usage has been duplicated in the async integration test, I expect to release 3.2.1 with symmetric async integration tests before 2025-07.
add_organizer() now accepts an optional explicit organizer argument (a Principal, vCalAddress, or email string)If-Schedule-Tag-Match or If-Match headers will be sent. A ScheduleTagMismatchError or ETagMismatchError will be raised on 412.save() then inserts SEQUENCE:1 unless the increase_seqno parameter is set to False.search()-results - https://github.com/python-caldav/caldav/issues/650_resolve_properties() would crash for some disbehaving servers. https://github.com/pycalendar/calendar-cli/issues/114Calendar.get_supported_components() would crash for some servers. https://github.com/python-caldav/caldav/issues/653accept_invite(), decline_invite() and tentatively_accept_invite() when the server does not expose the calendar-user-address-set property. https://github.com/python-caldav/caldav/issues/399get_object_by_uid(), aligning it with the rest of the search API. Closes https://github.com/python-caldav/caldav/issues/586I've been experimenting with Claude Code over the last few months, concerns have been raised that it may have negatively affected code quality - and indeed, this is probably a major reason why the async support in v3.0 was simply not good enough. I've been working a bit more on the AI-POLICY.md, some of the directions for the future looks like this:
The 3.2-release may not be fully up to those standards, as they were made while working on 3.2.
The branch v3.2-development contains "raw" commits, most of the commits are either AI-written (including commit message) or human-written. I've done quite some work trying to squash the commits into fewer commits, in the main branch all the recent commit messages are handwritten, and most of the commits have some notes on how much is AI-generated and why AI-generation was chosen. The manual walk-through of all the commits has been tedious, but useful for QA-purposes. I'm considering this to be the way forward.
I have all relatively fresh communication with Claude in JSON-files, and I was considering to embed them into the repository for increased transparency. Everything considered, I think it would involve too much noise, so I've skipped it as for now. If you want it, I will publish it.
copy, lxml.etree, CalendarSet, cdav/dav re-exports, Optional, timezone, Event/Todo type stubs), replaced bare except: clauses with specific exception types (KeyError, AttributeError, Exception where broad catching is intentional), and removed unused local variables.funding.json (https://fundingjson.org/) at the repository root. Closes https://github.com/python-caldav/caldav/issues/608_AsyncTestSchedulingBase added: async counterpart of _TestSchedulingBase with test_invite_and_respond and test_freebusy; TestAsyncSchedulingFor<Server> classes generated for each server with scheduling_users configured.scheduling.freebusy, rfc4791-freebusy have been collapsed down into freebusy (instead of freebusy.rfc4791).search.text.by-uid was removed, there is (probably?) no servers supporting one but not the other. (Though the checks on this may be wrong, as workarounds are automatically employed for servers not supporting text search). https://github.com/python-caldav/caldav/issues/586Fixups on the async support. Perhaps the "sans-io" design concept wasn't such a great idea - quite some gaps in the async support has been identified
Highlights:
get_calendars(): a single get_calendars() call can now span multiple config-file sections (including glob/wildcard expansion), aggregating calendars from multiple servers into one CalendarCollection. This was the idea (and has been implemented in my plann project for quite some time), but fell short of getting into the v3.0-release.Highlights:
get_calendars(): a single get_calendars() call can now span multiple config-file sections (including glob/wildcard expansion), aggregating calendars from multiple servers into one CalendarCollection. This was the idea (and has been implemented in my plann project for quite some time), but fell short of getting into the v3.0-release.get_icalendar_component() returns a deep-copy of the inner VEVENT/VTODO/VJOURNAL sub-component for read-only inspection, consistent with the get_icalendar_instance() naming convention.edit_icalendar_component() context manager yields the inner component for editing and delegates to edit_icalendar_instance() so all borrow/state/save machinery is reused.get_calendars() now accepts a config_section value that is expanded via expand_config_section(), so wildcards like "work_*" or "all" resolve to multiple leaf sections; each section gets its own DAVClient and all calendars are aggregated into a CalendarCollection. CalendarCollection now closes all its clients on context-manager exit.get_all_file_connection_params(config_file, section).PYTHON_CALDAV_USE_TEST_SERVER=1 (or testconfig=True) falls back to automatically starting the first available enabled server from the test-server registry when no testing_allowed config section is present. Three new env vars (PYTHON_CALDAV_TEST_EMBEDDED, PYTHON_CALDAV_TEST_DOCKER, PYTHON_CALDAV_TEST_EXTERNAL) control which server categories are eligible. Per-server priority: keys in config files are honoured.caldav/testing.py (shipped with the package): EmbeddedServer, XandikosServer, RadicaleServer — so pip-installed users can use PYTHON_CALDAV_USE_TEST_SERVER=1 without a source checkout.get_object_by_uid() (and get_event_by_uid(), get_todo_by_uid(), get_journal_by_uid(), and their deprecated aliases) raised TypeError with async clients because search() returned a coroutine that was iterated directly. Fixes https://github.com/python-caldav/caldav/issues/642complete() and the save()-recurrence path were not awaited for async clients.uncomplete(), set_relation(), get_relatives(), and invite() lacked async dispatch._handle_reverse_relations() called get_relatives() without await, silently returning a coroutine.get_calendar() and get_calendars() were missing from the caldav.aio re-export.get_calendars(config_section=…) silently ignored calendar_name and calendar_url keys in config sections — they were stripped before reaching the filter logic.expand_config_section() was not called when reading the config file, so contains:-style meta-sections had no effect.date objects passed to calendar.search() or calendar.searcher() as time-range boundaries now get coerced to UTC datetime before being forwarded to icalendar_searcher, silencing the "Date-range searches not well supported yet" warning.XandikosServer.is_accessible() now sends a minimal PROPFIND requesting only {DAV:}resourcetype instead of an implicit allprop, avoiding spurious NotImplementedError log lines from Xandikos during test-server startup.docs/source/async_tutorial.rst. Covers the same ground as the sync tutorial plus a "Parallel Operations" section demonstrating asyncio.gather(). The sync tutorial now links to it.docs/source/configfile.rst has been rewritten and extended; tests for inherits and env-var expansion added.docs/source/tutorial.rst rewritten and fixed.docs/source/.Highlight: Reintroducing debug communication dump functionality.
Highlight: Reintroducing debug communication dump functionality.
Highlight: Reintroducing debug communication dump functionality.
PYTHON_CALDAV_COMMDUMP is given, caldav communication is dumped to /tmp - details in https://github.com/python-caldav/caldav/issues/248 . This is regarded as "fix" rather than "feature" as it was introduced in v1.4.0 and accidentally dropped during the v3.0 refactoring. Restored, with the dump logic extracted into a shared helper so both the sync and async code paths benefit. Test code added to make sure it won't disappear again. Fixes https://github.com/python-caldav/caldav/issues/638search() raised NotImplementedError when a full calendar-query XML was passed and the server does not support search.comp-type.optional. This is a really rare and deprecated code path, but still NotImplementedError isn't good. Now it falls back to a single REPORT with the XML as-is. Fixes https://github.com/python-caldav/caldav/issues/637Minor bugfix to support old versions of httpx
Highlights:
Highlights:
tests/docker-test-servers/ox/. However, OX is undertested as both the caldav-server-checker and the test suite does not play well with OX (events with historic DTSTART etc are used, OX doesn't support that).search.unlimited-time-range feature flag with a workaround in search.py that injects a broad time range (1970–2126) for servers that return an empty result set when no time range is specified (but this still doesn't help to OX).AsyncDAVClient failed to initialize when using httpx < 0.23.0 because proxy=None was unconditionally passed to httpx.AsyncClient which did not accept a proxy keyword argument in older releases. Fixes https://github.com/python-caldav/caldav/issues/632"Deviation from expectations found" log error in production, or an assertion failure in debug mode.search.comp-type-optional has been renamed to search.comp-type.optional for consistency with the dotted-key naming convention used elsewhere. If you have this key set in a local server configuration, update it accordingly.Some minor improvements, including a fix for https://github.com/python-caldav/caldav/issues/635 - use canonical RFC-links.
Version 3.0 should be fully backward-compatible with version 2.x - but there are massive code changes in version 3.0, so if you're using the Python Ca
Version 3.0 should be fully backward-compatible with version 2.x - but there are massive code changes in version 3.0, so if you're using the Python CalDAV client library in some sharp production environment, I would recommend to wait for two months before upgrading.
Highlights
AsyncDAVClient and async domain objects using a Sans-I/O architecture. The same Calendar, Event, Todo, etc. objects work with both sync and async clients.caldav.jmap package with JMAPClient and AsyncJMAPClient for servers implementing RFC 8620 (JMAP Core) and RFC 8984 (JMAP Calendars). Note that this is experimental, and the public API may be changed in upcoming minor-releases.Version 3.0 should be fully backward-compatible with version 2.x - but there are massive code changes in version 3.0, so if you're using the Python CalDAV client library in some sharp production environment, I would recommend to wait for two months before upgrading.
Highlights
AsyncDAVClient and async domain objects using a Sans-I/O architecture. The same Calendar, Event, Todo, etc. objects work with both sync and async clients.caldav.jmap package with JMAPClient and AsyncJMAPClient for servers implementing RFC 8620 (JMAP Core) and RFC 8984 (JMAP Calendars). Note that this is experimental, and the public API may be changed in upcoming minor-releases.The tests broke with lots of AuthorizationErrors with GMX. The tests were running successfully towards GMX before releasing the last alpha-release. It's probably a transient issue. I don't want to delay the release by doing more research into it.
Be aware that some of the 2.x minor-versions also tagged some "Potentially Breaking Changes" - so if you're upgrading i.e. from 2.1, you may want to browse through the "Potentially Breaking Changes" for the intermediate minor releases too.
tests/conf.py has been removed and conf_private.py will be ignored. See the Test Framework section below.caldav/objects.py removed -- the backward-compatibility re-export shim has been deleted. Any code doing from caldav.objects import <something> must be updated; all public symbols remain available directly via caldav or from their respective submodules.caldav.config.read_config() now raises ValueError on YAML/JSON parse errors instead of logging and returning an empty dict. This ensures config errors are detected early.The following have been deprecated and emit DeprecationWarning:
calendar.date_search() - use calendar.search() insteadclient.principals() - use client.search_principals() insteadobj.split_expanded - may be removed in a future versionobj.expand_rrule - may be removed in a future version.instance property on calendar objects - use .vobject_instance or .icalendar_instanceresponse.find_objects_and_props() - use response.results insteadThe save_*-methods are deprecated but do not yet emit warnings (see https://github.com/python-caldav/caldav/issues/71):
calendar.save_event() - use calendar.add_event() insteadcalendar.save_todo() - use calendar.add_todo() insteadcalendar.save_journal() - use calendar.add_journal() insteadcalendar.save_object() - use calendar.add_object() insteadMethods that fetch data from the server should use the get_ prefix (see https://github.com/python-caldav/caldav/issues/92). The following are deprecated but do not yet emit warnings:
calendar.event_by_uid() - use calendar.get_event_by_uid() insteadcalendar.todo_by_uid() - use calendar.get_todo_by_uid() insteadcalendar.journal_by_uid() - use calendar.get_journal_by_uid() insteadcalendar.object_by_uid() - use calendar.get_object_by_uid() insteadprincipal.calendars() - use principal.get_calendars() insteadcalendar.events() - use calendar.get_events() insteadcalendar.todos() - use calendar.get_todos() insteadcalendar.journals() - use calendar.get_journals() insteadcalendar.objects_by_sync_token() - use calendar.get_objects_by_sync_token() insteadThe following check_*_support() methods are deprecated but do not yet emit warnings:
client.check_dav_support() - use client.supports_dav() insteadclient.check_cdav_support() - use client.supports_caldav() insteadclient.check_scheduling_support() - use client.supports_scheduling() instead
(Those methods actively probe the server; is_supported() is a configuration lookup.)Additionally, direct DAVClient() instantiation should migrate to get_davclient() factory method (see docs/design/API_NAMING_CONVENTIONS.md)
Experimental JMAP calendar client — new caldav.jmap package providing a JMAP client
for servers implementing RFC 8620 (JMAP Core) and RFC 8984 (JMAP Calendars).
Features:
JMAPClient and asynchronous AsyncJMAPClient with mirrored APIscreate_event, get_event, update_event,
delete_event, search_events)get_sync_token / get_objects_by_sync_tokencreate_task, get_task, update_task, delete_taskget_jmap_client() factory reads from the same config sources as
get_davclient() (env vars, config file)Full async API - New AsyncDAVClient and async-compatible domain objects:
from caldav.async_davclient import get_davclient
async with await get_davclient(url="...", username="...", password="...") as client:
principal = await client.get_principal()
calendars = await client.get_calendars()
for cal in calendars:
events = await cal.get_events()
Retry-After / rate-limit handling (RFC 6585 / RFC 9110) -- DAVClient and AsyncDAVClient now expose rate_limit_handle, rate_limit_default_sleep, and rate_limit_max_sleep parameters (this may be specified in the configuration file as well). When rate_limit_handle=True the client automatically sleeps and retries on 429 Too Many Requests and 503 Service Unavailable responses that include a Retry-After header. When rate_limit_handle=False (default) a RateLimitError is raised immediately so callers can implement their own back-off strategy. New caldav.lib.error.RateLimitError has retry_after (raw header string) and retry_after_seconds (parsed float) attributes. https://github.com/python-caldav/caldav/issues/627
search.is-not-defined.category and search.is-not-defined.dtend -- new client-side workaround sub-features for servers that do not support the CALDAV:is-not-defined filter natively for these properties.
Base+override feature profiles -- YAML config now supports inheriting from a base profile:
my-server:
features:
base: nextcloud
search.comp-type: unsupported
Compatibility fixes
save-load.event.recurrences.exception which is supported if the server stores master+exception VEVENTs as a single calendar object as per the RFC. Stalwart splits them into separate objects. Stalwart recombines the data when doing an expanded search, so expand=True searches now automatically fall back to server-side CALDAV:expand. (Arguably, unsupported here could also mean the exception data was simply discarded. If needed, I'll refine this in a future version)save-load.journal.mixed-calendar - some calendar servers offers a separate journal list.save-load.reuse-deleted-uid - server allows immediate reuse of an uid if the old object has been deletedsearch.time-range.*.old-dates - test data mostly have historic dates. Calendars are primarily made for future happenings. Some calendar servers does not support searching for things that happened 20 years ago, even for a very small calendar.search.is-not-defined.category and search.is-not-defined.dtend - actually, those are artifacts. The bug was on the client side, not server side. I may delete them in a future release.calendar-home-set property is not available (e.g. GMX).CalendarObjectResource.load() now falls back to UID-based lookup when servers change object URLs after a save.Added python-dateutil and PyYAML as explicit dependencies (were transitive)
Quite some methods have been renamed for consistency and to follow best current practices. See the Deprecated section.
Calendar class now accepts a name parameter in its constructor, addressing a long-standing API inconsistency (https://github.com/python-caldav/caldav/issues/128)
CalendarObjectResource.id property - Returns the UID of calendar objects (https://github.com/python-caldav/caldav/issues/515)
calendar.searcher() API - Factory method for advanced search queries (https://github.com/python-caldav/caldav/issues/590):
searcher = calendar.searcher()
searcher.add_filter(...)
results = searcher.search()
``
Improved API for accessing the CalendarObjectResource properties (https://github.com/python-caldav/caldav/issues/613 ):
get_data(), get_icalendar_instance, get_vobject_instance, get_icalendar_component:
edit_* (but no edit_data - the data is an immutable string, should use simply object.data = foo for editing it)
with obj.get_foo, the client may edit foo, and then obj.save() to send it to the server.is-not-defined filter for CATEGORIES did not work, and for DTEND it did not work for full day events. (this was fixes in the icalendar-searcher, version 1.0.5).CalendarObjectResource properties (https://github.com/python-caldav/caldav/issues/613 )import caldav is now significantly faster. Heavy dependencies (lxml, niquests, icalendar) are deferred until first use. https://github.com/python-caldav/caldav/issues/621_search_impl yields (SearchAction, data) tuples consumed by sync or async wrappersget_connection_params() provides unified config discovery with clear priority (explicit params > test server config > env vars > config file)${VAR} and ${VAR:-default} environment variable expansion in config valuestests/conf.py to new tests/test_servers/ frameworktests/test_servers/ module. It provides YAML-based server configuration: see tests/test_servers/__init__.py for usageconvert_conf_private.py migration tool for legacy config formattest_lazy_import.py; expanded test_async_davclient.py, test_async_integration.py, test_compatibility_hints.py, test_search.py, test_caldav_unit.pyCheckRecurrenceSearch now also verifies implicit recurrence support for all-day (VALUE=DATE) recurring events, marking the feature as fragile (with behaviour description) when only datetime recurring events work.calendar.search -- Tobias Brox (@tobixen)get_display_name() -- Tobias Brox (@tobixen)add_object vs save_object (reopened, reverted and closed)get_davclient to be importable from caldav -- Tobias Brox (@tobixen)The following people contributed to this release through issue reports, pull requests, and/or commits:
Since the 2.2.1-release and excluding the JMAP-work done by Sashank, Tobias has spent around 132 hours on this project.
In the 3.0-release, AI-tools have been used for improving quality and speed. My first impression was very good. It seemed like the AI understood the project, and it could fix things faster and better than what I could do myself - I really didn't expect it to create any good code at all. Well, sometimes it does, other times not. Soon enough I also learned that the AI is good at creating crap code, breaking things and Claude is particularly good at duplicating code and code paths. In the end, despite using Claude I've spent more time on this release than what I had estimated. However, I believe I've done a quite thorough work on preserving backward-compatibility while also developing a better API.
From my roadmap, those are the estimates:
In addition, lots of time spent on things that aren't covered by the roadmap:
> AI-generated release notes — see CHANGELOG.md for the full entry.
AI-generated release notes — see CHANGELOG.md for the full entry.
Mostly bugfixes and server compatibility improvements since 3.0.0a1, plus a few new features.
This is an alpha release for testing purposes. Please report issues at https://github.com/python-caldav/caldav/issues
rate_limit_handle, rate_limit_default_sleep, rate_limit_max_sleep parameters on DAVClient and AsyncDAVClient; new RateLimitError exception with retry_after / retry_after_seconds attributes (PR #628, thanks temsocial)import caldav is now significantly faster; heavy dependencies deferred until first usesearch.is-not-defined.category and search.is-not-defined.dtend client-side workaround sub-featuresbase: nextcloud)caldav.configcalendar-home-set property is missingCalendarObjectResource.load() fallback to UID lookup when servers change object URLs after savecaldav/objects.py re-export shim removed — update from caldav.objects import X to from caldav import Xcaldav.config.read_config() now raises ValueError on YAML/JSON parse errors instead of silently returning {}icalendar-searcher >= 1.0.5 now required (fixes is-not-defined filter for CATEGORIES with icalendar >= 6.x, and for DTEND on recurring all-day events)ssl_verify_cert not passed through in get_sync_client / get_async_client_derive_from_subfeatures partial-config derivation bugcompatibility_hints. prefix_search_with_comptypes fallback when search.comp-type is brokencreate-calendar feature incorrectly derived as unsupportedget_object_by_uid() routing through search() for server-specific retry logicCCS (Apple CalendarServer), Zimbra, Cyrus, Bedework, PurelyMail, GMX, ecloud, Posteo, SOGo, Baikal/Radicale, DAViCal, Synology, Xandikos
Nothing published for this version
This will hopefully be the last releases before v3.0. 2.2.4 is without niquests dependency, 2.2.6 depends on niquests
v2.2.6 - various minor fixes
This will hopefully be the last releases before v3.0. 2.2.4 is without niquests dependency, 2.2.6 depends on niquests
See CHANGELOG.md for details.
Nothing published for this version
Nothing published for this version
Users of the ckulka/baikal:nginx docker image could not get HTTP/2 multiplexing to work together with authentication. Workarounds done to turn off mul
Users of the ckulka/baikal:nginx docker image could not get HTTP/2 multiplexing to work together with authentication. Workarounds done to turn off multiplexing on affected systems.
Nothing published for this version
New ways to set up client connections:
Highlights:
features configuration flag.v2.2.1 comes with the requests dependency, v2.2.2 comes with niquests dependency (and v2.2.0 with a non-existing riquests dependency ... duh)
Version 2.1.0 comes without niquests in the dependency file. Version 2.1.2 come with niquests in the dependency file. Also fixed up some minor mistake
Version 2.1.0 comes without niquests in the dependency file. Version 2.1.2 come with niquests in the dependency file. Also fixed up some minor mistakes in the CHANGELOG. Version 2.1.1 was yet another mistake done during the release process and should be ignored.
See description of version 2.1.0 or CHANGELOG.md for more details.
Nothing published for this version
I'm working on a caldav compatibility checker side project. While doing so, I'm working on redefining the "compatibility matrix". This should only aff
I'm working on a caldav compatibility checker side project. While doing so, I'm working on redefining the "compatibility matrix". This should only affect the test code. If you maintain a file tests/conf_private.py, chances are that the latest changesets will break Since "running tests towards private CalDAV servers" is not considered to be part of the public API, I deem this to be allowed without bumping the major version number. If you are affected and can't figure out of it, reach out by email, GitHub issue or GitHub discussions. (Frankly, I'm interessted if anyone except me uses this, so feel free to reach out also if you can figure out of it).
As always, the new release comes with quite some bugfixes, compatibility fixes and workarounds improving the support for various calendar servers observed in the wild.
See https://github.com/python-caldav/caldav/issues/530
See https://github.com/python-caldav/caldav/issues/530
Here are the most important changes in 2.0:
Here are the most important changes in 2.0:
davclient.principals() to search for other principals on the server - and from there it's possible to do calendar searches and probe what calendars one have access to. If the server will allow it.This will be the last minor release before 2.0. The scheduling support has been fixed up a bit, and saving a single recurrence does what it should do,
This will be the last minor release before 2.0. The scheduling support has been fixed up a bit, and saving a single recurrence does what it should do, rather than messing up the whole series.
Version 1.5 comes with support for alarms (searching for alarms if the server permits and easy interface for adding alamrs when creating events), lots
Version 1.5 comes with support for alarms (searching for alarms if the server permits and easy interface for adding alamrs when creating events), lots of workarounds and fixes ensuring compatibility with various servers, refactored some code, and done some preparations for the upcoming server compatibility hints project.
Python 3.7 is no longer tested (dependency problems) - but it should work. Please file a bug report if it doesn't work. (Note that the caldav library pulls in many dependencies, and not all of them supports dead snakes).
event.load() fails, it will retry the load by doing a multiget - https://github.com/python-caldav/caldav/pull/460 and https://github.com/python-caldav/caldav/pull/475 - https://github.com/python-caldav/caldav/issues/459tests/compatibility_issues.py has been moved to caldav/compatibility_hints.py, this to make it available for a caldav-server-tester-tool that I'm splitting off to a separate project/repository, and also to make https://github.com/python-caldav/caldav/issues/402 possible.objects.py-file has been split into three - https://github.com/python-caldav/caldav/pull/483multiget in https://github.com/python-caldav/caldav/pull/492tests/test_caldav.py to tests/conf.py. This allows for:
conf_private.pytests.client-method, which is also used in the examples-section.check_server_compatibility-scriptauto_conn, auto_calendar and auto_calendars may read caldav connection and calendar configuration from a config file, environmental variables or other sources. Currently I've made the minimal possible work to be able to test the caldav-server-tester script.calendar.search(..., sort_keys=("DTSTART") will work. Sort keys expects a list or a tuple, but it's easy to send an attribute by mistake. https://github.com/python-caldav/caldav/issues/448 https://github.com/python-caldav/caldav/pull/449class_-parameter now works when sending data to save_event() etc.journal=True. ref https://github.com/python-caldav/caldav/issues/237 and https://github.com/python-caldav/caldav/pull/486Lots of work lifting the project up to more modern standards and improving code, thanks to Georges Toth (github @sim0nx), Matthias Urlichs (github @sm
["--application-directories", "src"], nothing doing by @ArtemIsmagilov in https://github.com/python-caldav/caldav/pull/421datetime.utcnow(). by @smurfix in https://github.com/python-caldav/caldav/pull/440Full Changelog: https://github.com/python-caldav/caldav/compare/v1.3.9...v1.4.0
Nothing published for this version
Refer to the CHANGELOG.md for a full list
Refer to the CHANGELOG.md for a full list
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Thanks to @bvanjeelharia for reporting and investigating (actually the traceback was reported already by @robinmayol in https://github.com/python-cald
Thanks to @bvanjeelharia for reporting and investigating (actually the traceback was reported already by @robinmayol in https://github.com/python-caldav/caldav/issues/270 but we failed to connect the dots).
…merging this pull request. To avoid breaking changes in v1.x, I threw in an assert instead.
Not much, but one bugfix, again the caldav library was not working for everyone despite lots of functional testing ...
Full Changelog: https://github.com/python-caldav/caldav/compare/v1.1.3...v1.2.0
Pull request by @danigm in https://github.com/python-caldav/caldav/pull/228
Python2 has not been tested for quite some time, hence it has probably been broken since one of the 0.x-releases. I decided to officially drop support for python2 in version 1.0 - but since the release was overdue I procrastinated merging this pull request. To avoid breaking changes in v1.x, I threw in an assert instead.
Pull request by @JasonSanDiego in https://github.com/python-caldav/caldav/pull/288 (with style fixup in https://github.com/python-caldav/caldav/pull/291 ) allows headers parameter to the DAVClient constructor.
Rationale given in https://github.com/python-caldav/caldav/issues/285 :
I'm using Nextcloud and want to retrieve calendar (read only) subscriptions along with the normal read/write calendars. Nextcloud supports two ways of doing this. The easier of the two is to pass the custom HTTP header: X-NC-CalDAV-Webcal-Caching: On
A bug was introduced in version 1.0, via https://github.com/python-caldav/caldav/pull/260 - the code would only work if there was a space in the WWW-Authenticate header. This works for most servers as they will challenge for credentials using a header like WWW-Authenticate: Basic realm="My CalDAV server" - however, WWW-Authenticate: Basic is fully allowed by RFC2617.
Thanks to @jdrozdnovak for debugging and reporting.
https://github.com/python-caldav/caldav/issues/289 - https://github.com/python-caldav/caldav/pull/290
Full Changelog: https://github.com/python-caldav/caldav/compare/v1.1.2...v1.1.3
Full Changelog: https://github.com/python-caldav/caldav/compare/v1.1.2...v1.1.3
Nothing published for this version
Nothing published for this version
Tests broke for what was tagged with 1.0.0 ... because I got interrupted while doing the last changes in the changelog!
Tests broke for what was tagged with 1.0.0 ... because I got interrupted while doing the last changes in the changelog!
Version 1.0 is no big gamechanger compared to version 0.x. The biggest change is the commitment that there shouldn't be any breaking changes in any su…
Version 1.0 is no big gamechanger compared to version 0.x. The biggest change is the commitment that there shouldn't be any breaking changes in any subsequent 1.x-releases.
I had some thoughts in https://github.com/python-caldav/caldav/issues/92 to introduce new API in version 1.0, but have reconsidered. For one thing SEMVER states:
All three points apply and they have done that for a long time, hence a 1.0-release is way overdue, and any API changes will have to wait for 2.0. I'm also intending that the only major breaking changes in 2.0 will be the removal of things that already is marked as deprecated in 1.0. If your library depends on caldav, put "caldav<3.0" in the requirements.
Python2.x is now officially not supported - I've thrown in an assert that we're using python3. In version 1.1, some code for supporting python2 will be cleaned away ... unless, if anyone screams out that they would like to run the newest caldav library version on an old python version, I may reconsider.
Timezones are difficult! When an event is created through the "new" interface, and a datetime object without time zone is passed, version 0.x would send it over to the calendar servr without time zone information as a "floating time" - now it will be converted to UTC-time and sent to the server as such.
"Floating time" is defined at https://www.rfc-editor.org/rfc/rfc5545#section-3.3.5 and may be useful in some circumstances (like, from time to time, I'm trying to maintain a ritual "raise the flag on the boat at 08:00 local time every morning"). However, I believe that in a majority of cases the lack of time zone is unintentional and meant to be local time. In recent versions of python, dt.astimezone(utc) will assume dt is in local time and convert it to UTC, which in most cases probably is the correct thing to do.
If one intentionally wants to create events or tasks with "floating time", then one may use the ical_fragment parameter.
This may have the unfortunate side effect that some clients that aren't aware of their time zone (possibly including my calendar-cli - I will have to look into that) will show events in UTC-time rather than local time. I think the proper thing would be to either fix those clients to be timezone-aware, or even better, to always be explicit and always put the local time zone into the datetimes passed to calendar.save_event().
len(calendar.objects()) did not workDocumentation now has sections on backward compatibility and "Schrodingers support" for old python versions.
The basic_usage_examples.py has been rewritten from scratch.
As always, the test suite is an evergrowing everchanging beast.
Almost all the work has been done through github pull requests this time - making it a lot easier to maintain the changelog. Those pull requests have gone into 1.0:
Some few fixups were done to some of the pull requests after the pull request was added to the master branch - in those cases I've just pushed it directly to the master branch.
I used to have a list of github issues that were touched by a release, and I also used to give credits to people that have contributed simply by raising issues. It's a lot of work going through all the issues, so I will skip it this time.
Full Changelog: https://github.com/python-caldav/caldav/compare/v0.11.0...v1.0.0
Version 0.11 contains minor changes that may break backward compatibility (according to the SemVer specification backward incompatible changes are all…
v0.10 and v0.11 does introduce some "bugfixes" and refactorings which
are supposed to be harmless and which haven't caused any breakages in
tests - but I cannot vouch for that it will not have unintended side
effects in your environment. If you're using the caldav library for
production-critical tasks, you may want to hang on for a while before
upgrading, or wait for v0.11.1. Version 0.11 contains minor changes
that may break backward compatibility (according to the SemVer
specification backward incompatible changes are allowed when doing
0.x-releases. Anyway, according to my knowledge this is the first
time a release contained things breaking backward-compatibility. The
return from the search method has changed a bit, I think I can do this
because v0.10 hasn't been out for long, hence most likely most users
will be using calendar.date_search() rather than calendar.search()
for doing timerange searches, and because the change is relatively
harmless and unlikely to break things. The return from the data
property is now enforced to be a normal string with unix linebreaks,
this is more likely to cause problems, but the previous behaviour was
unpredictable and would anyway sooner or later cause problems for
people depending on the return type to be a binary or being with
carriage returns).
Daniele Ricci has made support for client-side expanding, intended for the calendar servers that supports recurrences but not server-side expanding.
For expanded recurrences, the search-method will (by default)
deliver each recurrence as a separate object (i.e. caldav.Event).
This is slightly backward incompatible with v0.10.
Now obj.data will always return an ordinary string with ordinary
line breaks, while obj.wire_data will always return a byte string
with CRLN line endings. This may break thinsg if the client
expects a binary return, or depends on carriage returns in the
output. While the return type of obj.data has been slightly
unpredictable, it may still have been deterministic dependent on
usage pattern - so the caller may have gotten some expectations
which may now be broken.
Bugfixes, some of the new code in v0.10 didn't handle icalendar data containing a timezone. Some other minor bugfixes.
Nothing published for this version
Nothing published for this version
Tweaks to support the DAVMail server implementation
It's now possible to put things into calendars without first creating valid ical data (just pass things like tdstart etc to the save_event/save_journa
It's now possible to put things into calendars without first
creating valid ical data (just pass things like tdstart etc to the
save_event/save_journal/save_todo methods).
The DAVClient object can now work as a context manager
The authentication code was "fixed" in 0.8.x to work around some weird bug in one specific calendar server, but this introduced lots of problems for other servers. It was then rewritten in 0.9, but still causing (regression) problems with some servers deviating slightly from the relevant RFCs. With some help from Bjoern Kahl, Markus Behrens and Michael Thingnes I believe it should now be quite robust. (IMO, this work does not belong in the caldav library, ideally it should suffice to pass username and password to the requests library).
Some other minor bugfixes and more work on the test framework
Nothing published for this version
Nothing published for this version
Nothing published for this version
The tagged version has been tested towards xandikos, radicale, google, icloud plus an old version of DAViCal. It is expected to work for other servers
The tagged version has been tested towards xandikos, radicale, google, icloud plus an old version of DAViCal. It is expected to work for other servers as well.
The most significant enhancement for v0.8 is support for syncing collections using a sync-token. Some (untested) meta-code in the examples folder on how to use it. It's also thoroughly tested in the functional tests, but the test code is not optimized for readability.
Convenience-methods for finding CalDAV inbox and outbox, for accepting calendar invites, for adding invitations to an icalendar object, doing freebusy-requests towards email addresses and misc. Should be ready for use, but it's relatively untested.
Apple seems to have more or less abandoned their position in the CalDAV ecosystem. While they've never said officially that iCloud supports CalDAV, they do support some basic CalDAV operations. There hasn't been done much dedicated work on supporting iCloud in this release, but a lot of testing has been done, some few tweaks, and some documentation.
Google has two APIs, one legacy API and one new API. The new API is not supported yet. As with iCloud, while very little dedicated work has been done to support Google, I've done a lot of testing, some few tweaks, and some documentation. Unlike Apple, Google is very transparent on their (lack of proper) CalDAV support.
I'm not much good at documentation, for one thing English is not my first language. Anyway, I've tried my best to improve on it and I'm prioritizing good example code. I believe a good and readable code example is worth more than a thousand pages of documentation. I do appreciate all help I can get on this field (even when it's just pointing out typos and grammar errors).
0.7.1 was pretty solid. There are some few new bugfixes in 0.8, nothing significant.
Probably the bulk of the work for this release has been on the test suite; work that won't be visible for the end user, but which hopefully will reduce the number of regression problems. It should also be a little bit easier to run the test suite towards private test servers now.
"Rewritten code" is hardly a selling point, it typically means more bugs and sometimes even less features - but I think the effort done here will pay back relatively soon. I'd say it's a bit important that the maintainer of the library understands the code he's trying to maintain :-)
Thanks to Nylas, Mincheol Song (@mtorange), @olf42, @tfinke, Teymour Aldridge (@teymour-aldridge), @VanKurt, Ian Bottomley (@kyloe), Michael Wieland (@Programie), Herve Commowick (@vr), Jelmer Vernooij (@jelmer), Stephan (@kiffie), @pleasedonotwatch, @frank-pet.
Version 0.7 introduced a bug for caldav servers returning absolute rather than relative URLs. Credits to @mzyy94 for discovery, analysis and a fix.
Version 0.7 introduced a bug for caldav servers returning absolute rather than relative URLs. Credits to @mzyy94 for discovery, analysis and a fix.
API change: add_event, add_todo and add_journal methods are now deprecated and aliases of save_*. New attributes no_create and no_overwrite if one wan…
As for real code changes - actually, not that much. The library is relatively mature now - the remaining changes and issues are mostly either sufficiently significant that they should be procrastinated for a 1.0-release, or I consider them too insignificant to bother with it (except #87 which may come in a 0.8-release). Here is the list of changes:
ref #97
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →