client: JMAP calendar client#
Synchronous JMAP client.
Wraps session establishment, HTTP communication, and method dispatching into a single object with a clean public API.
Auth note: JMAP has no 401-challenge-retry dance. Credentials are sent upfront on every request. A 401/403 is a hard failure.
- class calendaring_jmap.client.JMAPClient(url: str, username: str | None = None, password: str | None = None, auth=None, auth_type: str | None = None, timeout: int = 30)[source]#
Bases:
_JMAPClientBaseSynchronous JMAP client for calendar operations.
Usage:
from calendaring_jmap import get_jmap_client client = get_jmap_client(url="https://jmap.example.com/.well-known/jmap", username="alice", password="secret") calendars = client.get_calendars()
- Parameters:
url – URL of the JMAP session endpoint (
/.well-known/jmap).username – Username for Basic auth.
password – Password for Basic auth, or bearer token if no username.
auth – A pre-built requests-compatible auth object. Takes precedence over username/password if provided.
auth_type – Force a specific auth type:
"basic"or"bearer".timeout – HTTP request timeout in seconds.
Unless a method’s own docstring says otherwise, its
account_idparameter is the JMAP account to operate on, defaulting to the authenticated user’s own primary account; pass a different account (typically one shared with you viashare_calendar) to operate on that account’s data instead.- accept_invitation(event_id: str, own_email: str, account_id: str | None = None) None[source]#
Accept a meeting invitation, notifying the organizer.
Finds the participant on
event_idmatchingown_emailand sets theirparticipationStatusto accepted. Only that one property is touched; per draft-ietf-jmap-calendars section 5.9, a non-origin account may never modify anything but its own participant properties.There is no counter-proposal method (iTIP COUNTER): neither RFC 8984 nor draft-ietf-jmap-calendars-29 define one. Confirmed live against Cyrus and Stalwart that a non-origin account can still write
start/durationdirectly through the server’s own rights model, but doing so is a plain update outside the section 5.9 per-user-property restriction above, not a supported scheduling primitive, and is not exposed as a method here.- Parameters:
event_id – The JMAP event ID to respond to.
own_email – The email address identifying which participant on the event is you.
account_id – The account owning
event_id.
- Raises:
JMAPMethodError – If no participant matches
own_email, or if the server rejects the update.
- attach_to_event(event_id: str, blob_id: str, name: str, content_type: str, account_id: str | None = None) None[source]#
Attach an uploaded blob to a calendar event.
Adds one Link to the event’s
linksproperty (RFC 8984 Section 4.2.7) withrel: "enclosure". The Link’shrefis set toblob_id’s own download URL, notblobId: draft-ietf-jmap-calendars section 5.3 allows a Link to carryblobIddirectly, but confirmed live against Cyrus and Stalwart that neither server persists it (Cyrus rejects the update; Stalwart accepts it and silently drops the Link).hrefround-trips correctly on both.Not safe against a concurrent write to this event’s
links: reads the current value, then replaces the whole property with the merged result, with noifInStateguard. Whichever write lands second wins silently. Serialize calls for a givenevent_idif that matters to your use case.Known Cyrus limitation: Cyrus accepts a Link with
rel: "enclosure"but does not reliably persist that property, soget_event_attachments()may not find what this method just wrote. SeeJMAPAttachment.is_attachmentfor the full finding. Stalwart is unaffected.- Parameters:
event_id – The JMAP event ID to attach to.
blob_id – The blob id, as returned by
upload_attachment().name – Human-readable title for the attachment.
content_type – Media type of the blob.
account_id – The account owning
event_id.
- Raises:
JMAPCapabilityError – If the server’s Session object has no
downloadUrl.JMAPMethodError – If the event is not found, or the server rejects the update.
- close() None[source]#
Release the persistent HTTP session and its connection pool.
Only needed when the client was not used as a context manager: the documented Quick Start builds one directly, and without this there was no way to hand the sockets back. Idempotent; the session is recreated on the next request.
- confirm_push_verification(subscription_id: str, verification_code: str) None[source]#
Submit the verification code the server POSTed to
callback_url.Required to complete
subscribe_push()’s handshake: the server “MUST NOT make any further requests to the URL” until this is called with the matching code (RFC 8620 Section 7.2.2). Stalwart rejects a wrong code withinvalidPropertiesrather than silently ignoring it, verified against a running instance. Seesubscribe_push()for theaccount_idparameter note.- Parameters:
subscription_id – The JMAP PushSubscription ID returned by
subscribe_push().verification_code – The
verificationCodefrom thePushVerificationpayload the server POSTed to the subscription’s callback URL.
- Raises:
JMAPMethodError – If the code doesn’t match (
error_type"invalidProperties"), or the subscription doesn’t exist.
- create_calendar(name: str, color: str | None = None, timezone: str | None = None) str[source]#
Create a calendar.
- Returns:
The server-assigned JMAP calendar ID.
- Raises:
JMAPMethodError – If the server rejects the create request.
- create_event(calendar_id: str, ical_str: str, account_id: str | None = None, if_in_state: str | None = None) str[source]#
Create a calendar event from an iCalendar string.
- Parameters:
calendar_id – The JMAP calendar ID to create the event in.
ical_str – A VCALENDAR string representing the event.
account_id – The account owning
calendar_id; pass the owner’s account here to add an event to a calendar shared with you (seeget_calendars(account_id=...)).if_in_state – A state string from a prior call that read this account’s calendar state. If the account has changed since, the server rejects the create with
JMAPMethodError(error_type == "stateMismatch") instead of applying it (RFC 8620 Section 5.3). The state covers every event in the account, not just this one.Noneapplies unconditionally.
- Returns:
The server-assigned JMAP event ID.
- Raises:
JMAPMethodError – If the server rejects the create request.
ValueError – If
ical_stris malformed in a wayical_to_jscalrejects (missingUID/DTSTART, a negative duration, mismatchedDTSTART/DTENDvalue types).
- create_task(task_list_id: str, title: str, **kwargs) str[source]#
Create a task in a task list.
- Parameters:
task_list_id – The JMAP task list ID to create the task in.
title – Task title (maps to VTODO
SUMMARY).**kwargs – Optional JMAP Task fields using wire names:
description,due,start,timeZone,estimatedDuration,percentComplete,progress,priority.
- Returns:
The server-assigned JMAP task ID.
- Raises:
JMAPMethodError – If the server rejects the create request.
- decline_invitation(event_id: str, own_email: str, account_id: str | None = None) None[source]#
Decline a meeting invitation, notifying the organizer.
Same as
accept_invitation(), settingparticipationStatusto declined instead.
- delete_calendar(calendar_id: str, on_destroy_remove_events: bool = False, account_id: str | None = None) None[source]#
Delete a calendar.
- Parameters:
calendar_id – The JMAP calendar ID to delete.
on_destroy_remove_events – If
False(the default), deleting a calendar that still has events fails with acalendarHasEventerror instead of deleting anything. PassTrueto remove those events along with the calendar.account_id – The account owning
calendar_id; pass the owner’s account here to delete a calendar shared with you.
- Raises:
JMAPMethodError – If the server rejects the delete.
error_typeis"calendarHasEvent"when the calendar still has events andon_destroy_remove_eventswas leftFalse.
- delete_event(event_id: str, account_id: str | None = None, send_scheduling_messages: bool = False, if_in_state: str | None = None) None[source]#
Delete a calendar event.
- Parameters:
event_id – The JMAP event ID to delete.
account_id – The account owning
event_id; pass the owner’s account here to delete an event on a calendar shared with you.send_scheduling_messages – If true, and this account is the event’s origin, the server sends an iTIP CANCEL to the event’s participants (draft-ietf-jmap-calendars section 5.9.2.2).
if_in_state – A state string from a prior call that read this account’s calendar state. If the account has changed since, the server rejects the delete with
JMAPMethodError(error_type == "stateMismatch") instead of applying it (RFC 8620 Section 5.3). The state covers every event in the account, not just this one.Noneapplies unconditionally.
- Raises:
JMAPMethodError – If the server rejects the delete.
- delete_task(task_id: str) None[source]#
Delete a task.
- Parameters:
task_id – The JMAP task ID to delete.
- Raises:
JMAPMethodError – If the server rejects the delete.
- download_attachment(blob_id: str, content_type: str | None = None, filename: str | None = None, account_id: str | None = None) bytes[source]#
Download a JMAP blob by id (RFC 8620 Section 6.2).
Raw HTTP GET to the Session’s
downloadUrl; the response body is the raw binary content.- Parameters:
blob_id – The blob id, as returned by
upload_attachment().content_type – Media type of the blob, if known. Both Cyrus and Stalwart look up a blob by
blobIdalone, tested directly against each: an omitted or wrongcontent_type/filenameonly affects the response’s ownContent-Type/Content-Dispositionheaders, not whether the download succeeds.filename – Filename to suggest for the download, if known.
account_id – The account owning
blob_id.
- Returns:
The blob’s raw bytes.
- Raises:
JMAPCapabilityError – If the server’s Session object has no
downloadUrl.JMAPAuthError – On HTTP 401 or 403.
requests.HTTPError – On any other non-2xx HTTP response, including 404 for an unknown
blob_id(confirmed live on both Cyrus and Stalwart).
- get_address_books(account_id: str | None = None) list[JMAPAddressBook][source]#
Fetch all address books for an account.
A server that doesn’t advertise RFC 9610 Contacts support at all does not raise here: it logs a warning and returns an empty list instead, regardless of
account_id. For a server that does support Contacts in general but a differentaccount_idlacks it specifically, a genuine error surfaces normally instead, the same assearch_contacts()always does (see_can_skip_contacts_requestfor why only the session’s own account gets the graceful behavior).- Parameters:
account_id – Pass a different account here to browse address books another user has shared with you. Defaults to the session’s own (calendar) account.
- Returns:
List of
JMAPAddressBookobjects, or[]if Contacts isn’t supported (see above).- Raises:
JMAPMethodError – If a different account’s own lack of Contacts support surfaces as a method-level error (e.g.
accountNotSupportedByMethod), or the request otherwise fails.requests.HTTPError – If the request otherwise fails at the HTTP level.
- get_availability(account_ids: list[str], start: str, end: str, show_details: bool = False) dict[str, list[BusyInterval]][source]#
Return busy intervals for each of your own accounts over a time period.
Tries
Principal/getAvailability(draft-ietf-jmap-calendars section 2.2) first, using the current user’s own Principal id (from the session, no extra round trip). Falls back per account to aCalendarEvent/queryscan of that account’s own calendars when the account doesn’t advertise RFC 9670 Principal support at all, the call fails withunknownMethod/accountNotSupportedByMethod, oraccount_idisn’t the session’s own account. The check for Principal support itself is the base RFC 9670 Section 1.5.1 Principal capability, not the draft’s own narrower:availabilitysub-capability: confirmed live that Cyrus implements this method fully without ever advertising that sub-capability, so gating on it would wrongly skip Cyrus every time.Principal/getAvailabilitynever carries an explicitaccountId(confirmed live: Cyrus rejects one in this method’s own args), so it can only ever report on the session’s own account; any other entry inaccount_idsalways goes through the fallback, which does scope to an explicit account. Only ever reports your own availability, never another user’s: checking someone else’s by email would need resolving that email to a Principal id viaPrincipal/query, which this client doesn’t implement yet (the same limitationshare_calendar()already has for Principal ids in general).The fallback path builds each interval from a matching event’s own
freeBusyStatus(RFC 8984 Section 4.4.2): an event marked"free"is excluded from the result entirely, and an event with nofreeBusyStatusat all is treated as"busy", the spec’s own default.- Parameters:
account_ids – JMAP accounts to check, one entry in the result per account. Only the session’s own account can use the primary path; other accounts always use the fallback.
start – Start of the period, inclusive (
YYYY-MM-DDTHH:MM:SS, treated as UTC; matchessearch_events()’s convention).end – End of the period, exclusive (
YYYY-MM-DDTHH:MM:SS, treated as UTC).show_details – If true, populate each interval’s
eventwhere permitted. On Stalwart this needs the account’s server to support returning event details at all, verified directly: omittingeventPropertiesthere returnsevent: Noneeven withshow_details=True. On Cyrus, details are returned by default.
- Returns:
Dict mapping each account id to its list of
BusyIntervalobjects.- Raises:
JMAPMethodError – If
Principal/getAvailabilityfails for a reason other than lack of support (e.g.forbidden,tooLarge), or if the fallback’sCalendarEvent/queryfails (e.g.expandDurationTooLarge,cannotCalculateOccurrencesfor a window with too many recurrence instances to expand).
- get_calendar_subscriptions() list[JMAPCalendar[Literal[False]]][source]#
Fetch the calendars the authenticated account is subscribed to.
JMAP Calendars has no separate subscription object; this is
get_calendars()filtered tois_subscribed.- Returns:
List of
JMAPCalendarobjects withis_subscribedset.
- get_calendars(account_id: str | None = None) list[JMAPCalendar[Literal[False]]][source]#
Fetch all calendars for an account.
- Parameters:
account_id – Pass a different account here to browse calendars another user has shared with you, once
share_calendarhas granted access; the session only lists accounts you can actually reach (seeSession.raw["accounts"]).- Returns:
List of
JMAPCalendarobjects.
- get_event(event_id: str, account_id: str | None = None) JMAPCalendarObject[source]#
Fetch a calendar event by JMAP event ID.
- Parameters:
event_id – The JMAP event ID to retrieve.
account_id – The account owning
event_id; pass the owner’s account here to fetch an event on a calendar shared with you.
- Returns:
A
JMAPCalendarObjectwrapping the raw JSCalendar dict.parentisNonesince noJMAPCalendaris available at the client level.- Raises:
JMAPMethodError – If the event is not found.
- get_event_attachments(event_id: str, account_id: str | None = None) list[JMAPAttachment][source]#
Return the attachments on a calendar event.
- Parameters:
event_id – The JMAP event ID to inspect.
account_id – The account owning
event_id.
- Returns:
List of
JMAPAttachment, one perlinksentry whoserelis"enclosure"(seeJMAPAttachment.is_attachmentfor the known Cyrus limitation). Otherlinksentries (e.g. a conference URL) are not included.- Raises:
JMAPMethodError – If the event is not found.
- get_objects_by_sync_token(sync_token: str, account_id: str | None = None, max_changes: int | None = None) tuple[list[JMAPCalendarObject], list[JMAPCalendarObject], list[str], str, str, bool][source]#
Fetch one page of events changed since a previous sync token.
Calls
CalendarEvent/changesto discover which events were created, modified, or destroyed sincesync_tokenwas issued. Created and modified events are returned asJMAPCalendarObjectinstances; destroyed events are returned as IDs (the objects no longer exist on the server).- Parameters:
sync_token – A state string previously returned by
get_sync_token()or by a prior call to this method.account_id – The account to fetch changes for; pass a different account here to sync a calendar shared with you. Must match the account
sync_tokenwas issued for.max_changes – Optional upper bound on the number of changes the server returns in this page. The server may return fewer than this regardless;
Noneleaves the page size up to the server. Passing a small value forces a large change set to page across multiple calls, confirmed live against both Cyrus and Stalwart to producehas_more_changes=True.
- Returns:
added: objects for newly created events (parentisNone).modified: objects for updated events (parentisNone).deleted: Event IDs that were destroyed.old_state: Thesync_tokenargument echoed back by the server. Compare this againstsync_tokento detect a server that silently accepted an invalid or stale state instead of reporting an error (RFC 8620 Section 5.2 does not require a server to validatesinceStateagainst a real prior state; confirmed live that Cyrus echoes back whatever value it was given, including one that was never actually issued, rather than rejecting it).new_state: Pass to the next call to this method assync_token, whether or nothas_more_changesis set. Named to matcholdState/newStateas a pair (RFC 8620 Section 5.2), notsync_token: both names refer to the same kind of value, a state string previously returned by this method orget_sync_token().has_more_changes: If true, the server capped this page and more changes remain; call this method again withnew_stateto fetch the next page (RFC 8620 Section 5.2).
- Return type:
A 6-tuple
(added, modified, deleted, old_state, new_state, has_more_changes)
- get_sync_token(account_id: str | None = None) str[source]#
Return the current CalendarEvent state string for use as a sync token.
Calls
CalendarEvent/getwith an empty ID list, so no event data is transferred, only thestatefield from the response.- Parameters:
account_id – The account to read the state of; pass a different account here to sync a calendar shared with you, since a JMAP state string is scoped to one account, not the whole session.
- Returns:
Opaque state string. Pass to
get_objects_by_sync_token()to retrieve only what changed since this point.
- get_task(task_id: str) dict[source]#
Fetch a task by ID.
- Parameters:
task_id – The JMAP task ID to retrieve.
- Returns:
Raw JMAP Task dict as returned by the server.
- Raises:
JMAPMethodError – If the task is not found.
- get_task_lists() list[dict][source]#
Fetch all task lists for the authenticated account.
- Returns:
List of raw JMAP TaskList dicts as returned by the server.
- get_task_sync_token(account_id: str | None = None) str[source]#
Return the current Task state string for use as a sync token.
Calls
Task/getwith an empty ID list, so no task data is transferred, only thestatefield from the response. Reads theTasktype’s own state counter, notTaskList’s: RFC 8620 Section 5.2’s/changesis scoped per type, andget_tasks_by_sync_token()callsTask/changes, which only accepts a state previously returned forTaskitself.- Parameters:
account_id – The account to read the state of; pass a different account here to sync a task list shared with you, since a JMAP state string is scoped to one account, not the whole session.
- Returns:
Opaque state string. Pass to
get_tasks_by_sync_token()to retrieve only what changed since this point.
- get_tasks_by_sync_token(sync_token: str, account_id: str | None = None, max_changes: int | None = None) tuple[list[dict], list[dict], list[str], str, str, bool][source]#
Fetch one page of tasks changed since a previous sync token.
Calls
Task/changesto discover which tasks were created, modified, or destroyed sincesync_tokenwas issued. Created and modified tasks are returned as raw JMAP Task dicts, matching every other task method’s own return convention; destroyed tasks are returned as IDs.- Parameters:
sync_token – A state string previously returned by
get_task_sync_token()or by a prior call to this method.account_id – The account to fetch changes for; pass a different account here to sync a task list shared with you. Must match the account
sync_tokenwas issued for.max_changes – Optional upper bound on the number of changes the server returns in this page. See
get_objects_by_sync_token()for the paging behavior this enables.
- Returns:
added: raw dicts for newly created tasks.modified: raw dicts for updated tasks.deleted: Task IDs that were destroyed.old_state: Thesync_tokenargument echoed back by the server. Seeget_objects_by_sync_token()for why this is worth comparing againstsync_tokenyourself.new_state: Pass to the next call to this method assync_token, whether or nothas_more_changesis set. Seeget_objects_by_sync_token()for why this is namednew_staterather thansync_token.has_more_changes: If true, the server capped this page and more changes remain; call this method again withnew_stateto fetch the next page (RFC 8620 Section 5.2).
- Return type:
A 6-tuple
(added, modified, deleted, old_state, new_state, has_more_changes)
- renew_push(subscription_id: str, expires: str | None = None) None[source]#
Extend (or shorten) a push subscription’s expiry.
Takes an
expiresparameter rather than none at all: a renewal with no way to state the new expiry would have nothing to send in the update.- Parameters:
subscription_id – The JMAP PushSubscription ID to renew.
expires – New
UTCDateTimeexpiry, orNoneto request no expiration at all (RFC 8620 Section 7.2:nullmeans “no expiration”). The server may cap or otherwise modify the requested value; confirmed live that Stalwart caps a far-future request to its own maximum (around 7 days out).
See
subscribe_push()for theaccount_idparameter note.- Raises:
JMAPMethodError – If the server rejects the update, or the subscription doesn’t exist.
- search_contacts(text: str | None = None, email: str | None = None, account_id: str | None = None) list[JMAPContact][source]#
Search for contact cards.
All parameters are optional; omitting both returns every contact in the account. Results are fetched in a single batched JMAP request using a result reference from
ContactCard/queryintoContactCard/get, the same patternsearch_events()uses.Unlike
get_address_books(), an account that doesn’t advertise RFC 9610 Contacts support raises normally here, not an empty list: this method’s whole purpose is resolving contact information for something the caller is about to act on (e.g. an invitation), so silently returning nothing risks going unnoticed. Tested directly, the actual error is either a request-levelrequests.HTTPError(HTTP 400,unknownCapability) if the server has no Contacts support at all, or a method-levelJMAPMethodErrorwitherror_type"accountNotSupportedByMethod"if the server supports Contacts but this particular account doesn’t.- Parameters:
text – Free-text search across the whole card. Against a running Cyrus instance, this matches as a substring against the name, not exact-only. Stalwart v0.16.21 behaves differently: it doesn’t match against the card’s name at all, only against the email address (the same reach
emailalready has there), so a name-only search that works on Cyrus can silently return nothing on Stalwart. Useemailwhen the value being searched for might be an email address.email – Match against any address in the card’s
emails. Both Cyrus and Stalwart match this as a substring too, verified directly, e.g."alice"matches"alice@example.com".account_id – Pass a different account here to search an address book shared with you.
- Returns:
List of
JMAPContactinstances.- Raises:
JMAPMethodError – If the account doesn’t support Contacts (see above), or the request otherwise fails.
requests.HTTPError – If the server has no Contacts support at all (see above).
- search_events(calendar_id: str | None = None, start: str | None = None, end: str | None = None, text: str | None = None, has_attachment: bool | None = None, participant_email: str | None = None, participation_status: str | None = None, participant_role: str | None = None, account_id: str | None = None, position: int = 0, limit: int | None = None, calculate_total: bool = False) EventSearchResult[source]#
Search for calendar events.
All parameters are optional; omitting all returns every event in the account. Results are fetched in a single batched JMAP request using a result reference from
CalendarEvent/queryintoCalendarEvent/get.has_attachment,participant_email,participation_status, andparticipant_rolehave no correspondingFilterConditionproperty (draft-ietf-jmap-calendars-29 section 5.11.1); they filter client-side, in Python, against every event the server-side filters above already matched, not on the server itself. All filters combine as AND, matchingFilterCondition’s own “all must apply” rule, but the three participant-based filters are each satisfied independently across an event’s participants: an event where one participant matchesparticipant_emailand a different participant matchesparticipant_rolestill matches both. Since these filters run after the server-side page is fetched,limitcaps the page the server returns, not the number of events left after this client-side filtering, a page can come back smaller thanlimitonce these are applied.Cyrus drops a Link’s
rel: "enclosure"on read-back, confirmed against a running instance, sohas_attachment=Truemisses real attachments there. Both Cyrus and Stalwart drop theattendeerole from a participant’srolesmap, soparticipant_role="attendee"misses matches on both servers.- Parameters:
calendar_id – Limit results to this calendar.
start – Only events ending after this datetime (
YYYY-MM-DDTHH:MM:SS).end – Only events starting before this datetime (
YYYY-MM-DDTHH:MM:SS).text – Free-text search across title, description, locations, and participants.
has_attachment – Only events with (
True) or without (False) at least one attachment Link (is_attachment()).participant_email – Only events with a participant matching this email (case-insensitive, falling back to
calendarAddress; see_participant_matches_emailfor the matching rule). Matches against a participant’semail/calendarAddressproperty, neverparticipantId(a server-assigned id with no email semantics), so no separate contacts lookup is needed to resolve a name to an email before calling this.participation_status – Only events with a participant whose
participationStatusequals this value, defaulting an absent value to"needs-action"per RFC 8984 Section 4.4.6.participant_role – Only events with a participant whose
rolesmap has this key set toTrue.account_id – Pass a different account here to search a calendar shared with you.
position – Zero-based index of the first result to return (RFC 8620 Section 5.5). Use with
limitto page through a full listing.limit – Maximum number of events the server returns for this page.
Nonemeans no limit, letting the server return everything (or whatever its own server-side cap is, which this client cannot detect unlesscalculate_totalis also passed).calculate_total – If true, the server includes the total number of matching events in the result, letting a caller detect when it’s seen every page. This count is server-side only; it does not account for
has_attachment/participant_email/participation_status/participant_role, which filter after the server’s own count is taken. SeeEventSearchResultfor howtotalbehaves when this is false.
- Returns:
An
EventSearchResult. Itseventscarryparent=Nonesince noJMAPCalendaris available at the client level; usesearch()if you needparentset.
- search_tasks(text: str | None = None, due_before: str | None = None, due_after: str | None = None, progress: str | None = None) list[dict][source]#
Search for tasks.
All parameters are optional; omitting all returns every task in the account. Tries
Task/queryfirst, sendingtextserver-side via its filter. Falls back to fetching every task withTask/getand filtering everything, includingtext, client-side when the server rejects the request specifically for lackingTask/query, the same fallback shapeget_availability()already uses forPrincipal/getAvailability.Neither server makes this fallback worth reaching today, tested directly against both: Cyrus and Stalwart reject the entire
urn:ietf:params:jmap:taskscapability, notTask/queryspecifically, so the fallback’s ownTask/getcall (which needs that same capability) fails identically and the error still propagates. It only helps a future server that implements the tasks capability well enough forTask/getto work but specifically lacksTask/query, a plausible partial-implementation gap given draft-ietf-jmap-tasks section 4.13 itself never defines that method’s filter shape.due_before,due_after, andprogresshave no corresponding Task/query filter property (draft-ietf-jmap-tasks section 4.13 never defines one), so they always filter client-side, in Python, against whatever the primary or fallback path already returned. All filters combine as AND.- Parameters:
text – Free-text search. Sent server-side via
Task/queryon the primary path; matched as a case-insensitive substring oftitleon the fallback path (see_task_matches_search_filters).due_before – Only tasks whose
duesorts before this value (RFC 8984 Section 5.2.1 LocalDateTime,YYYY-MM-DDTHH:MM:SS). A task with nodueset never matches.due_after – Only tasks whose
duesorts after this value. A task with nodueset never matches.progress – Only tasks whose
progressequals this value, treating an absent value as"needs-action"(RFC 8984 Section 5.2.5’s real default derives from participant progress values; this client never sets a Task’sparticipants, so that derivation always lands on"needs-action"here).
- Returns:
List of raw JMAP Task dicts.
- send_invite(calendar_id: str, ical_str: str, account_id: str | None = None, if_in_state: str | None = None) str[source]#
Create a calendar event and notify its participants.
Identical to
create_event(), except the server actually dispatches iTIP invitations to the event’s participants (an iCalendar string with ATTENDEE/ORGANIZER properties).create_eventitself never sends scheduling messages; use this instead when the event has participants who need to be notified.- Parameters:
calendar_id – The JMAP calendar ID to create the event in.
ical_str – A VCALENDAR string representing the event.
account_id – The account owning
calendar_id, same ascreate_event().if_in_state – See
create_event().
- Returns:
The server-assigned JMAP event ID.
- Raises:
JMAPMethodError – If the server rejects the create request, or with
error_type == "noSupportedScheduleMethods"if a participant has no usable delivery method.ValueError – Same conditions as
create_event().
- set_default_alerts(calendar_id: str, alerts_with_time: dict | None = None, alerts_without_time: dict | None = None, account_id: str | None = None) None[source]#
Set a calendar’s default alerts for new events.
alerts_with_timeapplies to timed events,alerts_without_timeto all-day events (draft-ietf-jmap-calendars section 4). Each is a map of alert ID to Alert dict (RFC 8984 Section 4.5.2). PassNoneto leave a property unchanged; pass{}to clear it.- Parameters:
account_id – The account owning
calendar_id; pass the owner’s account here to set default alerts on a calendar shared with you.- Raises:
JMAPMethodError – If the server rejects the update.
Share a calendar with another account.
account_idmust already be a resolved JMAP Principal ID, not an email address. This client has noPrincipal/query/Principal/getsupport yet (RFC 9670), so resolving an email address to a Principal ID is left to the caller.Replaces
account_id’s entry in the calendar’sshareWithmap wholesale; other accounts already sharing the calendar are left untouched.rightsitself is not merged with any rights that account already had, so granting an additional right to an account that is already shared with needs the full rights dict, not just the new key. Requires the caller to already hold themayShareright, and cannot grant a right the caller does not themselves hold.- Parameters:
account_id – The account to grant rights to.
rights – Dict of right names to bool, e.g.
{"mayReadItems": True}. Replaces any rights this account already had on this calendar.owning_account_id – The account owning
calendar_id; pass the owner’s account here to re-share a calendar that was itself shared with you (if you holdmayShareon it).
- Raises:
JMAPMethodError – If the server rejects the update, for example with
forbiddenwhen the caller lacksmayShare.
- subscribe_push(callback_url: str, device_client_id: str, types: list[str] | None = None) str[source]#
Register a push subscription (RFC 8620 Section 7.2).
This only creates the subscription; it does not complete the verification handshake. Immediately after this call, the server POSTs a
PushVerificationobject (containing averificationCode) tocallback_url. The server will not push any further notifications until that code is submitted viaconfirm_push_verification(). This client has no way to receive that POST itself, since it is an API client, not an HTTP server; the caller’s owncallback_urlendpoint must extract the code from the request it receives and pass it along.Unlike most create/update/delete methods in this client, none of the push subscription methods (this one,
confirm_push_verification(),renew_push(),unsubscribe_push()) take anaccount_idparameter: PushSubscription is not account-scoped (RFC 8620 Section 7.2.2).- Parameters:
callback_url – Absolute URL the server will POST notifications to. Must begin with
https://and be publicly reachable by the server: this will not work behind localhost without tunneling.device_client_id – Caller-chosen id identifying the device/app combination creating this subscription, so the caller can recognize its own subscriptions later (e.g. to renew or unsubscribe them) even after losing other local state. Tested against both: neither test server generates or substitutes one, so a fixed value across every install of a library or app would collide across devices; the RFC’s own text requires this to differ per device and per vendor, and to not contain an unobfuscated device id, so it cannot be generated internally on the caller’s behalf.
types – List of type names to receive notifications for (e.g.
"CalendarEvent"). Defaults to["CalendarEvent"]. PassNoneexplicitly for the server behavior of pushing all types (not the default here, since it is a broader behavior than this default).
- Returns:
The server-assigned JMAP PushSubscription ID.
- Raises:
JMAPMethodError – If the server rejects the create request, or does not implement PushSubscription at all. Cyrus doesn’t implement
PushSubscription/set(unknownMethod), confirmed against a running instance; Stalwart does.
- tentatively_accept(event_id: str, own_email: str, account_id: str | None = None) None[source]#
Tentatively accept a meeting invitation, notifying the organizer.
Same as
accept_invitation(), settingparticipationStatusto tentative instead.
- unsubscribe_push(subscription_id: str) None[source]#
Destroy a push subscription.
See
subscribe_push()for theaccount_idparameter note.- Parameters:
subscription_id – The JMAP PushSubscription ID to destroy.
- Raises:
JMAPMethodError – If the server rejects the delete, or the subscription doesn’t exist.
- update_calendar(calendar_id: str, name: str | None = None, color: str | None = None, timezone: str | None = None, account_id: str | None = None) None[source]#
Update a calendar’s name, color, or time zone.
name,color, andtimeZoneare per-user properties (draft-ietf-jmap-calendars section 4.3). Called by the calendar’s owner, this changes the value for everyone until a sharee sets their own override. Called by a sharee, it only ever changes that sharee’s own view; it can never rename or recolor the calendar for the owner or anyone else it is shared with.- Parameters:
account_id – The account owning
calendar_id; pass the owner’s account here to update a calendar shared with you.- Raises:
JMAPMethodError – If the server rejects the update.
- update_event(event_id: str, ical_str: str, account_id: str | None = None, if_in_state: str | None = None) None[source]#
Update a calendar event from an iCalendar string.
A property present on the server’s own copy but absent from
ical_stris explicitly cleared, not left alone: RFC 8620 Section 5.3 merge semantics mean an omitted property would otherwise be preserved rather than removed, so this nulls it to actually drop it server-side.- Parameters:
event_id – The JMAP event ID to update.
ical_str – A VCALENDAR string with the updated event data.
account_id – The account owning
event_id; pass the owner’s account here to update an event on a calendar shared with you.if_in_state – A state string from a prior call that read this account’s calendar state. If the account has changed since, the server rejects the update with
JMAPMethodError(error_type == "stateMismatch") instead of applying it (RFC 8620 Section 5.3). The state covers every event in the account, not just this one.Noneapplies unconditionally. Sent unchanged on every retry of the null-key-dropping loop below, since those retries narrow the same patch rather than re-reading the account’s state.
- Raises:
JMAPMethodError – If the server rejects the update.
ValueError – If
ical_stris malformed in a wayical_to_jscalrejects (missingUID/DTSTART, a negative duration, mismatchedDTSTART/DTENDvalue types).
- update_task(task_id: str, patch: dict) None[source]#
Update a task with a partial patch.
- Parameters:
task_id – The JMAP task ID to update.
patch – Partial patch dict mapping property names to new values.
- Raises:
JMAPMethodError – If the server rejects the update.
- upload_attachment(data: bytes, content_type: str, account_id: str | None = None) str[source]#
Upload binary data as a JMAP blob (RFC 8620 Section 6.1).
Unlike every other method on this client, this is a raw HTTP POST to the Session’s
uploadUrl, not a JMAP method call: there is nomethodResponsesenvelope, the server returns a flat JSON object instead.- Parameters:
data – The raw bytes to upload.
content_type – Media type of
data, sent as the request’sContent-Typeheader.account_id – The account to upload into.
- Returns:
The server-assigned blob id, for use with
download_attachment()orattach_to_event().- Raises:
JMAPCapabilityError – If the server’s Session object has no
uploadUrl.JMAPAuthError – On HTTP 401 or 403.
requests.HTTPError – On any other non-2xx HTTP response.