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 as name.

  • 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). None falls back to the account’s own time zone. Per-user, same override rule as name.

  • 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 the mayShare right; the server returns None here 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 useDefaultAlerts is 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/set response does not include the full object, so a follow-up GET would be required.

Raises:
color: str | None = None#
default_alerts_with_time: dict | None = None#
default_alerts_without_time: dict | None = None#
description: str | None = None#
classmethod from_jmap(data: dict) → JMAPCalendar[source]#

Construct a JMAPCalendar from a raw JMAP Calendar JSON dict.

Unknown keys in data are 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/query has no native component-type filter, so this argument is currently ignored.

Returns:

A JMAPCalendarObject for the matching object.

Raises:

JMAPMethodError – If no object with this UID is found.

id: str#
is_subscribed: bool = True#
is_visible: bool = True#
my_rights: dict#
name: str#
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 JMAP after filter).

  • end (datetime or str): only events starting before this time (maps to JMAP before filter).

  • text (str): free-text search across title, description, locations, and participants.

  • has_attachment (bool), participant_email (str), participation_status (str), participant_role (str): see search_events().

Does not support position/limit/calculate_total: unlike search_events(), this always returns every matching object in one call. Passing them is silently ignored rather than an error, since this accepts **searchargs; use search_events directly instead if you need to page through a large result set.

Returns:

List of JMAPCalendarObject for all matching objects.

share_with: dict | None = None#
sort_order: int = 0#
time_zone: str | None = None#
to_jmap() → dict[source]#

Serialise to a JMAP Calendar JSON dict for Calendar/set.

id and myRights are intentionally excluded, both are server-set and must not appear in create or update payloads. Optional fields are included only when they hold a non-default value.

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: object

Thin 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 via get_icalendar_instance().

Variables:
  • data (dict) – Raw JSCalendar CalendarEvent dict as returned by CalendarEvent/get.

  • parent (JMAPCalendar | None) – The JMAPCalendar this object belongs to, or None when fetched without a calendar context (e.g. via get_event()).

data: dict#
edit_icalendar_instance()[source]#

Borrow an editable icalendar.Calendar for this object.

Yields the cached icalendar.Calendar for in-place editing. Call save() after the with block 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_data() → dict[source]#

Return the raw JSCalendar dict as returned by CalendarEvent/get.

get_icalendar_instance() → Calendar[source]#

Return an icalendar.Calendar for this object.

The result is cached after the first conversion. Treat it as read-only; use edit_icalendar_instance() to make and persist changes.

property id: str#

Server-assigned JMAP event ID.

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:

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: object

One busy/free interval.

Variables:
  • start (str) – Start of the interval (UTCDateTime string, always Z-suffixed). Normalized to this one shape regardless of whether it came from Principal/getAvailability or the CalendarEvent/query fallback, since RFC 8984 allows a JSCalendar start to 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 (UTCDateTime string), same normalization as start.

  • busy_status (str) – One of "confirmed", "tentative", "unavailable" (see constants.BUSY_STATUS_*).

  • event (calendaring_jmap.objects.calendar_object.JMAPCalendarObject | None) – The underlying event, when the server populated it. This can be None even when details were requested: confirmed live that this happens when the caller lacks mayReadItems, the event is marked private, or (Stalwart-specific) the caller didn’t also pass a server-supported eventProperties list.

busy_status: str#
end: str#
event: JMAPCalendarObject | None = None#
classmethod from_jmap(data: dict) → BusyInterval[source]#

Construct a BusyInterval from a raw JMAP BusyPeriod JSON dict.

Unknown keys in data are silently ignored so that forward compatibility is maintained as the spec evolves.

start: str#

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: object

An event attachment, backed by a JSCalendar Link object.

Variables:
  • link_id (str) – The key under which this Link appears in the event’s links map.

  • href (str | None) – URL from which the attachment can be fetched, or None if the Link carries a blobId instead (a calendars-draft extension this client does not itself write, see attach_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.

blob_id: str | None = None#
content_type: str | None = None#
classmethod from_jmap(link_id: str, data: dict) → JMAPAttachment[source]#

Construct a JMAPAttachment from a raw JSCalendar Link dict.

Unknown keys in data are silently ignored so that forward compatibility is maintained as the spec evolves.

href: str | None = None#
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 rel is "enclosure" (RFC 8984 Section 1.4.11); other links entries (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 (other rel values round-trip fine, isolating this to that one value). An attachment written by attach_to_event() may therefore not be classified as one by this method when read back from Cyrus. Stalwart has no such issue.

size: int | None = None#
title: str | None = None#

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: object

A 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). None if unshared or the server lacks RFC 9670 support (RFC 9610 Section 2), verified against Cyrus. Stalwart behaves differently: it returns {} here instead when unshared, not None, 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 for is None specifically.

  • my_rights (dict) – AddressBookRights dict for the current user, server-set.

description: str | None = None#
classmethod from_jmap(data: dict) → JMAPAddressBook[source]#

Construct a JMAPAddressBook from a raw JMAP AddressBook JSON dict.

Unknown keys in data are silently ignored so that forward compatibility is maintained as the spec evolves.

id: str#
is_default: bool = False#
is_subscribed: bool = True#
my_rights: dict#
name: str#
share_with: dict | None = None#
sort_order: int = 0#
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: object

A 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/get never returns it at all, even when explicitly requested via properties. Unlike id, 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 (full vs. components) is read through display_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, ...}}, or None.

address_book_ids: dict#
display_name() → str | None[source]#

Best-effort human-readable name.

RFC 9553 Section 2.2.1.1: a Name object has either full or components present, not necessarily both. Prefers full; falls back to components otherwise. When isOrdered is true, a "separator" component’s own value (or defaultSeparator if 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". When isOrdered is false, the RFC forbids any "separator" component at all, so components are simply space-joined in list order. Returns None if neither full nor components is present.

emails: dict | None = None#
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.

id: str#
kind: str = 'individual'#
name: dict | None = None#
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 no pref at all, or an explicit null, is treated as least preferred of all).

uid: str | None = None#

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: object

One 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 events within 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, or None if the caller didn’t request it. Set only when the caller’s own calculate_total was true, never inferred from whether the server’s response happened to include a total field: confirmed live that Cyrus returns total unconditionally, even when calculateTotal was never sent, which RFC 8620 Section 5.5 says the server “MUST” omit in that case. Does not account for has_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 with calculate_total=True gets back a total that can be larger than the number of events actually returned across every page once the client-side filter is applied.

events: list[JMAPCalendarObject]#
position: int = 0#
total: int | None = None#

See Errors for the exception hierarchy.

Last updated on 2026-10-18.