Skip to content

[TT-5545] feat(events): add events.batch() for the Events batch endpoint - #493

Merged
pengfeiye merged 3 commits into
mainfrom
TT-5545-python-sdk-support-for-new-events-batch-operations
Nov 4, 2026
Merged

pengfeiye merged 3 commits into
mainfrom
TT-5545-python-sdk-support-for-new-events-batch-operations

Conversation

@pengfeiye

@pengfeiye pengfeiye commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

https://nylas.atlassian.net/browse/TT-5545

Summary

The v3 API has a new endpoint, POST /v3/grants/{grant_id}/events/batch, that runs many event create, update, and delete operations in one HTTP call. Only Google and Microsoft grants support it. This adds events.batch() to the Python SDK, following the SDK spec in TT-5542.

response = nylas.events.batch(
    identifier=grant_id,
    query_params={"calendar_id": "primary"},
    request_body={"operations": [
        {"method": "create", "body": {"title": "Standup", "when": {"start_time": 1798794000, "end_time": 1798795800}}},
        {"method": "update", "event_id": "EVENT_ID_1", "body": {"title": "Renamed"}},
        {"method": "delete", "event_id": "EVENT_ID_2"},
    ]},
)
# response.data[i] is the result of operations[i].
for result in response.data:
    if result.error is not None:
        print(result.status, result.error.type, result.error.message, result.retry_after, result.provider_event_created)

Changes

  • Add Events.batch(identifier, request_body, query_params, overrides=None), which returns Response[List[BatchEventResult]].
  • Add a new module, nylas.models.events_batch, for the batch models. A separate module keeps nylas/models/events.py under pylint's 1000-line limit, the same way the free/busy and availability models live outside calendars.py.
    • The request models are BatchCreateEventOperation, BatchUpdateEventOperation, and BatchDeleteEventOperation (combined as BatchEventOperation), and BatchEventsRequest. Each operation is a TypedDict with a Literal method, so a type checker flags an update or delete without event_id, and a create or update without body. The operation bodies reuse CreateEventRequest and UpdateEventRequest.
    • BatchEventsQueryParams extends CreateEventQueryParams (the query params of create, update, and delete) with grant_email and select.
    • BatchEventResult has status, data (the existing Event), error (the existing NylasApiErrorResponseData, which has the API's normal error shape), retry_after (integer seconds; 0 is kept as 0), and provider_event_created. Results are decoded with infer_missing=True, as ListResponse does, so the partial events that select returns are deserialized instead of raising a KeyError.
  • 207 handling: the HTTP client raises only for status codes of 400 and higher, so a 207 already takes the success path and no client change was needed. New tests pin this behavior in _validate_response, and through events.batch() with only requests.request mocked.
  • As the spec asks, the SDK doesn't check the operation limit, split batches, retry failed items, or check the provider.
  • Add examples/events_batch_demo (a README and a runnable script). It shows how to loop over the results, retry items with status 429 or 503 after retry_after, and never retry items with provider_event_created: true. Link the example from the main README, and add a CHANGELOG entry.
  • Add BatchEventResult.from_dict to pylint's generated-members.

Why there is no dedicated response type: the spec suggests one because the list wrapper has a next_cursor. In this SDK, Response already holds only data, request_id, and headers, and calendars.get_free_busy() already returns Response[List[...]]. So batch() reuses Response, which also keeps destructuring working: results, request_id, headers = nylas.events.batch(...).

Testing

  • Full unit suite passes (501 tests, e2e excluded). 9 new tests in tests/resources/test_events.py and 1 in tests/handler/test_http_client.py.
  • The new tests cover spec cases 1–8:
    • the request path, the query params, and the snake_case JSON body on the wire
    • a 200 response, with a typed Event for create and update and no data for delete
    • a 207 partial failure that is returned, not raised, with status, error.type, error.message, retry_after, and provider_event_created
    • a 207 response where every item failed
    • retry_after: 0 deserializes as 0
    • a 400 response raises NylasApiError with request_id, type, and message
    • the result order matches the operation order
    • the required keys of the three operation types
  • Ran the example's retry helper against a fake client. Only the 429 and 503 items were sent again; the provider_event_created and 404 items were not.
  • Ran pylint nylas locally (pylint 4.1.3, Python 3.10): no new messages compared with main. black reports no changes for the new code.
  • Changed files parse on Python 3.8.
  • CI is blocked by an unrelated runner change: ubuntu-latest is now ubuntu-26.04, which has no Python 3.8, so every 3.8 job fails at "Setup python" on every PR. fix(ci): pin Python 3.8 jobs to ubuntu-24.04 #494 fixes this. After fix(ci): pin Python 3.8 jobs to ubuntu-24.04 #494 merges, merge main into this branch to rerun the checks.
  • Optional staging smoke test with a real Google grant: not run. The example script does it: one create batch, one batch with an update and a bad event_id (expects 207), and one delete batch.

Related

License

I confirm that this contribution is made under the terms of the MIT license and that I have the authority necessary to make this contribution on behalf of its copyright owner.

Add Events.batch() for POST /v3/grants/{grant_id}/events/batch, which runs
create, update, and delete operations in one request (Google and Microsoft).

- BatchCreateEventOperation, BatchUpdateEventOperation, and
  BatchDeleteEventOperation reuse CreateEventRequest and UpdateEventRequest
  as the operation bodies.
- BatchEventsQueryParams extends CreateEventQueryParams with grant_email
  and select.
- BatchEventResult reuses Event and NylasApiErrorResponseData, and adds
  retry_after and provider_event_created.
- A 207 response returns the per-operation results. A 400 response raises
  NylasApiError, as for any other endpoint.
- Add the examples/events_batch_demo example with retry guidance, and a
  CHANGELOG entry.

Co-authored-by: Cursor <cursoragent@cursor.com>
…batch

The batch models pushed nylas/models/events.py past pylint's 1000-line
module limit (too-many-lines). Move them to their own module, as the
free/busy and availability models are for Calendars, and apply black
formatting to the new test helper and example.

Co-authored-by: Cursor <cursoragent@cursor.com>
ubuntu-latest now resolves to ubuntu-26.04, and actions/python-versions has
no Python 3.8 build for 26.04 (its builds start at 3.10). setup-python fails
with "Version 3.8 with arch x64 not found", so the Python 3.8, Pylint and
Black, and docs jobs fail on every PR, and fail-fast cancels Python 3.x.

Pin these jobs to ubuntu-24.04, the image they last passed on, where
setup-python downloads python-3.8.18-linux-24.04-x64.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 6523e56)
@pengfeiye
pengfeiye merged commit 03ef7af into main Nov 4, 2026
7 checks passed
@pengfeiye pengfeiye mentioned this pull request Nov 5, 2026
3 of 4 tasks

This branch was successfully deployed

1 active deployment
sdk-reference — 558a11a0 Deployed Nov 4, 2026 by pengfeiye via docs #596
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants