Skip to content

Commit 48dde00

Browse files
authored
Make vobject into an optional dependency
Fixes #477 Piggybacking in some documetation fixes, CHANGELOG, etc, preparing for release 2.0 #527
1 parent 993cce2 commit 48dde00

6 files changed

Lines changed: 48 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,20 @@ This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0
88

99
## [2.0.0] - [Unreleased]
1010

11+
Here are the most important changes in 2.0:
12+
13+
* Version 2.0 drops support for old python versions and replaces requests 2.x with niquests 3.x, a fork of requests.
14+
* Major overhaul of the documentation
15+
* 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?
16+
* 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.
17+
1118
### Deprecated
1219

20+
* `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:
21+
* `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).
22+
* 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.
23+
* 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)
1324
* 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`.
14-
* `calendar.date_search` - use `calendar.search` instead. (this one has been deprecated for a while, but only with info-logging)
1525
* `davclient.auto_conn` that was introduced just some days ago has already been renamed to `davclient.get_davclient`.
1626

1727
### Added
@@ -24,7 +34,7 @@ This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0
2434
* Improved tests (but no test for inheritance yet).
2535
* Documentation, linked up from the reference section of the doc.
2636
* 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
27-
* 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
37+
* 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
2838
* `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.
2939

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

4252
* 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.
43-
* 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.
53+
* 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.
54+
* To force server-side expansion, a new parameter server_expand can be used
4455

4556
### Removed
4657

@@ -50,6 +61,13 @@ If you disagree with any of this, please raise an issue and I'll consider if it'
5061
* Dependency on the requests library.
5162
* 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?)
5263

64+
### Changes in test framework
65+
66+
* Proxy test has been rewritten. https://github.com/python-caldav/caldav/issues/462 / https://github.com/python-caldav/caldav/pull/514
67+
* Some more work done on improving test coverage
68+
* 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
69+
* 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
70+
5371
## [1.6.0] - 2025-05-30
5472

5573
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.

RELEASE-HOWTO.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ I have no clue on the proper procedures for doing releases, and I keep on doing
88

99
* Go through changes since last release and compare it with the `CHANGELOG.md`. Any change should be logged.
1010
* Run tests towards as many servers as possible
11+
* Use the `PYTHON_CALDAV_DEBUGMODE=pdb` environment variable! Should do some research if we hit any "soft asserts" or "weirdness".
1112
* 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
1213
* It's proper to document somewhere (TODO: where? how?) what servers have been tested
1314
* Does any of the changes require documentation to be rewritten? The documentation should ideally be in sync with the code upon release time.

caldav/calendarobjectresource.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,9 @@
3131
from urllib.parse import unquote
3232

3333
import icalendar
34-
import vobject
3534
from dateutil.rrule import rrulestr
3635
from icalendar import vCalAddress
3736
from icalendar import vText
38-
from vobject.base import VBase
3937

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

705705
## TODO: self.id should either always be available or never
706+
## TODO: run this logic on load, to ensure `self.id` is set after loading
706707
def _find_id_path(self, id=None, path=None) -> None:
707708
"""
708709
With CalDAV, every object has a URL. With icalendar, every object
@@ -715,7 +716,7 @@ def _find_id_path(self, id=None, path=None) -> None:
715716
2) if ID is not given, but the path is given, generate the ID from the
716717
path
717718
3) If neither ID nor path is given, use the uuid method to generate an
718-
ID (TODO: recommendation is to concat some timestamp, serial or
719+
ID (TODO: recommendation in the RFC is to concat some timestamp, serial or
719720
random number and a domain)
720721
4) if no path is given, generate the URL from the ID
721722
"""
@@ -732,6 +733,7 @@ def _find_id_path(self, id=None, path=None) -> None:
732733
id = re.search("(/|^)([^/]*).ics", str(path)).group(2)
733734
if id is None:
734735
id = str(uuid.uuid1())
736+
735737
i.pop("UID", None)
736738
i.add("UID", id)
737739

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

1081-
def _set_vobject_instance(self, inst: vobject.base.Component):
1088+
def _set_vobject_instance(self, inst: "vobject.base.Component"):
10821089
self._vobject_instance = inst
10831090
self._data = None
10841091
self._icalendar_instance = None
10851092
return self
10861093

1087-
def _get_vobject_instance(self) -> Optional[vobject.base.Component]:
1094+
def _get_vobject_instance(self) -> Optional["vobject.base.Component"]:
1095+
try:
1096+
import vobject
1097+
except ImportError:
1098+
logging.critical(
1099+
"A vobject instance has been requested, but the vobject library is not installed (vobject is no longer an official dependency in 2.0)"
1100+
)
1101+
return None
10881102
if not self._vobject_instance:
10891103
if self._get_data() is None:
10901104
return None
@@ -1102,29 +1116,29 @@ def _get_vobject_instance(self) -> Optional[vobject.base.Component]:
11021116

11031117
## event.instance has always yielded a vobject, but will probably yield an icalendar_instance
11041118
## in version 3.0!
1105-
def _get_deprecated_vobject_instance(self) -> Optional[vobject.base.Component]:
1119+
def _get_deprecated_vobject_instance(self) -> Optional["vobject.base.Component"]:
11061120
warnings.warn(
11071121
"use event.vobject_instance or event.icalendar_instance",
11081122
DeprecationWarning,
11091123
stacklevel=2,
11101124
)
11111125
return self._get_vobject_instance()
11121126

1113-
def _set_deprecated_vobject_instance(self, inst: vobject.base.Component):
1127+
def _set_deprecated_vobject_instance(self, inst: "vobject.base.Component"):
11141128
warnings.warn(
11151129
"use event.vobject_instance or event.icalendar_instance",
11161130
DeprecationWarning,
11171131
stacklevel=2,
11181132
)
11191133
return self._get_vobject_instance(inst)
11201134

1121-
vobject_instance: VBase = property(
1135+
vobject_instance: "vobject.base.VBase" = property(
11221136
_get_vobject_instance,
11231137
_set_vobject_instance,
11241138
doc="vobject instance of the object",
11251139
)
11261140

1127-
instance: VBase = property(
1141+
instance: "vobject.base.VBase" = property(
11281142
_get_deprecated_vobject_instance,
11291143
_set_deprecated_vobject_instance,
11301144
doc="vobject instance of the object (DEPRECATED! This will yield an icalendar instance in caldav 3.0)",

caldav/collection.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -689,12 +689,6 @@ def date_search(
689689
else:
690690
comp_class = None
691691

692-
## xandikos now yields a 5xx-error when trying to pass
693-
## expand=True, after I prodded the developer that it doesn't
694-
## work. By now there is some workaround in the test code to
695-
## avoid sending expand=True to xandikos, but perhaps we
696-
## should run a try-except-retry here with expand=False in the
697-
## retry, and warnings logged ... or perhaps not.
698692
objects = self.search(
699693
start=start,
700694
end=end,

caldav/davclient.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ def __init__(
472472
username and password may be omitted.
473473
474474
THe niquest library will honor standard proxy environmental variables like
475-
HTTP_PROXY, HTTPS_PROXY and ALL_PROXY.
475+
HTTP_PROXY, HTTPS_PROXY and ALL_PROXY. See https://niquests.readthedocs.io/en/latest/user/advanced.html#proxies
476476
"""
477477
headers = headers or {}
478478

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ classifiers = [
3232
]
3333

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

5049
[project.optional-dependencies]
5150
test = [
51+
"vobject",
5252
"pytest",
5353
"coverage",
5454
"manuel",

0 commit comments

Comments
 (0)