From 1c23f93f78b688d849bded6a62b807405e441842 Mon Sep 17 00:00:00 2001 From: pengfeiye Date: Wed, 8 Jul 2026 11:18:30 -0400 Subject: [PATCH] [Python SDK] Add resources field to Event, CreateEventRequest, and UpdateEventRequest models (CUST-5372) --- nylas/models/events.py | 54 ++++++++ tests/resources/test_events.py | 219 +++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+) diff --git a/nylas/models/events.py b/nylas/models/events.py index 7a5a2df..7441d20 100644 --- a/nylas/models/events.py +++ b/nylas/models/events.py @@ -49,6 +49,31 @@ class Participant: phone_number: Optional[str] = None +@dataclass_json +@dataclass +class Resource: + """ + Class representing an Event resource, such as a bookable room or piece of equipment. + + Attributes: + email: The resource's email address. + name: The resource's name. + capacity: The maximum number of people the resource can accommodate. + building: The building the resource is located in. + floor_name: The name of the floor the resource is located on. + floor_section: The section of the floor the resource is located in. + floor_number: The number of the floor the resource is located on. + """ + + email: Optional[str] = None + name: Optional[str] = None + capacity: Optional[int] = None + building: Optional[str] = None + floor_name: Optional[str] = None + floor_section: Optional[str] = None + floor_number: Optional[int] = None + + class EmailName(TypedDict): """ Interface representing an email address and optional name. @@ -351,6 +376,7 @@ class Event: created_at: Unix timestamp representing the Event's creation time. updated_at: Unix timestamp representing the time when the Event was last updated. participants: List of participants invited to the Event. Participants may be people, rooms, or resources. + resources: List of resources (such as bookable rooms or equipment) associated with the Event. when: Representation of an Event's time and duration. conferencing: Representation of an Event's conferencing details. object: The type of object. @@ -402,6 +428,7 @@ class Event: updated_at: Optional[int] = None master_event_id: Optional[str] = None notetaker: Optional[EventNotetaker] = None + resources: Optional[List[Resource]] = None class CreateParticipant(TypedDict): @@ -421,6 +448,29 @@ class CreateParticipant(TypedDict): phone_number: NotRequired[str] +class WritableResource(TypedDict): + """ + Interface representing a resource for event creation or updating. + + Attributes: + email: The resource's email address. + name: The resource's name. + capacity: The maximum number of people the resource can accommodate. + building: The building the resource is located in. + floor_name: The name of the floor the resource is located on. + floor_section: The section of the floor the resource is located in. + floor_number: The number of the floor the resource is located on. + """ + + email: NotRequired[str] + name: NotRequired[str] + capacity: NotRequired[int] + building: NotRequired[str] + floor_name: NotRequired[str] + floor_section: NotRequired[str] + floor_number: NotRequired[int] + + class UpdateParticipant(TypedDict): """ Interface representing a participant for updating an event. @@ -743,6 +793,7 @@ class CreateEventRequest(TypedDict): If left empty or omitted, the event uses the provider defaults. metadata: Metadata associated with the event. participants: The participants of the event. + resources: The resources (such as bookable rooms or equipment) of the event. recurrence: The recurrence rules of the event. visibility: The visibility of the event. capacity: The capacity of the event. @@ -759,6 +810,7 @@ class CreateEventRequest(TypedDict): reminders: NotRequired[CreateReminders] metadata: NotRequired[Dict[str, Any]] participants: NotRequired[List[CreateParticipant]] + resources: NotRequired[List[WritableResource]] recurrence: NotRequired[List[str]] visibility: NotRequired[Visibility] capacity: NotRequired[int] @@ -780,6 +832,7 @@ class UpdateEventRequest(TypedDict): reminders: A list of reminders to send for the event. metadata: Metadata associated with the event. participants: The participants of the event. + resources: The resources (such as bookable rooms or equipment) of the event. recurrence: The recurrence rules of the event. visibility: The visibility of the event. capacity: The capacity of the event. @@ -796,6 +849,7 @@ class UpdateEventRequest(TypedDict): reminders: NotRequired[UpdateReminders] metadata: NotRequired[Dict[str, Any]] participants: NotRequired[List[UpdateParticipant]] + resources: NotRequired[List[WritableResource]] recurrence: NotRequired[List[str]] visibility: NotRequired[Visibility] capacity: NotRequired[int] diff --git a/tests/resources/test_events.py b/tests/resources/test_events.py index 14e6288..fdcee6d 100644 --- a/tests/resources/test_events.py +++ b/tests/resources/test_events.py @@ -35,6 +35,17 @@ def test_event_deserialization(self): "status": "maybe", } ], + "resources": [ + { + "email": "room-a@example.com", + "name": "Conference Room A", + "capacity": 10, + "building": "Headquarters", + "floor_name": "Main", + "floor_section": "West Wing", + "floor_number": 3, + } + ], "read_only": False, "reminders": { "use_default": False, @@ -83,6 +94,13 @@ def test_event_deserialization(self): assert event.participants[0].name == "Aristotle" assert event.participants[0].phone_number == "+1 23456778" assert event.participants[0].status == "maybe" + assert event.resources[0].email == "room-a@example.com" + assert event.resources[0].name == "Conference Room A" + assert event.resources[0].capacity == 10 + assert event.resources[0].building == "Headquarters" + assert event.resources[0].floor_name == "Main" + assert event.resources[0].floor_section == "West Wing" + assert event.resources[0].floor_number == 3 assert event.read_only is False assert event.reminders.use_default is False assert event.reminders.overrides[0].reminder_minutes == 10 @@ -357,6 +375,17 @@ def test_create_event(self, http_client_response): "description": "Description of my new event", "location": "Los Angeles, CA", "metadata": {"your-key": "value"}, + "resources": [ + { + "email": "room-a@example.com", + "name": "Conference Room A", + "capacity": 10, + "building": "Headquarters", + "floor_name": "Main", + "floor_section": "West Wing", + "floor_number": 3, + } + ], } events.create( @@ -386,6 +415,17 @@ def test_update_event(self, http_client_response): "description": "Updated description of my event", "location": "Los Angeles, CA", "metadata": {"your-key": "value"}, + "resources": [ + { + "email": "room-a@example.com", + "name": "Conference Room A", + "capacity": 10, + "building": "Headquarters", + "floor_name": "Main", + "floor_section": "West Wing", + "floor_number": 3, + } + ], } events.update( @@ -660,3 +700,182 @@ def test_event_with_unknown_conferencing_fields_deserialization(self): assert event.id == "test-event-id" assert event.title == "Test Event with Unknown Conferencing Fields" assert event.conferencing is None + + def test_event_without_resources_deserialization(self): + """Test event deserialization when the resources field is absent.""" + event_json = { + "id": "test-event-id", + "grant_id": "test-grant-id", + "calendar_id": "test-calendar-id", + "busy": True, + "participants": [ + {"email": "test@example.com", "name": "Test User", "status": "yes"} + ], + "when": { + "start_time": 1497916800, + "end_time": 1497920400, + "object": "timespan", + }, + "title": "Test Event without Resources", + } + + event = Event.from_dict(event_json) + + assert event.id == "test-event-id" + assert event.resources is None + + def test_event_with_empty_resources_deserialization(self): + """Test event deserialization when the resources field is an empty list.""" + event_json = { + "id": "test-event-id", + "grant_id": "test-grant-id", + "calendar_id": "test-calendar-id", + "busy": True, + "participants": [ + {"email": "test@example.com", "name": "Test User", "status": "yes"} + ], + "when": { + "start_time": 1497916800, + "end_time": 1497920400, + "object": "timespan", + }, + "resources": [], + "title": "Test Event with Empty Resources", + } + + event = Event.from_dict(event_json) + + assert event.id == "test-event-id" + assert event.resources == [] + + def test_event_with_partial_resource_deserialization(self): + """Test event deserialization when a resource only has some fields set.""" + event_json = { + "id": "test-event-id", + "grant_id": "test-grant-id", + "calendar_id": "test-calendar-id", + "busy": True, + "participants": [ + {"email": "test@example.com", "name": "Test User", "status": "yes"} + ], + "when": { + "start_time": 1497916800, + "end_time": 1497920400, + "object": "timespan", + }, + "resources": [ + {"email": "room-b@example.com", "name": "Conference Room B"} + ], + "title": "Test Event with Partial Resource", + } + + event = Event.from_dict(event_json) + + assert event.resources[0].email == "room-b@example.com" + assert event.resources[0].name == "Conference Room B" + assert event.resources[0].capacity is None + assert event.resources[0].building is None + assert event.resources[0].floor_name is None + assert event.resources[0].floor_section is None + assert event.resources[0].floor_number is None + + def test_event_with_multiple_resources_deserialization(self): + """Test event deserialization with multiple resources.""" + event_json = { + "id": "test-event-id", + "grant_id": "test-grant-id", + "calendar_id": "test-calendar-id", + "busy": True, + "participants": [ + {"email": "test@example.com", "name": "Test User", "status": "yes"} + ], + "when": { + "start_time": 1497916800, + "end_time": 1497920400, + "object": "timespan", + }, + "resources": [ + { + "email": "room-a@example.com", + "name": "Conference Room A", + "capacity": 10, + "building": "Headquarters", + "floor_name": "Main", + "floor_section": "West Wing", + "floor_number": 3, + }, + { + "email": "room-b@example.com", + "name": "Conference Room B", + "capacity": 4, + "building": "Annex", + "floor_name": "Ground", + "floor_section": "East Wing", + "floor_number": 1, + }, + ], + "title": "Test Event with Multiple Resources", + } + + event = Event.from_dict(event_json) + + assert len(event.resources) == 2 + assert event.resources[0].email == "room-a@example.com" + assert event.resources[0].capacity == 10 + assert event.resources[1].email == "room-b@example.com" + assert event.resources[1].name == "Conference Room B" + assert event.resources[1].capacity == 4 + assert event.resources[1].building == "Annex" + assert event.resources[1].floor_number == 1 + + def test_create_event_with_empty_resources(self, http_client_response): + """Test creating an event passes through an empty resources list.""" + events = Events(http_client_response) + request_body = { + "when": { + "start_time": 1661874192, + "end_time": 1661877792, + }, + "title": "Event with empty resources", + "resources": [], + } + + events.create( + identifier="abc-123", + request_body=request_body, + query_params={"calendar_id": "abc-123"}, + ) + + http_client_response._execute.assert_called_once_with( + "POST", + "/v3/grants/abc-123/events", + None, + {"calendar_id": "abc-123"}, + request_body, + overrides=None, + ) + + def test_update_event_with_partial_resource(self, http_client_response): + """Test updating an event passes through a resource with only some fields.""" + events = Events(http_client_response) + request_body = { + "resources": [ + {"email": "room-b@example.com", "name": "Conference Room B"} + ], + } + + events.update( + identifier="abc-123", + event_id="event-123", + request_body=request_body, + query_params={"calendar_id": "abc-123"}, + ) + + http_client_response._execute.assert_called_once_with( + "PUT", + "/v3/grants/abc-123/events/event-123", + None, + {"calendar_id": "abc-123"}, + request_body, + overrides=None, + )