diff --git a/nylas/models/notetakers.py b/nylas/models/notetakers.py index 2d196b3..a889fec 100644 --- a/nylas/models/notetakers.py +++ b/nylas/models/notetakers.py @@ -35,6 +35,34 @@ class NotetakerState(str, Enum): MEDIA_DELETED = "media_deleted" +class NotetakerOrderBy(str, Enum): + """ + Enum representing the possible fields to order Notetaker bots by. + + Values: + NAME: Order by the Notetaker's name. + JOIN_TIME: Order by the Notetaker's join time. + CREATED_AT: Order by when the Notetaker was created. + """ + + NAME = "name" + JOIN_TIME = "join_time" + CREATED_AT = "created_at" + + +class NotetakerOrderDirection(str, Enum): + """ + Enum representing the possible directions to order Notetaker bots by. + + Values: + ASC: Ascending order. + DESC: Descending order. + """ + + ASC = "asc" + DESC = "desc" + + class MeetingProvider(str, Enum): """ Enum representing the possible meeting providers for Notetaker. @@ -57,7 +85,7 @@ class NotetakerMeetingSettingsRequest(TypedDict): Attributes: video_recording: When true, Notetaker records the meeting's video. audio_recording: When true, Notetaker records the meeting's audio. - transcription: When true, Notetaker transcribes the meeting's audio. + transcription: When true, Notetaker transcribes the meeting's audio. If transcription is true, audio_recording must also be true. """ @@ -75,7 +103,7 @@ class NotetakerMeetingSettings: Attributes: video_recording: When true, Notetaker records the meeting's video. audio_recording: When true, Notetaker records the meeting's audio. - transcription: When true, Notetaker transcribes the meeting's audio. + transcription: When true, Notetaker transcribes the meeting's audio. If transcription is true, audio_recording must also be true. """ @@ -182,7 +210,7 @@ class InviteNotetakerRequest(TypedDict): Attributes: meeting_link: A meeting invitation link that Notetaker uses to join the meeting. - join_time: When Notetaker should join the meeting, in Unix timestamp format. + join_time: When Notetaker should join the meeting, in Unix timestamp format. If empty, Notetaker joins the meeting immediately. name: The display name for the Notetaker bot. meeting_settings: Notetaker Meeting Settings. @@ -217,23 +245,41 @@ class ListNotetakerQueryParams(ListQueryParams): state: Filter for Notetaker bots with the specified meeting state. Use the NotetakerState enum. Example: state=NotetakerState.SCHEDULED - join_time_from: Filter for Notetaker bots that are scheduled to join meetings after the specified time. - join_time_until: Filter for Notetaker bots that are scheduled to join meetings until the specified time. + join_time_start: Filter for Notetaker bots that have join times that start at or after a specific time, + in Unix timestamp format. + join_time_end: Filter for Notetaker bots that have join times that end at or are before a specific time, + in Unix timestamp format. limit: The maximum number of objects to return. This field defaults to 50. The maximum allowed value is 200. page_token: An identifier that specifies which page of data to return. prev_page_token: An identifier that specifies which page of data to return. + order_by: The field to order the Notetaker bots by. Defaults to created_at. + Use the NotetakerOrderBy enum. + Example: order_by=NotetakerOrderBy.NAME + order_direction: The direction to order the Notetaker bots by. Defaults to asc. + Use the NotetakerOrderDirection enum. + Example: order_direction=NotetakerOrderDirection.DESC """ state: NotRequired[NotetakerState] - join_time_from: NotRequired[int] - join_time_until: NotRequired[int] + join_time_start: NotRequired[int] + join_time_end: NotRequired[int] + order_by: NotRequired[NotetakerOrderBy] + order_direction: NotRequired[NotetakerOrderDirection] def __post_init__(self): - """Convert NotetakerState enum to string value for API requests.""" + """Convert enums to string values for API requests.""" super().__post_init__() # Convert state enum to string if present if hasattr(self, "state") and isinstance(self.state, NotetakerState): self.state = self.state.value + # Convert order_by enum to string if present + if hasattr(self, "order_by") and isinstance(self.order_by, NotetakerOrderBy): + self.order_by = self.order_by.value + # Convert order_direction enum to string if present + if hasattr(self, "order_direction") and isinstance( + self.order_direction, NotetakerOrderDirection + ): + self.order_direction = self.order_direction.value class FindNotetakerQueryParams(TypedDict): diff --git a/tests/resources/test_notetakers.py b/tests/resources/test_notetakers.py index 44bca39..a094139 100644 --- a/tests/resources/test_notetakers.py +++ b/tests/resources/test_notetakers.py @@ -1,14 +1,13 @@ from nylas.resources.notetakers import Notetakers from nylas.models.notetakers import ( - Notetaker, - NotetakerMeetingSettings, - NotetakerMeetingSettingsRequest, - NotetakerMedia, - NotetakerMediaRecording, + Notetaker, + NotetakerMedia, NotetakerState, MeetingProvider, ListNotetakerQueryParams, - NotetakerLeaveResponse + NotetakerLeaveResponse, + NotetakerOrderBy, + NotetakerOrderDirection, ) @@ -25,8 +24,8 @@ def test_notetaker_deserialization(self): "meeting_settings": { "video_recording": True, "audio_recording": True, - "transcription": True - } + "transcription": True, + }, } notetaker = Notetaker.from_dict(notetaker_json) @@ -56,7 +55,7 @@ def test_notetaker_state_enum(self): ("media_error", NotetakerState.MEDIA_ERROR), ("media_deleted", NotetakerState.MEDIA_DELETED), ] - + for state_str, state_enum in states: notetaker_json = { "id": "notetaker-123", @@ -67,21 +66,26 @@ def test_notetaker_state_enum(self): "meeting_settings": { "video_recording": True, "audio_recording": True, - "transcription": True - } + "transcription": True, + }, } - + notetaker = Notetaker.from_dict(notetaker_json) assert notetaker.state == state_enum assert notetaker.state.value == state_str - + def test_list_notetakers(self, http_client_list_response): notetakers = Notetakers(http_client_list_response) notetakers.list(identifier="abc-123", query_params=None) http_client_list_response._execute.assert_called_once_with( - "GET", "/v3/grants/abc-123/notetakers", None, None, None, overrides=None + "GET", + "/v3/grants/abc-123/notetakers", + None, + None, + None, + overrides=None, ) def test_list_notetakers_without_identifier(self, http_client_list_response): @@ -97,18 +101,15 @@ def test_list_notetakers_with_query_params(self, http_client_list_response): notetakers = Notetakers(http_client_list_response) notetakers.list( - identifier="abc-123", - query_params={ - "limit": 20, - "state": NotetakerState.SCHEDULED.value # Use enum value as string for raw dict - } + identifier="abc-123", + query_params={"state": NotetakerState.SCHEDULED, "limit": 20}, ) http_client_list_response._execute.assert_called_once_with( "GET", "/v3/grants/abc-123/notetakers", None, - {"limit": 20, "state": "scheduled"}, + {"state": "scheduled", "limit": 20}, None, overrides=None, ) @@ -116,17 +117,13 @@ def test_list_notetakers_with_query_params(self, http_client_list_response): def test_list_notetakers_with_enum_query_params(self, http_client_list_response): """Test that the NotetakerState enum can be used directly in query params.""" notetakers = Notetakers(http_client_list_response) - + # Create query params using the enum directly query_params = ListNotetakerQueryParams( - state=NotetakerState.SCHEDULED, - limit=20 + state=NotetakerState.SCHEDULED, limit=20 ) - notetakers.list( - identifier="abc-123", - query_params=query_params - ) + notetakers.list(identifier="abc-123", query_params=query_params) # Verify the enum is converted to string in the API call http_client_list_response._execute.assert_called_once_with( @@ -175,8 +172,8 @@ def test_invite_notetaker(self, http_client_response): "meeting_settings": { "video_recording": True, "audio_recording": True, - "transcription": True - } + "transcription": True, + }, } notetakers.invite(identifier="abc-123", request_body=request_body) @@ -199,8 +196,8 @@ def test_invite_notetaker_without_identifier(self, http_client_response): "meeting_settings": { "video_recording": True, "audio_recording": True, - "transcription": True - } + "transcription": True, + }, } notetakers.invite(request_body=request_body) @@ -222,8 +219,8 @@ def test_update_notetaker(self, http_client_response): "meeting_settings": { "video_recording": False, "audio_recording": True, - "transcription": True - } + "transcription": True, + }, } notetakers.update( @@ -243,10 +240,7 @@ def test_update_notetaker(self, http_client_response): def test_update_notetaker_without_identifier(self, http_client_response): notetakers = Notetakers(http_client_response) - request_body = { - "name": "Updated Notetaker", - "join_time": 1656100000 - } + request_body = {"name": "Updated Notetaker", "join_time": 1656100000} notetakers.update( notetaker_id="notetaker-123", @@ -370,7 +364,7 @@ def test_media_deserialization(self): "created_at": 1744222418, "expires_at": 1744481618, "url": "url_for_recording", - "ttl": 259106 + "ttl": 259106, }, "transcript": { "size": 862, @@ -379,8 +373,8 @@ def test_media_deserialization(self): "created_at": 1744222418, "expires_at": 1744481618, "url": "url_for_transcript", - "ttl": 259106 - } + "ttl": 259106, + }, } media = NotetakerMedia.from_dict(media_json) @@ -409,7 +403,7 @@ def test_meeting_provider_enum(self): ("Zoom Meeting", MeetingProvider.ZOOM), ("Microsoft Teams", MeetingProvider.MICROSOFT_TEAMS), ] - + for provider_str, provider_enum in providers: notetaker_json = { "id": "notetaker-123", @@ -421,14 +415,14 @@ def test_meeting_provider_enum(self): "meeting_settings": { "video_recording": True, "audio_recording": True, - "transcription": True - } + "transcription": True, + }, } - + notetaker = Notetaker.from_dict(notetaker_json) assert notetaker.meeting_provider == provider_enum assert notetaker.meeting_provider.value == provider_str - + def test_state_enum_comparison(self): """Test that enum values can be compared directly.""" # Create a notetaker with a state enum @@ -441,18 +435,18 @@ def test_state_enum_comparison(self): "meeting_settings": { "video_recording": True, "audio_recording": True, - "transcription": True - } + "transcription": True, + }, } - + notetaker = Notetaker.from_dict(notetaker_json) - + # Check direct comparison with enum assert notetaker.state == NotetakerState.SCHEDULED - + # Value of the enum matches original string assert notetaker.state.value == "scheduled" - + def test_meeting_provider_enum_comparison(self): """Test that meeting provider enum values can be compared directly.""" # Create a notetaker with a meeting provider enum @@ -466,103 +460,113 @@ def test_meeting_provider_enum_comparison(self): "meeting_settings": { "video_recording": True, "audio_recording": True, - "transcription": True - } + "transcription": True, + }, } - + notetaker = Notetaker.from_dict(notetaker_json) - + # Check direct comparison with enum assert notetaker.meeting_provider == MeetingProvider.GOOGLE_MEET - + # Value of the enum matches original string assert notetaker.meeting_provider.value == "Google Meet" def test_notetaker_helper_methods(self): """Test the helper methods for checking state and provider.""" # Test with a scheduled notetaker - scheduled_notetaker = Notetaker.from_dict({ - "id": "notetaker-123", - "name": "Nylas Notetaker", - "join_time": 1656090000, - "meeting_link": "https://meet.google.com/abc-def-ghi", - "meeting_provider": "Google Meet", - "state": "scheduled", - "meeting_settings": { - "video_recording": True, - "audio_recording": True, - "transcription": True + scheduled_notetaker = Notetaker.from_dict( + { + "id": "notetaker-123", + "name": "Nylas Notetaker", + "join_time": 1656090000, + "meeting_link": ("https://meet.google.com/abc-def-ghi"), + "meeting_provider": "Google Meet", + "state": "scheduled", + "meeting_settings": { + "video_recording": True, + "audio_recording": True, + "transcription": True, + }, } - }) - + ) + assert scheduled_notetaker.is_state(NotetakerState.SCHEDULED) is True assert scheduled_notetaker.is_scheduled() is True assert scheduled_notetaker.is_attending() is False assert scheduled_notetaker.has_media_available() is False - + # Test with an attending notetaker - attending_notetaker = Notetaker.from_dict({ - "id": "notetaker-456", - "name": "Nylas Notetaker", - "join_time": 1656090000, - "meeting_link": "https://zoom.us/j/123456789", - "meeting_provider": "Zoom Meeting", - "state": "attending", - "meeting_settings": { - "video_recording": True, - "audio_recording": True, - "transcription": True + attending_notetaker = Notetaker.from_dict( + { + "id": "notetaker-456", + "name": "Nylas Notetaker", + "join_time": 1656090000, + "meeting_link": "https://zoom.us/j/123456789", + "meeting_provider": "Zoom Meeting", + "state": "attending", + "meeting_settings": { + "video_recording": True, + "audio_recording": True, + "transcription": True, + }, } - }) - + ) + assert attending_notetaker.is_state(NotetakerState.ATTENDING) is True assert attending_notetaker.is_scheduled() is False assert attending_notetaker.is_attending() is True assert attending_notetaker.has_media_available() is False - + # Test with a media available notetaker - media_available_notetaker = Notetaker.from_dict({ - "id": "notetaker-789", - "name": "Nylas Notetaker", - "join_time": 1656090000, - "meeting_link": "https://teams.microsoft.com/l/meetup-join/123", - "meeting_provider": "Microsoft Teams", - "state": "media_available", - "meeting_settings": { - "video_recording": True, - "audio_recording": True, - "transcription": True + media_available_notetaker = Notetaker.from_dict( + { + "id": "notetaker-789", + "name": "Nylas Notetaker", + "join_time": 1656090000, + "meeting_link": ("https://teams.microsoft.com/l/meetup-join/123"), + "meeting_provider": "Microsoft Teams", + "state": "media_available", + "meeting_settings": { + "video_recording": True, + "audio_recording": True, + "transcription": True, + }, } - }) - - assert media_available_notetaker.is_state(NotetakerState.MEDIA_AVAILABLE) is True + ) + + assert ( + media_available_notetaker.is_state(NotetakerState.MEDIA_AVAILABLE) is True + ) assert media_available_notetaker.is_scheduled() is False assert media_available_notetaker.is_attending() is False assert media_available_notetaker.has_media_available() is True - def test_query_params_with_enum_state(self, http_client_list_response): - """Test that query params require enum state values.""" - from nylas.models.notetakers import ListNotetakerQueryParams - - # Create query params directly with the enum - query_params = { - "state": NotetakerState.SCHEDULED, # Use enum directly in dict - "limit": 20 - } - - notetakers = Notetakers(http_client_list_response) - - notetakers.list( - identifier="abc-123", - query_params=query_params + def test_list_notetakers_with_time_filters(self, http_client_list_response): + """Test that join_time_start and join_time_end query parameters work correctly.""" + # Using Unix timestamps for Jan 1, 2024 and Jan 2, 2024 + start_time = 1704067200 # Jan 1, 2024 + end_time = 1704153600 # Jan 2, 2024 + + # Create query params with time filters + query_params = ListNotetakerQueryParams( + join_time_start=start_time, join_time_end=end_time, limit=20 ) - - # Verify the enum is converted to string in the API call - http_client_list_response._execute.assert_called_with( + + notetakers = Notetakers(http_client_list_response) + + notetakers.list(identifier="abc-123", query_params=query_params) + + # Verify the API call includes the time filter parameters + http_client_list_response._execute.assert_called_once_with( "GET", "/v3/grants/abc-123/notetakers", None, - {"state": "scheduled", "limit": 20}, + { + "join_time_start": start_time, + "join_time_end": end_time, + "limit": 20, + }, None, overrides=None, ) @@ -572,11 +576,45 @@ def test_notetaker_leave_response_deserialization(self): leave_response_json = { "id": "notetaker-123", "message": "Notetaker has left the meeting", - "object": "notetaker_leave_response" + "object": "notetaker_leave_response", } leave_response = NotetakerLeaveResponse.from_dict(leave_response_json) assert leave_response.id == "notetaker-123" assert leave_response.message == "Notetaker has left the meeting" - assert leave_response.object == "notetaker_leave_response" \ No newline at end of file + assert leave_response.object == "notetaker_leave_response" + + def test_list_notetakers_with_order_params(self, http_client_list_response): + notetakers = Notetakers(http_client_list_response) + + notetakers.list( + identifier="abc-123", + query_params={ + "order_by": NotetakerOrderBy.NAME, + "order_direction": NotetakerOrderDirection.DESC, + }, + ) + + http_client_list_response._execute.assert_called_once_with( + "GET", + "/v3/grants/abc-123/notetakers", + None, + {"order_by": "name", "order_direction": "desc"}, + None, + overrides=None, + ) + + def test_list_notetakers_with_default_order(self, http_client_list_response): + notetakers = Notetakers(http_client_list_response) + + notetakers.list(identifier="abc-123") + + http_client_list_response._execute.assert_called_once_with( + "GET", + "/v3/grants/abc-123/notetakers", + None, + None, + None, + overrides=None, + )