Skip to content

Commit bf69793

Browse files
committed
uuid1 -> uuid4 fix, plus preparing for v3.0.1
1 parent 1b947f0 commit bf69793

7 files changed

Lines changed: 30 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,32 @@ Changelogs prior to v2.0 is pruned, but was available in the v2.x releases
1212

1313
This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), though for pre-releases PEP 440 takes precedence.
1414

15-
## [Unreleased]
15+
## [3.0.1] - 2026-03-04
16+
17+
Highlights:
18+
19+
* Minor bugfix to support old versions of httpx
20+
* New test server docker container: OX
21+
* Minor other fixes and workarounds
22+
* Started working on proper documentation for the 3.x-series
23+
24+
### Test runs before release
25+
26+
* Xandikos, Radicale, all docker servers (including OX), an external Zimbra server, but no other external servers.
1627

1728
### Added
1829

19-
* **OX App Suite** compatibility hints added; the library is now tested against OX App Suite via a new Docker test server (`tests/docker-test-servers/ox/`).
20-
* New `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.
30+
* **OX App Suite** included in the docker test servers. Compatibility hints added. To get OX running it's needed to do an extra build step. See `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).
31+
* New `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).
2132

2233
### Fixed
2334

2435
* `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
36+
* Stalwart (like purelymail) includes extra "not found" error data in some responses. This could trigger a spurious `"Deviation from expectations found"` log error in production, or an assertion failure in debug mode.
37+
38+
### Security
39+
40+
* UUID1 was replaced with UUID4 before releasing v3.0 ... some places. Unfortunately I forgot to grep for UUID1 before preparing the release. When UIDs are generated by UUID1, it may embed the host MAC address in calendar data shared with third parties. Switched to UUID4 throughout.
2541

2642
### Potentially Breaking Changes
2743

@@ -222,6 +238,7 @@ Additionally, direct `DAVClient()` instantiation should migrate to `get_davclien
222238

223239
### GitHub Issues Closed
224240

241+
* #71 - `add_object` vs `save_object` (reopened, reverted and closed)
225242
* #128 - Calendar constructor should accept name parameter (long-standing issue) -- Tobias Brox (@tobixen)
226243
* #342 - need support asyncio -- @ArtemIsmagilov
227244
* #424 - implement support for JMAP protocol -- @ArtemIsmagilov

caldav/base_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ def _normalize_to_list(obj: Any) -> list:
389389
"""Convert a string or None to a list for uniform handling."""
390390
if not obj:
391391
return []
392-
if isinstance(obj, (str, bytes)):
392+
if isinstance(obj, str | bytes):
393393
return [obj]
394394
return list(obj)
395395

caldav/calendarobjectresource.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,7 @@ def copy(self, keep_uid: bool = False, new_parent: Any | None = None) -> Self:
662662
obj = self.__class__(
663663
parent=new_parent or self.parent,
664664
data=self.data,
665-
id=self.id if keep_uid else str(uuid.uuid1()),
665+
id=self.id if keep_uid else str(uuid.uuid4()),
666666
)
667667
if new_parent or not keep_uid:
668668
obj.url = obj._generate_url()
@@ -843,7 +843,7 @@ def _find_id_path(self, id=None, path=None) -> None:
843843
## TODO: do we ever get here? Perhaps this if is completely moot?
844844
id = re.search("(/|^)([^/]*).ics", str(path)).group(2)
845845
if id is None:
846-
id = str(uuid.uuid1())
846+
id = str(uuid.uuid4())
847847

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

caldav/collection.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ def freebusy_request(self, dtstart, dtend, attendees):
473473
freebusy_ical.add("prodid", "-//tobixen/python-caldav//EN")
474474
freebusy_ical.add("version", "2.0")
475475
freebusy_ical.add("method", "REQUEST")
476-
uid = uuid.uuid1()
476+
uid = uuid.uuid4()
477477
freebusy_comp = icalendar.FreeBusy()
478478
freebusy_comp.add("uid", uid)
479479
freebusy_comp.add("dtstamp", datetime.now())
@@ -568,7 +568,7 @@ def _create(
568568
return self._async_create(name, id, supported_calendar_component_set, method)
569569

570570
if id is None:
571-
id = str(uuid.uuid1())
571+
id = str(uuid.uuid4())
572572
self.id = id
573573

574574
if method is None:
@@ -636,7 +636,7 @@ async def _async_create(
636636
) -> None:
637637
"""Async implementation of _create."""
638638
if id is None:
639-
id = str(uuid.uuid1())
639+
id = str(uuid.uuid4())
640640
self.id = id
641641

642642
if method is None:

caldav/lib/vcal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ def create_ical(ical_fragment=None, objtype=None, language="en_DK", **props):
209209
if not component.get("dtstamp") and not props.get("dtstamp"):
210210
component.add("dtstamp", datetime.datetime.now(tz=datetime.timezone.utc))
211211
if not component.get("uid") and not props.get("uid"):
212-
component.add("uid", uuid.uuid1())
212+
component.add("uid", uuid.uuid4())
213213

214214
alarm = {}
215215
for prop in props:

caldav/operations/calendarobject_ops.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ def _get_primary_component(icalendar_instance: Any) -> Any | None:
469469
for comp in components:
470470
if isinstance(
471471
comp,
472-
(icalendar.Event, icalendar.Todo, icalendar.Journal, icalendar.FreeBusy),
472+
icalendar.Event | icalendar.Todo | icalendar.Journal | icalendar.FreeBusy,
473473
):
474474
return comp
475475

examples/scheduling_examples.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def cleanup(self, calendar_name, calendar_id):
7676
caldata.add("prodid", "-//tobixen//python-icalendar//en_DK")
7777
caldata.add("version", "2.0")
7878

79-
uid = uuid.uuid1()
79+
uid = uuid.uuid4()
8080
event = Event()
8181
event.add("dtstamp", datetime.now())
8282
event.add("dtstart", datetime.now() + timedelta(days=4000))
@@ -89,7 +89,7 @@ def cleanup(self, calendar_name, calendar_id):
8989
caldata2.add("prodid", "-//tobixen//python-icalendar//en_DK")
9090
caldata2.add("version", "2.0")
9191

92-
uid = uuid.uuid1()
92+
uid = uuid.uuid4()
9393
event = Event()
9494
event.add("dtstamp", datetime.now())
9595
event.add("dtstart", datetime.now() + timedelta(days=4000))

0 commit comments

Comments
 (0)