Skip to content

Commit 679f447

Browse files
tobixenclaude
andcommitted
fix: make multiget async-aware, fix _post_load_by_multiget, deprecate calendar_multiget
- multiget() now dispatches to _async_multiget_objects() for async clients, following the ASYNC_DUAL_MODE pattern - extracted _post_multiget() shared post-processing helper - added _async_multiget_objects() async counterpart - _post_load_by_multiget() now calls iter() so it works with both generator (_multiget) and list (_async_multiget) inputs - calendar_multiget() now emits DeprecationWarning pointing to multiget() - test_multi_get updated to use multiget() directly (not deprecated calendar_multiget) - fix test_async_integration.py: use save-load.mutable instead of no-overwrite flag prompt: tests/test_async_integration.py::TestAsyncForXandikos::test_multi_get fails in the git worktree branch on /tmp/caldav-async-tests/, fix multiget in collection.py to be async-aware, following the pattern in docs/design/ASYNC_DUAL_MODE.md followup-prompt: wait. calendar_multiget is deprecated and scheduled for deletion. Any test code exercising calendar_multiget can be removed or rewritten to use self.multiget instead. followup-prompt: Add deprecation warnings to that calendar_multiget method followup-prompt: commit (include the save-load.mutable fix) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2cd6332 commit 679f447

3 files changed

Lines changed: 38 additions & 15 deletions

File tree

caldav/calendarobjectresource.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,7 @@ async def _async_load_by_multiget(self) -> Self:
10711071
def _post_load_by_multiget(self, items):
10721072
if not items:
10731073
raise error.NotFoundError(self.url)
1074+
items = iter(items)
10741075
url_data = next(items, None)
10751076
if url_data is None:
10761077
## We shouldn't come here. Something is wrong.

caldav/collection.py

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,6 +1080,7 @@ def _multiget(self, event_urls: Iterable[URL], raise_notfound: bool = False) ->
10801080
"""
10811081
get multiple events' data.
10821082
TODO: Does it overlap the _request_report_build_resultlist method
1083+
## WARNING: async logic is duplicated in _async_multiget — mirror any changes there
10831084
"""
10841085
if self.url is None:
10851086
raise ValueError("Unexpected value None for self.url")
@@ -1098,28 +1099,33 @@ def _multiget(self, event_urls: Iterable[URL], raise_notfound: bool = False) ->
10981099
for r in results:
10991100
yield (r, results[r][cdav.CalendarData.tag])
11001101

1101-
## Replace the last lines with
1102+
def _post_multiget(self, results: Iterable[tuple[str, str]]) -> list[_CC]:
1103+
"""Post-processing shared by multiget and _async_multiget_objects."""
1104+
return [
1105+
self._calendar_comp_class_by_data(data)(
1106+
self.client,
1107+
# Quote path to handle servers returning unencoded spaces (e.g., Zimbra)
1108+
url=self.url.join(quote(unquote(str(url)), safe="/:@")),
1109+
data=data,
1110+
parent=self,
1111+
)
1112+
for url, data in results
1113+
]
1114+
11021115
def multiget(self, event_urls: Iterable[URL], raise_notfound: bool = False) -> Iterable[_CC]:
11031116
"""
11041117
get multiple events' data
11051118
TODO: Does it overlap the _request_report_build_resultlist method?
11061119
@author mtorange@gmail.com (refactored by Tobias)
11071120
"""
1108-
results = self._multiget(event_urls, raise_notfound=raise_notfound)
1109-
for url, data in results:
1110-
# Quote path to handle servers returning unencoded spaces (e.g., Zimbra)
1111-
quoted_url = quote(unquote(str(url)), safe="/:@")
1112-
yield self._calendar_comp_class_by_data(data)(
1113-
self.client,
1114-
url=self.url.join(quoted_url),
1115-
data=data,
1116-
parent=self,
1117-
)
1121+
if self.is_async_client:
1122+
return self._async_multiget_objects(event_urls, raise_notfound=raise_notfound)
1123+
return self._post_multiget(self._multiget(event_urls, raise_notfound=raise_notfound))
11181124

11191125
async def _async_multiget(
11201126
self, event_urls: Iterable[URL], raise_notfound: bool = False
11211127
) -> list[tuple[str, str]]:
1122-
"""Async version of _multiget — returns a list of (url, data) tuples."""
1128+
## WARNING: sync logic is duplicated in _multiget — mirror any changes there
11231129
if self.url is None:
11241130
raise ValueError("Unexpected value None for self.url")
11251131

@@ -1134,13 +1140,29 @@ async def _async_multiget(
11341140
raise error.NotFoundError(f"Status {status} in {href}")
11351141
return [(r, results[r][cdav.CalendarData.tag]) for r in results]
11361142

1143+
async def _async_multiget_objects(
1144+
self, event_urls: Iterable[URL], raise_notfound: bool = False
1145+
) -> list[_CC]:
1146+
"""Async version of multiget."""
1147+
return self._post_multiget(
1148+
await self._async_multiget(event_urls, raise_notfound=raise_notfound)
1149+
)
1150+
11371151
def calendar_multiget(self, *largs, **kwargs):
11381152
"""
11391153
get multiple events' data
11401154
@author mtorange@gmail.com
11411155
(refactored by Tobias)
11421156
This is for backward compatibility. It may be removed in 3.0 or later release.
1157+
1158+
.. deprecated::
1159+
Use :meth:`multiget` instead.
11431160
"""
1161+
warnings.warn(
1162+
"calendar_multiget is deprecated, use multiget instead",
1163+
DeprecationWarning,
1164+
stacklevel=2,
1165+
)
11441166
return list(self.multiget(*largs, **kwargs))
11451167

11461168
def date_search(

tests/test_async_integration.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ async def test_create_overwrite_delete_event(self, async_calendar: Any) -> None:
620620
assert e1.url is not None
621621

622622
# same UID again → overwrite (unless server forbids it)
623-
if not self.is_supported("no-overwrite"):
623+
if not self.is_supported("save-load.mutable"):
624624
e2 = await c.add_event(ev1_static)
625625

626626
# no_create on an existing event must succeed
@@ -718,7 +718,7 @@ async def test_copy_event(self, async_calendar: Any, async_calendar2: Any) -> No
718718

719719
@pytest.mark.asyncio
720720
async def test_multi_get(self, async_calendar: Any) -> None:
721-
"""calendar_multiget() retrieves multiple events in one request."""
721+
"""multiget() retrieves multiple events in one request."""
722722
self.skip_unless_support("save-load.event")
723723

724724
c = async_calendar
@@ -736,7 +736,7 @@ async def test_multi_get(self, async_calendar: Any) -> None:
736736
summary="test-multiget-2",
737737
)
738738

739-
results = await c.calendar_multiget([event1.url, event2.url])
739+
results = await c.multiget([event1.url, event2.url])
740740
assert len(results) == 2
741741
uids = {str(r.icalendar_component["uid"]) for r in results}
742742
assert uids == {"test-multiget-1", "test-multiget-2"}

0 commit comments

Comments
 (0)