Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions nylas/models/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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):
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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]
Expand All @@ -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.
Expand All @@ -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]
Expand Down
219 changes: 219 additions & 0 deletions tests/resources/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
)
Loading