objects: JMAP data objects#
The data objects calendaring-jmap’s methods return: calendars, calendar events, busy intervals, attachments, and contacts.
JMAP Calendar object.
Represents a JMAP Calendar resource as returned by Calendar/get.
Properties are defined in the JMAP Calendars specification.
- class calendaring_jmap.objects.calendar.JMAPCalendar(id: str, name: str, description: str | None = None, color: str | None = None, is_subscribed: bool = True, my_rights: dict = <factory>, sort_order: int = 0, is_visible: bool = True, time_zone: str | None = None, share_with: dict | None = None, default_alerts_with_time: dict | None = None, default_alerts_without_time: dict | None = None)[source]#
Bases:
Generic[_M]A JMAP Calendar object.
- Variables:
id (str) – Server-assigned calendar identifier.
name (str) – Display name of the calendar. Per-user: a sharee who sets this gets their own copy, leaving the owner’s name unchanged.
description (str | None) – Optional longer description.
color (str | None) – Optional CSS color string (e.g.
"#ff0000"). Per-user, same override rule asname.is_subscribed (bool) – Whether the user is subscribed to this calendar.
my_rights (dict) – Dict of right names to bool for the current user.
sort_order (int) – Hint for display ordering (lower = first). Per-user.
is_visible (bool) – Whether the calendar should be displayed. Per-user.
time_zone (str | None) – IANA time zone used to resolve floating events on this calendar (e.g. for alerts, availability).
Nonefalls back to the account’s own time zone. Per-user, same override rule asname.share_with (dict | None) – Map of Principal ID to a dict of right names to bool (
mayReadItems,mayWriteAll, etc.). Only visible to and settable by users with themayShareright; the server returnsNonehere for anyone else. Not per-user: this is the calendar’s actual sharing configuration.default_alerts_with_time (dict | None) – Map of alert ID to Alert dict, applied to new timed events on this calendar when
useDefaultAlertsis set. Per-user, not inherited from the owner.default_alerts_without_time (dict | None) – Same as
default_alerts_with_time, for all-day events.
- add_event(ical_str: str) str[source]#
- add_event(ical_str: str) Coroutine[Any, Any, str]
Add an event to this calendar from an iCalendar string.
When called on an async-backed calendar, returns a coroutine that must be awaited.
- Parameters:
ical_str – A VCALENDAR string representing the event.
- Returns:
The server-assigned JMAP event ID. This returns a string ID rather than a calendar object: the
CalendarEvent/setresponse does not include the full object, so a follow-up GET would be required.- Raises:
JMAPMethodError – If the server rejects the create request.
ValueError – Same conditions as
create_event().
- classmethod from_jmap(data: dict) JMAPCalendar[source]#
Construct a JMAPCalendar from a raw JMAP Calendar JSON dict.
Unknown keys in
dataare silently ignored so that forward compatibility is maintained as the spec evolves.
- get_object_by_uid(uid: str, comp_class: Any = None) JMAPCalendarObject[source]#
- get_object_by_uid(uid: str, comp_class: Any = None) Coroutine[Any, Any, JMAPCalendarObject]
Get a calendar object by its iCalendar UID.
When called on an async-backed calendar, returns a coroutine that must be awaited.
- Parameters:
uid – The iCalendar UID to search for.
comp_class – JMAP
CalendarEvent/queryhas no native component-type filter, so this argument is currently ignored.
- Returns:
A
JMAPCalendarObjectfor the matching object.- Raises:
JMAPMethodError – If no object with this UID is found.
- search(**searchargs: Any) list[JMAPCalendarObject][source]#
- search(**searchargs: Any) Coroutine[Any, Any, list[JMAPCalendarObject]]
Search for calendar objects in this calendar.
When called on an async-backed calendar, returns a coroutine that must be awaited.
Accepted keyword arguments (all optional):
start(datetime or str): only events ending after this time (maps to JMAPafterfilter).end(datetime or str): only events starting before this time (maps to JMAPbeforefilter).text(str): free-text search across title, description, locations, and participants.has_attachment(bool),participant_email(str),participation_status(str),participant_role(str): seesearch_events().
Does not support
position/limit/calculate_total: unlikesearch_events(), this always returns every matching object in one call. Passing them is silently ignored rather than an error, since this accepts**searchargs; usesearch_eventsdirectly instead if you need to page through a large result set.- Returns:
List of
JMAPCalendarObjectfor all matching objects.
JMAP calendar resource object.
Wraps a raw JSCalendar CalendarEvent dict with a minimal interface:
.id, .parent, get_data, get_icalendar_instance,
edit_icalendar_instance, and save.
- class calendaring_jmap.objects.calendar_object.JMAPCalendarObject(data: dict, parent: JMAPCalendar | None)[source]#
Bases:
objectThin wrapper around a raw JSCalendar CalendarEvent dict.
Stores the server’s JSON response as-is. No JMAP field names are mapped to typed attributes: callers work with the dict directly via
get_data(), or convert to iCalendar viaget_icalendar_instance().- Variables:
data (dict) – Raw JSCalendar CalendarEvent dict as returned by
CalendarEvent/get.parent (JMAPCalendar | None) – The
JMAPCalendarthis object belongs to, orNonewhen fetched without a calendar context (e.g. viaget_event()).
- edit_icalendar_instance()[source]#
Borrow an editable
icalendar.Calendarfor this object.Yields the cached
icalendar.Calendarfor in-place editing. Callsave()after thewithblock to persist changes to the server.Note:
save()is sync-only. Async-backed calendars cannot use this path yet.Example:
with obj.edit_icalendar_instance() as cal: cal.subcomponents[0]["SUMMARY"] = vText("New title") obj.save()
- get_icalendar_instance() Calendar[source]#
Return an
icalendar.Calendarfor this object.The result is cached after the first conversion. Treat it as read-only; use
edit_icalendar_instance()to make and persist changes.
- parent: JMAPCalendar | None#
- save() None[source]#
Persist changes made via
edit_icalendar_instance()to the server.Serialises the (possibly edited) icalendar object back to an iCalendar string and calls
update_event()on the parent calendar’s client.- Raises:
JMAPMethodError – If no parent calendar is set (
parentisNone).RuntimeError – If called on an async-backed calendar.
ValueError – If an edit made via
edit_icalendar_instance()leaves the object in a shape rejected byupdate_event()(e.g. removingUID/DTSTART, or movingDTENDbeforeDTSTART).
JMAP BusyPeriod object.
Represents one interval of a Principal/getAvailability response
(draft-ietf-jmap-calendars section 2.2), or one computed client-side by the
CalendarEvent/query fallback path when a server doesn’t implement that
method. Read-only: nothing here is ever sent back to the server, so there
is no to_jmap.
- class calendaring_jmap.objects.busy_interval.BusyInterval(start: str, end: str, busy_status: str, event: JMAPCalendarObject | None = None)[source]#
Bases:
objectOne busy/free interval.
- Variables:
start (str) – Start of the interval (
UTCDateTimestring, alwaysZ-suffixed). Normalized to this one shape regardless of whether it came fromPrincipal/getAvailabilityor theCalendarEvent/queryfallback, since RFC 8984 allows a JSCalendarstartto be UTC, time-zone-qualified, or floating; a floating start has no true UTC equivalent and is treated as UTC.end (str) – End of the interval (
UTCDateTimestring), same normalization asstart.busy_status (str) – One of
"confirmed","tentative","unavailable"(seeconstants.BUSY_STATUS_*).event (calendaring_jmap.objects.calendar_object.JMAPCalendarObject | None) – The underlying event, when the server populated it. This can be
Noneeven when details were requested: confirmed live that this happens when the caller lacksmayReadItems, the event is marked private, or (Stalwart-specific) the caller didn’t also pass a server-supportedeventPropertieslist.
- event: JMAPCalendarObject | None = None#
- classmethod from_jmap(data: dict) BusyInterval[source]#
Construct a BusyInterval from a raw JMAP BusyPeriod JSON dict.
Unknown keys in
dataare silently ignored so that forward compatibility is maintained as the spec evolves.
JMAP event attachment object.
Wraps a JSCalendar Link (RFC 8984 Section 1.4.11) whose rel marks it
as an attachment, as returned in a CalendarEvent’s links property.
- class calendaring_jmap.objects.attachment.JMAPAttachment(link_id: str, href: str | None = None, blob_id: str | None = None, title: str | None = None, content_type: str | None = None, size: int | None = None)[source]#
Bases:
objectAn event attachment, backed by a JSCalendar Link object.
- Variables:
link_id (str) – The key under which this Link appears in the event’s
linksmap.href (str | None) – URL from which the attachment can be fetched, or
Noneif the Link carries ablobIdinstead (a calendars-draft extension this client does not itself write, seeattach_to_event()).blob_id (str | None) – The referenced blob’s id, when the server returns one.
title (str | None) – Human-readable description of the attachment.
content_type (str | None) – Media type of the attachment, if known.
size (int | None) – Size in octets, if known.
- classmethod from_jmap(link_id: str, data: dict) JMAPAttachment[source]#
Construct a JMAPAttachment from a raw JSCalendar Link dict.
Unknown keys in
dataare silently ignored so that forward compatibility is maintained as the spec evolves.
- static is_attachment(link_data: dict) bool[source]#
Return whether a raw JSCalendar Link dict represents an attachment.
A Link is an attachment when its
relis"enclosure"(RFC 8984 Section 1.4.11); otherlinksentries (e.g. a conference URL) are not.Known Cyrus limitation, confirmed live: Cyrus (3.13.7-545-gc04cece19) accepts a Link with
rel: "enclosure"with no error, but does not reliably persist it (otherrelvalues round-trip fine, isolating this to that one value). An attachment written byattach_to_event()may therefore not be classified as one by this method when read back from Cyrus. Stalwart has no such issue.
JMAP Contacts objects (RFC 9610).
AddressBook and ContactCard, as returned by AddressBook/get and
ContactCard/get. ContactCard properties follow JSContact (RFC 9553).
Rights maps (share_with, my_rights) and the raw JSContact name/
emails dicts are all kept as untyped dicts rather than typed
sub-objects, matching JMAPCalendar’s
own share_with/my_rights convention.
- class calendaring_jmap.objects.contact.JMAPAddressBook(id: str, name: str, description: str | None = None, sort_order: int = 0, is_default: bool = False, is_subscribed: bool = True, share_with: dict | None = None, my_rights: dict = <factory>)[source]#
Bases:
objectA JMAP AddressBook object (RFC 9610 Section 2).
- Variables:
id (str) – Server-assigned identifier.
name (str) – Display name, non-empty, at most 255 UTF-8 octets.
description (str | None) – Optional longer description.
sort_order (int) – Hint for display ordering (lower = first). Default 0.
is_default (bool) – True for at most one address book in the account (RFC 9610 Section 2 says this SHOULD be true for exactly one, but only MUST NOT be true for more than one), server-set.
is_subscribed (bool) – Whether the user is subscribed to this address book.
share_with (dict | None) – Map of Principal id (RFC 9670 Section 2) to a dict of AddressBookRights (
mayRead,mayWrite,mayShare,mayDelete).Noneif unshared or the server lacks RFC 9670 support (RFC 9610 Section 2), verified against Cyrus. Stalwart behaves differently: it returns{}here instead when unshared, notNone, found by testing directly against it. A caller checking “is this shared” should treat the value as falsy (not address_book.share_with), not test foris Nonespecifically.my_rights (dict) – AddressBookRights dict for the current user, server-set.
- classmethod from_jmap(data: dict) JMAPAddressBook[source]#
Construct a JMAPAddressBook from a raw JMAP AddressBook JSON dict.
Unknown keys in
dataare silently ignored so that forward compatibility is maintained as the spec evolves.
- class calendaring_jmap.objects.contact.JMAPContact(id: str, uid: str | None = None, address_book_ids: dict = <factory>, kind: str = 'individual', name: dict | None = None, emails: dict | None = None)[source]#
Bases:
objectA JMAP ContactCard object (RFC 9610 Section 3), a JSContact Card (RFC 9553 Section 2) plus JMAP’s own
id/addressBookIds.- Variables:
id (str) – Server-assigned identifier. May differ from
uid.uid (str | None) – The card’s own JSContact uid (RFC 9553 Section 2.1.9). That section marks it mandatory on every Card, but confirmed live that Stalwart’s
ContactCard/getnever returns it at all, even when explicitly requested viaproperties. Unlikeid, this is not required here: a strict-missing check would raise on every Stalwart contact. Cyrus does return it.address_book_ids (dict) – Map of AddressBook id to
True; a card belongs to at least one address book.kind (str) – JSContact Card kind (RFC 9553 Section 2.1.4): one of
"individual"(default),"group","org","location","device","application".name (dict | None) – Raw JSContact Name dict (RFC 9553 Section 2.2.1), or
None. Its own shape (fullvs.components) is read throughdisplay_name(), not by exposing typed fields here.emails (dict | None) – Raw JSContact EmailAddress map (RFC 9553 Section 2.3.1):
{id: {"address": str, "contexts": {...}, "pref": int, ...}}, orNone.
- display_name() str | None[source]#
Best-effort human-readable name.
RFC 9553 Section 2.2.1.1: a Name object has either
fullorcomponentspresent, not necessarily both. Prefersfull; falls back tocomponentsotherwise. WhenisOrderedis true, a"separator"component’s ownvalue(ordefaultSeparatorif there’s no explicit one at that position, or a single space if neither is set) is inserted between the surrounding non-separator components, per the RFC’s own guidance, e.g.given="Jean",separator="-",surname="Paul"joins to"Jean-Paul", not"Jean Paul". WhenisOrderedis false, the RFC forbids any"separator"component at all, so components are simply space-joined in list order. ReturnsNoneif neitherfullnorcomponentsis present.
- classmethod from_jmap(data: dict) JMAPContact[source]#
Construct a JMAPContact from a raw JMAP ContactCard JSON dict.
Unknown keys (including JSContact properties this client doesn’t model, e.g.
organizations,addresses,notes) are silently ignored for forward compatibility.
- primary_email() str | None[source]#
Return one email address for this contact, or
None.Picks the entry with the lowest
pref(RFC 9553 Section 1.5.3: 1 is most preferred, 100 least; an entry with noprefat all, or an explicitnull, is treated as least preferred of all).
Paged CalendarEvent/query result.
Carries a page of events alongside the CalendarEvent/query response’s
own paging fields (RFC 8620 Section 5.5), so a caller doing a full
listing can detect truncation instead of treating a short page as complete.
- class calendaring_jmap.objects.event_search_result.EventSearchResult(events: list[JMAPCalendarObject] = <factory>, position: int = 0, total: int | None = None)[source]#
Bases:
objectOne page of
search_events()results.- Variables:
events (list[calendaring_jmap.objects.calendar_object.JMAPCalendarObject]) – Events matching this page, in server-returned order.
position (int) – Zero-based index of the first event in
eventswithin the complete result set (RFC 8620 Section 5.5).total (int | None) – Total number of events matching the server-side filter (
calendar_id/start/end/text) across every page, orNoneif the caller didn’t request it. Set only when the caller’s owncalculate_totalwas true, never inferred from whether the server’s response happened to include atotalfield: confirmed live that Cyrus returnstotalunconditionally, even whencalculateTotalwas never sent, which RFC 8620 Section 5.5 says the server “MUST” omit in that case. Does not account forhas_attachment/participant_email/participation_status/participant_role, which filter client-side after the server’s own count is taken: a search combining one of those withcalculate_total=Truegets back atotalthat can be larger than the number of events actually returned across every page once the client-side filter is applied.
- events: list[JMAPCalendarObject]#
See Errors for the exception hierarchy.