Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,20 @@ This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0

## [2.0.0] - [Unreleased]

Here are the most important changes in 2.0:

* Version 2.0 drops support for old python versions and replaces requests 2.x with niquests 3.x, a fork of requests.
* Major overhaul of the documentation
* Support for reading configuration from a config file or environmental variables - I didn't consider that to be within the scope of the caldav library, but why not - why should every application reinvent some configuration file format, and if an end-user have several applications based on python-caldav, why should he need to configure the caldav credentials explicitly for each of them?
* New method `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.

### Deprecated

* `calendar.date_search` - use `calendar.search` instead. (this one has been deprecated for a while, but only with info-logging). This is almost a drop-in replacement, except for two caveats:
* `date_search` does by default to recurrence-expansion when doing searches on closed time ranges. The default value is `False` in search (this gives better consistency - no surprise differences when changing between open-ended and closed-ended searches, but it's recommended to use `expand=True` when possible).
* In `calendar.search`, `split_expanded` is set to `True`. This may matter if you have any special code for handling recurrences in your code. If not, probably the recurrences that used to be hidden will now be visible in your search results.
* I introduced the possibility to set `expand='server'` and `expand='client'` in 1.x to force through expansion either at the server side or client side (and the default was to try server side with fallback to client side). The four possible values "`True`/`False`/`client`/`server`" does not look that good in my opinion so the two latter is now deprecated, a new parameter `server_expand=True` will force server-side expansion now (see also the Changes section)
* The `event.instance` property currently yields a vobject. For quite many years people have asked for the python vobject library to be replaced with the python icalendar objects, but I haven't been able to do that due to backward compatibility. In version 2.0 deprecation warnings will be given whenever someone uses the `event.instance` property. In 3.0, perhaps `event.instance` will yield a `icalendar` instance. Old test code has been updated to use `.vobject_instance` instead of `.instance`.
* `calendar.date_search` - use `calendar.search` instead. (this one has been deprecated for a while, but only with info-logging)
* `davclient.auto_conn` that was introduced just some days ago has already been renamed to `davclient.get_davclient`.

### Added
Expand All @@ -24,7 +34,7 @@ This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0
* Improved tests (but no test for inheritance yet).
* Documentation, linked up from the reference section of the doc.
* It's allowable with a yaml config file, but the yaml module is not included in the dependencies yet ... so late imports as for now, and the import is wrapped in a try/except-block
* New method `davclient.principals()` will return all principals on server - if server permits. It can also do server-side search for a principal with a given user name - if server permits
* New method `davclient.principals()` will return all principals on server - if server permits. It can also do server-side search for a principal with a given user name - if server permits - https://github.com/python-caldav/caldav/pull/514 / https://github.com/python-caldav/caldav/issues/131
* `todo.is_pending` returns a bool. This was an internal method, but is now promoted to a public method. Arguably, it belongs to icalendar library and not here.

### Documentation and examples
Expand All @@ -40,7 +50,8 @@ This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0
### Changed

* The request library has been in a feature freeze for ages and may seem like a dead end. There exists a fork of the project niquests, we're migrating to that one. This means nothing except for one additional dependency. (httpx was also considered, but it's not a drop-in replacement for the requests library, and it's a risk that such a change will break compatibility with various other servers - see https://github.com/python-caldav/caldav/issues/457 for details). Work by @ArtemIsmagilov, https://github.com/python-caldav/caldav/pull/455.
* Search has a new parameter server_expand, which defaults to False. Earlier it would default to True if expand was set. This change makes `search(expand=True, ...)` more consistent regardless of which server is in use, but it breaks bug-backward-compatibility with 1.x.
* Expanded date searches (using either `event.search(..., expand=True)` or the deprecated `event.date_search`) will now by default do a client-side expand. This gives better consistency and probably improved performance, but makes 2.0 bug-incompatible with 1.x.
* To force server-side expansion, a new parameter server_expand can be used

### Removed

Expand All @@ -50,6 +61,13 @@ If you disagree with any of this, please raise an issue and I'll consider if it'
* Dependency on the requests library.
* The `calendar.build_date_search_query` was ripped out. (it was deprecated for a while, but only with info-logging - however, this was an obscure internal method, probably not used by anyone?)

### Changes in test framework

* Proxy test has been rewritten. https://github.com/python-caldav/caldav/issues/462 / https://github.com/python-caldav/caldav/pull/514
* Some more work done on improving test coverage
* Fixed a test issue that would break arbitrarily doe to clock changes during the test run - https://github.com/python-caldav/caldav/issues/380 / https://github.com/python-caldav/caldav/pull/520
* Added test code for some observed problem that I couldn't reproduce - https://github.com/python-caldav/caldav/issues/397 - https://github.com/python-caldav/caldav/pull/521

## [1.6.0] - 2025-05-30

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.
Expand Down
1 change: 1 addition & 0 deletions RELEASE-HOWTO.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ I have no clue on the proper procedures for doing releases, and I keep on doing

* Go through changes since last release and compare it with the `CHANGELOG.md`. Any change should be logged.
* Run tests towards as many servers as possible
* Use the `PYTHON_CALDAV_DEBUGMODE=pdb` environment variable! Should do some research if we hit any "soft asserts" or "weirdness".
* Do research on breakages. If the test breaks also for the previous release of the caldav library, then it's likely to be due to some regression on the server side. For patch-level releases we don't care about such breakages, for minor-level releases we should try to work around problems
* It's proper to document somewhere (TODO: where? how?) what servers have been tested
* Does any of the changes require documentation to be rewritten? The documentation should ideally be in sync with the code upon release time.
Expand Down
34 changes: 24 additions & 10 deletions caldav/calendarobjectresource.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,9 @@
from urllib.parse import unquote

import icalendar
import vobject
from dateutil.rrule import rrulestr
from icalendar import vCalAddress
from icalendar import vText
from vobject.base import VBase

try:
from typing import ClassVar, Optional, Union, Type
Expand Down Expand Up @@ -150,6 +148,7 @@ def set_end(self, end, move_dtstart=False):
please put caldav<3.0 in the requirements.
"""
i = self.icalendar_component
## TODO: are those lines useful for anything?
if hasattr(end, "tzinfo") and not end.tzinfo:
end = end.astimezone(timezone.utc)
duration = self.get_duration()
Expand Down Expand Up @@ -249,8 +248,9 @@ def expand_rrule(
and occurrence.get("STATUS") in ("COMPLETED", "CANCELLED")
):
continue
## TODO: If there are no reports of missing RECURRENCE-ID until 2027,
## the if-statement below may be deleted
error.assert_("RECURRENCE-ID" in occurrence)
## TODO: do we need this?
if "RECURRENCE-ID" not in occurrence:
occurrence.add("RECURRENCE-ID", occurrence.get("DTSTART").dt)
calendar.add_component(occurrence)
Expand Down Expand Up @@ -703,6 +703,7 @@ def load_by_multiget(self) -> Self:
return self

## TODO: self.id should either always be available or never
## TODO: run this logic on load, to ensure `self.id` is set after loading
def _find_id_path(self, id=None, path=None) -> None:
"""
With CalDAV, every object has a URL. With icalendar, every object
Expand All @@ -715,7 +716,7 @@ def _find_id_path(self, id=None, path=None) -> None:
2) if ID is not given, but the path is given, generate the ID from the
path
3) If neither ID nor path is given, use the uuid method to generate an
ID (TODO: recommendation is to concat some timestamp, serial or
ID (TODO: recommendation in the RFC is to concat some timestamp, serial or
random number and a domain)
4) if no path is given, generate the URL from the ID
"""
Expand All @@ -732,6 +733,7 @@ def _find_id_path(self, id=None, path=None) -> None:
id = re.search("(/|^)([^/]*).ics", str(path)).group(2)
if id is None:
id = str(uuid.uuid1())

i.pop("UID", None)
i.add("UID", id)

Expand All @@ -756,6 +758,11 @@ def _put(self, retry_on_failure=True):
if r.status == 302:
path = [x[1] for x in r.headers if x[0] == "location"][0]
elif r.status not in (204, 201):
if retry_on_failure:
try:
import vobject
except ImportError:
retry_on_failure = False
if retry_on_failure:
## This looks like a noop, but the object may be "cleaned".
## See https://github.com/python-caldav/caldav/issues/43
Expand Down Expand Up @@ -1078,13 +1085,20 @@ def _get_wire_data(self):
doc="vCal representation of the object in wire format (UTF-8, CRLN)",
)

def _set_vobject_instance(self, inst: vobject.base.Component):
def _set_vobject_instance(self, inst: "vobject.base.Component"):
self._vobject_instance = inst
self._data = None
self._icalendar_instance = None
return self

def _get_vobject_instance(self) -> Optional[vobject.base.Component]:
def _get_vobject_instance(self) -> Optional["vobject.base.Component"]:
try:
import vobject
except ImportError:
logging.critical(
"A vobject instance has been requested, but the vobject library is not installed (vobject is no longer an official dependency in 2.0)"
)
return None
if not self._vobject_instance:
if self._get_data() is None:
return None
Expand All @@ -1102,29 +1116,29 @@ def _get_vobject_instance(self) -> Optional[vobject.base.Component]:

## event.instance has always yielded a vobject, but will probably yield an icalendar_instance
## in version 3.0!
def _get_deprecated_vobject_instance(self) -> Optional[vobject.base.Component]:
def _get_deprecated_vobject_instance(self) -> Optional["vobject.base.Component"]:
warnings.warn(
"use event.vobject_instance or event.icalendar_instance",
DeprecationWarning,
stacklevel=2,
)
return self._get_vobject_instance()

def _set_deprecated_vobject_instance(self, inst: vobject.base.Component):
def _set_deprecated_vobject_instance(self, inst: "vobject.base.Component"):
warnings.warn(
"use event.vobject_instance or event.icalendar_instance",
DeprecationWarning,
stacklevel=2,
)
return self._get_vobject_instance(inst)

vobject_instance: VBase = property(
vobject_instance: "vobject.base.VBase" = property(
_get_vobject_instance,
_set_vobject_instance,
doc="vobject instance of the object",
)

instance: VBase = property(
instance: "vobject.base.VBase" = property(
_get_deprecated_vobject_instance,
_set_deprecated_vobject_instance,
doc="vobject instance of the object (DEPRECATED! This will yield an icalendar instance in caldav 3.0)",
Expand Down
6 changes: 0 additions & 6 deletions caldav/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,12 +689,6 @@ def date_search(
else:
comp_class = None

## xandikos now yields a 5xx-error when trying to pass
## expand=True, after I prodded the developer that it doesn't
## work. By now there is some workaround in the test code to
## avoid sending expand=True to xandikos, but perhaps we
## should run a try-except-retry here with expand=False in the
## retry, and warnings logged ... or perhaps not.
objects = self.search(
start=start,
end=end,
Expand Down
2 changes: 1 addition & 1 deletion caldav/davclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ def __init__(
username and password may be omitted.

THe niquest library will honor standard proxy environmental variables like
HTTP_PROXY, HTTPS_PROXY and ALL_PROXY.
HTTP_PROXY, HTTPS_PROXY and ALL_PROXY. See https://niquests.readthedocs.io/en/latest/user/advanced.html#proxies
"""
headers = headers or {}

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ classifiers = [
]

dependencies = [
"vobject",
"lxml",
"niquests",
"recurring-ical-events>=2.0.0",
Expand All @@ -49,6 +48,7 @@ Changelog = "https://github.com/python-caldav/caldav/blob/master/CHANGELOG.md"

[project.optional-dependencies]
test = [
"vobject",
"pytest",
"coverage",
"manuel",
Expand Down