Skip to content

Commit

Permalink
v2.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremyephron committed Dec 23, 2019
1 parent d830143 commit 76e151c
Show file tree
Hide file tree
Showing 9 changed files with 850 additions and 223 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ dmypy.json
# Pyre type checker
.pyre/

client_secrets.json
client_secret.json
gmail-token.json
test.py

Expand Down
95 changes: 82 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,47 @@ Current Supported Behavior:
* Sending messages with attachments
* Sending messages with your Gmail account signature
* Retrieving messages with the full suite of Gmail's search capabilities
* Retrieving messages with attachments, and downloading attachments
* Modifying message labels (includes marking as read/unread, important/not
important, starred/unstarred, trash/untrash, inbox/archive)

## Getting Started
The only setup required is to download a "client secrets" file from Google that will allow your applications to do its thing.
The only setup required is to download an OAuth 2.0 Client ID file from Google
that will authorize your application.

Follow the instructions here: https://developers.google.com/gmail/api/quickstart/python.
This can be done at: https://console.developers.google.com/apis/credentials.
For those who haven't created a credential for Google's API, after clicking the
link above (and logging in to the appropriate account),

Name the file you download "client_secrets.json" and place it in the root directory of your application.
1. Select/create the project that this authentication is for (if creating a new
project make sure to configure the OAuth consent screen; you only need to set
an Application name).

The first time you create a new instance of the `Gmail` class, a browser window will open and you'll be asked to give permissions to the application. This will only happen once.
2. Click on the "Dashboard" tab, then "Enable APIs and Services". Search for
Gmail and enable.

3. Click on the Credentials tab, then "Create Credentials" > "OAuth client ID".

4. Select what kind of application this is for, and give it a memorable name.

5. Back on the credentials screen, click the download icon next to the
credential you just created to download it as a JSON object.

6. Save this file as "client_secret.json" and place it in the root directory of
your application. (The `Gmail` class takes in an argument for the name of this
file if you choose to name it otherwise.)

The first time you create a new instance of the `Gmail` class, a browser window
will open, and you'll be asked to give permissions to the application. This
will save an access token in a file named "gmail-token.json", and only needs to
occur once.

You are now good to go!

Note about authentication method: I have opted not to use a username-password
authentication (through imap/smtp), since using Google's authorization is both
significantly safer and avoids clashing with Google's many security measures.

## Usage
### Send a simple message:
```python
Expand All @@ -34,7 +63,7 @@ params = {
"msg_plain": "Hi\nThis is a plain text email.",
"signature": True # use my account signature
}
gmail.send_message(**params) # equivalent to send_message(to="[email protected]", sender=...)
message = gmail.send_message(**params) # equivalent to send_message(to="[email protected]", sender=...)
```

### Send a message with attachments, cc, bcc fields:
Expand All @@ -54,7 +83,7 @@ params = {
"attachments": ["path/to/something/cool.pdf", "path/to/image.jpg", "path/to/script.py"],
"signature": True # use my account signature
}
gmail.send_message(**params) # equivalent to send_message(to="[email protected]", sender=...)
message = gmail.send_message(**params) # equivalent to send_message(to="[email protected]", sender=...)
```

It couldn't be easier!
Expand All @@ -71,17 +100,57 @@ messages = gmail.get_unread_inbox()
# Starred messages
messages = gmail.get_starred_messages()

# ...and many more easy to use functions...
# ...and many more easy to use functions can be found in gmail.py!

# Print them out!
for message in messages:
print("To: " + message['To'])
print("From: " + message['From'])
print("Subject: " + message['Subject'])
print("Date: " + message['Date'])
print("Preview: " + message['Snippet'])
print("To: " + message.recipient)
print("From: " + message.sender)
print("Subject: " + message.subject)
print("Date: " + message.date)
print("Preview: " + message.snippet)

# print("Message Body: " + message['Message Body'])
print("Message Body: " + message.plain) # or message.html
```

### Marking messages:
```python
from simplegmail import Gmail

gmail = Gmail()

messages = gmail.get_unread_inbox()

message_to_read = messages[0]
message_to_read.mark_as_read()

# Oops, I want to mark as unread now
message_to_read.mark_as_unread()

message_to_star = messages[1]
message_to_star.star()

message_to_trash = messages[2]
message_to_trash.trash()

# ...and many more functions can be found in message.py!
```

### Downloading attachments:
```python
from simplegmail import Gmail

gmail = Gmail()

messages = gmail.get_unread_inbox()

message = messages[0]
if message.attachments:
for attm in message.attachments:
print('File: ' + attm.filename)
attm.save() # downloads and saves each attachment under it's stored
# filename. You can download without saving with `attm.download()`

```

### Retrieving messages (advanced, with queries!):
Expand Down
12 changes: 7 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@

setuptools.setup(
name="simplegmail",
version="1.0.0",
url="https://github.com/illiteratecoder/simple-gmail",
version="2.0.0",
url="https://github.com/jeremyephron/simple-gmail",
author="Jeremy Ephron",
author_email="[email protected]",
description="A simple Python API client for Gmail.",
long_description=open('README.md').read(),
long_description_content_type='text/markdown',
packages=setuptools.find_packages(),
install_requires=[
'google-api-python-client>=1.7.3',
'bs4>=0.0.1',
'py-dateutil>=2.2',
'oauth2client>=4.1.3'
'python-dateutil>=2.8.1',
'oauth2client>=4.1.3',
'lxml>=4.4.2'
],
setup_requires=["pytest-runner"],
tests_require=["pytest"],
Expand All @@ -24,4 +26,4 @@
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
)
)
4 changes: 3 additions & 1 deletion simplegmail/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from simplegmail.gmail import Gmail
from simplegmail import query
from simplegmail import labels

__all__ = ['Gmail']
__all__ = ['Gmail', 'query', 'labels']
88 changes: 88 additions & 0 deletions simplegmail/attachment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""
This module contains the implementation of the Attachment object.
"""

import base64 # for base64.urlsafe_b64decode
import os # for os.path.exists

class Attachment(object):
"""
The Attachment class for attachments to emails in your Gmail mailbox. This
class should not be manually instantiated.
Args:
service (googleapiclient.discovery.Resource): the Gmail service object.
user_id (str): the username of the account the message belongs to.
msg_id (str): the id of message the attachment belongs to.
att_id (str): the id of the attachment.
filename (str): the filename associated with the attachment.
filetype (str): the mime type of the file.
data (bytes): the raw data of the file. Default None.
Attributes:
_service (googleapiclient.discovery.Resource): the Gmail service object.
user_id (str): the username of the account the message belongs to.
msg_id (str): the id of message the attachment belongs to.
id (str): the id of the attachment.
filename (str): the filename associated with the attachment.
filetype (str): the mime type of the file.
data (bytes): the raw data of the file.
"""

def __init__(self, service, user_id, msg_id, att_id, filename, filetype,
data=None):
self._service = service
self.user_id = user_id
self.msg_id = msg_id
self.id = att_id
self.filename = filename
self.filetype = filetype
self.data = data

def download(self):
"""
Downloads the data for an attachment if it does not exist.
"""

if self.data is not None:
return

res = self._service.users().messages().attachments().get(
userId=self.user_id, messageId=self.msg_id, id=self.id
).execute()

data = res['data']
self.data = base64.urlsafe_b64decode(data)

def save(self, filepath=None, overwrite=False):
"""
Saves the attachment. Downloads file data if not downloaded.
Args:
filepath (str): where to save the attachment. Default None, which
uses the filename stored.
overwrite (bool): whether to overwrite existing files. Default False.
Raises:
FileExistsError: if the call would overwrite an existing file and
overwrite is not set to True.
"""

if filepath is None:
filepath = self.filename

if self.data is None:
self.download()

if overwrite and os.path.exists(filepath):
raise FileExistsError(
f"Cannot overwrite file '{filepath}'. Use overwrite=True if "
f"you would like to overwrite the file."
)

with open(filepath, 'wb') as f:
f.write(self.data)

Loading

0 comments on commit 76e151c

Please sign in to comment.