88from __future__ import annotations
99
1010from dataclasses import dataclass , field
11+ from datetime import datetime
12+ from typing import TYPE_CHECKING
13+
14+ if TYPE_CHECKING :
15+ from caldav .jmap .async_client import AsyncJMAPClient
16+ from caldav .jmap .client import JMAPClient
1117
1218
1319@dataclass
@@ -34,6 +40,12 @@ class JMAPCalendar:
3440 sort_order : int = 0
3541 is_visible : bool = True
3642
43+ # Injected by JMAPClient.get_calendars() / AsyncJMAPClient.get_calendars()
44+ _client : JMAPClient | AsyncJMAPClient | None = field (
45+ default = None , init = False , repr = False , compare = False
46+ )
47+ _is_async : bool = field (default = False , init = False , repr = False , compare = False )
48+
3749 @classmethod
3850 def from_jmap (cls , data : dict ) -> JMAPCalendar :
3951 """Construct a JMAPCalendar from a raw JMAP Calendar JSON dict.
@@ -70,3 +82,103 @@ def to_jmap(self) -> dict:
7082 if self .color is not None :
7183 d ["color" ] = self .color
7284 return d
85+
86+ def search (self , ** searchargs ) -> list [str ]:
87+ """Search for calendar objects and return them as iCalendar strings.
88+
89+ Mirrors :meth:`caldav.collection.Calendar.search`. When called on an
90+ async-backed calendar, returns a coroutine that must be awaited.
91+
92+ Accepted keyword arguments (all optional):
93+
94+ - ``event`` (bool): search for events (VEVENT components).
95+ - ``todo`` (bool): search for tasks (VTODO). Note: requires server
96+ JMAP Task capability; raises :class:`NotImplementedError` if absent.
97+ - ``start`` (datetime or str): only events ending after this time
98+ (maps to JMAP ``after`` filter).
99+ - ``end`` (datetime or str): only events starting before this time
100+ (maps to JMAP ``before`` filter).
101+ - ``text`` (str): free-text search across title, description,
102+ locations, and participants.
103+
104+ Unknown searchargs keys are silently ignored for forward compatibility.
105+
106+ Returns:
107+ List of VCALENDAR strings for all matching objects.
108+ """
109+ if self ._is_async :
110+ return self ._async_search (** searchargs )
111+ start = searchargs .get ("start" )
112+ end = searchargs .get ("end" )
113+ if isinstance (start , datetime ):
114+ start = start .isoformat ()
115+ if isinstance (end , datetime ):
116+ end = end .isoformat ()
117+ return self ._client ._search (
118+ calendar_id = self .id ,
119+ start = start ,
120+ end = end ,
121+ text = searchargs .get ("text" ),
122+ )
123+
124+ async def _async_search (self , ** searchargs ) -> list [str ]:
125+ start = searchargs .get ("start" )
126+ end = searchargs .get ("end" )
127+ if isinstance (start , datetime ):
128+ start = start .isoformat ()
129+ if isinstance (end , datetime ):
130+ end = end .isoformat ()
131+ return await self ._client ._search (
132+ calendar_id = self .id ,
133+ start = start ,
134+ end = end ,
135+ text = searchargs .get ("text" ),
136+ )
137+
138+ def get_object_by_uid (self , uid : str , comp_class = None ) -> str :
139+ """Get a calendar object by its iCalendar UID.
140+
141+ Mirrors :meth:`caldav.collection.Calendar.get_object_by_uid`. When
142+ called on an async-backed calendar, returns a coroutine that must be
143+ awaited.
144+
145+ Args:
146+ uid: The iCalendar UID to search for.
147+ comp_class: Accepted for API compatibility with the CalDAV interface;
148+ JMAP ``CalendarEvent/query`` has no native component-type filter,
149+ so this argument is currently ignored.
150+
151+ Returns:
152+ A VCALENDAR string for the matching object.
153+
154+ Raises:
155+ JMAPMethodError: If no object with this UID is found.
156+ """
157+ if self ._is_async :
158+ return self ._async_get_object_by_uid (uid )
159+ return self ._client ._get_object_by_uid (uid , calendar_id = self .id )
160+
161+ async def _async_get_object_by_uid (self , uid : str ) -> str :
162+ return await self ._client ._get_object_by_uid (uid , calendar_id = self .id )
163+
164+ def add_event (self , ical_str : str ) -> str :
165+ """Add an event to this calendar from an iCalendar string.
166+
167+ Mirrors :meth:`caldav.collection.Calendar.add_event`. When called on
168+ an async-backed calendar, returns a coroutine that must be awaited.
169+
170+ Args:
171+ ical_str: A VCALENDAR string representing the event.
172+
173+ Returns:
174+ The server-assigned JMAP event ID.
175+
176+ Raises:
177+ JMAPMethodError: If the server rejects the create request.
178+ """
179+ if self ._is_async :
180+ return self ._async_add_event (ical_str )
181+ return self ._client .create_event (self .id , ical_str )
182+
183+ async def _async_add_event (self , ical_str : str ) -> str :
184+ return await self ._client .create_event (self .id , ical_str )
0 commit comments