From 1490632eaf0b57e6605f6251bb8f9a46899dd6e0 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:33:19 +0000 Subject: [PATCH 1/7] Add metadata field to SendMessageRequest model - Added metadata field to CreateDraftRequest which is inherited by SendMessageRequest - Added test cases for metadata in draft creation and message sending - Updated docstring to include metadata field description Fixes #394 Co-Authored-By: Aaron de Mello --- nylas/models/drafts.py | 4 +++- tests/resources/test_drafts.py | 41 ++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/nylas/models/drafts.py b/nylas/models/drafts.py index f77a125..f6e3803 100644 --- a/nylas/models/drafts.py +++ b/nylas/models/drafts.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import List, get_type_hints +from typing import List, Dict, Any, get_type_hints from dataclasses_json import dataclass_json from typing_extensions import TypedDict, NotRequired @@ -87,6 +87,7 @@ class CreateDraftRequest(TypedDict): reply_to_message_id: The ID of the message that you are replying to. tracking_options: Options for tracking opens, links, and thread replies. custom_headers: Custom headers to add to the message. + metadata: A dictionary of key-value pairs storing additional data. """ body: NotRequired[str] @@ -101,6 +102,7 @@ class CreateDraftRequest(TypedDict): reply_to_message_id: NotRequired[str] tracking_options: NotRequired[TrackingOptions] custom_headers: NotRequired[List[CustomHeader]] + metadata: NotRequired[Dict[str, Any]] UpdateDraftRequest = CreateDraftRequest diff --git a/tests/resources/test_drafts.py b/tests/resources/test_drafts.py index ecd51eb..c501e1b 100644 --- a/tests/resources/test_drafts.py +++ b/tests/resources/test_drafts.py @@ -143,6 +143,27 @@ def test_create_draft(self, http_client_response): overrides=None, ) + def test_create_draft_with_metadata(self, http_client_response): + drafts = Drafts(http_client_response) + request_body = { + "subject": "Hello from Nylas!", + "to": [{"name": "Jon Snow", "email": "jsnow@gmail.com"}], + "cc": [{"name": "Arya Stark", "email": "astark@gmail.com"}], + "body": "This is the body of my draft message.", + "metadata": {"custom_field": "value", "another_field": 123} + } + + drafts.create(identifier="abc-123", request_body=request_body) + + http_client_response._execute.assert_called_once_with( + "POST", + "/v3/grants/abc-123/drafts", + None, + None, + request_body, + overrides=None, + ) + def test_create_draft_small_attachment(self, http_client_response): drafts = Drafts(http_client_response) request_body = { @@ -349,6 +370,26 @@ def test_send_draft(self, http_client_response): method="POST", path="/v3/grants/abc-123/drafts/draft-123", overrides=None ) + def test_send_message_with_metadata(self, http_client_response): + drafts = Drafts(http_client_response) + request_body = { + "subject": "Hello from Nylas!", + "to": [{"name": "Jon Snow", "email": "jsnow@gmail.com"}], + "body": "This is the body of my message.", + "metadata": {"custom_field": "value", "another_field": 123} + } + + drafts.send(identifier="abc-123", request_body=request_body) + + http_client_response._execute.assert_called_once_with( + "POST", + "/v3/grants/abc-123/messages/send", + None, + None, + request_body, + overrides=None, + ) + def test_send_draft_encoded_id(self, http_client_response): drafts = Drafts(http_client_response) From 977c695bf5b3d8fe9f64847e12ff5a85e0edcd0b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:39:23 +0000 Subject: [PATCH 2/7] Update test assertion to use keyword arguments Co-Authored-By: Aaron de Mello --- tests/resources/test_drafts.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/resources/test_drafts.py b/tests/resources/test_drafts.py index c501e1b..8613a2f 100644 --- a/tests/resources/test_drafts.py +++ b/tests/resources/test_drafts.py @@ -2,6 +2,7 @@ from nylas.models.drafts import Draft from nylas.resources.drafts import Drafts +from nylas.resources.messages import Messages class TestDraft: @@ -371,7 +372,7 @@ def test_send_draft(self, http_client_response): ) def test_send_message_with_metadata(self, http_client_response): - drafts = Drafts(http_client_response) + messages = Messages(http_client_response) request_body = { "subject": "Hello from Nylas!", "to": [{"name": "Jon Snow", "email": "jsnow@gmail.com"}], @@ -379,14 +380,13 @@ def test_send_message_with_metadata(self, http_client_response): "metadata": {"custom_field": "value", "another_field": 123} } - drafts.send(identifier="abc-123", request_body=request_body) + messages.send(identifier="abc-123", request_body=request_body) http_client_response._execute.assert_called_once_with( - "POST", - "/v3/grants/abc-123/messages/send", - None, - None, - request_body, + method="POST", + path="/v3/grants/abc-123/messages/send", + request_body=request_body, + data=None, overrides=None, ) From 4204bb220ba0730c19606d962d92496117ab5583 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 15:31:57 +0000 Subject: [PATCH 3/7] Update CHANGELOG.md for metadata field addition Co-Authored-By: Aaron de Mello --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5ecbb7..7fe592f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ nylas-python Changelog ====================== +v6.5.0 +---------------- +* Add metadata field support for drafts and messages through CreateDraftRequest model + v6.4.0 ---------------- * Add support for from field for sending messages From da7c1eee07bb0ae00303a47e35c779344f0bcc2e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 15:54:56 +0000 Subject: [PATCH 4/7] Update changelog format to match version entry style Co-Authored-By: Aaron de Mello --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f428c70..0b2bbd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ nylas-python Changelog ====================== -# Unreleased +Unreleased ---------------- * Add support for Scheduler APIs * Fixed attachment download response handling From 2c73eeae871188bfc2df05bef70c06f2fa849a16 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:02:12 +0000 Subject: [PATCH 5/7] Add E2E example for metadata field functionality Co-Authored-By: Aaron de Mello --- examples/metadata_field_demo/README.md | 61 +++++++++ .../metadata_field_demo/metadata_example.py | 117 ++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 examples/metadata_field_demo/README.md create mode 100644 examples/metadata_field_demo/metadata_example.py diff --git a/examples/metadata_field_demo/README.md b/examples/metadata_field_demo/README.md new file mode 100644 index 0000000..fc649b1 --- /dev/null +++ b/examples/metadata_field_demo/README.md @@ -0,0 +1,61 @@ +# Metadata Field Example + +This example demonstrates how to use metadata fields when creating drafts and sending messages using the Nylas Python SDK. + +## Features + +- Create drafts with custom metadata fields +- Send messages with custom metadata fields +- Error handling and environment variable configuration +- Clear output and status messages + +## Prerequisites + +1. A Nylas account with API access +2. Python 3.x installed +3. The Nylas Python SDK installed (`pip install nylas`) + +## Setup + +1. Set your environment variables: +```bash +export NYLAS_API_KEY="your_api_key" +export NYLAS_GRANT_ID="your_grant_id" +export TEST_EMAIL="recipient@example.com" # Optional +``` + +2. Run the example: +```bash +python metadata_example.py +``` + +## Example Output + +``` +Demonstrating Metadata Field Usage +================================= + +1. Creating draft with metadata... +✓ Created draft with ID: draft-abc123 + Request ID: req-xyz789 + +2. Sending message with metadata... +✓ Sent message with ID: msg-def456 + Request ID: req-uvw321 + +Example completed successfully! +``` + +## Error Handling + +The example includes proper error handling for: +- Missing environment variables +- API authentication errors +- Draft creation failures +- Message sending failures + +## Documentation + +For more information about the Nylas Python SDK and its features, visit: +- [Nylas Python SDK Documentation](https://developer.nylas.com/docs/sdks/python/) +- [Nylas API Reference](https://developer.nylas.com/docs/api/) diff --git a/examples/metadata_field_demo/metadata_example.py b/examples/metadata_field_demo/metadata_example.py new file mode 100644 index 0000000..eef7880 --- /dev/null +++ b/examples/metadata_field_demo/metadata_example.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +""" +Nylas SDK Example: Using Metadata Fields with Drafts and Messages + +This example demonstrates how to use metadata fields when creating drafts +and sending messages using the Nylas Python SDK. + +Required Environment Variables: + NYLAS_API_KEY: Your Nylas API key + NYLAS_GRANT_ID: Your Nylas grant ID + TEST_EMAIL: Email address for sending test messages (optional) + +Usage: + export NYLAS_API_KEY="your_api_key" + export NYLAS_GRANT_ID="your_grant_id" + export TEST_EMAIL="recipient@example.com" + python metadata_example.py +""" + +import os +import sys +from typing import Dict, Any, Optional +from nylas import Client +from nylas.models.errors import NylasAPIError + + +def get_env_or_exit(var_name: str, required: bool = True) -> Optional[str]: + """Get an environment variable or exit if required and not found.""" + value = os.getenv(var_name) + if required and not value: + print(f"Error: {var_name} environment variable is required") + sys.exit(1) + return value + + +def create_draft_with_metadata( + client: Client, grant_id: str, metadata: Dict[str, Any], recipient: str +) -> str: + """Create a draft message with metadata fields.""" + try: + draft_request = { + "subject": "Test Draft with Metadata", + "to": [{"email": recipient}], + "body": "This is a test draft with metadata fields.", + "metadata": metadata + } + + draft, request_id = client.drafts.create( + identifier=grant_id, + request_body=draft_request + ) + print(f"✓ Created draft with ID: {draft.id}") + print(f" Request ID: {request_id}") + return draft.id + except NylasAPIError as e: + print(f"✗ Failed to create draft: {e}") + sys.exit(1) + + +def send_message_with_metadata( + client: Client, grant_id: str, metadata: Dict[str, Any], recipient: str +) -> None: + """Send a message directly with metadata fields.""" + try: + message_request = { + "subject": "Test Message with Metadata", + "to": [{"email": recipient}], + "body": "This is a test message with metadata fields.", + "metadata": metadata + } + + message, request_id = client.messages.send( + identifier=grant_id, + request_body=message_request + ) + print(f"✓ Sent message with ID: {message.id}") + print(f" Request ID: {request_id}") + except NylasAPIError as e: + print(f"✗ Failed to send message: {e}") + sys.exit(1) + + +def main(): + """Main function demonstrating metadata field usage.""" + # Get required environment variables + api_key = get_env_or_exit("NYLAS_API_KEY") + grant_id = get_env_or_exit("NYLAS_GRANT_ID") + recipient = get_env_or_exit("TEST_EMAIL", required=False) or "recipient@example.com" + + # Initialize Nylas client + client = Client( + api_key=api_key, + ) + + # Example metadata + metadata = { + "campaign_id": "example-123", + "user_id": "user-456", + "custom_field": "test-value" + } + + print("\nDemonstrating Metadata Field Usage") + print("=================================") + + # Create a draft with metadata + print("\n1. Creating draft with metadata...") + draft_id = create_draft_with_metadata(client, grant_id, metadata, recipient) + + # Send a message with metadata + print("\n2. Sending message with metadata...") + send_message_with_metadata(client, grant_id, metadata, recipient) + + print("\nExample completed successfully!") + + +if __name__ == "__main__": + main() From 572964a3b0548f82ae11b20ef59bfb61c4303730 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 18:43:35 +0000 Subject: [PATCH 6/7] Update metadata example to use local package instead of pip install Co-Authored-By: Aaron de Mello --- examples/metadata_field_demo/README.md | 14 ++++++++++---- examples/metadata_field_demo/metadata_example.py | 9 ++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/examples/metadata_field_demo/README.md b/examples/metadata_field_demo/README.md index fc649b1..f5310f3 100644 --- a/examples/metadata_field_demo/README.md +++ b/examples/metadata_field_demo/README.md @@ -13,20 +13,26 @@ This example demonstrates how to use metadata fields when creating drafts and se 1. A Nylas account with API access 2. Python 3.x installed -3. The Nylas Python SDK installed (`pip install nylas`) +3. Local installation of the Nylas Python SDK (this repository) ## Setup -1. Set your environment variables: +1. Install the SDK in development mode from the repository root: +```bash +cd /path/to/nylas-python +pip install -e . +``` + +2. Set your environment variables: ```bash export NYLAS_API_KEY="your_api_key" export NYLAS_GRANT_ID="your_grant_id" export TEST_EMAIL="recipient@example.com" # Optional ``` -2. Run the example: +3. Run the example from the repository root: ```bash -python metadata_example.py +python examples/metadata_field_demo/metadata_example.py ``` ## Example Output diff --git a/examples/metadata_field_demo/metadata_example.py b/examples/metadata_field_demo/metadata_example.py index eef7880..8336b78 100644 --- a/examples/metadata_field_demo/metadata_example.py +++ b/examples/metadata_field_demo/metadata_example.py @@ -11,15 +11,22 @@ TEST_EMAIL: Email address for sending test messages (optional) Usage: + First, install the SDK in development mode: + cd /path/to/nylas-python + pip install -e . + + Then set environment variables and run: export NYLAS_API_KEY="your_api_key" export NYLAS_GRANT_ID="your_grant_id" export TEST_EMAIL="recipient@example.com" - python metadata_example.py + python examples/metadata_field_demo/metadata_example.py """ import os import sys from typing import Dict, Any, Optional + +# Import from local nylas package from nylas import Client from nylas.models.errors import NylasAPIError From f7c471eb33154f3dd0e5c87b40539f8ed5cfc6fb Mon Sep 17 00:00:00 2001 From: Aaron de Mello Date: Fri, 20 Dec 2024 14:01:09 -0500 Subject: [PATCH 7/7] Added the metadata property to the Message model --- .../metadata_field_demo/metadata_example.py | 23 +- nylas/models/messages.py | 1 + tests/resources/test_messages.py | 336 ++++++++++++++++++ 3 files changed, 355 insertions(+), 5 deletions(-) diff --git a/examples/metadata_field_demo/metadata_example.py b/examples/metadata_field_demo/metadata_example.py index 8336b78..737dbaf 100644 --- a/examples/metadata_field_demo/metadata_example.py +++ b/examples/metadata_field_demo/metadata_example.py @@ -28,7 +28,7 @@ # Import from local nylas package from nylas import Client -from nylas.models.errors import NylasAPIError +from nylas.models.errors import NylasApiError def get_env_or_exit(var_name: str, required: bool = True) -> Optional[str]: @@ -59,14 +59,14 @@ def create_draft_with_metadata( print(f"✓ Created draft with ID: {draft.id}") print(f" Request ID: {request_id}") return draft.id - except NylasAPIError as e: + except NylasApiError as e: print(f"✗ Failed to create draft: {e}") sys.exit(1) def send_message_with_metadata( client: Client, grant_id: str, metadata: Dict[str, Any], recipient: str -) -> None: +) -> str: """Send a message directly with metadata fields.""" try: message_request = { @@ -82,7 +82,9 @@ def send_message_with_metadata( ) print(f"✓ Sent message with ID: {message.id}") print(f" Request ID: {request_id}") - except NylasAPIError as e: + + return message.id + except NylasApiError as e: print(f"✗ Failed to send message: {e}") sys.exit(1) @@ -115,10 +117,21 @@ def main(): # Send a message with metadata print("\n2. Sending message with metadata...") - send_message_with_metadata(client, grant_id, metadata, recipient) + message_id = send_message_with_metadata(client, grant_id, metadata, recipient) print("\nExample completed successfully!") + # Get the draft and message to demonstrate metadata retrieval + draft = client.drafts.find(identifier=grant_id, draft_id=draft_id) + message = client.messages.find(identifier=grant_id, message_id=message_id) + + print("\nRetrieved Draft Metadata:") + print("-------------------------") + print(draft.data) + + print("\nRetrieved Message Metadata:") + print("---------------------------") + print(message.data) if __name__ == "__main__": main() diff --git a/nylas/models/messages.py b/nylas/models/messages.py index 7d5d405..b6f0bbd 100644 --- a/nylas/models/messages.py +++ b/nylas/models/messages.py @@ -80,6 +80,7 @@ class Message: date: Optional[int] = None schedule_id: Optional[str] = None send_at: Optional[int] = None + metadata: Optional[Dict[str, Any]] = None # Need to use Functional typed dicts because "from" and "in" are Python diff --git a/tests/resources/test_messages.py b/tests/resources/test_messages.py index 1af9a95..5e9b372 100644 --- a/tests/resources/test_messages.py +++ b/tests/resources/test_messages.py @@ -37,6 +37,7 @@ def test_message_deserialization(self): "thread_id": "1t8tv3890q4vgmwq6pmdwm8qgsaer", "to": [{"name": "Jon Snow", "email": "j.snow@example.com"}], "unread": True, + "metadata": {"custom_field": "value", "another_field": 123}, } message = Message.from_dict(message_json) @@ -64,6 +65,341 @@ def test_message_deserialization(self): assert message.thread_id == "1t8tv3890q4vgmwq6pmdwm8qgsaer" assert message.to == [{"name": "Jon Snow", "email": "j.snow@example.com"}] assert message.unread is True + assert message.metadata == {"custom_field": "value", "another_field": 123} + + def test_list_messages(self, http_client_list_response): + messages = Messages(http_client_list_response) + + messages.list(identifier="abc-123") + + http_client_list_response._execute.assert_called_once_with( + "GET", "/v3/grants/abc-123/messages", None, None, None, overrides=None + ) + + def test_list_messages_with_query_params(self, http_client_list_response): + messages = Messages(http_client_list_response) + + messages.list( + identifier="abc-123", + query_params={ + "subject": "Hello from Nylas!", + }, + ) + + http_client_list_response._execute.assert_called_once_with( + "GET", + "/v3/grants/abc-123/messages", + None, + { + "subject": "Hello from Nylas!", + }, + None, + overrides=None, + ) + + def test_find_message(self, http_client_response): + messages = Messages(http_client_response) + + messages.find(identifier="abc-123", message_id="message-123") + + http_client_response._execute.assert_called_once_with( + "GET", + "/v3/grants/abc-123/messages/message-123", + None, + None, + None, + overrides=None, + ) + + def test_find_message_encoded_id(self, http_client_response): + messages = Messages(http_client_response) + + messages.find( + identifier="abc-123", + message_id="", + ) + + http_client_response._execute.assert_called_once_with( + "GET", + "/v3/grants/abc-123/messages/%3C%21%26%21AAAAAAAAAAAuAAAAAAAAABQ%2FwHZyqaNCptfKg5rnNAoBAMO2jhD3dRHOtM0AqgC7tuYAAAAAAA4AABAAAACTn3BxdTQ%2FT4N%2F0BgqPmf%2BAQAAAAA%3D%40example.com%3E", + None, + None, + None, + overrides=None, + ) + + def test_find_message_with_query_params(self, http_client_response): + messages = Messages(http_client_response) + + messages.find( + identifier="abc-123", + message_id="message-123", + query_params={"fields": "standard"}, + ) + + http_client_response._execute.assert_called_once_with( + "GET", + "/v3/grants/abc-123/messages/message-123", + None, + {"fields": "standard"}, + None, + overrides=None, + ) + + def test_update_message(self, http_client_response): + messages = Messages(http_client_response) + request_body = { + "starred": True, + "unread": False, + "folders": ["folder-123"], + "metadata": {"foo": "bar"}, + } + + messages.update( + identifier="abc-123", + message_id="message-123", + request_body=request_body, + ) + + http_client_response._execute.assert_called_once_with( + "PUT", + "/v3/grants/abc-123/messages/message-123", + None, + None, + request_body, + overrides=None, + ) + + def test_update_message_encoded_id(self, http_client_response): + messages = Messages(http_client_response) + request_body = { + "starred": True, + "unread": False, + "folders": ["folder-123"], + "metadata": {"foo": "bar"}, + } + + messages.update( + identifier="abc-123", + message_id="", + request_body=request_body, + ) + + http_client_response._execute.assert_called_once_with( + "PUT", + "/v3/grants/abc-123/messages/%3C%21%26%21AAAAAAAAAAAuAAAAAAAAABQ%2FwHZyqaNCptfKg5rnNAoBAMO2jhD3dRHOtM0AqgC7tuYAAAAAAA4AABAAAACTn3BxdTQ%2FT4N%2F0BgqPmf%2BAQAAAAA%3D%40example.com%3E", + None, + None, + request_body, + overrides=None, + ) + + def test_destroy_message(self, http_client_delete_response): + messages = Messages(http_client_delete_response) + + messages.destroy(identifier="abc-123", message_id="message-123") + + http_client_delete_response._execute.assert_called_once_with( + "DELETE", + "/v3/grants/abc-123/messages/message-123", + None, + None, + None, + overrides=None, + ) + + def test_destroy_message_encoded_id(self, http_client_delete_response): + messages = Messages(http_client_delete_response) + + messages.destroy( + identifier="abc-123", + message_id="", + ) + + http_client_delete_response._execute.assert_called_once_with( + "DELETE", + "/v3/grants/abc-123/messages/%3C%21%26%21AAAAAAAAAAAuAAAAAAAAABQ%2FwHZyqaNCptfKg5rnNAoBAMO2jhD3dRHOtM0AqgC7tuYAAAAAAA4AABAAAACTn3BxdTQ%2FT4N%2F0BgqPmf%2BAQAAAAA%3D%40example.com%3E", + None, + None, + None, + overrides=None, + ) + + def test_send_message(self, http_client_response): + messages = Messages(http_client_response) + request_body = { + "subject": "Hello from Nylas!", + "to": [{"name": "Jon Snow", "email": "jsnow@gmail.com"}], + "cc": [{"name": "Arya Stark", "email": "astark@gmail.com"}], + "body": "This is the body of my draft message.", + "metadata": {"custom_field": "value", "another_field": 123}, + } + + messages.send(identifier="abc-123", request_body=request_body) + + http_client_response._execute.assert_called_once_with( + method="POST", + path="/v3/grants/abc-123/messages/send", + request_body=request_body, + data=None, + overrides=None, + ) + + def test_send_message_small_attachment(self, http_client_response): + messages = Messages(http_client_response) + request_body = { + "subject": "Hello from Nylas!", + "to": [{"name": "Jon Snow", "email": "jsnow@gmail.com"}], + "cc": [{"name": "Arya Stark", "email": "astark@gmail.com"}], + "body": "This is the body of my draft message.", + "attachments": [ + { + "filename": "file1.txt", + "content_type": "text/plain", + "content": "this is a file", + "size": 3, + }, + ], + "metadata": {"custom_field": "value", "another_field": 123}, + } + + messages.send(identifier="abc-123", request_body=request_body) + + http_client_response._execute.assert_called_once_with( + method="POST", + path="/v3/grants/abc-123/messages/send", + request_body=request_body, + data=None, + overrides=None, + ) + + def test_send_message_large_attachment(self, http_client_response): + messages = Messages(http_client_response) + mock_encoder = Mock() + request_body = { + "subject": "Hello from Nylas!", + "to": [{"name": "Jon Snow", "email": "jsnow@gmail.com"}], + "cc": [{"name": "Arya Stark", "email": "astark@gmail.com"}], + "body": "This is the body of my draft message.", + "attachments": [ + { + "filename": "file1.txt", + "content_type": "text/plain", + "content": "this is a file", + "size": 3 * 1024 * 1024, + }, + ], + "metadata": {"custom_field": "value", "another_field": 123}, + } + + with patch( + "nylas.resources.messages._build_form_request", return_value=mock_encoder + ): + messages.send(identifier="abc-123", request_body=request_body) + + http_client_response._execute.assert_called_once_with( + method="POST", + path="/v3/grants/abc-123/messages/send", + request_body=None, + data=mock_encoder, + overrides=None, + ) + + def test_list_scheduled_messages(self, http_client_list_scheduled_messages): + messages = Messages(http_client_list_scheduled_messages) + + res = messages.list_scheduled_messages(identifier="abc-123") + + http_client_list_scheduled_messages._execute.assert_called_once_with( + method="GET", path="/v3/grants/abc-123/messages/schedules", overrides=None + ) + assert res.request_id == "dd3ec9a2-8f15-403d-b269-32b1f1beb9f5" + assert len(res.data) == 2 + assert res.data[0].schedule_id == "8cd56334-6d95-432c-86d1-c5dab0ce98be" + assert res.data[0].status.code == "pending" + assert res.data[0].status.description == "schedule send awaiting send at time" + assert res.data[1].schedule_id == "rb856334-6d95-432c-86d1-c5dab0ce98be" + assert res.data[1].status.code == "success" + assert res.data[1].status.description == "schedule send succeeded" + assert res.data[1].close_time == 1690579819 + + def test_find_scheduled_message(self, http_client_response): + messages = Messages(http_client_response) + + messages.find_scheduled_message( + identifier="abc-123", schedule_id="schedule-123" + ) + + http_client_response._execute.assert_called_once_with( + method="GET", + path="/v3/grants/abc-123/messages/schedules/schedule-123", + overrides=None, + ) + + def test_stop_scheduled_message(self, http_client_response): + messages = Messages(http_client_response) + + messages.stop_scheduled_message( + identifier="abc-123", schedule_id="schedule-123" + ) + + http_client_response._execute.assert_called_once_with( + method="DELETE", + path="/v3/grants/abc-123/messages/schedules/schedule-123", + overrides=None, + ) + + def test_clean_messages(self, http_client_clean_messages): + messages = Messages(http_client_clean_messages) + request_body = { + "message_id": ["message-1", "message-2"], + "ignore_images": True, + "ignore_links": True, + "ignore_tables": True, + "images_as_markdown": True, + "remove_conclusion_phrases": True, + } + + response = messages.clean_messages( + identifier="abc-123", + request_body=request_body, + ) + + http_client_clean_messages._execute.assert_called_once_with( + method="PUT", + path="/v3/grants/abc-123/messages/clean", + request_body=request_body, + overrides=None, + ) + + # Assert the conversation field, and the typical message fields serialize properly + assert len(response.data) == 2 + assert response.data[0].body == "Hello, I just sent a message using Nylas!" + assert response.data[0].from_ == [ + {"name": "Daenerys Targaryen", "email": "daenerys.t@example.com"} + ] + assert response.data[0].object == "message" + assert response.data[0].id == "message-1" + assert response.data[0].grant_id == "41009df5-bf11-4c97-aa18-b285b5f2e386" + assert response.data[0].conversation == "cleaned example" + assert response.data[1].conversation == "another example" + assert message.folders[0] == "8l6c4d11y1p4dm4fxj52whyr9" + assert message.folders[1] == "d9zkcr2tljpu3m4qpj7l2hbr0" + assert message.from_ == [ + {"name": "Daenerys Targaryen", "email": "daenerys.t@example.com"} + ] + assert message.grant_id == "41009df5-bf11-4c97-aa18-b285b5f2e386" + assert message.id == "5d3qmne77v32r8l4phyuksl2x" + assert message.object == "message" + assert message.reply_to == [ + {"name": "Daenerys Targaryen", "email": "daenerys.t@example.com"} + ] + assert message.snippet == "Hello, I just sent a message using Nylas!" + assert message.starred is True + assert message.subject == "Hello from Nylas!" + assert message.thread_id == "1t8tv3890q4vgmwq6pmdwm8qgsaer" + assert message.to == [{"name": "Jon Snow", "email": "j.snow@example.com"}] + assert message.unread is True def test_list_messages(self, http_client_list_response): messages = Messages(http_client_list_response)