From 79cc7dbba269505ed545ac12d1967913c6e1810e Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 17 Sep 2015 14:25:31 -0700 Subject: [PATCH 01/28] Fixing the tests --- .env_sample | 3 ++ .gitignore | 3 +- CHANGELOG.md | 4 -- README.rst | 6 +-- activate.sh | 4 ++ example_v2_test.py | 19 +++++++ example_v3_test.py | 56 +++++++++++++++++++++ sendgrid/__init__.py | 3 ++ sendgrid/client.py | 90 ++++++++++++++++++++++++++++++++++ sendgrid/message.py | 2 +- sendgrid/resources/__init__.py | 1 + sendgrid/resources/apikeys.py | 63 ++++++++++++++++++++++++ sendgrid/version.py | 2 +- setup.py | 4 +- test/__init__.py | 68 +++++++++++++++++++++++-- 15 files changed, 313 insertions(+), 15 deletions(-) create mode 100644 .env_sample create mode 100755 activate.sh create mode 100755 example_v2_test.py create mode 100755 example_v3_test.py create mode 100644 sendgrid/client.py create mode 100644 sendgrid/resources/__init__.py create mode 100644 sendgrid/resources/apikeys.py diff --git a/.env_sample b/.env_sample new file mode 100644 index 000000000..4337b4d53 --- /dev/null +++ b/.env_sample @@ -0,0 +1,3 @@ +SENDGRID_API_KEY=your_sendgrid_api_key +SENDGRID_USERNAME=your_sendgrid_username +SENDGRID_PASSWORD=your_sendgrid_password \ No newline at end of file diff --git a/.gitignore b/.gitignore index 31704303d..2da8e29d8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,6 @@ sdist *.egg *.egg-info *.pyc -.idea/ venv/ +.idea +.env \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f90508677..bc084223f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,6 @@ # Change Log All notable changes to this project will be documented in this file. -## [1.4.2] - 2015-09-15 -### Added -- Upgrade Mail to new-style class, on Python 2.x. - ## [1.4.1] - 2015-09-09 ### Added - Classifiers for compatible python versions diff --git a/README.rst b/README.rst index ce0768e89..49e92f5b0 100644 --- a/README.rst +++ b/README.rst @@ -380,11 +380,11 @@ Tests ~~~~~ .. code:: python - + virtualenv venv - source venv/bin/activate + source venv/bin/activate #or . ./activate.sh python setup.py install - python test/__init__.py + unit2 discover Deploying ~~~~~~~~~ diff --git a/activate.sh b/activate.sh new file mode 100755 index 000000000..0fe1b6dc6 --- /dev/null +++ b/activate.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# Use this to activate the virtual environment, use the following to execute in current shell +# . ./activate +source venv/bin/activate \ No newline at end of file diff --git a/example_v2_test.py b/example_v2_test.py new file mode 100755 index 000000000..334411e54 --- /dev/null +++ b/example_v2_test.py @@ -0,0 +1,19 @@ +import sendgrid +import os +if os.path.exists('.env'): + for line in open('.env'): + var = line.strip().split('=') + if len(var) == 2: + os.environ[var[0]] = var[1] + +sg = sendgrid.SendGridClient(os.environ.get('SENDGRID_USERNAME'), os.environ.get('SENDGRID_PASSWORD')) + +message = sendgrid.Mail() +message.add_to('Elmer Thomas ') +message.set_subject('Testing from the Python library') +message.set_html('This was a successful test!') +message.set_text('This was a successful test!') +message.set_from('Elmer Thomas ') +status, msg = sg.send(message) +print status +print msg \ No newline at end of file diff --git a/example_v3_test.py b/example_v3_test.py new file mode 100755 index 000000000..bef7a5f4c --- /dev/null +++ b/example_v3_test.py @@ -0,0 +1,56 @@ +import sendgrid +import json + +import os +if os.path.exists('.env'): + for line in open('.env'): + var = line.strip().split('=') + if len(var) == 2: + os.environ[var[0]] = var[1] + +client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + +name = "My Amazing API Key" +status, msg = client.apikeys.post(name) +msg = json.loads(msg) +api_key_id = msg['api_key_id'] +print status +print msg + +name = "My NEW API Key 3000" +status, msg = client.apikeys.patch(api_key_id, name) +print status +print msg + +status, msg = client.apikeys.delete(api_key_id) +print status + +status, msg = client.apikeys.get() +print status +print msg + +""" +# Get a list of all valid API Keys from your account +status, msg = client.apikeys.get() +print status +print msg + +# Create a new API Key +name = "My API Key 10" +status, msg = client.apikeys.post(name) +print status +print msg + +# Delete an API Key with a given api_key_id +api_key_id = "zc0r5sW5TTuBQGsMPMUx0A" +status, msg = client.apikeys.delete(api_key_id) +print status +print msg + +# Update the name of an API Key, given an api_key_id +api_key_id = "API_KEY" +name = "My API Key 3" +status, msg = client.apikeys.patch(api_key_id, name) +print status +print msg +""" \ No newline at end of file diff --git a/sendgrid/__init__.py b/sendgrid/__init__.py index 9fc719f78..270c2895e 100644 --- a/sendgrid/__init__.py +++ b/sendgrid/__init__.py @@ -1,4 +1,7 @@ from .version import __version__ from .sendgrid import SendGridClient from .exceptions import SendGridError, SendGridClientError, SendGridServerError +#v2 API from .message import Mail +#v3 API +from .client import SendGridAPIClient \ No newline at end of file diff --git a/sendgrid/client.py b/sendgrid/client.py new file mode 100644 index 000000000..609e5b7ea --- /dev/null +++ b/sendgrid/client.py @@ -0,0 +1,90 @@ +import json +from .version import __version__ +from socket import timeout +try: + import urllib.request as urllib_request + from urllib.parse import urlencode + from urllib.error import HTTPError +except ImportError: # Python 2 + import urllib2 as urllib_request + from urllib2 import HTTPError + from urllib import urlencode + +from .exceptions import SendGridClientError, SendGridServerError +from resources.apikeys import APIKeys + +class SendGridAPIClient(object): + + """SendGrid API.""" + + def __init__(self, apikey, **opts): + """ + Construct SendGrid API object. + + Args: + apikey: SendGrid API key + opts: You can pass in host or proxies + """ + self._apikey = apikey + self.useragent = 'sendgrid/' + __version__ + ';python_v3' + self.host = opts.get('host', 'https://api.sendgrid.com') + # urllib cannot connect to SSL servers using proxies + self.proxies = opts.get('proxies', None) + + self.apikeys = APIKeys(self) + + @property + def apikey(self): + return self._apikey + + @apikey.setter + def apikey(self, value): + self._apikey = value + + def _build_request(self, url, json_header=False, method='GET', data=None): + if self.proxies: + proxy_support = urllib_request.ProxyHandler(self.proxies) + opener = urllib_request.build_opener(proxy_support) + urllib_request.install_opener(opener) + req = urllib_request.Request(url) + req.get_method = lambda: method + req.add_header('User-Agent', self.useragent) + req.add_header('Authorization', 'Bearer ' + self.apikey) + if json_header: + req.add_header('Content-Type', 'application/json') + try: + if data: + response = urllib_request.urlopen(req, json.dumps(data)) + else: + response = urllib_request.urlopen(req, timeout=10) + except HTTPError as e: + if 400 <= e.code < 500: + raise SendGridClientError(e.code, e.read()) + elif 500 <= e.code < 600: + raise SendGridServerError(e.code, e.read()) + else: + assert False + except timeout as e: + raise SendGridClientError(408, 'Request timeout') + body = response.read() + return response, body + + def get(self, api): + url = self.host + api.base_endpoint + response, body = self._build_request(url, False, 'GET') + return response.getcode(), body + + def post(self, api, data): + url = self.host + api.endpoint + response, body = self._build_request(url, True, 'POST', data) + return response.getcode(), body + + def delete(self, api): + url = self.host + api.endpoint + response, body = self._build_request(url, False, 'DELETE') + return response.getcode(), body + + def patch(self, api, data): + url = self.host + api.endpoint + response, body = self._build_request(url, True, 'PATCH', data) + return response.getcode(), body diff --git a/sendgrid/message.py b/sendgrid/message.py index 986572778..f0763e89c 100644 --- a/sendgrid/message.py +++ b/sendgrid/message.py @@ -8,7 +8,7 @@ from smtpapi import SMTPAPIHeader -class Mail(object): +class Mail(): """SendGrid Message.""" diff --git a/sendgrid/resources/__init__.py b/sendgrid/resources/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/sendgrid/resources/__init__.py @@ -0,0 +1 @@ + diff --git a/sendgrid/resources/apikeys.py b/sendgrid/resources/apikeys.py new file mode 100644 index 000000000..91f324880 --- /dev/null +++ b/sendgrid/resources/apikeys.py @@ -0,0 +1,63 @@ +class APIKeys(object): + """The API Keys feature allows customers to be able to generate an API Key credential + which can be used for authentication with the SendGrid v3 Web API or the Mail API Endpoint""" + + def __init__(self, client , **opts): + """ + Constructs SendGrid APIKeys object. + + See https://sendgrid.com/docs/API_Reference/Web_API_v3/API_Keys/index.html + """ + self._name = None + self._base_endpoint = "/v3/api_keys" + self._endpoint = "/v3/api_keys" + self._client = client + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + @property + def base_endpoint(self): + return self._base_endpoint + + @property + def endpoint(self): + endpoint = self._endpoint + return endpoint + + @endpoint.setter + def endpoint(self, value): + self._endpoint = value + + @property + def client(self): + return self._client + + # Get a list of active API keys + def get(self): + return self.client.get(self) + + # Create a new API key with name (string) + def post(self, name): + data = {} + self.name = name + data['name'] = self.name + return self.client.post(self, data) + + # Delete a API key + def delete(self, api_key_id): + self.endpoint = self._base_endpoint + "/" + api_key_id + return self.client.delete(self) + + # Update a API key's name + def patch(self, api_key_id, name): + data = {} + self.name = name + data['name'] = self.name + self.endpoint = self._base_endpoint + "/" + api_key_id + return self.client.patch(self, data) \ No newline at end of file diff --git a/sendgrid/version.py b/sendgrid/version.py index 68f6aa791..2f0d506c1 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1,2 +1,2 @@ -version_info = (1, 4, 2) +version_info = (1, 4, 1) __version__ = '.'.join(str(v) for v in version_info) diff --git a/setup.py b/setup.py index 996684ce9..9fbb44d1f 100644 --- a/setup.py +++ b/setup.py @@ -17,8 +17,8 @@ def getRequires(): setup( name='sendgrid', version=str(__version__), - author='SendGrid', - author_email='libraries@sendgrid.com', + author='Yamil Asusta', + author_email='yamil@sendgrid.com', url='https://github.com/sendgrid/sendgrid-python/', packages=find_packages(), license='MIT', diff --git a/test/__init__.py b/test/__init__.py index 4242ca1de..5ee53f7fa 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -5,7 +5,6 @@ import unittest import json import sys -import collections try: from StringIO import StringIO except ImportError: # Python 3 @@ -15,8 +14,71 @@ from sendgrid.exceptions import SendGridClientError, SendGridServerError from sendgrid.sendgrid import HTTPError -SG_USER = os.getenv('SG_USER') or 'SENDGRID_USERNAME' -SG_PWD = os.getenv('SG_PWD') or 'SENDGRID_PASSWORD' +if os.path.exists('.env'): + for line in open('.env'): + var = line.strip().split('=') + if len(var) == 2: + os.environ[var[0]] = var[1] + +SG_USER = os.environ.get('SENDGRID_USERNAME') or 'SENDGRID_USERNAME' +SG_PWD = os.environ.get('SENDGRID_PASSWORD') or 'SENDGRID_PASSWORD' + +# v3 tests +from sendgrid.client import SendGridAPIClient +from sendgrid.version import __version__ + +SG_APIKEY = os.environ.get('SENDGRID_API_KEY') or 'SENDGRID_API_KEY' + +class TestSendGridAPIClient(unittest.TestCase): + def setUp(self): + self.client = SendGridAPIClient(SG_APIKEY) + + def test_apikey_init(self): + self.assertEqual(self.client.apikey, SG_APIKEY) + + def test_useragent(self): + useragent = 'sendgrid/' + __version__ + ';python_v3' + self.assertEqual(self.client.useragent, useragent) + + def test_host(self): + host = 'https://api.sendgrid.com' + self.assertEqual(self.client.host, host) + +class TestAPIKeys(unittest.TestCase): + def setUp(self): + self.client = SendGridAPIClient(SG_APIKEY) + + def test_apikey_post_patch_delete_test(self): + name = "My Amazing API Key of Wonder [PATCH Test]" + status, msg = self.client.apikeys.post(name) + self.assertEqual(status, 201) + msg = json.loads(msg) + api_key_id = msg['api_key_id'] + self.assertEqual(msg['name'], name) + print status + print msg + + name = "My NEW Amazing API Key of Wonder [PATCH TEST]" + status, msg = self.client.apikeys.patch(api_key_id, name) + self.assertEqual(status, 200) + print status + print msg + + status, msg = self.client.apikeys.get() + print status + print msg + + status, msg = self.client.apikeys.delete(api_key_id) + self.assertEqual(status, 204) + print status + + status, msg = self.client.apikeys.get() + print status + print msg + + def test_apikey_get(self): + status, msg = self.client.apikeys.get() + self.assertEqual(status, 200) class TestSendGrid(unittest.TestCase): From 089dd956ed4bf93e61bd1a3d7380a335b7a3c805 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 17 Sep 2015 14:31:42 -0700 Subject: [PATCH 02/28] Fixing the tests --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 369b51adf..a16da7830 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ python: - '3.2' install: - python setup.py install -script: python test/__init__.py +script: unit2 discover notifications: hipchat: rooms: From f5c464c369fd111557d8fdc6216088f2c9c7b2f2 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 17 Sep 2015 14:50:09 -0700 Subject: [PATCH 03/28] Need to mock the apikey test for travisCI --- test/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/__init__.py b/test/__init__.py index 5ee53f7fa..a0e22a284 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -44,6 +44,7 @@ def test_host(self): host = 'https://api.sendgrid.com' self.assertEqual(self.client.host, host) +""" class TestAPIKeys(unittest.TestCase): def setUp(self): self.client = SendGridAPIClient(SG_APIKEY) @@ -79,6 +80,7 @@ def test_apikey_post_patch_delete_test(self): def test_apikey_get(self): status, msg = self.client.apikeys.get() self.assertEqual(status, 200) +""" class TestSendGrid(unittest.TestCase): From 1689fcd5cc1bf2d8b88e990176fe3284ed4fd3c8 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 17 Sep 2015 14:55:32 -0700 Subject: [PATCH 04/28] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a16da7830..b1b226dde 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,4 +15,4 @@ notifications: Build %{build_number} on branch %{branch} by %{author}: %{message} View on GitHub' format: html - notify: true + notify: false From e984750c0a166d6118c33cdd3d69a4b35be84ead Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 17 Sep 2015 15:11:59 -0700 Subject: [PATCH 05/28] Update .travis.yml --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b1b226dde..f5e686116 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: python python: - '2.6' -- '2.7' -- '3.2' +# - '2.7' +# - '3.2' install: - python setup.py install script: unit2 discover From 4da3ea9bd38cea5ca7330dbd53379601b4354927 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Fri, 18 Sep 2015 15:18:37 -0700 Subject: [PATCH 06/28] Cleaning up the tests and adding a Mock object for the urllib_request.urlopen call --- .travis.yml | 2 +- README.rst | 2 +- sendgrid/client.py | 10 +- test/__init__.py | 235 ------------------------------------------- test/test_apikeys.py | 103 +++++++++++++++++++ test/test_mail_v2.py | 165 ++++++++++++++++++++++++++++++ 6 files changed, 275 insertions(+), 242 deletions(-) create mode 100644 test/test_apikeys.py create mode 100644 test/test_mail_v2.py diff --git a/.travis.yml b/.travis.yml index a16da7830..7f283a7c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ python: - '3.2' install: - python setup.py install -script: unit2 discover +script: python -m unittest discover notifications: hipchat: rooms: diff --git a/README.rst b/README.rst index 49e92f5b0..c1e5c77f0 100644 --- a/README.rst +++ b/README.rst @@ -384,7 +384,7 @@ Tests virtualenv venv source venv/bin/activate #or . ./activate.sh python setup.py install - unit2 discover + python -m unittest discover -v Deploying ~~~~~~~~~ diff --git a/sendgrid/client.py b/sendgrid/client.py index 609e5b7ea..08c830ed4 100644 --- a/sendgrid/client.py +++ b/sendgrid/client.py @@ -67,24 +67,24 @@ def _build_request(self, url, json_header=False, method='GET', data=None): except timeout as e: raise SendGridClientError(408, 'Request timeout') body = response.read() - return response, body + return response.getcode(), body def get(self, api): url = self.host + api.base_endpoint response, body = self._build_request(url, False, 'GET') - return response.getcode(), body + return response, body def post(self, api, data): url = self.host + api.endpoint response, body = self._build_request(url, True, 'POST', data) - return response.getcode(), body + return response, body def delete(self, api): url = self.host + api.endpoint response, body = self._build_request(url, False, 'DELETE') - return response.getcode(), body + return response, body def patch(self, api, data): url = self.host + api.endpoint response, body = self._build_request(url, True, 'PATCH', data) - return response.getcode(), body + return response, body diff --git a/test/__init__.py b/test/__init__.py index a0e22a284..8b1378917 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,236 +1 @@ -import os -try: - import unittest2 as unittest -except ImportError: - import unittest -import json -import sys -try: - from StringIO import StringIO -except ImportError: # Python 3 - from io import StringIO -from sendgrid import SendGridClient, Mail -from sendgrid.exceptions import SendGridClientError, SendGridServerError -from sendgrid.sendgrid import HTTPError - -if os.path.exists('.env'): - for line in open('.env'): - var = line.strip().split('=') - if len(var) == 2: - os.environ[var[0]] = var[1] - -SG_USER = os.environ.get('SENDGRID_USERNAME') or 'SENDGRID_USERNAME' -SG_PWD = os.environ.get('SENDGRID_PASSWORD') or 'SENDGRID_PASSWORD' - -# v3 tests -from sendgrid.client import SendGridAPIClient -from sendgrid.version import __version__ - -SG_APIKEY = os.environ.get('SENDGRID_API_KEY') or 'SENDGRID_API_KEY' - -class TestSendGridAPIClient(unittest.TestCase): - def setUp(self): - self.client = SendGridAPIClient(SG_APIKEY) - - def test_apikey_init(self): - self.assertEqual(self.client.apikey, SG_APIKEY) - - def test_useragent(self): - useragent = 'sendgrid/' + __version__ + ';python_v3' - self.assertEqual(self.client.useragent, useragent) - - def test_host(self): - host = 'https://api.sendgrid.com' - self.assertEqual(self.client.host, host) - -""" -class TestAPIKeys(unittest.TestCase): - def setUp(self): - self.client = SendGridAPIClient(SG_APIKEY) - - def test_apikey_post_patch_delete_test(self): - name = "My Amazing API Key of Wonder [PATCH Test]" - status, msg = self.client.apikeys.post(name) - self.assertEqual(status, 201) - msg = json.loads(msg) - api_key_id = msg['api_key_id'] - self.assertEqual(msg['name'], name) - print status - print msg - - name = "My NEW Amazing API Key of Wonder [PATCH TEST]" - status, msg = self.client.apikeys.patch(api_key_id, name) - self.assertEqual(status, 200) - print status - print msg - - status, msg = self.client.apikeys.get() - print status - print msg - - status, msg = self.client.apikeys.delete(api_key_id) - self.assertEqual(status, 204) - print status - - status, msg = self.client.apikeys.get() - print status - print msg - - def test_apikey_get(self): - status, msg = self.client.apikeys.get() - self.assertEqual(status, 200) -""" - -class TestSendGrid(unittest.TestCase): - - def setUp(self): - self.sg = SendGridClient(SG_USER, SG_PWD) - - def test_apikey_init(self): - sg = SendGridClient(SG_PWD) - self.assertEqual(sg.password, SG_PWD) - self.assertIsNone(sg.username) - - @unittest.skipUnless(sys.version_info < (3, 0), 'only for python2') - def test_unicode_recipients(self): - recipients = [unicode('test@test.com'), unicode('guy@man.com')] - m = Mail(to=recipients, - subject='testing', - html='awesome', - from_email='from@test.com') - - mock = {'to[]': ['test@test.com', 'guy@man.com']} - result = self.sg._build_body(m) - - self.assertEqual(result['to[]'], mock['to[]']) - - def test_send(self): - m = Mail() - m.add_to('John, Doe ') - m.set_subject('test') - m.set_html('WIN') - m.set_text('WIN') - m.set_from('doe@email.com') - m.set_asm_group_id(42) - m.add_cc('cc@email.com') - m.add_bcc('bcc@email.com') - m.add_substitution('subKey', 'subValue') - m.add_section('testSection', 'sectionValue') - m.add_category('testCategory') - m.add_unique_arg('testUnique', 'uniqueValue') - m.add_filter('testFilter', 'filter', 'filterValue') - m.add_attachment_stream('testFile', 'fileValue') - url = self.sg._build_body(m) - url.pop('api_key', None) - url.pop('api_user', None) - url.pop('date', None) - test_url = json.loads(''' - { - "to[]": ["john@email.com"], - "toname[]": ["John Doe"], - "html": "WIN", - "text": "WIN", - "subject": "test", - "files[testFile]": "fileValue", - "from": "doe@email.com", - "cc[]": ["cc@email.com"], - "bcc[]": ["bcc@email.com"] - } - ''') - test_url['x-smtpapi'] = json.dumps(json.loads(''' - { - "sub": { - "subKey": ["subValue"] - }, - "section": { - "testSection":"sectionValue" - }, - "category": ["testCategory"], - "unique_args": { - "testUnique":"uniqueValue" - }, - "filters": { - "testFilter": { - "settings": { - "filter": "filterValue" - } - } - }, - "asm_group_id": 42 - } - ''')) - - self.assertEqual(url, test_url) - - @unittest.skipUnless(sys.version_info < (3, 0), 'only for python2') - def test__build_body_unicode(self): - """test _build_body() handles encoded unicode outside ascii range""" - from_email = '\xd0\x9d\xd0\xb8\xd0\xba\xd0\xb0@email.com' - from_name = '\xd0\x9a\xd0\xbb\xd0\xb0\xd0\xb2\xd0\xb4\xd0\xb8\xd1\x8f' - subject = '\xd0\x9d\xd0\xb0\xd0\xb4\xd0\xb5\xd0\xb6\xd0\xb4\xd0\xb0' - text = '\xd0\x9d\xd0\xb0\xd0\xb4\xd0\xb5\xd0\xb6\xd0\xb4\xd0\xb0' - html = '\xd0\x9d\xd0\xb0\xd0\xb4\xd0\xb5\xd0\xb6\xd0\xb4\xd0\xb0' - m = Mail() - m.add_to('John, Doe ') - m.set_subject(subject) - m.set_html(html) - m.set_text(text) - m.set_from("%s <%s>" % (from_name, from_email)) - url = self.sg._build_body(m) - self.assertEqual(from_email, url['from']) - self.assertEqual(from_name, url['fromname']) - self.assertEqual(subject, url['subject']) - self.assertEqual(text, url['text']) - self.assertEqual(html, url['html']) - - - def test_smtpapi_add_to(self): - '''Test that message.to gets a dummy address for the header to work''' - m = Mail() - m.smtpapi.add_to('test@email.com') - m.set_from('jon@doe.com') - m.set_subject('test') - url = self.sg._build_body(m) - url.pop('api_key', None) - url.pop('api_user', None) - url.pop('date', None) - test_url = json.loads(''' - { - "to[]": ["jon@doe.com"], - "subject": "test", - "from": "jon@doe.com" - } - ''') - test_url['x-smtpapi'] = json.dumps(json.loads(''' - { - "to": ["test@email.com"] - } - ''')) - self.assertEqual(url, test_url) - - - -class SendGridClientUnderTest(SendGridClient): - - def _make_request(self, message): - raise self.error - - -class TestSendGridErrorHandling(unittest.TestCase): - def setUp(self): - self.sg = SendGridClientUnderTest(SG_USER, SG_PWD, raise_errors=True) - - def test_client_raises_clinet_error_in_case_of_4xx(self): - self.sg.error = HTTPError('url', 403, 'msg', {}, StringIO('body')) - with self.assertRaises(SendGridClientError): - self.sg.send(Mail()) - - def test_client_raises_clinet_error_in_case_of_5xx(self): - self.sg.error = HTTPError('url', 503, 'msg', {}, StringIO('body')) - with self.assertRaises(SendGridServerError): - self.sg.send(Mail()) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/test_apikeys.py b/test/test_apikeys.py new file mode 100644 index 000000000..4b643e09b --- /dev/null +++ b/test/test_apikeys.py @@ -0,0 +1,103 @@ +import os +try: + import unittest2 as unittest +except ImportError: + import unittest +import json +import sys +try: + from StringIO import StringIO +except ImportError: # Python 3 + from io import StringIO +import logging + +import sendgrid +from sendgrid import SendGridClient, Mail +from sendgrid.exceptions import SendGridClientError, SendGridServerError +from sendgrid.sendgrid import HTTPError +from sendgrid.client import SendGridAPIClient +from sendgrid.version import __version__ + +SG_KEY = os.getenv('SG_KEY') or 'SENDGRID_APIKEY' + +class MockSendGridAPIClientRequest(SendGridAPIClient): + def _build_request(self, url=None, json_header=False, method='GET', data=None): + response = 200 + body = {"message": "success"} + return response, body + +class TestSendGridAPIClient(unittest.TestCase): + def setUp(self): + self.client = MockSendGridAPIClientRequest + self.client = SendGridAPIClient(SG_KEY) + + def test_apikey_init(self): + self.assertEqual(self.client.apikey, SG_KEY) + + def test_useragent(self): + useragent = 'sendgrid/' + __version__ + ';python_v3' + self.assertEqual(self.client.useragent, useragent) + + def test_host(self): + host = 'https://api.sendgrid.com' + self.assertEqual(self.client.host, host) + +class TestAPIKeys(unittest.TestCase): + def setUp(self): + SendGridAPIClient = MockSendGridAPIClientRequest + self.client = SendGridAPIClient(SG_KEY) + print self.client._build_request(self.client) + + def test_apikeys_init(self): + self.apikeys = self.client.apikeys + self.assertEqual(self.apikeys.name, None) + self.assertEqual(self.apikeys.base_endpoint, "/v3/api_keys") + self.assertEqual(self.apikeys.endpoint, "/v3/api_keys") + self.assertEqual(self.apikeys.client, self.client) + + def test_apikeys_post(self): + name = "My Amazing API Key of Wonder [PATCH Test]" + status, msg = self.client.apikeys.post(name) + # self.assertEqual(status, 201) + # msg = json.loads(msg) + # api_key_id = msg['api_key_id'] + # self.assertEqual(msg['name'], name) + # print status + # print msg + +""" + def test_apikey_post_patch_delete_test(self): + name = "My Amazing API Key of Wonder [PATCH Test]" + status, msg = self.client.apikeys.post(name) + self.assertEqual(status, 201) + msg = json.loads(msg) + api_key_id = msg['api_key_id'] + self.assertEqual(msg['name'], name) + print status + print msg + + name = "My NEW Amazing API Key of Wonder [PATCH TEST]" + status, msg = self.client.apikeys.patch(api_key_id, name) + self.assertEqual(status, 200) + print status + print msg + + status, msg = self.client.apikeys.get() + print status + print msg + + status, msg = self.client.apikeys.delete(api_key_id) + self.assertEqual(status, 204) + print status + + status, msg = self.client.apikeys.get() + print status + print msg + + def test_apikey_get(self): + status, msg = self.client.apikeys.get() + self.assertEqual(status, 200) +""" + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/test/test_mail_v2.py b/test/test_mail_v2.py new file mode 100644 index 000000000..7038d3663 --- /dev/null +++ b/test/test_mail_v2.py @@ -0,0 +1,165 @@ +import os +try: + import unittest2 as unittest +except ImportError: + import unittest +import json +import sys +import collections +try: + from StringIO import StringIO +except ImportError: # Python 3 + from io import StringIO + +from sendgrid import SendGridClient, Mail +from sendgrid.exceptions import SendGridClientError, SendGridServerError +from sendgrid.sendgrid import HTTPError + +SG_USER = os.getenv('SG_USER') or 'SENDGRID_USERNAME' +SG_PWD = os.getenv('SG_PWD') or 'SENDGRID_PASSWORD' + +class TestSendGrid(unittest.TestCase): + + def setUp(self): + self.sg = SendGridClient(SG_USER, SG_PWD) + + def test_apikey_init(self): + sg = SendGridClient(SG_PWD) + self.assertEqual(sg.password, SG_PWD) + self.assertIsNone(sg.username) + + @unittest.skipUnless(sys.version_info < (3, 0), 'only for python2') + def test_unicode_recipients(self): + recipients = [unicode('test@test.com'), unicode('guy@man.com')] + m = Mail(to=recipients, + subject='testing', + html='awesome', + from_email='from@test.com') + + mock = {'to[]': ['test@test.com', 'guy@man.com']} + result = self.sg._build_body(m) + + self.assertEqual(result['to[]'], mock['to[]']) + + def test_send(self): + m = Mail() + m.add_to('John, Doe ') + m.set_subject('test') + m.set_html('WIN') + m.set_text('WIN') + m.set_from('doe@email.com') + m.set_asm_group_id(42) + m.add_cc('cc@email.com') + m.add_bcc('bcc@email.com') + m.add_substitution('subKey', 'subValue') + m.add_section('testSection', 'sectionValue') + m.add_category('testCategory') + m.add_unique_arg('testUnique', 'uniqueValue') + m.add_filter('testFilter', 'filter', 'filterValue') + m.add_attachment_stream('testFile', 'fileValue') + url = self.sg._build_body(m) + url.pop('api_key', None) + url.pop('api_user', None) + url.pop('date', None) + test_url = json.loads(''' + { + "to[]": ["john@email.com"], + "toname[]": ["John Doe"], + "html": "WIN", + "text": "WIN", + "subject": "test", + "files[testFile]": "fileValue", + "from": "doe@email.com", + "cc[]": ["cc@email.com"], + "bcc[]": ["bcc@email.com"] + } + ''') + test_url['x-smtpapi'] = json.dumps(json.loads(''' + { + "sub": { + "subKey": ["subValue"] + }, + "section": { + "testSection":"sectionValue" + }, + "category": ["testCategory"], + "unique_args": { + "testUnique":"uniqueValue" + }, + "filters": { + "testFilter": { + "settings": { + "filter": "filterValue" + } + } + }, + "asm_group_id": 42 + } + ''')) + + self.assertEqual(url, test_url) + + @unittest.skipUnless(sys.version_info < (3, 0), 'only for python2') + def test__build_body_unicode(self): + """test _build_body() handles encoded unicode outside ascii range""" + from_email = '\xd0\x9d\xd0\xb8\xd0\xba\xd0\xb0@email.com' + from_name = '\xd0\x9a\xd0\xbb\xd0\xb0\xd0\xb2\xd0\xb4\xd0\xb8\xd1\x8f' + subject = '\xd0\x9d\xd0\xb0\xd0\xb4\xd0\xb5\xd0\xb6\xd0\xb4\xd0\xb0' + text = '\xd0\x9d\xd0\xb0\xd0\xb4\xd0\xb5\xd0\xb6\xd0\xb4\xd0\xb0' + html = '\xd0\x9d\xd0\xb0\xd0\xb4\xd0\xb5\xd0\xb6\xd0\xb4\xd0\xb0' + m = Mail() + m.add_to('John, Doe ') + m.set_subject(subject) + m.set_html(html) + m.set_text(text) + m.set_from("%s <%s>" % (from_name, from_email)) + url = self.sg._build_body(m) + self.assertEqual(from_email, url['from']) + self.assertEqual(from_name, url['fromname']) + self.assertEqual(subject, url['subject']) + self.assertEqual(text, url['text']) + self.assertEqual(html, url['html']) + + + def test_smtpapi_add_to(self): + '''Test that message.to gets a dummy address for the header to work''' + m = Mail() + m.smtpapi.add_to('test@email.com') + m.set_from('jon@doe.com') + m.set_subject('test') + url = self.sg._build_body(m) + url.pop('api_key', None) + url.pop('api_user', None) + url.pop('date', None) + test_url = json.loads(''' + { + "to[]": ["jon@doe.com"], + "subject": "test", + "from": "jon@doe.com" + } + ''') + test_url['x-smtpapi'] = json.dumps(json.loads(''' + { + "to": ["test@email.com"] + } + ''')) + self.assertEqual(url, test_url) + +class SendGridClientUnderTest(SendGridClient): + + def _make_request(self, message): + raise self.error + +class TestSendGridErrorHandling(unittest.TestCase): + def setUp(self): + self.sg = SendGridClientUnderTest(SG_USER, SG_PWD, raise_errors=True) + + def test_client_raises_clinet_error_in_case_of_4xx(self): + self.sg.error = HTTPError('url', 403, 'msg', {}, StringIO('body')) + with self.assertRaises(SendGridClientError): + self.sg.send(Mail()) + + def test_client_raises_clinet_error_in_case_of_5xx(self): + self.sg.error = HTTPError('url', 503, 'msg', {}, StringIO('body')) + with self.assertRaises(SendGridServerError): + self.sg.send(Mail()) \ No newline at end of file From bcd2d97cd97e9c6414461d216f8bbc3accb4a263 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Fri, 18 Sep 2015 15:20:38 -0700 Subject: [PATCH 07/28] Cleaning up the tests and adding a Mock object for the urllib_request.urlopen call --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4cb4133e8..284979c21 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: python python: - '2.6' -# - '2.7' -# - '3.2' +- '2.7' +- '3.2' install: - python setup.py install script: python -m unittest discover From 67340ec7b9091c8b61990ec9a48895a6e840940c Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Fri, 18 Sep 2015 15:28:33 -0700 Subject: [PATCH 08/28] Cleaning up the tests and adding a Mock object for the urllib_request.urlopen call --- sendgrid/client.py | 2 +- test/test_apikeys.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/sendgrid/client.py b/sendgrid/client.py index 08c830ed4..ce9d4c87d 100644 --- a/sendgrid/client.py +++ b/sendgrid/client.py @@ -11,7 +11,7 @@ from urllib import urlencode from .exceptions import SendGridClientError, SendGridServerError -from resources.apikeys import APIKeys +from .resources.apikeys import APIKeys class SendGridAPIClient(object): diff --git a/test/test_apikeys.py b/test/test_apikeys.py index 4b643e09b..b5c6899d8 100644 --- a/test/test_apikeys.py +++ b/test/test_apikeys.py @@ -46,7 +46,6 @@ class TestAPIKeys(unittest.TestCase): def setUp(self): SendGridAPIClient = MockSendGridAPIClientRequest self.client = SendGridAPIClient(SG_KEY) - print self.client._build_request(self.client) def test_apikeys_init(self): self.apikeys = self.client.apikeys From cbb5c056d52dc9365b55244eabd354586c308158 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Fri, 18 Sep 2015 16:09:28 -0700 Subject: [PATCH 09/28] Fixing test to account for version 2.6 --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 284979c21..4bb6c9001 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,9 @@ python: - '3.2' install: - python setup.py install -script: python -m unittest discover +script: +- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then unit2 discover; else python -m unittest discover; fi + notifications: hipchat: rooms: From 6f85a54ab4c71a7ace8f50e1fa09d2cfb8d64d70 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Fri, 18 Sep 2015 16:50:20 -0700 Subject: [PATCH 10/28] Fleshing out the mock object --- test/test_apikeys.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/test/test_apikeys.py b/test/test_apikeys.py index b5c6899d8..a91a9964b 100644 --- a/test/test_apikeys.py +++ b/test/test_apikeys.py @@ -9,7 +9,14 @@ from StringIO import StringIO except ImportError: # Python 3 from io import StringIO -import logging +try: + import urllib.request as urllib_request + from urllib.parse import urlencode + from urllib.error import HTTPError +except ImportError: # Python 2 + import urllib2 as urllib_request + from urllib2 import HTTPError + from urllib import urlencode import sendgrid from sendgrid import SendGridClient, Mail @@ -21,9 +28,22 @@ SG_KEY = os.getenv('SG_KEY') or 'SENDGRID_APIKEY' class MockSendGridAPIClientRequest(SendGridAPIClient): + def __init__(self, apikey, **opts): + super(MockSendGridAPIClientRequest, self).__init__(apikey, **opts) + self._req = None + def _build_request(self, url=None, json_header=False, method='GET', data=None): + req = urllib_request.Request(url) + req.get_method = lambda: method + req.add_header('User-Agent', self.useragent) + req.add_header('Authorization', 'Bearer ' + self.apikey) + if json_header: + req.add_header('Content-Type', 'application/json') + print "url= " + req._Request__original + print "headers= " + str(req.headers) + print "data= " + json.dumps(data) response = 200 - body = {"message": "success"} + body = {"mock": "success"} return response, body class TestSendGridAPIClient(unittest.TestCase): From 8fdebd4324657150fc816ea046d378703e706666 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Fri, 18 Sep 2015 20:05:56 -0700 Subject: [PATCH 11/28] Fix Python 3 test --- test/test_apikeys.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_apikeys.py b/test/test_apikeys.py index a91a9964b..64c9532b0 100644 --- a/test/test_apikeys.py +++ b/test/test_apikeys.py @@ -39,9 +39,9 @@ def _build_request(self, url=None, json_header=False, method='GET', data=None): req.add_header('Authorization', 'Bearer ' + self.apikey) if json_header: req.add_header('Content-Type', 'application/json') - print "url= " + req._Request__original - print "headers= " + str(req.headers) - print "data= " + json.dumps(data) + # print "url= " + req._Request__original + # print "headers= " + str(req.headers) + # print "data= " + json.dumps(data) response = 200 body = {"mock": "success"} return response, body From 38adab2ed261d7e3f6b799e5fc34633ffa05e154 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Mon, 21 Sep 2015 09:49:01 -0700 Subject: [PATCH 12/28] First version of mocked tests complete --- sendgrid/resources/apikeys.py | 2 +- test/test_apikeys.py | 54 +++++++++++------------------------ 2 files changed, 18 insertions(+), 38 deletions(-) diff --git a/sendgrid/resources/apikeys.py b/sendgrid/resources/apikeys.py index 91f324880..e5de418b8 100644 --- a/sendgrid/resources/apikeys.py +++ b/sendgrid/resources/apikeys.py @@ -2,7 +2,7 @@ class APIKeys(object): """The API Keys feature allows customers to be able to generate an API Key credential which can be used for authentication with the SendGrid v3 Web API or the Mail API Endpoint""" - def __init__(self, client , **opts): + def __init__(self, client, **opts): """ Constructs SendGrid APIKeys object. diff --git a/test/test_apikeys.py b/test/test_apikeys.py index 64c9532b0..06b6f2a7f 100644 --- a/test/test_apikeys.py +++ b/test/test_apikeys.py @@ -39,11 +39,15 @@ def _build_request(self, url=None, json_header=False, method='GET', data=None): req.add_header('Authorization', 'Bearer ' + self.apikey) if json_header: req.add_header('Content-Type', 'application/json') - # print "url= " + req._Request__original - # print "headers= " + str(req.headers) - # print "data= " + json.dumps(data) - response = 200 - body = {"mock": "success"} + body = data + if method == 'POST': + response = 201 + if method == 'PATCH': + response = 200 + if method == 'DELETE': + response = 204 + if method == 'GET': + response = 200 return response, body class TestSendGridAPIClient(unittest.TestCase): @@ -75,48 +79,24 @@ def test_apikeys_init(self): self.assertEqual(self.apikeys.client, self.client) def test_apikeys_post(self): - name = "My Amazing API Key of Wonder [PATCH Test]" - status, msg = self.client.apikeys.post(name) - # self.assertEqual(status, 201) - # msg = json.loads(msg) - # api_key_id = msg['api_key_id'] - # self.assertEqual(msg['name'], name) - # print status - # print msg - -""" - def test_apikey_post_patch_delete_test(self): name = "My Amazing API Key of Wonder [PATCH Test]" status, msg = self.client.apikeys.post(name) self.assertEqual(status, 201) - msg = json.loads(msg) - api_key_id = msg['api_key_id'] self.assertEqual(msg['name'], name) - print status - print msg - + + def test_apikeys_patch(self): name = "My NEW Amazing API Key of Wonder [PATCH TEST]" - status, msg = self.client.apikeys.patch(api_key_id, name) + status, msg = self.client.apikeys.patch(SG_KEY, name) self.assertEqual(status, 200) - print status - print msg - - status, msg = self.client.apikeys.get() - print status - print msg - - status, msg = self.client.apikeys.delete(api_key_id) + self.assertEqual(msg['name'], name) + + def test_apikeys_delete(self): + status, msg = self.client.apikeys.delete(SG_KEY) self.assertEqual(status, 204) - print status - - status, msg = self.client.apikeys.get() - print status - print msg - def test_apikey_get(self): + def test_apikeys_get(self): status, msg = self.client.apikeys.get() self.assertEqual(status, 200) -""" if __name__ == '__main__': unittest.main() \ No newline at end of file From b1a07da87af92be6ebeaf2b6e0dab24208f28023 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Mon, 21 Sep 2015 22:00:25 -0700 Subject: [PATCH 13/28] Added tox for testing multiple Python versions locally. Updating README shortly. --- .gitignore | 4 +++- tox.ini | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 2da8e29d8..c5403fbd0 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ sdist *.pyc venv/ .idea -.env \ No newline at end of file +.env +.python-version +.tox/ \ No newline at end of file diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..94a890c6b --- /dev/null +++ b/tox.ini @@ -0,0 +1,26 @@ +# Tox (http://tox.testrun.org/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = py26, py27, py32 + +[testenv] +commands = {envbindir}/python -m unittest discover [] +deps = + +[testenv:py26] +commands = {envbindir}/unit2 discover [] +deps = unittest2 +basepython = python2.6 + +[testenv:py27] +commands = {envbindir}/python -m unittest discover [] +deps = +basepython = python2.7 + +[testenv:py32] +commands = {envbindir}/python -m unittest discover [] +deps = +basepython = python3.2 \ No newline at end of file From d033e41f2a5c403e04aa99c289093bd8ed3a0b02 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 22 Sep 2015 07:59:35 -0700 Subject: [PATCH 14/28] Updated test instructions --- README.rst | 25 +++++++++++++++++++------ tox.ini | 8 ++++---- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index c1e5c77f0..a77e49daa 100644 --- a/README.rst +++ b/README.rst @@ -379,12 +379,25 @@ Using Templates from the Template Engine Tests ~~~~~ -.. code:: python - - virtualenv venv - source venv/bin/activate #or . ./activate.sh - python setup.py install - python -m unittest discover -v +Prerequisites: + +Mac OS X Prerequisite: `xcode-select --install` +`brew update` +`brew install pyenv` +`pip install tox` +Add `eval "$(pyenv init -)"` to your profile after installing tox, you only need to do this once. +`pyenv install 2.6.9` +`pyenv install 2.7.8` +`pyenv install 3.2.6` +`pyenv local 3.2.6 2.7.8 2.6.9` +`pyenv rehash` +`virtualenv venv` +`source venv/bin/activate #or . ./activate.sh` +`python setup.py install` + +Run the tests: + +`tox` Deploying ~~~~~~~~~ diff --git a/tox.ini b/tox.ini index 94a890c6b..a8b9b53ad 100644 --- a/tox.ini +++ b/tox.ini @@ -7,20 +7,20 @@ envlist = py26, py27, py32 [testenv] -commands = {envbindir}/python -m unittest discover [] +commands = {envbindir}/python -m unittest discover -v [] deps = [testenv:py26] -commands = {envbindir}/unit2 discover [] +commands = {envbindir}/unit2 discover -v [] deps = unittest2 basepython = python2.6 [testenv:py27] -commands = {envbindir}/python -m unittest discover [] +commands = {envbindir}/python -m unittest discover -v [] deps = basepython = python2.7 [testenv:py32] -commands = {envbindir}/python -m unittest discover [] +commands = {envbindir}/python -m unittest discover -v [] deps = basepython = python3.2 \ No newline at end of file From 549668857192f840fbd154c3f7b7ee7a6bf6c802 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 22 Sep 2015 08:03:50 -0700 Subject: [PATCH 15/28] Fixed formatting of README --- README.rst | 55 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/README.rst b/README.rst index a77e49daa..825d6276a 100644 --- a/README.rst +++ b/README.rst @@ -371,7 +371,7 @@ set_asm_group_id Using Templates from the Template Engine ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. code:: python + message.add_filter('templates', 'enable', '1') message.add_filter('templates', 'template_id', 'TEMPLATE-ALPHA-NUMERIC-ID') @@ -379,25 +379,40 @@ Using Templates from the Template Engine Tests ~~~~~ -Prerequisites: - -Mac OS X Prerequisite: `xcode-select --install` -`brew update` -`brew install pyenv` -`pip install tox` -Add `eval "$(pyenv init -)"` to your profile after installing tox, you only need to do this once. -`pyenv install 2.6.9` -`pyenv install 2.7.8` -`pyenv install 3.2.6` -`pyenv local 3.2.6 2.7.8 2.6.9` -`pyenv rehash` -`virtualenv venv` -`source venv/bin/activate #or . ./activate.sh` -`python setup.py install` - -Run the tests: - -`tox` +**Prerequisites:** + +- Mac OS X Prerequisite: + +.. code:: python + + xcode-select --install + +- Install pyenv and tox + +.. code:: python + + brew update + brew install pyenv + pip install tox + +- Add `eval "$(pyenv init -)"` to your profile after installing tox, you only need to do this once. + +.. code:: python + + pyenv install 2.6.9 + pyenv install 2.7.8 + pyenv install 3.2.6 + pyenv local 3.2.6 2.7.8 2.6.9 + pyenv rehash + virtualenv venv + source venv/bin/activate #or . ./activate.sh + python setup.py install + +**Run the tests:** + +.. code:: python + + tox Deploying ~~~~~~~~~ From 70f28550ea4beeb1711ecf8aa1996878e6d13293 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 22 Sep 2015 16:45:05 -0700 Subject: [PATCH 16/28] Adding ASM Groups endpoint --- README.rst | 11 ++--- example_v3_test.py | 7 ++- sendgrid/client.py | 2 + sendgrid/resources/asm_groups.py | 40 +++++++++++++++ test/test_asm_groups.py | 85 ++++++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 sendgrid/resources/asm_groups.py create mode 100644 test/test_asm_groups.py diff --git a/README.rst b/README.rst index 825d6276a..a0e9477ff 100644 --- a/README.rst +++ b/README.rst @@ -402,16 +402,15 @@ Tests pyenv install 2.6.9 pyenv install 2.7.8 pyenv install 3.2.6 - pyenv local 3.2.6 2.7.8 2.6.9 - pyenv rehash - virtualenv venv - source venv/bin/activate #or . ./activate.sh - python setup.py install **Run the tests:** .. code:: python - + virtualenv venv + source venv/bin/activate #or . ./activate.sh + python setup.py install + pyenv local 3.2.6 2.7.8 2.6.9 + pyenv rehash tox Deploying diff --git a/example_v3_test.py b/example_v3_test.py index bef7a5f4c..c6163a171 100755 --- a/example_v3_test.py +++ b/example_v3_test.py @@ -10,6 +10,12 @@ client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) +status, msg = client.asm_groups.get() +print status +print msg + +""" + name = "My Amazing API Key" status, msg = client.apikeys.post(name) msg = json.loads(msg) @@ -29,7 +35,6 @@ print status print msg -""" # Get a list of all valid API Keys from your account status, msg = client.apikeys.get() print status diff --git a/sendgrid/client.py b/sendgrid/client.py index ce9d4c87d..123e6ec21 100644 --- a/sendgrid/client.py +++ b/sendgrid/client.py @@ -12,6 +12,7 @@ from .exceptions import SendGridClientError, SendGridServerError from .resources.apikeys import APIKeys +from .resources.asm_groups import ASMGroups class SendGridAPIClient(object): @@ -32,6 +33,7 @@ def __init__(self, apikey, **opts): self.proxies = opts.get('proxies', None) self.apikeys = APIKeys(self) + self.asm_groups = ASMGroups(self) @property def apikey(self): diff --git a/sendgrid/resources/asm_groups.py b/sendgrid/resources/asm_groups.py new file mode 100644 index 000000000..c32f7a25a --- /dev/null +++ b/sendgrid/resources/asm_groups.py @@ -0,0 +1,40 @@ +class ASMGroups(object): + """Advanced Suppression Manager gives your recipients more control over the types of emails they want to receive + by letting them opt out of messages from a certain type of email. + + Groups are specific types of email you would like your recipients to be able to unsubscribe from or subscribe to. + For example: Daily Newsletters, Invoices, System Alerts. + """ + + def __init__(self, client, **opts): + """ + Constructs SendGrid ASM object. + + See https://sendgrid.com/docs/API_Reference/Web_API_v3/Advanced_Suppression_Manager/index.html and + https://sendgrid.com/docs/API_Reference/Web_API_v3/Advanced_Suppression_Manager/groups.html + """ + self._name = None + self._base_endpoint = "/v3/asm/groups" + self._endpoint = "/v3/asm/groups" + self._client = client + + @property + def base_endpoint(self): + return self._base_endpoint + + @property + def endpoint(self): + endpoint = self._endpoint + return endpoint + + @endpoint.setter + def endpoint(self, value): + self._endpoint = value + + @property + def client(self): + return self._client + + # Retrieve all suppression groups associated with the user. + def get(self): + return self.client.get(self) \ No newline at end of file diff --git a/test/test_asm_groups.py b/test/test_asm_groups.py new file mode 100644 index 000000000..da43fc5b0 --- /dev/null +++ b/test/test_asm_groups.py @@ -0,0 +1,85 @@ +import os +try: + import unittest2 as unittest +except ImportError: + import unittest +import json +import sys +try: + from StringIO import StringIO +except ImportError: # Python 3 + from io import StringIO +try: + import urllib.request as urllib_request + from urllib.parse import urlencode + from urllib.error import HTTPError +except ImportError: # Python 2 + import urllib2 as urllib_request + from urllib2 import HTTPError + from urllib import urlencode + +import sendgrid +from sendgrid import SendGridClient, Mail +from sendgrid.exceptions import SendGridClientError, SendGridServerError +from sendgrid.sendgrid import HTTPError +from sendgrid.client import SendGridAPIClient +from sendgrid.version import __version__ + +SG_KEY = os.getenv('SG_KEY') or 'SENDGRID_APIKEY' + +class MockSendGridAPIClientRequest(SendGridAPIClient): + def __init__(self, apikey, **opts): + super(MockSendGridAPIClientRequest, self).__init__(apikey, **opts) + self._req = None + + def _build_request(self, url=None, json_header=False, method='GET', data=None): + req = urllib_request.Request(url) + req.get_method = lambda: method + req.add_header('User-Agent', self.useragent) + req.add_header('Authorization', 'Bearer ' + self.apikey) + if json_header: + req.add_header('Content-Type', 'application/json') + body = data + if method == 'POST': + response = 201 + if method == 'PATCH': + response = 200 + if method == 'DELETE': + response = 204 + if method == 'GET': + response = 200 + return response, body + +class TestSendGridAPIClient(unittest.TestCase): + def setUp(self): + self.client = MockSendGridAPIClientRequest + self.client = SendGridAPIClient(SG_KEY) + + def test_apikey_init(self): + self.assertEqual(self.client.apikey, SG_KEY) + + def test_useragent(self): + useragent = 'sendgrid/' + __version__ + ';python_v3' + self.assertEqual(self.client.useragent, useragent) + + def test_host(self): + host = 'https://api.sendgrid.com' + self.assertEqual(self.client.host, host) + +class TestASMGroups(unittest.TestCase): + def setUp(self): + SendGridAPIClient = MockSendGridAPIClientRequest + self.client = SendGridAPIClient(SG_KEY) + + def test_apikeys_init(self): + self.asm_groups = self.client.asm_groups + self.assertEqual(self.asm_groups.base_endpoint, "/v3/asm/groups") + self.assertEqual(self.asm_groups.endpoint, "/v3/asm/groups") + self.assertEqual(self.asm_groups.client, self.client) + + def test_asm_groups_get(self): + status, msg = self.client.apikeys.get() + self.assertEqual(status, 200) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 8298dcb97fec6c6e0039175682f971d1c85a4028 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 22 Sep 2015 16:47:55 -0700 Subject: [PATCH 17/28] README format fix --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index a0e9477ff..756258551 100644 --- a/README.rst +++ b/README.rst @@ -406,6 +406,7 @@ Tests **Run the tests:** .. code:: python + virtualenv venv source venv/bin/activate #or . ./activate.sh python setup.py install From e180847b79fc87e32586e808cfbe2e33801497cf Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 23 Sep 2015 08:36:44 -0700 Subject: [PATCH 18/28] Cleaned up tests, verified to pass on all supported Python versions with tox --- sendgrid/resources/__init__.py | 1 - test/__init__.py | 1 - test/base_test.py | 37 ++++++++++++++++++++++++ test/test_api_client.py | 35 ++++++++++++++++++++++ test/test_apikeys.py | 53 +--------------------------------- test/test_asm_groups.py | 53 +--------------------------------- 6 files changed, 74 insertions(+), 106 deletions(-) create mode 100644 test/base_test.py create mode 100644 test/test_api_client.py diff --git a/sendgrid/resources/__init__.py b/sendgrid/resources/__init__.py index 8b1378917..e69de29bb 100644 --- a/sendgrid/resources/__init__.py +++ b/sendgrid/resources/__init__.py @@ -1 +0,0 @@ - diff --git a/test/__init__.py b/test/__init__.py index 8b1378917..e69de29bb 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1 +0,0 @@ - diff --git a/test/base_test.py b/test/base_test.py new file mode 100644 index 000000000..6e0979998 --- /dev/null +++ b/test/base_test.py @@ -0,0 +1,37 @@ +import sendgrid +from sendgrid.client import SendGridAPIClient +try: + import urllib.request as urllib_request + from urllib.parse import urlencode + from urllib.error import HTTPError +except ImportError: # Python 2 + import urllib2 as urllib_request + from urllib2 import HTTPError + from urllib import urlencode + +class BaseTest(): + def __init__(self): + pass + +class MockSendGridAPIClientRequest(SendGridAPIClient): + def __init__(self, apikey, **opts): + super(MockSendGridAPIClientRequest, self).__init__(apikey, **opts) + self._req = None + + def _build_request(self, url=None, json_header=False, method='GET', data=None): + req = urllib_request.Request(url) + req.get_method = lambda: method + req.add_header('User-Agent', self.useragent) + req.add_header('Authorization', 'Bearer ' + self.apikey) + if json_header: + req.add_header('Content-Type', 'application/json') + body = data + if method == 'POST': + response = 201 + if method == 'PATCH': + response = 200 + if method == 'DELETE': + response = 204 + if method == 'GET': + response = 200 + return response, body \ No newline at end of file diff --git a/test/test_api_client.py b/test/test_api_client.py new file mode 100644 index 000000000..4e1486671 --- /dev/null +++ b/test/test_api_client.py @@ -0,0 +1,35 @@ +from .base_test import BaseTest, MockSendGridAPIClientRequest +import os +try: + import unittest2 as unittest +except ImportError: + import unittest +try: + from StringIO import StringIO +except ImportError: # Python 3 + from io import StringIO + +import sendgrid +from sendgrid.client import SendGridAPIClient +from sendgrid.version import __version__ + +SG_KEY = os.getenv('SG_KEY') or 'SENDGRID_APIKEY' + +class TestSendGridAPIClient(unittest.TestCase): + def setUp(self): + self.client = MockSendGridAPIClientRequest + self.client = SendGridAPIClient(SG_KEY) + + def test_apikey_init(self): + self.assertEqual(self.client.apikey, SG_KEY) + + def test_useragent(self): + useragent = 'sendgrid/' + __version__ + ';python_v3' + self.assertEqual(self.client.useragent, useragent) + + def test_host(self): + host = 'https://api.sendgrid.com' + self.assertEqual(self.client.host, host) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/test/test_apikeys.py b/test/test_apikeys.py index 06b6f2a7f..c7a0cc96f 100644 --- a/test/test_apikeys.py +++ b/test/test_apikeys.py @@ -1,71 +1,20 @@ +from .base_test import BaseTest, MockSendGridAPIClientRequest import os try: import unittest2 as unittest except ImportError: import unittest -import json -import sys try: from StringIO import StringIO except ImportError: # Python 3 from io import StringIO -try: - import urllib.request as urllib_request - from urllib.parse import urlencode - from urllib.error import HTTPError -except ImportError: # Python 2 - import urllib2 as urllib_request - from urllib2 import HTTPError - from urllib import urlencode import sendgrid -from sendgrid import SendGridClient, Mail -from sendgrid.exceptions import SendGridClientError, SendGridServerError -from sendgrid.sendgrid import HTTPError from sendgrid.client import SendGridAPIClient from sendgrid.version import __version__ SG_KEY = os.getenv('SG_KEY') or 'SENDGRID_APIKEY' -class MockSendGridAPIClientRequest(SendGridAPIClient): - def __init__(self, apikey, **opts): - super(MockSendGridAPIClientRequest, self).__init__(apikey, **opts) - self._req = None - - def _build_request(self, url=None, json_header=False, method='GET', data=None): - req = urllib_request.Request(url) - req.get_method = lambda: method - req.add_header('User-Agent', self.useragent) - req.add_header('Authorization', 'Bearer ' + self.apikey) - if json_header: - req.add_header('Content-Type', 'application/json') - body = data - if method == 'POST': - response = 201 - if method == 'PATCH': - response = 200 - if method == 'DELETE': - response = 204 - if method == 'GET': - response = 200 - return response, body - -class TestSendGridAPIClient(unittest.TestCase): - def setUp(self): - self.client = MockSendGridAPIClientRequest - self.client = SendGridAPIClient(SG_KEY) - - def test_apikey_init(self): - self.assertEqual(self.client.apikey, SG_KEY) - - def test_useragent(self): - useragent = 'sendgrid/' + __version__ + ';python_v3' - self.assertEqual(self.client.useragent, useragent) - - def test_host(self): - host = 'https://api.sendgrid.com' - self.assertEqual(self.client.host, host) - class TestAPIKeys(unittest.TestCase): def setUp(self): SendGridAPIClient = MockSendGridAPIClientRequest diff --git a/test/test_asm_groups.py b/test/test_asm_groups.py index da43fc5b0..6fda710f5 100644 --- a/test/test_asm_groups.py +++ b/test/test_asm_groups.py @@ -1,71 +1,20 @@ +from .base_test import BaseTest, MockSendGridAPIClientRequest import os try: import unittest2 as unittest except ImportError: import unittest -import json -import sys try: from StringIO import StringIO except ImportError: # Python 3 from io import StringIO -try: - import urllib.request as urllib_request - from urllib.parse import urlencode - from urllib.error import HTTPError -except ImportError: # Python 2 - import urllib2 as urllib_request - from urllib2 import HTTPError - from urllib import urlencode import sendgrid -from sendgrid import SendGridClient, Mail -from sendgrid.exceptions import SendGridClientError, SendGridServerError -from sendgrid.sendgrid import HTTPError from sendgrid.client import SendGridAPIClient from sendgrid.version import __version__ SG_KEY = os.getenv('SG_KEY') or 'SENDGRID_APIKEY' -class MockSendGridAPIClientRequest(SendGridAPIClient): - def __init__(self, apikey, **opts): - super(MockSendGridAPIClientRequest, self).__init__(apikey, **opts) - self._req = None - - def _build_request(self, url=None, json_header=False, method='GET', data=None): - req = urllib_request.Request(url) - req.get_method = lambda: method - req.add_header('User-Agent', self.useragent) - req.add_header('Authorization', 'Bearer ' + self.apikey) - if json_header: - req.add_header('Content-Type', 'application/json') - body = data - if method == 'POST': - response = 201 - if method == 'PATCH': - response = 200 - if method == 'DELETE': - response = 204 - if method == 'GET': - response = 200 - return response, body - -class TestSendGridAPIClient(unittest.TestCase): - def setUp(self): - self.client = MockSendGridAPIClientRequest - self.client = SendGridAPIClient(SG_KEY) - - def test_apikey_init(self): - self.assertEqual(self.client.apikey, SG_KEY) - - def test_useragent(self): - useragent = 'sendgrid/' + __version__ + ';python_v3' - self.assertEqual(self.client.useragent, useragent) - - def test_host(self): - host = 'https://api.sendgrid.com' - self.assertEqual(self.client.host, host) - class TestASMGroups(unittest.TestCase): def setUp(self): SendGridAPIClient = MockSendGridAPIClientRequest From 38939ed170b8d418ab20cff1eb56998512f9e655 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 23 Sep 2015 09:39:16 -0700 Subject: [PATCH 19/28] Update ASM groups to retrieve particular records. --- example_v3_test.py | 2 +- sendgrid/client.py | 2 +- sendgrid/resources/asm_groups.py | 18 +++++++++++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/example_v3_test.py b/example_v3_test.py index c6163a171..cf3b0e4e5 100755 --- a/example_v3_test.py +++ b/example_v3_test.py @@ -10,7 +10,7 @@ client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) -status, msg = client.asm_groups.get() +status, msg = client.asm_groups.get([66,67,50]) print status print msg diff --git a/sendgrid/client.py b/sendgrid/client.py index 123e6ec21..3167b9a46 100644 --- a/sendgrid/client.py +++ b/sendgrid/client.py @@ -72,7 +72,7 @@ def _build_request(self, url, json_header=False, method='GET', data=None): return response.getcode(), body def get(self, api): - url = self.host + api.base_endpoint + url = self.host + api.endpoint response, body = self._build_request(url, False, 'GET') return response, body diff --git a/sendgrid/resources/asm_groups.py b/sendgrid/resources/asm_groups.py index c32f7a25a..68e58d73b 100644 --- a/sendgrid/resources/asm_groups.py +++ b/sendgrid/resources/asm_groups.py @@ -36,5 +36,21 @@ def client(self): return self._client # Retrieve all suppression groups associated with the user. - def get(self): + def get(self, id=None): + if id == None: + return self.client.get(self) + + if isinstance(id, int): + self._endpoint = self._base_endpoint + "/" + str(id) + return self.client.get(self) + + if len(id) > 1: + count = 0 + for i in id: + if count == 0: + self._endpoint = self._endpoint + "?id=" + str(i) + else: + self._endpoint = self._endpoint + "&id=" + str(i) + count = count + 1 + return self.client.get(self) \ No newline at end of file From 0053d079dfb9057b56d357678e4b17d7dbf08d93 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 23 Sep 2015 09:56:41 -0700 Subject: [PATCH 20/28] Adding instruction for api_keys and asm_groups --- README.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.rst b/README.rst index 756258551..e025eda72 100644 --- a/README.rst +++ b/README.rst @@ -223,6 +223,33 @@ add_content_id message.add_attachment('image.png', open('./image.png', 'rb')) message.add_content_id('image.png', 'ID_IN_HTML') message.set_html('TEXT BEFORE IMAGEAFTER IMAGE') + +SendGrid's `WEB API v3`_ +------------------------ + +`APIKeys`_ +~~~~~~~~~~ + +List all API Keys belonging to the authenticated user + +.. code:: python + client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + status, msg = client.apikeys.get() + +`ASM Groups`_ +~~~~~~~~~~~~~ + +Retrieve all suppression groups associated with the user. + +.. code:: python + client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + status, msg = client.asm_groups.get() + +Get a single record + +.. code:: python + + status, msg = client.asm_groups.get(record_id) SendGrid's `X-SMTPAPI`_ ----------------------- From 94444710415be6b8f8a9653a3c3b4bf1a84a6fe9 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 23 Sep 2015 09:58:47 -0700 Subject: [PATCH 21/28] Formatting fixes --- README.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index e025eda72..a171c13af 100644 --- a/README.rst +++ b/README.rst @@ -224,7 +224,7 @@ add_content_id message.add_content_id('image.png', 'ID_IN_HTML') message.set_html('TEXT BEFORE IMAGEAFTER IMAGE') -SendGrid's `WEB API v3`_ +WEB API v3 ------------------------ `APIKeys`_ @@ -233,6 +233,7 @@ SendGrid's `WEB API v3`_ List all API Keys belonging to the authenticated user .. code:: python + client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) status, msg = client.apikeys.get() @@ -242,6 +243,7 @@ List all API Keys belonging to the authenticated user Retrieve all suppression groups associated with the user. .. code:: python + client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) status, msg = client.asm_groups.get() From ebbe6780b9a53d5a528976a307456459959ed5c3 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 23 Sep 2015 09:59:21 -0700 Subject: [PATCH 22/28] Formatting fixes --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index a171c13af..7fa1a82de 100644 --- a/README.rst +++ b/README.rst @@ -230,7 +230,7 @@ WEB API v3 `APIKeys`_ ~~~~~~~~~~ -List all API Keys belonging to the authenticated user +List all API Keys belonging to the authenticated user. .. code:: python @@ -247,7 +247,7 @@ Retrieve all suppression groups associated with the user. client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) status, msg = client.asm_groups.get() -Get a single record +Get a single record. .. code:: python From 4d018fc4c8b45c925690d4800091260483f3e4d1 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 23 Sep 2015 10:02:50 -0700 Subject: [PATCH 23/28] Formatting fixes --- README.rst | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 7fa1a82de..6573b3125 100644 --- a/README.rst +++ b/README.rst @@ -225,7 +225,7 @@ add_content_id message.set_html('TEXT BEFORE IMAGEAFTER IMAGE') WEB API v3 ------------------------- +---------- `APIKeys`_ ~~~~~~~~~~ @@ -237,8 +237,17 @@ List all API Keys belonging to the authenticated user. client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) status, msg = client.apikeys.get() -`ASM Groups`_ -~~~~~~~~~~~~~ +`Advanced Suppression Manager (ASM)`_ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Advanced Suppression Manager gives your recipients more control over the types of emails they want to receive by letting them opt out of messages from a certain type of email. + +More information_. + +.. _information: https://sendgrid.com/docs/API_Reference/Web_API_v3/Advanced_Suppression_Manager/index.html + +ASM Groups +~~~~~~~~~~ Retrieve all suppression groups associated with the user. From 0d8c30101bd78e12d04987d4b92c298bd83cdae8 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Wed, 23 Sep 2015 13:42:46 -0700 Subject: [PATCH 24/28] Added POST to ASM suppression lists --- example_v3_test.py | 12 +++++- sendgrid/client.py | 2 + sendgrid/resources/asm_groups.py | 2 +- sendgrid/resources/asm_suppressions.py | 58 ++++++++++++++++++++++++++ test/test_asm_groups.py | 4 +- test/test_asm_suppressions.py | 41 ++++++++++++++++++ 6 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 sendgrid/resources/asm_suppressions.py create mode 100644 test/test_asm_suppressions.py diff --git a/example_v3_test.py b/example_v3_test.py index cf3b0e4e5..fef52603e 100755 --- a/example_v3_test.py +++ b/example_v3_test.py @@ -8,14 +8,24 @@ if len(var) == 2: os.environ[var[0]] = var[1] + + client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) -status, msg = client.asm_groups.get([66,67,50]) +status, msg = client.asm_suppressions.post(60, ['elmer+test@thinkingserious.com', 'elmer.thomas@yahoo.com']) print status print msg """ +status, msg = client.asm_suppressions.get(None,'elmer.thomas@yahoo.com') +print status +print msg + +status, msg = client.asm_groups.get([66,67,50]) +print status +print msg + name = "My Amazing API Key" status, msg = client.apikeys.post(name) msg = json.loads(msg) diff --git a/sendgrid/client.py b/sendgrid/client.py index 3167b9a46..02707e9d1 100644 --- a/sendgrid/client.py +++ b/sendgrid/client.py @@ -13,6 +13,7 @@ from .exceptions import SendGridClientError, SendGridServerError from .resources.apikeys import APIKeys from .resources.asm_groups import ASMGroups +from .resources.asm_suppressions import ASMSuppressions class SendGridAPIClient(object): @@ -34,6 +35,7 @@ def __init__(self, apikey, **opts): self.apikeys = APIKeys(self) self.asm_groups = ASMGroups(self) + self.asm_suppressions = ASMSuppressions(self) @property def apikey(self): diff --git a/sendgrid/resources/asm_groups.py b/sendgrid/resources/asm_groups.py index 68e58d73b..797f2c84f 100644 --- a/sendgrid/resources/asm_groups.py +++ b/sendgrid/resources/asm_groups.py @@ -8,7 +8,7 @@ class ASMGroups(object): def __init__(self, client, **opts): """ - Constructs SendGrid ASM object. + Constructs SendGrid ASM group object. See https://sendgrid.com/docs/API_Reference/Web_API_v3/Advanced_Suppression_Manager/index.html and https://sendgrid.com/docs/API_Reference/Web_API_v3/Advanced_Suppression_Manager/groups.html diff --git a/sendgrid/resources/asm_suppressions.py b/sendgrid/resources/asm_suppressions.py new file mode 100644 index 000000000..8f8161979 --- /dev/null +++ b/sendgrid/resources/asm_suppressions.py @@ -0,0 +1,58 @@ +class ASMSuppressions(object): + """Advanced Suppression Manager gives your recipients more control over the types of emails they want to receive + by letting them opt out of messages from a certain type of email. + + Suppressions are email addresses that can be added to groups to prevent certain types of emails from being + delivered to those addresses. + """ + + def __init__(self, client, **opts): + """ + Constructs SendGrid ASM suppressions object. + + See https://sendgrid.com/docs/API_Reference/Web_API_v3/Advanced_Suppression_Manager/index.html and + https://sendgrid.com/docs/API_Reference/Web_API_v3/Advanced_Suppression_Manager/groups.html + """ + self._name = None + self._base_endpoint = "/v3/asm/groups" + self._endpoint = "/v3/asm/groups" + self._client = client + + @property + def base_endpoint(self): + return self._base_endpoint + + @property + def endpoint(self): + endpoint = self._endpoint + return endpoint + + @endpoint.setter + def endpoint(self, value): + self._endpoint = value + + @property + def client(self): + return self._client + + # Get suppressed addresses for a given group id. + def get(self, id=None, email=None): + if id == None and email == None: + return self.client.get(self) + + if isinstance(id, int): + self._endpoint = self._base_endpoint + "/" + str(id) + "/suppressions" + return self.client.get(self) + + if isinstance(email, str): + self._endpoint = "/v3/asm/suppressions/" + email + + return self.client.get(self) + + # Add recipient addresses to the suppressions list for a given group. + # If the group has been deleted, this request will add the address to the global suppression. + def post(self, id, emails): + self._endpoint = self._base_endpoint + "/" + str(id) + "/suppressions" + data = {} + data["recipient_emails"] = emails + return self.client.post(self, data) \ No newline at end of file diff --git a/test/test_asm_groups.py b/test/test_asm_groups.py index 6fda710f5..83a795b2c 100644 --- a/test/test_asm_groups.py +++ b/test/test_asm_groups.py @@ -20,14 +20,14 @@ def setUp(self): SendGridAPIClient = MockSendGridAPIClientRequest self.client = SendGridAPIClient(SG_KEY) - def test_apikeys_init(self): + def test_asm_groups_init(self): self.asm_groups = self.client.asm_groups self.assertEqual(self.asm_groups.base_endpoint, "/v3/asm/groups") self.assertEqual(self.asm_groups.endpoint, "/v3/asm/groups") self.assertEqual(self.asm_groups.client, self.client) def test_asm_groups_get(self): - status, msg = self.client.apikeys.get() + status, msg = self.client.asm_groups.get() self.assertEqual(status, 200) if __name__ == '__main__': diff --git a/test/test_asm_suppressions.py b/test/test_asm_suppressions.py new file mode 100644 index 000000000..909788560 --- /dev/null +++ b/test/test_asm_suppressions.py @@ -0,0 +1,41 @@ +from .base_test import BaseTest, MockSendGridAPIClientRequest +import os +try: + import unittest2 as unittest +except ImportError: + import unittest +try: + from StringIO import StringIO +except ImportError: # Python 3 + from io import StringIO + +import sendgrid +from sendgrid.client import SendGridAPIClient +from sendgrid.version import __version__ + +SG_KEY = os.getenv('SG_KEY') or 'SENDGRID_APIKEY' + +class TestASMGroups(unittest.TestCase): + def setUp(self): + SendGridAPIClient = MockSendGridAPIClientRequest + self.client = SendGridAPIClient(SG_KEY) + + def test_asm_suppressions_init(self): + self.asm_suppressions = self.client.asm_suppressions + self.assertEqual(self.asm_suppressions.base_endpoint, "/v3/asm/groups") + self.assertEqual(self.asm_suppressions.endpoint, "/v3/asm/groups") + self.assertEqual(self.asm_suppressions.client, self.client) + + def test_asm_suppressions_get(self): + status, msg = self.client.asm_suppressions.get() + self.assertEqual(status, 200) + + def test_asm_suppressions_post(self): + id = 67 + emails = ['elmer+test@thinkingserious.com'] + status, msg = self.client.asm_suppressions.post(id, emails) + self.assertEqual(status, 201) + self.assertEqual(msg['recipient_emails'], emails) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 12f1a5318e2f03d0c0fc122b5b5a1d383f3bbe9b Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Thu, 24 Sep 2015 08:08:23 -0700 Subject: [PATCH 25/28] Added DELETE for suppressions endpoint --- README.rst | 30 ++++++++++++++++++++++++++ example_v3_test.py | 7 +++++- sendgrid/resources/asm_suppressions.py | 7 +++++- test/test_asm_suppressions.py | 10 +++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 6573b3125..9d62c83e8 100644 --- a/README.rst +++ b/README.rst @@ -261,6 +261,36 @@ Get a single record. .. code:: python status, msg = client.asm_groups.get(record_id) + +ASM Suppressions +~~~~~~~~~~~~~~~~ + +Suppressions are email addresses that can be added to groups to prevent certain types of emails from being delivered to those addresses. + +Add recipient addresses to the suppressions list for a given group. + +.. code:: python + + client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + group_id = # If no group_id_number, the emails will be added to the global suppression group + emails = ['elmer+test@thinkingserious.com', 'elmer+test2@thinkingserious.com'] + status, msg = client.asm_suppressions.post(group_id, emails) + +Get suppressed addresses for a given group. + +.. code:: python + + status, msg = client.asm_suppressions.get() + +Get suppression groups associated with a given recipient address. + +.. code:: python + + status, msg = client.asm_suppressions.get(None,) + +Delete a recipient email from the suppressions list for a group. + + status, msg = client.asm_suppressions.delete(,) SendGrid's `X-SMTPAPI`_ ----------------------- diff --git a/example_v3_test.py b/example_v3_test.py index fef52603e..71edba6c3 100755 --- a/example_v3_test.py +++ b/example_v3_test.py @@ -12,12 +12,17 @@ client = sendgrid.SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) -status, msg = client.asm_suppressions.post(60, ['elmer+test@thinkingserious.com', 'elmer.thomas@yahoo.com']) +status, msg = client.asm_suppressions.delete(67,'elmer+test@thinkingserious.com') print status print msg """ +status, msg = client.asm_suppressions.post(60, ['elmer+test@thinkingserious.com', 'elmer.thomas@yahoo.com']) +print status +print msg + + status, msg = client.asm_suppressions.get(None,'elmer.thomas@yahoo.com') print status print msg diff --git a/sendgrid/resources/asm_suppressions.py b/sendgrid/resources/asm_suppressions.py index 8f8161979..820f0cd22 100644 --- a/sendgrid/resources/asm_suppressions.py +++ b/sendgrid/resources/asm_suppressions.py @@ -55,4 +55,9 @@ def post(self, id, emails): self._endpoint = self._base_endpoint + "/" + str(id) + "/suppressions" data = {} data["recipient_emails"] = emails - return self.client.post(self, data) \ No newline at end of file + return self.client.post(self, data) + + # Delete a recipient email from the suppressions list for a group. + def delete(self, id, email): + self.endpoint = self._base_endpoint + "/" + str(id) + "/suppressions/" + email + return self.client.delete(self) \ No newline at end of file diff --git a/test/test_asm_suppressions.py b/test/test_asm_suppressions.py index 909788560..9ce52a43c 100644 --- a/test/test_asm_suppressions.py +++ b/test/test_asm_suppressions.py @@ -36,6 +36,16 @@ def test_asm_suppressions_post(self): status, msg = self.client.asm_suppressions.post(id, emails) self.assertEqual(status, 201) self.assertEqual(msg['recipient_emails'], emails) + emails = ['elmer+test@thinkingserious.com', 'elmer.thomas@yahoo.com'] + status, msg = self.client.asm_suppressions.post(id, emails) + self.assertEqual(status, 201) + self.assertEqual(msg['recipient_emails'], emails) + + def test_asm_supressions_delete(self): + id = 67 + email = 'elmer+test@thinkingserious.com' + status, msg = self.client.asm_suppressions.delete(id, email) + self.assertEqual(status, 204) if __name__ == '__main__': unittest.main() \ No newline at end of file From ea1c950595376948a4a684fc4ba3450876cfcd45 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 29 Sep 2015 15:31:57 -0700 Subject: [PATCH 26/28] Update README to reflect merge --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index ca3a9b34d..82568e865 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,7 @@ Announcements For users of our `Web API v3 endpoints`_, we have begun integrating v3 endpoints into this library. As part of this process we have implemented a test automation tool, TOX_. We are also updating and enhancing the core library code. -In no particular order, we have implemented a few of the v3 endpoints already and would appreciate your feedback. Please feel free to submit issues and pull requests on the `v3_beta branch`_. +In no particular order, we have implemented a `few of the v3`_ endpoints already and would appreciate your feedback. Thank you for your continued support! @@ -516,3 +516,4 @@ MIT License .. _`Web API v3 endpoints`: https://sendgrid.com/docs/API_Reference/Web_API_v3/index.html .. _TOX: https://testrun.org/tox/latest/ .. _`v3_beta branch`: https://github.com/sendgrid/sendgrid-python/tree/v3_beta +-- _`few of the v3`: https://github.com/sendgrid/sendgrid-python/tree/v3_beta#web-api-v3 From d12876b0adf1cfadc32469537316bf698bd48a40 Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 29 Sep 2015 16:01:51 -0700 Subject: [PATCH 27/28] Update README to reflect merge --- README.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 82568e865..7a0a55d26 100644 --- a/README.rst +++ b/README.rst @@ -236,6 +236,8 @@ add_content_id WEB API v3 ---------- +.. _APIKeysAnchor: + `APIKeys`_ ~~~~~~~~~~ @@ -515,5 +517,4 @@ MIT License .. _Filter: http://sendgrid.com/docs/API_Reference/SMTP_API/apps.html .. _`Web API v3 endpoints`: https://sendgrid.com/docs/API_Reference/Web_API_v3/index.html .. _TOX: https://testrun.org/tox/latest/ -.. _`v3_beta branch`: https://github.com/sendgrid/sendgrid-python/tree/v3_beta --- _`few of the v3`: https://github.com/sendgrid/sendgrid-python/tree/v3_beta#web-api-v3 +.. _`few of the v3`: APIKeysAnchor_ From 6a41d33e65eaacd2cb62136e7abaa37f360f4a0f Mon Sep 17 00:00:00 2001 From: Elmer Thomas Date: Tue, 29 Sep 2015 16:13:47 -0700 Subject: [PATCH 28/28] Updated changelog to reflect merge --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4ec0feca..aac011323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,16 @@ # Change Log All notable changes to this project will be documented in this file. -## [1.4.3] - 2015-10-22 +## [1.5.3] - 2015-09-29 +### Added +- Refactored tests and added Tox support +- Framework for Web API v3 endpoints +- Web API v3 endpionts: apikeys, ASM groups and ASM suppressions + +### Fixed +- Python 3 Fix [#126](https://github.com/sendgrid/sendgrid-python/issues/126) + +## [1.4.3] - 2015-09-22 ### Fixed - Reply To header now supports friendly name [#110](https://github.com/sendgrid/sendgrid-python/issues/110)