feat(jmap): add caldav/jmap — JMAP calendar and task client#625
Merged
feat(jmap): add caldav/jmap — JMAP calendar and task client#625
Conversation
tobixen
reviewed
Feb 20, 2026
SashankBhamidi
commented
Feb 20, 2026
SashankBhamidi
commented
Feb 20, 2026
SashankBhamidi
commented
Feb 20, 2026
Collaborator
Author
|
@tobixen Is this better now? I’ll pull it tomorrow and work on CI errors. I committed the changes as suggestions for now. |
SashankBhamidi
commented
Feb 20, 2026
Apply suggestions from code review
Apply suggestions from code review
…tion label; fix async docs wording
…erve RELATED=END in alarm round-trip
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ed Fastmail claim by @tobixen
… attribution for tasks
…t-response parsing
… port rewriting
4da88eb to
8bc2adf
Compare
Collaborator
Author
|
@tobixen Added Stalwart JMAP integration tests (7 tests pass). Two quirks: it rejects |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
caldav/jmap/, a new module providing JMAP calendar and task support alongside the existing CalDAV client. Zero modifications to any existing file.The module follows the same layered sans-I/O design as the CalDAV side: pure method builders/parsers in
methods/, dataclasses inobjects/, bidirectional iCalendar ↔ JSCalendar conversion inconvert/, HTTP + session logic inclient.pyandasync_client.py.Usage documentation in
docs/source/jmap.rstcovers auth, event CRUD, search, incremental sync, tasks, async API, and error handling.Session bootstrap (
session.py): GET/.well-known/jmap, resolve relativeapiUrlviaurljoin(Cyrus returns a relative path), select account viaprimaryAccounts[CALENDAR_CAPABILITY]with a fallback scan of all accounts. RaisesJMAPCapabilityErrorif no calendar-capable account is found.Auth (
client.py): Basic whenusernameis supplied, Bearer when onlypasswordis given, or a pre-built auth object via theauthkwarg. No 401-challenge-retry — a 401/403 from session or API endpoint raisesJMAPAuthErrorimmediately.JMAPErrorextendsDAVErrorso existing CalDAV exception handlers catch JMAP errors too.Calendar-scoped API (
objects/calendar.py):JMAPCalendarobjects returned byget_calendars()carry three methods that mirrorcaldav.collection.Calendarexactly:cal.add_event(ical_str),cal.get_object_by_uid(uid), andcal.search(event=True, start=, end=, text=). Both sync and async clients inject themselves into each calendar object so the same method works regardless of which client was used.Client-level operations on both
JMAPClientandAsyncJMAPClient:get_calendars,create_event,get_event,update_event,delete_event,search_events,get_sync_token,get_objects_by_sync_token.search_eventsuses a single batched request —CalendarEvent/query+ a result reference intoCalendarEvent/get— one HTTP round-trip regardless of result size.get_objects_by_sync_tokenraisesJMAPMethodError(error_type="serverPartialFail")when the server truncates the change list (hasMoreChanges: true).Task operations (
urn:ietf:params:jmap:tasks):get_task_lists,create_task,get_task,update_task,delete_task. Task methods send_TASK_USING = [CORE_CAPABILITY, TASK_CAPABILITY]; servers withouturn:ietf:params:jmap:tasksreturn an error methodResponse which_requestconverts toJMAPMethodError. The tasks specification is an expired IETF draft (draft-ietf-jmap-tasks, expired Sep 2023) with no RFC number — this is the most spec-unstable part of the implementation. Cyrus does not implement it, so task integration tests are deferred until a Stalwart Docker setup is added.AsyncJMAPClient(async_client.py) mirrors every method as a coroutine. Each request opens its ownniquests.AsyncSession— no long-lived connection is held.iCalendar ↔ JSCalendar conversion (
convert/): full bidirectional mapping covering DTSTART (all-day, floating, UTC, IANA-tz, non-IANA TZID passthrough), DTEND/DURATION, RRULE, EXRULE, EXDATE, RECURRENCE-ID overrides, ORGANIZER/ATTENDEE with roles and participation status, VALARM (relative and absolute triggers), CATEGORIES, LOCATION, CLASS, TRANSP, SEQUENCE, PRIORITY, COLOR. Shared duration/datetime primitives (_timedelta_to_duration,_duration_to_timedelta,_format_local_dt) live only inconvert/_utils.py.Entry points (
__init__.py):get_jmap_client(**kwargs)andget_async_jmap_client(**kwargs)read from the same sources asget_davclient— explicit kwargs, env vars, config file — and returnNonewhen no configuration is found.264 unit tests (zero network, all mocked). 17 integration tests against live Cyrus Docker (5 session/calendar checks, 6 sync event CRUD/search/sync, 6 async equivalents); auto-skipped if server unreachable.