Skip to content

Commit

Permalink
feat: add support for Twilio Email (#882)
Browse files Browse the repository at this point in the history
  • Loading branch information
childish-sambino authored Apr 21, 2020
1 parent 83b28b8 commit 1adc410
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 107 deletions.
8 changes: 4 additions & 4 deletions sendgrid/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
Modules to help with common tasks.
"""

from .version import __version__
from .sendgrid import SendGridAPIClient # noqa
from .helpers.mail import * # noqa
from .helpers.endpoints import * # noqa
# from .helpers.inbound import * # noqa
from .helpers.mail import * # noqa
from .helpers.stats import * # noqa
from .sendgrid import SendGridAPIClient # noqa
from .twilio_email import TwilioEmailAPIClient # noqa
from .version import __version__
62 changes: 62 additions & 0 deletions sendgrid/base_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import python_http_client


class BaseInterface(object):
def __init__(self, auth, host, impersonate_subuser):
"""
Construct the Twilio SendGrid v3 API object.
Note that the underlying client is being set up during initialization,
therefore changing attributes in runtime will not affect HTTP client
behaviour.
:param auth: the authorization header
:type auth: string
:param impersonate_subuser: the subuser to impersonate. Will be passed
by "On-Behalf-Of" header by underlying
client. See
https://sendgrid.com/docs/User_Guide/Settings/subusers.html
for more details
:type impersonate_subuser: string
:param host: base URL for API calls
:type host: string
"""
from . import __version__
self.auth = auth
self.host = host
self.impersonate_subuser = impersonate_subuser
self.version = __version__
self.useragent = 'sendgrid/{};python'.format(self.version)

self.client = python_http_client.Client(
host=self.host,
request_headers=self._default_headers,
version=3)

@property
def _default_headers(self):
"""Set the default header for a Twilio SendGrid v3 API call"""
headers = {
"Authorization": self.auth,
"User-Agent": self.useragent,
"Accept": 'application/json'
}
if self.impersonate_subuser:
headers['On-Behalf-Of'] = self.impersonate_subuser

return headers

def reset_request_headers(self):
self.client.request_headers = self._default_headers

def send(self, message):
"""Make a Twilio SendGrid v3 API request with the request body generated by
the Mail object
:param message: The Twilio SendGrid v3 API request body generated by the Mail
object
:type message: Mail
"""
if not isinstance(message, dict):
message = message.get()

return self.client.mail.send.post(request_body=message)
56 changes: 9 additions & 47 deletions sendgrid/sendgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@

import os

import python_http_client
from .base_interface import BaseInterface


class SendGridAPIClient(object):
class SendGridAPIClient(BaseInterface):
"""The Twilio SendGrid API Client.
Use this object to interact with the v3 API. For example:
sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
Use this object to interact with the v3 API. For example:
mail_client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
...
mail = Mail(from_email, subject, to_email, content)
response = sg.client.mail.send.post(request_body=mail.get())
response = mail_client.send(mail)
For examples and detailed use instructions, see
https://github.com/sendgrid/sendgrid-python
Expand All @@ -40,8 +40,8 @@ def __init__(
therefore changing attributes in runtime will not affect HTTP client
behaviour.
:param api_key: Twilio SendGrid API key to use. If not provided, key will be
read from environment variable "SENDGRID_API_KEY"
:param api_key: Twilio SendGrid API key to use. If not provided, value
will be read from environment variable "SENDGRID_API_KEY"
:type api_key: string
:param impersonate_subuser: the subuser to impersonate. Will be passed
by "On-Behalf-Of" header by underlying
Expand All @@ -52,45 +52,7 @@ def __init__(
:param host: base URL for API calls
:type host: string
"""
from . import __version__
self.api_key = api_key or os.environ.get('SENDGRID_API_KEY')
self.impersonate_subuser = impersonate_subuser
self.host = host
self.version = __version__
self.useragent = 'sendgrid/{};python'.format(self.version)
auth = 'Bearer {}'.format(self.api_key)

self.client = python_http_client.Client(
host=self.host,
request_headers=self._default_headers,
version=3)

@property
def _default_headers(self):
"""Set the default header for a Twilio SendGrid v3 API call"""
headers = {
"Authorization": 'Bearer {}'.format(self.api_key),
"User-Agent": self.useragent,
"Accept": 'application/json'
}
if self.impersonate_subuser:
headers['On-Behalf-Of'] = self.impersonate_subuser

return headers

def reset_request_headers(self):

self.client.request_headers = self._default_headers

def send(self, message):
"""Make a Twilio SendGrid v3 API request with the request body generated by
the Mail object
:param message: The Twilio SendGrid v3 API request body generated by the Mail
object
:type message: Mail
"""
if isinstance(message, dict):
response = self.client.mail.send.post(request_body=message)
else:
response = self.client.mail.send.post(request_body=message.get())
return response
super(SendGridAPIClient, self).__init__(auth, host, impersonate_subuser)
73 changes: 73 additions & 0 deletions sendgrid/twilio_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""
This library allows you to quickly and easily use the Twilio Email Web API v3 via Python.
For more information on this library, see the README on GitHub.
http://github.com/sendgrid/sendgrid-python
For more information on the Twilio SendGrid v3 API, see the v3 docs:
http://sendgrid.com/docs/API_Reference/api_v3.html
For the user guide, code examples, and more, visit the main docs page:
http://sendgrid.com/docs/index.html
This file provides the Twilio Email API Client.
"""
import os
from base64 import b64encode

from .base_interface import BaseInterface


class TwilioEmailAPIClient(BaseInterface):
"""The Twilio Email API Client.
Use this object to interact with the v3 API. For example:
mail_client = sendgrid.TwilioEmailAPIClient(os.environ.get('TWILIO_API_KEY'),
os.environ.get('TWILIO_API_SECRET'))
...
mail = Mail(from_email, subject, to_email, content)
response = mail_client.send(mail)
For examples and detailed use instructions, see
https://github.com/sendgrid/sendgrid-python
"""

def __init__(
self,
username=None,
password=None,
host='https://email.twilio.com',
impersonate_subuser=None):
"""
Construct the Twilio Email v3 API object.
Note that the underlying client is being set up during initialization,
therefore changing attributes in runtime will not affect HTTP client
behaviour.
:param username: Twilio Email API key SID or Account SID to use. If not
provided, value will be read from the environment
variable "TWILIO_API_KEY" or "TWILIO_ACCOUNT_SID"
:type username: string
:param password: Twilio Email API key secret or Account Auth Token to
use. If not provided, value will be read from the
environment variable "TWILIO_API_SECRET" or
"TWILIO_AUTH_TOKEN"
:type password: string
:param impersonate_subuser: the subuser to impersonate. Will be passed
by "On-Behalf-Of" header by underlying
client. See
https://sendgrid.com/docs/User_Guide/Settings/subusers.html
for more details
:type impersonate_subuser: string
:param host: base URL for API calls
:type host: string
"""
self.username = username or \
os.environ.get('TWILIO_API_KEY') or \
os.environ.get('TWILIO_ACCOUNT_SID')

self.password = password or \
os.environ.get('TWILIO_API_SECRET') or \
os.environ.get('TWILIO_AUTH_TOKEN')

auth = 'Basic ' + b64encode('{}:{}'.format(self.username, self.password).encode()).decode()

super(TwilioEmailAPIClient, self).__init__(auth, host, impersonate_subuser)
18 changes: 5 additions & 13 deletions test/test_sendgrid.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import sendgrid
from sendgrid.helpers.endpoints.ip.unassigned import unassigned
from sendgrid.helpers.mail import *
import os
import datetime
import os
import unittest

import sendgrid
from sendgrid.helpers.endpoints.ip.unassigned import unassigned

host = "http://localhost:4010"


Expand All @@ -18,12 +18,9 @@ def setUpClass(cls):
os.path.dirname(__file__)), '/..')
cls.sg = sendgrid.SendGridAPIClient(host=host)
cls.devnull = open(os.devnull, 'w')
prism_cmd = None

def test_api_key_init(self):
self.assertEqual(self.sg.api_key, os.environ.get('SENDGRID_API_KEY'))
# Support the previous naming convention for API keys
self.assertEqual(self.sg.api_key, self.sg.api_key)
my_sendgrid = sendgrid.SendGridAPIClient(api_key="THISISMYKEY")
self.assertEqual(my_sendgrid.api_key, "THISISMYKEY")

Expand Down Expand Up @@ -2305,7 +2302,7 @@ def test_whitelabel_links__link_id__subuser_post(self):

def test_license_year(self):
LICENSE_FILE = 'LICENSE.md'
copyright_line=''
copyright_line = ''
with open(LICENSE_FILE, 'r') as f:
for line in f:
if line.startswith('Copyright'):
Expand All @@ -2314,8 +2311,3 @@ def test_license_year(self):
self.assertEqual(
'Copyright (C) %s, Twilio SendGrid, Inc. <help@twilio.com>' % datetime.datetime.now().year,
copyright_line)

# @classmethod
# def tearDownClass(cls):
# cls.p.kill()
# print("Prism Shut Down")
37 changes: 37 additions & 0 deletions test/test_twilio_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import os
import unittest

from sendgrid import TwilioEmailAPIClient


class UnitTests(unittest.TestCase):

@classmethod
def setUpClass(cls):
os.environ['TWILIO_API_KEY'] = 'api-key'
os.environ['TWILIO_API_SECRET'] = 'api-secret'
os.environ['TWILIO_ACCOUNT_SID'] = 'account-sid'
os.environ['TWILIO_AUTH_TOKEN'] = 'auth-token'

def test_init_key_over_token(self):
mail_client = TwilioEmailAPIClient()

self.assertEqual(mail_client.username, 'api-key')
self.assertEqual(mail_client.password, 'api-secret')
self.assertEqual(mail_client.host, 'https://email.twilio.com')

def test_init_token(self):
del os.environ['TWILIO_API_KEY']
del os.environ['TWILIO_API_SECRET']

mail_client = TwilioEmailAPIClient()

self.assertEqual(mail_client.username, 'account-sid')
self.assertEqual(mail_client.password, 'auth-token')

def test_init_args(self):
mail_client = TwilioEmailAPIClient('username', 'password')

self.assertEqual(mail_client.username, 'username')
self.assertEqual(mail_client.password, 'password')
self.assertEqual(mail_client.auth, 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=')
6 changes: 4 additions & 2 deletions use_cases/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ This directory provides examples for specific use cases of this library. Please
* [Integrate with Slack Events API](slack_event_api_integration.md)
* [Legacy Templates](legacy_templates.md)

### Working with SMS
* [Send a SMS Message](sms.md)
# Twilio Use Cases
* [Twilio Setup](twilio-setup.md)
* [Send an Email With Twilio Email (Pilot)](twilio-email.md)
* [Send an SMS Message](sms.md)

### Troubleshooting
* [Error Handling](error_handling.md)
Expand Down
50 changes: 9 additions & 41 deletions use_cases/sms.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,12 @@
Following are the steps to add Twilio SMS to your app:
First, follow the [Twilio Setup](twilio-setup.md) guide for creating a Twilio account and setting up environment variables with the proper credentials.

## 1. Obtain a Free Twilio Account

Sign up for a free Twilio account [here](https://www.twilio.com/try-twilio?source=sendgrid-python).

## 2. Update Your Environment Variables

You can obtain your Account Sid and Auth Token from [twilio.com/console](https://twilio.com/console).

### Mac
Then, install the Twilio Helper Library.

```bash
echo "export TWILIO_ACCOUNT_SID='YOUR_TWILIO_ACCOUNT_SID'" > twilio.env
echo "export TWILIO_AUTH_TOKEN='YOUR_TWILIO_AUTH_TOKEN'" >> twilio.env
echo "twilio.env" >> .gitignore
source ./twilio.env
pip install twilio
```

### Windows

Temporarily set the environment variable (accessible only during the current CLI session):

```bash
set TWILIO_ACCOUNT_SID=YOUR_TWILIO_ACCOUNT_SID
set TWILIO_AUTH_TOKEN=YOUR_TWILIO_AUTH_TOKEN
```

Permanently set the environment variable (accessible in all subsequent CLI sessions):

```bash
setx TWILIO_ACCOUNT_SID "YOUR_TWILIO_ACCOUNT_SID"
setx TWILIO_AUTH_TOKEN "YOUR_TWILIO_AUTH_TOKEN"
```

## 3. Install the Twilio Helper Library

`pip install twilio`

Then, you can execute the following code.
Finally, send a message.

```python
import os
Expand All @@ -50,12 +19,11 @@ to_number ='+15558675310'
body = "Join Earth's mightiest heroes. Like Kevin Bacon."
client = Client(account_sid, auth_token)

message = client.messages \
.create(
body=body,
from_=from_number,
to=to_number
)
message = client.messages.create(
body=body,
from_=from_number,
to=to_number
)

print(message.sid)
```
Expand Down
Loading

0 comments on commit 1adc410

Please sign in to comment.