Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Read, Update, and Delete for Messages #305

Merged
merged 12 commits into from
Oct 31, 2023
11 changes: 11 additions & 0 deletions nylas/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from nylas.resources.calendars import Calendars
from nylas.resources.connectors import Connectors
from nylas.resources.events import Events
from nylas.resources.messages import Messages
from nylas.resources.webhooks import Webhooks


Expand Down Expand Up @@ -83,6 +84,16 @@ def events(self) -> Events:
"""
return Events(self.http_client)

@property
def messages(self) -> Messages:
"""
Access the Messages API.

Returns:
The Messages API.
"""
return Messages(self.http_client)

@property
def webhooks(self) -> Webhooks:
"""
Expand Down
174 changes: 174 additions & 0 deletions nylas/models/messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
from dataclasses import dataclass, field
from dataclasses_json import dataclass_json, config
from typing import List, Literal, Optional, Dict, Any
from typing_extensions import TypedDict, NotRequired, get_type_hints
from datetime import datetime

from nylas.models.list_query_params import ListQueryParams
from nylas.models.events import EmailName


Fields = Literal["standard", "include_headers"]
""" Literal representing which headers to include with a message. """


@dataclass_json
@dataclass
class MessageHeader:
"""
A message header.

Attributes:
name: The header name.
value: The header value.
"""

name: str
value: str


@dataclass_json
@dataclass
class Attachment:
"""
An attachment on a message.

Attributes:
id: Globally unique object identifier.
size: Size of the attachment in bytes.
filename: Name of the attachment.
content_type: MIME type of the attachment.
"""

id: str
size: int
filename: Optional[str] = None
content_type: Optional[str] = None


@dataclass_json
@dataclass
class Message:
"""
A Message object.

Attributes:
id: Globally unique object identifier.
grant_id: The grant that this message belongs to.
thread_id: The thread that this message belongs to.
subject: The subject of the message.
from_: The sender of the message.
to: The recipients of the message.
cc: The CC recipients of the message.
bcc: The BCC recipients of the message.
reply_to: The reply-to recipients of the message.
date: The date the message was received.
unread: Whether the message is unread.
starred: Whether the message is starred.
snippet: A snippet of the message body.
body: The body of the message.
attachments: The attachments on the message.
folders: The folders that the message is in.
headers: The headers of the message.
"""

id: str
grant_id: str
thread_id: str
subject: str

from_: List[EmailName] = field(metadata=config(field_name="from"))
to: List[EmailName]

date: datetime

unread: bool
starred: bool

snippet: str
body: str

bcc: Optional[List[EmailName]] = None
cc: Optional[List[EmailName]] = None
reply_to: Optional[List[EmailName]] = None
attachments: Optional[List[Attachment]] = None
folders: Optional[List[str]] = None
headers: Optional[List[MessageHeader]] = None


# Need to use Functional typed dicts because "from" and "in" are Python
# keywords, and can't be declared using the declarative syntax
ListMessagesQueryParams = TypedDict(
"ListMessagesQueryParams",
{
**get_type_hints(ListQueryParams), # Inherit fields from ListQueryParams
"subject": NotRequired[str],
"any_email": NotRequired[str],
"from": NotRequired[str],
"to": NotRequired[str],
"cc": NotRequired[str],
"bcc": NotRequired[str],
"in": NotRequired[str],
"unread": NotRequired[bool],
"starred": NotRequired[bool],
"thread_id": NotRequired[str],
"received_before": NotRequired[int],
"received_after": NotRequired[int],
"has_attachment": NotRequired[bool],
"fields": NotRequired[Fields],
"search_query_native": NotRequired[str],
},
)
"""
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, I have no idea if this docstring will end up in the autogenerated docs. Couldn't figure out any way to include a docstring on a functionally declared TypedDict.

Query parameters for listing messages.

Attributes:
subject: Return messages with matching subject.
any_email: Return messages that have been sent or received by this comma-separated list of email addresses.
from: Return messages sent from this email address.
to: Return messages sent to this email address.
cc: Return messages cc'd to this email address.
bcc: Return messages bcc'd to this email address.
in: Return messages in this specific folder or label, specified by ID.
unread: Filter messages by unread status.
starred: Filter messages by starred status.
thread_id: Filter messages by thread_id.
received_before: Return messages with received dates before received_before.
received_after: Return messages with received dates after received_after.
has_attachment: Filter messages by whether they have an attachment.
fields: Specify "include_headers" to include headers in the response. "standard" is the default.
search_query_native: A native provider search query for Google or Microsoft.
limit (NotRequired[int]): The maximum number of objects to return.
This field defaults to 50. The maximum allowed value is 200.
page_token (NotRequired[str]): An identifier that specifies which page of data to return.
This value should be taken from a ListResponse object's next_cursor parameter.
"""


class FindMessageQueryParams(TypedDict):

"""Query parameters for finding a message.

Attributes:
fields: Specify "include_headers" to include headers in the response. "standard" is the default.
"""

fields: NotRequired[Fields]


class UpdateMessageRequest(TypedDict):

"""
Request payload for updating a message.

Attributes:
starred: The message's starred status
unread: The message's unread status
folders: The message's folders
metadata: A list of key-value pairs storing additional data
"""

unread: NotRequired[bool]
starred: NotRequired[bool]
folder: NotRequired[List[str]]
metadata: NotRequired[Dict[str, Any]]
108 changes: 108 additions & 0 deletions nylas/resources/messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from typing import Dict, Optional
from nylas.handler.api_resources import (
ListableApiResource,
FindableApiResource,
UpdatableApiResource,
DestroyableApiResource,
)
from nylas.models.messages import (
Message,
ListMessagesQueryParams,
FindMessageQueryParams,
UpdateMessageRequest,
)
from nylas.models.response import Response, ListResponse, DeleteResponse


class Messages(
ListableApiResource,
FindableApiResource,
UpdatableApiResource,
DestroyableApiResource,
):
def list(
self, identifier: str, query_params: ListMessagesQueryParams
) -> ListResponse[Message]:
"""
Return all Messages.

Args:
identifier: The identifier of the grant to get messages for.
query_params: The query parameters to filter messages by.

Returns:
A list of Messages.
"""
return super(Messages, self).list(
path=f"/v3/grants/{identifier}/messages",
response_type=Message,
query_params=query_params,
)

def find(
self,
identifier: str,
message_id: str,
query_params: Optional[FindMessageQueryParams] = None,
) -> Response[Message]:
"""
Return a Message.

Args:
identifier: The identifier of the grant to get the message for.
message_id: The identifier of the message to get.
query_params: The query parameters to include in the request.

Returns:
The requested Message.
"""
return super(Messages, self).find(
path=f"/v3/grants/{identifier}/messages/{message_id}",
response_type=Message,
query_params=query_params,
)

def update(
self,
identifier: str,
message_id: str,
request_body: UpdateMessageRequest,
query_params: Optional[Dict] = None,
) -> Response[Message]:
"""
Update a Message.

Args:
identifier: The identifier of the grant to update the message for.
message_id: The identifier of the message to update.
request_body: The request body to update the message with.
query_params: The query parameters to include in the request.

Returns:
The updated Message.
"""
return super(Messages, self).update(
path=f"/v3/grants/{identifier}/messages/{message_id}",
response_type=Message,
request_body=request_body,
query_params=query_params,
)

def destroy(
self, identifier: str, message_id: str, query_params: Optional[Dict] = None
) -> DeleteResponse:
"""
Delete a Message.

Args:
identifier: The identifier of the grant to delete the message for.
message_id: The identifier of the message to delete.
query_params: The query parameters to include in the request.

Returns:
The deletion response.
"""
return super(Messages, self).destroy(
path=f"/v3/grants/{identifier}/messages/{message_id}",
query_params=query_params,
)