From 9fd2fd7ff142b06030f4e36d6704911420f53892 Mon Sep 17 00:00:00 2001 From: Sam Harrison Date: Wed, 15 Apr 2020 17:01:48 -0500 Subject: [PATCH] feat: add support for Twilio Email --- sendgrid/__init__.py | 8 ++--- sendgrid/base_interface.py | 62 ++++++++++++++++++++++++++++++++ sendgrid/sendgrid.py | 56 +++++------------------------ sendgrid/twilio_email.py | 73 ++++++++++++++++++++++++++++++++++++++ test/test_sendgrid.py | 18 +++------- test/test_twilio_email.py | 37 +++++++++++++++++++ use_cases/README.md | 6 ++-- use_cases/sms.md | 50 +++++--------------------- use_cases/twilio-email.md | 16 +++++++++ use_cases/twilio-setup.md | 54 ++++++++++++++++++++++++++++ 10 files changed, 273 insertions(+), 107 deletions(-) create mode 100644 sendgrid/base_interface.py create mode 100644 sendgrid/twilio_email.py create mode 100644 test/test_twilio_email.py create mode 100644 use_cases/twilio-email.md create mode 100644 use_cases/twilio-setup.md diff --git a/sendgrid/__init__.py b/sendgrid/__init__.py index 8923a4484..eb6d58961 100644 --- a/sendgrid/__init__.py +++ b/sendgrid/__init__.py @@ -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__ diff --git a/sendgrid/base_interface.py b/sendgrid/base_interface.py new file mode 100644 index 000000000..92b38247e --- /dev/null +++ b/sendgrid/base_interface.py @@ -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) diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index 648f8e1cc..912d8336e 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -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 @@ -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 @@ -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) diff --git a/sendgrid/twilio_email.py b/sendgrid/twilio_email.py new file mode 100644 index 000000000..78bd01815 --- /dev/null +++ b/sendgrid/twilio_email.py @@ -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) diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index d158e402e..71b0ab6ff 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -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" @@ -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") @@ -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'): @@ -2314,8 +2311,3 @@ def test_license_year(self): self.assertEqual( 'Copyright (C) %s, Twilio SendGrid, Inc. ' % datetime.datetime.now().year, copyright_line) - - # @classmethod - # def tearDownClass(cls): - # cls.p.kill() - # print("Prism Shut Down") diff --git a/test/test_twilio_email.py b/test/test_twilio_email.py new file mode 100644 index 000000000..92269acff --- /dev/null +++ b/test/test_twilio_email.py @@ -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=') diff --git a/use_cases/README.md b/use_cases/README.md index 46a508795..a91f1f5a4 100644 --- a/use_cases/README.md +++ b/use_cases/README.md @@ -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) diff --git a/use_cases/sms.md b/use_cases/sms.md index bd8655171..1e51f0a25 100644 --- a/use_cases/sms.md +++ b/use_cases/sms.md @@ -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 @@ -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) ``` diff --git a/use_cases/twilio-email.md b/use_cases/twilio-email.md new file mode 100644 index 000000000..c8f0373d8 --- /dev/null +++ b/use_cases/twilio-email.md @@ -0,0 +1,16 @@ +First, follow the [Twilio Setup](twilio-setup.md) guide for creating a Twilio account and setting up environment variables with the proper credentials. + +Then, initialize the Twilio Email Client. + +```python +import sendgrid +import os + +mail_client = sendgrid.TwilioEmailAPIClient(os.environ.get('TWILIO_API_KEY'), os.environ.get('TWILIO_API_SECRET')) + +# or + +mail_client = sendgrid.TwilioEmailAPIClient(os.environ.get('TWILIO_ACCOUNT_SID'), os.environ.get('TWILIO_AUTH_TOKEN')) +``` + +This client has the same interface as the `SendGridAPIClient` client. diff --git a/use_cases/twilio-setup.md b/use_cases/twilio-setup.md new file mode 100644 index 000000000..315cd3b32 --- /dev/null +++ b/use_cases/twilio-setup.md @@ -0,0 +1,54 @@ +## 1. Obtain a Free Twilio Account + +Sign up for a free Twilio account [here](https://www.twilio.com/try-twilio?source=sendgrid-nodejs). + +## 2. Set Up Your Environment Variables + +The Twilio API allows for authentication using with either an API key/secret or your Account SID/Auth Token. You can create an API key [here](https://twil.io/get-api-key) or obtain your Account SID and Auth Token [here](https://twil.io/console). + +Once you have those, follow the steps below based on your operating system. + +### Linux/Mac + +```bash +echo "export TWILIO_API_KEY='YOUR_TWILIO_API_KEY'" > twilio.env +echo "export TWILIO_API_SECRET='YOUR_TWILIO_API_SECRET'" >> twilio.env + +# or + +echo "export TWILIO_ACCOUNT_SID='YOUR_TWILIO_ACCOUNT_SID'" > twilio.env +echo "export TWILIO_AUTH_TOKEN='YOUR_TWILIO_AUTH_TOKEN'" >> twilio.env +``` + +Then: + +```bash +echo "twilio.env" >> .gitignore +source ./twilio.env +``` + +### Windows + +Temporarily set the environment variable (accessible only during the current CLI session): + +```bash +set TWILIO_API_KEY=YOUR_TWILIO_API_KEY +set TWILIO_API_SECRET=YOUR_TWILIO_API_SECRET + +: or + +set TWILIO_ACCOUNT_SID=YOUR_TWILIO_ACCOUNT_SID +set TWILIO_AUTH_TOKEN=YOUR_TWILIO_AUTH_TOKEN +``` + +Or permanently set the environment variable (accessible in all subsequent CLI sessions): + +```bash +setx TWILIO_API_KEY "YOUR_TWILIO_API_KEY" +setx TWILIO_API_SECRET "YOUR_TWILIO_API_SECRET" + +: or + +setx TWILIO_ACCOUNT_SID "YOUR_TWILIO_ACCOUNT_SID" +setx TWILIO_AUTH_TOKEN "YOUR_TWILIO_AUTH_TOKEN" +```