Skip to content

Commit 1adc410

Browse files
author
childish-sambino
authored
feat: add support for Twilio Email (#882)
1 parent 83b28b8 commit 1adc410

File tree

10 files changed

+273
-107
lines changed

10 files changed

+273
-107
lines changed

sendgrid/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
Modules to help with common tasks.
1616
"""
1717

18-
from .version import __version__
19-
from .sendgrid import SendGridAPIClient # noqa
20-
from .helpers.mail import * # noqa
2118
from .helpers.endpoints import * # noqa
22-
# from .helpers.inbound import * # noqa
19+
from .helpers.mail import * # noqa
2320
from .helpers.stats import * # noqa
21+
from .sendgrid import SendGridAPIClient # noqa
22+
from .twilio_email import TwilioEmailAPIClient # noqa
23+
from .version import __version__

sendgrid/base_interface.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import python_http_client
2+
3+
4+
class BaseInterface(object):
5+
def __init__(self, auth, host, impersonate_subuser):
6+
"""
7+
Construct the Twilio SendGrid v3 API object.
8+
Note that the underlying client is being set up during initialization,
9+
therefore changing attributes in runtime will not affect HTTP client
10+
behaviour.
11+
12+
:param auth: the authorization header
13+
:type auth: string
14+
:param impersonate_subuser: the subuser to impersonate. Will be passed
15+
by "On-Behalf-Of" header by underlying
16+
client. See
17+
https://sendgrid.com/docs/User_Guide/Settings/subusers.html
18+
for more details
19+
:type impersonate_subuser: string
20+
:param host: base URL for API calls
21+
:type host: string
22+
"""
23+
from . import __version__
24+
self.auth = auth
25+
self.host = host
26+
self.impersonate_subuser = impersonate_subuser
27+
self.version = __version__
28+
self.useragent = 'sendgrid/{};python'.format(self.version)
29+
30+
self.client = python_http_client.Client(
31+
host=self.host,
32+
request_headers=self._default_headers,
33+
version=3)
34+
35+
@property
36+
def _default_headers(self):
37+
"""Set the default header for a Twilio SendGrid v3 API call"""
38+
headers = {
39+
"Authorization": self.auth,
40+
"User-Agent": self.useragent,
41+
"Accept": 'application/json'
42+
}
43+
if self.impersonate_subuser:
44+
headers['On-Behalf-Of'] = self.impersonate_subuser
45+
46+
return headers
47+
48+
def reset_request_headers(self):
49+
self.client.request_headers = self._default_headers
50+
51+
def send(self, message):
52+
"""Make a Twilio SendGrid v3 API request with the request body generated by
53+
the Mail object
54+
55+
:param message: The Twilio SendGrid v3 API request body generated by the Mail
56+
object
57+
:type message: Mail
58+
"""
59+
if not isinstance(message, dict):
60+
message = message.get()
61+
62+
return self.client.mail.send.post(request_body=message)

sendgrid/sendgrid.py

Lines changed: 9 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@
1313

1414
import os
1515

16-
import python_http_client
16+
from .base_interface import BaseInterface
1717

1818

19-
class SendGridAPIClient(object):
19+
class SendGridAPIClient(BaseInterface):
2020
"""The Twilio SendGrid API Client.
2121
22-
Use this object to interact with the v3 API. For example:
23-
sg = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
22+
Use this object to interact with the v3 API. For example:
23+
mail_client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
2424
...
2525
mail = Mail(from_email, subject, to_email, content)
26-
response = sg.client.mail.send.post(request_body=mail.get())
26+
response = mail_client.send(mail)
2727
2828
For examples and detailed use instructions, see
2929
https://github.com/sendgrid/sendgrid-python
@@ -40,8 +40,8 @@ def __init__(
4040
therefore changing attributes in runtime will not affect HTTP client
4141
behaviour.
4242
43-
:param api_key: Twilio SendGrid API key to use. If not provided, key will be
44-
read from environment variable "SENDGRID_API_KEY"
43+
:param api_key: Twilio SendGrid API key to use. If not provided, value
44+
will be read from environment variable "SENDGRID_API_KEY"
4545
:type api_key: string
4646
:param impersonate_subuser: the subuser to impersonate. Will be passed
4747
by "On-Behalf-Of" header by underlying
@@ -52,45 +52,7 @@ def __init__(
5252
:param host: base URL for API calls
5353
:type host: string
5454
"""
55-
from . import __version__
5655
self.api_key = api_key or os.environ.get('SENDGRID_API_KEY')
57-
self.impersonate_subuser = impersonate_subuser
58-
self.host = host
59-
self.version = __version__
60-
self.useragent = 'sendgrid/{};python'.format(self.version)
56+
auth = 'Bearer {}'.format(self.api_key)
6157

62-
self.client = python_http_client.Client(
63-
host=self.host,
64-
request_headers=self._default_headers,
65-
version=3)
66-
67-
@property
68-
def _default_headers(self):
69-
"""Set the default header for a Twilio SendGrid v3 API call"""
70-
headers = {
71-
"Authorization": 'Bearer {}'.format(self.api_key),
72-
"User-Agent": self.useragent,
73-
"Accept": 'application/json'
74-
}
75-
if self.impersonate_subuser:
76-
headers['On-Behalf-Of'] = self.impersonate_subuser
77-
78-
return headers
79-
80-
def reset_request_headers(self):
81-
82-
self.client.request_headers = self._default_headers
83-
84-
def send(self, message):
85-
"""Make a Twilio SendGrid v3 API request with the request body generated by
86-
the Mail object
87-
88-
:param message: The Twilio SendGrid v3 API request body generated by the Mail
89-
object
90-
:type message: Mail
91-
"""
92-
if isinstance(message, dict):
93-
response = self.client.mail.send.post(request_body=message)
94-
else:
95-
response = self.client.mail.send.post(request_body=message.get())
96-
return response
58+
super(SendGridAPIClient, self).__init__(auth, host, impersonate_subuser)

sendgrid/twilio_email.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""
2+
This library allows you to quickly and easily use the Twilio Email Web API v3 via Python.
3+
4+
For more information on this library, see the README on GitHub.
5+
http://github.com/sendgrid/sendgrid-python
6+
For more information on the Twilio SendGrid v3 API, see the v3 docs:
7+
http://sendgrid.com/docs/API_Reference/api_v3.html
8+
For the user guide, code examples, and more, visit the main docs page:
9+
http://sendgrid.com/docs/index.html
10+
11+
This file provides the Twilio Email API Client.
12+
"""
13+
import os
14+
from base64 import b64encode
15+
16+
from .base_interface import BaseInterface
17+
18+
19+
class TwilioEmailAPIClient(BaseInterface):
20+
"""The Twilio Email API Client.
21+
22+
Use this object to interact with the v3 API. For example:
23+
mail_client = sendgrid.TwilioEmailAPIClient(os.environ.get('TWILIO_API_KEY'),
24+
os.environ.get('TWILIO_API_SECRET'))
25+
...
26+
mail = Mail(from_email, subject, to_email, content)
27+
response = mail_client.send(mail)
28+
29+
For examples and detailed use instructions, see
30+
https://github.com/sendgrid/sendgrid-python
31+
"""
32+
33+
def __init__(
34+
self,
35+
username=None,
36+
password=None,
37+
host='https://email.twilio.com',
38+
impersonate_subuser=None):
39+
"""
40+
Construct the Twilio Email v3 API object.
41+
Note that the underlying client is being set up during initialization,
42+
therefore changing attributes in runtime will not affect HTTP client
43+
behaviour.
44+
45+
:param username: Twilio Email API key SID or Account SID to use. If not
46+
provided, value will be read from the environment
47+
variable "TWILIO_API_KEY" or "TWILIO_ACCOUNT_SID"
48+
:type username: string
49+
:param password: Twilio Email API key secret or Account Auth Token to
50+
use. If not provided, value will be read from the
51+
environment variable "TWILIO_API_SECRET" or
52+
"TWILIO_AUTH_TOKEN"
53+
:type password: string
54+
:param impersonate_subuser: the subuser to impersonate. Will be passed
55+
by "On-Behalf-Of" header by underlying
56+
client. See
57+
https://sendgrid.com/docs/User_Guide/Settings/subusers.html
58+
for more details
59+
:type impersonate_subuser: string
60+
:param host: base URL for API calls
61+
:type host: string
62+
"""
63+
self.username = username or \
64+
os.environ.get('TWILIO_API_KEY') or \
65+
os.environ.get('TWILIO_ACCOUNT_SID')
66+
67+
self.password = password or \
68+
os.environ.get('TWILIO_API_SECRET') or \
69+
os.environ.get('TWILIO_AUTH_TOKEN')
70+
71+
auth = 'Basic ' + b64encode('{}:{}'.format(self.username, self.password).encode()).decode()
72+
73+
super(TwilioEmailAPIClient, self).__init__(auth, host, impersonate_subuser)

test/test_sendgrid.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import sendgrid
2-
from sendgrid.helpers.endpoints.ip.unassigned import unassigned
3-
from sendgrid.helpers.mail import *
4-
import os
51
import datetime
2+
import os
63
import unittest
74

5+
import sendgrid
6+
from sendgrid.helpers.endpoints.ip.unassigned import unassigned
7+
88
host = "http://localhost:4010"
99

1010

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

2322
def test_api_key_init(self):
2423
self.assertEqual(self.sg.api_key, os.environ.get('SENDGRID_API_KEY'))
25-
# Support the previous naming convention for API keys
26-
self.assertEqual(self.sg.api_key, self.sg.api_key)
2724
my_sendgrid = sendgrid.SendGridAPIClient(api_key="THISISMYKEY")
2825
self.assertEqual(my_sendgrid.api_key, "THISISMYKEY")
2926

@@ -2305,7 +2302,7 @@ def test_whitelabel_links__link_id__subuser_post(self):
23052302

23062303
def test_license_year(self):
23072304
LICENSE_FILE = 'LICENSE.md'
2308-
copyright_line=''
2305+
copyright_line = ''
23092306
with open(LICENSE_FILE, 'r') as f:
23102307
for line in f:
23112308
if line.startswith('Copyright'):
@@ -2314,8 +2311,3 @@ def test_license_year(self):
23142311
self.assertEqual(
23152312
'Copyright (C) %s, Twilio SendGrid, Inc. <help@twilio.com>' % datetime.datetime.now().year,
23162313
copyright_line)
2317-
2318-
# @classmethod
2319-
# def tearDownClass(cls):
2320-
# cls.p.kill()
2321-
# print("Prism Shut Down")

test/test_twilio_email.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import os
2+
import unittest
3+
4+
from sendgrid import TwilioEmailAPIClient
5+
6+
7+
class UnitTests(unittest.TestCase):
8+
9+
@classmethod
10+
def setUpClass(cls):
11+
os.environ['TWILIO_API_KEY'] = 'api-key'
12+
os.environ['TWILIO_API_SECRET'] = 'api-secret'
13+
os.environ['TWILIO_ACCOUNT_SID'] = 'account-sid'
14+
os.environ['TWILIO_AUTH_TOKEN'] = 'auth-token'
15+
16+
def test_init_key_over_token(self):
17+
mail_client = TwilioEmailAPIClient()
18+
19+
self.assertEqual(mail_client.username, 'api-key')
20+
self.assertEqual(mail_client.password, 'api-secret')
21+
self.assertEqual(mail_client.host, 'https://email.twilio.com')
22+
23+
def test_init_token(self):
24+
del os.environ['TWILIO_API_KEY']
25+
del os.environ['TWILIO_API_SECRET']
26+
27+
mail_client = TwilioEmailAPIClient()
28+
29+
self.assertEqual(mail_client.username, 'account-sid')
30+
self.assertEqual(mail_client.password, 'auth-token')
31+
32+
def test_init_args(self):
33+
mail_client = TwilioEmailAPIClient('username', 'password')
34+
35+
self.assertEqual(mail_client.username, 'username')
36+
self.assertEqual(mail_client.password, 'password')
37+
self.assertEqual(mail_client.auth, 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=')

use_cases/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ This directory provides examples for specific use cases of this library. Please
1818
* [Integrate with Slack Events API](slack_event_api_integration.md)
1919
* [Legacy Templates](legacy_templates.md)
2020

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

2426
### Troubleshooting
2527
* [Error Handling](error_handling.md)

use_cases/sms.md

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,12 @@
1-
Following are the steps to add Twilio SMS to your app:
1+
First, follow the [Twilio Setup](twilio-setup.md) guide for creating a Twilio account and setting up environment variables with the proper credentials.
22

3-
## 1. Obtain a Free Twilio Account
4-
5-
Sign up for a free Twilio account [here](https://www.twilio.com/try-twilio?source=sendgrid-python).
6-
7-
## 2. Update Your Environment Variables
8-
9-
You can obtain your Account Sid and Auth Token from [twilio.com/console](https://twilio.com/console).
10-
11-
### Mac
3+
Then, install the Twilio Helper Library.
124

135
```bash
14-
echo "export TWILIO_ACCOUNT_SID='YOUR_TWILIO_ACCOUNT_SID'" > twilio.env
15-
echo "export TWILIO_AUTH_TOKEN='YOUR_TWILIO_AUTH_TOKEN'" >> twilio.env
16-
echo "twilio.env" >> .gitignore
17-
source ./twilio.env
6+
pip install twilio
187
```
198

20-
### Windows
21-
22-
Temporarily set the environment variable (accessible only during the current CLI session):
23-
24-
```bash
25-
set TWILIO_ACCOUNT_SID=YOUR_TWILIO_ACCOUNT_SID
26-
set TWILIO_AUTH_TOKEN=YOUR_TWILIO_AUTH_TOKEN
27-
```
28-
29-
Permanently set the environment variable (accessible in all subsequent CLI sessions):
30-
31-
```bash
32-
setx TWILIO_ACCOUNT_SID "YOUR_TWILIO_ACCOUNT_SID"
33-
setx TWILIO_AUTH_TOKEN "YOUR_TWILIO_AUTH_TOKEN"
34-
```
35-
36-
## 3. Install the Twilio Helper Library
37-
38-
`pip install twilio`
39-
40-
Then, you can execute the following code.
9+
Finally, send a message.
4110

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

53-
message = client.messages \
54-
.create(
55-
body=body,
56-
from_=from_number,
57-
to=to_number
58-
)
22+
message = client.messages.create(
23+
body=body,
24+
from_=from_number,
25+
to=to_number
26+
)
5927

6028
print(message.sid)
6129
```

0 commit comments

Comments
 (0)