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().

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#

See Errors for the exception hierarchy.

Last updated on 2026-10-08.