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
19 changes: 19 additions & 0 deletions caldav/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,8 @@ def get_object_by_uid(
Returns:
CalendarObjectResource (Event, Todo, or Journal)
"""
if self.is_async_client:
return self._async_get_object_by_uid(uid, comp_filter, comp_class)
## Use self.search() rather than CalDAVSearcher directly, so that any
## monkey-patching of Calendar.search (e.g. the search-cache delay for
## servers with lazy search indexes) is respected. This mirrors the
Expand All @@ -1504,6 +1506,23 @@ def get_object_by_uid(
error.assert_(len(items_found) == 1)
return items_found[0]

async def _async_get_object_by_uid(
self,
uid: str,
comp_filter: cdav.CompFilter | None = None,
comp_class: Optional["CalendarObjectResource"] = None,
) -> "Event":
"""Async helper for get_object_by_uid()."""
items_found = await self.search(
uid=uid, comp_class=comp_class, xml=comp_filter, post_filter=True, _hacks="insist"
)
items_found = [o for o in items_found if o.id == uid]

if not items_found:
raise error.NotFoundError("%s not found on server" % uid)
error.assert_(len(items_found) == 1)
return items_found[0]

def get_todo_by_uid(self, uid: str) -> "CalendarObjectResource":
"""
Get a task/todo from the calendar by UID.
Expand Down
65 changes: 65 additions & 0 deletions tests/test_caldav_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1837,6 +1837,71 @@ def testGetObjectByUidExactMatch(self):
calendar.get_object_by_uid("20010712T182145Z-123401@example.com-nope")


class TestAsyncGetObjectByUid:
"""Tests for async support in get_object_by_uid and friends (issue #642)."""

def _make_multistatus(self, ical_data, href="/calendar/ev1.ics"):
return f"""<d:multistatus xmlns:d="DAV:" xmlns:cal="urn:ietf:params:xml:ns:caldav">
<d:response>
<d:href>{href}</d:href>
<d:propstat>
<d:prop>
<cal:calendar-data>{ical_data}</cal:calendar-data>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>"""

def test_get_object_by_uid_returns_coroutine_for_async_client(self):
"""get_object_by_uid() must return a coroutine (not a result) when the
client is an AsyncDAVClient, so that the caller can await it."""
import asyncio

from caldav.async_davclient import AsyncDAVClient

xml_response = self._make_multistatus(ev1)
client = MockedDAVClient(xml_response)
# Pretend the client is async by patching the type name
client.__class__ = type(
"AsyncDAVClient", (MockedDAVClient,), {"__module__": AsyncDAVClient.__module__}
)
calendar = Calendar(client, url="/calendar/")
assert calendar.is_async_client
uid = "20010712T182145Z-123401@example.com"
result = calendar.get_object_by_uid(uid)
# Must be a coroutine, not an Event/CalendarObjectResource
assert asyncio.iscoroutine(result), (
f"expected coroutine from async client, got {type(result)}"
)
result.close() # clean up to avoid ResourceWarning

def test_get_object_by_uid_async_returns_correct_object(self):
"""Awaiting the coroutine from get_object_by_uid() returns the right object."""
import asyncio

from caldav.async_davclient import AsyncDAVClient

client = MockedDAVClient("")
client.__class__ = type(
"AsyncDAVClient", (MockedDAVClient,), {"__module__": AsyncDAVClient.__module__}
)
calendar = Calendar(client, url="/calendar/")
uid = "20010712T182145Z-123401@example.com"

# Build a real Event object that the mocked search will return
fake_event = Event(client=client, url="/calendar/ev1.ics", data=ev1, parent=calendar)

# Mock calendar.search as an async function returning our fake event
async def fake_search(**kwargs):
return [fake_event]

calendar.search = fake_search

obj = asyncio.run(calendar.get_object_by_uid(uid))
assert obj.id == uid


class TestRateLimitHelpers:
"""Unit tests for the shared rate-limit helper functions in caldav.lib.error."""

Expand Down
Loading