From 6a3917f2dd2fdde8f34cfb4adc534ec205c64faf Mon Sep 17 00:00:00 2001 From: Brad Pitcher Date: Mon, 27 Aug 2012 22:29:13 -0700 Subject: [PATCH 01/24] initial unit tests, Issue #9 --- .gitignore | 1 + mailsnake/tests.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 mailsnake/tests.py diff --git a/.gitignore b/.gitignore index 3d8e15f..43660ca 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ dist/ *.egg-info *~ *.geany +secret_keys.py diff --git a/mailsnake/tests.py b/mailsnake/tests.py new file mode 100644 index 0000000..fdf16e0 --- /dev/null +++ b/mailsnake/tests.py @@ -0,0 +1,21 @@ +import unittest + +from collections import MutableSequence +from mailsnake import MailSnake +from .secret_keys import MAILCHIMP_API_KEY + +class TestMailChimpAPI(unittest.TestCase): + def setUp(self): + self.mcapi = MailSnake(MAILCHIMP_API_KEY) + + def test_ping(self): + assert self.mcapi.ping() == "Everything's Chimpy!" + + def test_chimpChatter(self): + chimp_chatter = self.mcapi.chimpChatter() + # Check that the result is a list + assert isinstance(chimp_chatter, MutableSequence) + # If the list is not empty, check a few keys + if len(chimp_chatter) > 0: + assert 'message' in chimp_chatter[0].keys() + assert chimp_chatter[0]['url'].find('mailchimp') > -1 From c36a56d80170b50f1f669ba911b0c8bd5e59d359 Mon Sep 17 00:00:00 2001 From: Brad Pitcher Date: Wed, 17 Oct 2012 15:51:33 -0700 Subject: [PATCH 02/24] some template related method tests --- mailsnake/tests.py | 21 -- mailsnake/tests/__init__.py | 0 mailsnake/tests/template.html | 487 ++++++++++++++++++++++++++++++++++ mailsnake/tests/tests.py | 41 +++ 4 files changed, 528 insertions(+), 21 deletions(-) delete mode 100644 mailsnake/tests.py create mode 100644 mailsnake/tests/__init__.py create mode 100644 mailsnake/tests/template.html create mode 100644 mailsnake/tests/tests.py diff --git a/mailsnake/tests.py b/mailsnake/tests.py deleted file mode 100644 index fdf16e0..0000000 --- a/mailsnake/tests.py +++ /dev/null @@ -1,21 +0,0 @@ -import unittest - -from collections import MutableSequence -from mailsnake import MailSnake -from .secret_keys import MAILCHIMP_API_KEY - -class TestMailChimpAPI(unittest.TestCase): - def setUp(self): - self.mcapi = MailSnake(MAILCHIMP_API_KEY) - - def test_ping(self): - assert self.mcapi.ping() == "Everything's Chimpy!" - - def test_chimpChatter(self): - chimp_chatter = self.mcapi.chimpChatter() - # Check that the result is a list - assert isinstance(chimp_chatter, MutableSequence) - # If the list is not empty, check a few keys - if len(chimp_chatter) > 0: - assert 'message' in chimp_chatter[0].keys() - assert chimp_chatter[0]['url'].find('mailchimp') > -1 diff --git a/mailsnake/tests/__init__.py b/mailsnake/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mailsnake/tests/template.html b/mailsnake/tests/template.html new file mode 100644 index 0000000..bc1d63c --- /dev/null +++ b/mailsnake/tests/template.html @@ -0,0 +1,487 @@ + + + + + + + + + *|MC:SUBJECT|* + + + +
+ + + + +
+ + + + + +
+ + + + + + + +
+
+ Use one or two sentences in this area to offer a teaser of your email's content. Text here will show in a preview area in some email clients. +
+
+
+ Is this email not displaying correctly?
View it in your browser. +
+
+ + +
+ + + + + + + + + + + +
+ + + + + +
+ + + + + +
+ +
+ + + + + +
+ + + + + + +
+
+ Heading 1 + Heading 2 + Heading 3 + Heading 4 + Getting started: Customize your template by clicking on the style editor tabs up above. Set your fonts, colors, and styles. After setting your styling is all done you can click here in this area, delete the text, and start adding your own awesome content! +
+
+ After you enter your content, highlight the text you want to style and select the options you set in the style editor in the "styles" drop down box. Want to get rid of styling on a bit of text, but having trouble doing it? Just use the "clear styles" button to strip the text of any formatting and reset your style. +
+
+ + +
+ +
+ + + + + +
+ + + + + + + + + + + + + +
+ +
+
+
+ *|IF:LIST|* + Copyright © *|CURRENT_YEAR|* *|LIST:COMPANY|*, All rights reserved. +
+ + *|LIST:DESCRIPTION|* +
+ Our mailing address is: +
+ *|HTML:LIST_ADDRESS_HTML|* +
+ + *|ELSE:|* + + Copyright © *|CURRENT_YEAR|* *|USER:COMPANY|*, All rights reserved. +
+ Our mailing address is: +
+ *|USER:ADDRESS_HTML|* + + *|END:IF|* +
+
+
+
+
+ *|IF:REWARDS|* *|HTML:REWARDS|* *|END:IF|* +
+
+
+ +
+ + +
+ +
+
+
+
+ + diff --git a/mailsnake/tests/tests.py b/mailsnake/tests/tests.py new file mode 100644 index 0000000..abb427e --- /dev/null +++ b/mailsnake/tests/tests.py @@ -0,0 +1,41 @@ +import unittest + +from collections import MutableSequence +from mailsnake import MailSnake +from .secret_keys import MAILCHIMP_API_KEY + +class TestMailChimpAPI(unittest.TestCase): + def setUp(self): + self.mcapi = MailSnake(MAILCHIMP_API_KEY) + + def test_ping(self): + assert self.mcapi.ping() == "Everything's Chimpy!" + + def test_chimpChatter(self): + chimp_chatter = self.mcapi.chimpChatter() + # Check that the result is a list + assert isinstance(chimp_chatter, MutableSequence) + # If the list is not empty, check a few keys + if len(chimp_chatter) > 0: + assert 'message' in chimp_chatter[0].keys() + assert chimp_chatter[0]['url'].find('mailchimp') > -1 + + def test_templates(self): + types = {'user': False, 'gallery': False, 'base': False} + for t_type in types: + new_types = dict(types.items() + {t_type: True}.items()) + assert self.mcapi.templates(types=new_types).has_key(t_type) + + def test_templateAddDel(self): + templates = self.mcapi.templates(inactives={'include': True}) + template_names = [t['name'] for t in templates['user']] + html = open('mailsnake/tests/template.html', 'r').read() + index = 0 + base_name = 'mailsnake_test_template' + template_name = '%s%i' % (base_name, index) + while template_name in template_names: + index += 1 + template_name = '%s%i' % (base_name, index) + template_id = self.mcapi.templateAdd(name=template_name, html=html) + assert isinstance(template_id, int) + assert self.mcapi.templateDel(id=template_id) From 47eba410d3eaeac057bdff8bef3988ce6956c547 Mon Sep 17 00:00:00 2001 From: Brad Pitcher Date: Wed, 17 Oct 2012 16:08:24 -0700 Subject: [PATCH 03/24] some list related method tests --- mailsnake/tests/tests.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/mailsnake/tests/tests.py b/mailsnake/tests/tests.py index abb427e..d0e890e 100644 --- a/mailsnake/tests/tests.py +++ b/mailsnake/tests/tests.py @@ -2,12 +2,14 @@ from collections import MutableSequence from mailsnake import MailSnake -from .secret_keys import MAILCHIMP_API_KEY +from .secret_keys import MAILCHIMP_API_KEY, MAILCHIMP_LIST_ID class TestMailChimpAPI(unittest.TestCase): def setUp(self): self.mcapi = MailSnake(MAILCHIMP_API_KEY) + # Helper Methods + def test_ping(self): assert self.mcapi.ping() == "Everything's Chimpy!" @@ -20,6 +22,20 @@ def test_chimpChatter(self): assert 'message' in chimp_chatter[0].keys() assert chimp_chatter[0]['url'].find('mailchimp') > -1 + # List Related Methods + + def test_lists(self): + lists = self.mcapi.lists() + assert isinstance(lists, dict) + assert lists.has_key('total') + assert lists.has_key('data') + + def test_listActivity(self): + activity = self.mcapi.listActivity(id=MAILCHIMP_LIST_ID) + assert isinstance(activity, list) + + # Template Related Methods + def test_templates(self): types = {'user': False, 'gallery': False, 'base': False} for t_type in types: From ff14fed9b7ee52d97d550a2133c9617b3e4ffbc1 Mon Sep 17 00:00:00 2001 From: Brad Pitcher Date: Wed, 17 Oct 2012 16:12:24 -0700 Subject: [PATCH 04/24] document how to run the tests --- mailsnake/tests/tests.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mailsnake/tests/tests.py b/mailsnake/tests/tests.py index d0e890e..8551b8c 100644 --- a/mailsnake/tests/tests.py +++ b/mailsnake/tests/tests.py @@ -4,6 +4,20 @@ from mailsnake import MailSnake from .secret_keys import MAILCHIMP_API_KEY, MAILCHIMP_LIST_ID +""" +To run these tests, do the following: +- Place a file called secret_keys.py under the mailsnake directory + containing the following mailchimp keys: + * MAILCHIMP_API_KEY + * MAILCHIMP_LIST_ID + You must create a test list in MailChimp and get the ID for use here + because the API does not have a method for creating lists. + +- Install the python 'nose' library +- From the command-line, run 'nosetests' +""" + + class TestMailChimpAPI(unittest.TestCase): def setUp(self): self.mcapi = MailSnake(MAILCHIMP_API_KEY) From 0b5c1c313e7465b2c01229bc4d27b3dccb5959cb Mon Sep 17 00:00:00 2001 From: Brad Pitcher Date: Wed, 17 Oct 2012 16:13:50 -0700 Subject: [PATCH 05/24] update deprecated code --- mailsnake/tests/tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mailsnake/tests/tests.py b/mailsnake/tests/tests.py index 8551b8c..5d90bff 100644 --- a/mailsnake/tests/tests.py +++ b/mailsnake/tests/tests.py @@ -41,8 +41,8 @@ def test_chimpChatter(self): def test_lists(self): lists = self.mcapi.lists() assert isinstance(lists, dict) - assert lists.has_key('total') - assert lists.has_key('data') + assert 'total' in lists + assert 'data' in lists def test_listActivity(self): activity = self.mcapi.listActivity(id=MAILCHIMP_LIST_ID) @@ -54,7 +54,7 @@ def test_templates(self): types = {'user': False, 'gallery': False, 'base': False} for t_type in types: new_types = dict(types.items() + {t_type: True}.items()) - assert self.mcapi.templates(types=new_types).has_key(t_type) + assert t_type in self.mcapi.templates(types=new_types) def test_templateAddDel(self): templates = self.mcapi.templates(inactives={'include': True}) From bd896223a515c629dd1e6a82091bf238cf31bc8f Mon Sep 17 00:00:00 2001 From: Brad Pitcher Date: Wed, 17 Oct 2012 16:49:51 -0700 Subject: [PATCH 06/24] default std_content00 to blank --- mailsnake/tests/template.html | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/mailsnake/tests/template.html b/mailsnake/tests/template.html index bc1d63c..ddd5f76 100644 --- a/mailsnake/tests/template.html +++ b/mailsnake/tests/template.html @@ -389,17 +389,8 @@ +
+
-
- Heading 1 - Heading 2 - Heading 3 - Heading 4 - Getting started: Customize your template by clicking on the style editor tabs up above. Set your fonts, colors, and styles. After setting your styling is all done you can click here in this area, delete the text, and start adding your own awesome content! -
-
- After you enter your content, highlight the text you want to style and select the options you set in the style editor in the "styles" drop down box. Want to get rid of styling on a bit of text, but having trouble doing it? Just use the "clear styles" button to strip the text of any formatting and reset your style. -
-
From 67bb4ad23ed11248c123be095435d9a088fc0393 Mon Sep 17 00:00:00 2001 From: Brad Pitcher Date: Wed, 17 Oct 2012 17:21:20 -0700 Subject: [PATCH 07/24] add campaign related testing --- mailsnake/tests/template.html | 2 +- mailsnake/tests/tests.py | 42 +++++++++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/mailsnake/tests/template.html b/mailsnake/tests/template.html index ddd5f76..2be6383 100644 --- a/mailsnake/tests/template.html +++ b/mailsnake/tests/template.html @@ -389,7 +389,7 @@
-
+
default content
diff --git a/mailsnake/tests/tests.py b/mailsnake/tests/tests.py index 5d90bff..54caa05 100644 --- a/mailsnake/tests/tests.py +++ b/mailsnake/tests/tests.py @@ -2,6 +2,8 @@ from collections import MutableSequence from mailsnake import MailSnake +from random import random + from .secret_keys import MAILCHIMP_API_KEY, MAILCHIMP_LIST_ID """ @@ -36,6 +38,35 @@ def test_chimpChatter(self): assert 'message' in chimp_chatter[0].keys() assert chimp_chatter[0]['url'].find('mailchimp') > -1 + # Campaign Related Methods + + def test_campaignCreateDelete(self): + template_id = self._add_test_template() + from_email = self.mcapi.getAccountDetails()['contact']['email'] + options = { + 'list_id': MAILCHIMP_LIST_ID, 'subject': 'testing', + 'from_email': from_email, 'from_name': 'Test From', + 'to_name': 'Test To', 'template_id': template_id, + 'inline_css': True, 'generate_text': True, 'title': 'testing' + } + test_content = '%f' % random() + content = {'html_std_content00': test_content} + campaign_id = self.mcapi.campaignCreate( + type='regular', options=options, content=content) + assert isinstance(campaign_id, unicode) + campaign_content = self.mcapi.campaignContent(cid=campaign_id) + assert 'html' in campaign_content + assert 'text' in campaign_content + assert test_content in campaign_content['html'] + assert test_content in campaign_content['text'] + default_content = 'default content' + assert not default_content in campaign_content['html'] + assert not default_content in campaign_content['text'] + + # Clean up + assert self.mcapi.campaignDelete(cid=campaign_id) + assert self.mcapi.templateDel(id=template_id) + # List Related Methods def test_lists(self): @@ -57,15 +88,18 @@ def test_templates(self): assert t_type in self.mcapi.templates(types=new_types) def test_templateAddDel(self): + template_id = self._add_test_template() + assert isinstance(template_id, int) + assert self.mcapi.templateDel(id=template_id) + + def _add_test_template(self): + html = open('mailsnake/tests/template.html', 'r').read() templates = self.mcapi.templates(inactives={'include': True}) template_names = [t['name'] for t in templates['user']] - html = open('mailsnake/tests/template.html', 'r').read() index = 0 base_name = 'mailsnake_test_template' template_name = '%s%i' % (base_name, index) while template_name in template_names: index += 1 template_name = '%s%i' % (base_name, index) - template_id = self.mcapi.templateAdd(name=template_name, html=html) - assert isinstance(template_id, int) - assert self.mcapi.templateDel(id=template_id) + return self.mcapi.templateAdd(name=template_name, html=html) From 1b8ea82921eb0e912a1f567ffe02b47cabdbe15b Mon Sep 17 00:00:00 2001 From: Brad Pitcher Date: Tue, 8 Jan 2013 21:51:58 -0800 Subject: [PATCH 08/24] test listSubscribe and listUnsubscribe --- mailsnake/tests/tests.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mailsnake/tests/tests.py b/mailsnake/tests/tests.py index 54caa05..060c5d7 100644 --- a/mailsnake/tests/tests.py +++ b/mailsnake/tests/tests.py @@ -4,7 +4,7 @@ from mailsnake import MailSnake from random import random -from .secret_keys import MAILCHIMP_API_KEY, MAILCHIMP_LIST_ID +from .secret_keys import MAILCHIMP_API_KEY, MAILCHIMP_LIST_ID, TEST_EMAIL """ To run these tests, do the following: @@ -79,6 +79,14 @@ def test_listActivity(self): activity = self.mcapi.listActivity(id=MAILCHIMP_LIST_ID) assert isinstance(activity, list) + def test_listSubscribeUnsubscribe(self): + assert self.mcapi.listSubscribe( + id=MAILCHIMP_LIST_ID, email_address=TEST_EMAIL, double_optin=False, + send_welcome=False) + assert self.mcapi.listUnsubscribe( + id=MAILCHIMP_LIST_ID, email_address=TEST_EMAIL, send_goodbye=False, + send_notify=False) + # Template Related Methods def test_templates(self): From 90817d31f57eba10bb09739bda847728eef62f31 Mon Sep 17 00:00:00 2001 From: Brad Pitcher Date: Tue, 8 Jan 2013 22:08:51 -0800 Subject: [PATCH 09/24] export API tests --- mailsnake/tests/tests.py | 46 ++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/mailsnake/tests/tests.py b/mailsnake/tests/tests.py index 060c5d7..2a8bfae 100644 --- a/mailsnake/tests/tests.py +++ b/mailsnake/tests/tests.py @@ -20,10 +20,25 @@ """ -class TestMailChimpAPI(unittest.TestCase): +class TestMailChimp(unittest.TestCase): def setUp(self): self.mcapi = MailSnake(MAILCHIMP_API_KEY) + self.mcapi.listBatchUnsubscribe( + id=MAILCHIMP_LIST_ID, emails=[TEST_EMAIL], delete_member=False, + send_goodbye=False, send_notify=False) + def _subscribe(self, email=None): + return self.mcapi.listSubscribe( + id=MAILCHIMP_LIST_ID, email_address=email if email else TEST_EMAIL, + double_optin=False, send_welcome=False) + + def _unsubscribe(self, email=None): + return self.mcapi.listUnsubscribe( + id=MAILCHIMP_LIST_ID, email_address=email if email else TEST_EMAIL, + send_goodbye=False, send_notify=False) + + +class TestMailChimpAPI(TestMailChimp): # Helper Methods def test_ping(self): @@ -80,12 +95,8 @@ def test_listActivity(self): assert isinstance(activity, list) def test_listSubscribeUnsubscribe(self): - assert self.mcapi.listSubscribe( - id=MAILCHIMP_LIST_ID, email_address=TEST_EMAIL, double_optin=False, - send_welcome=False) - assert self.mcapi.listUnsubscribe( - id=MAILCHIMP_LIST_ID, email_address=TEST_EMAIL, send_goodbye=False, - send_notify=False) + assert self._subscribe() + assert self._unsubscribe() # Template Related Methods @@ -111,3 +122,24 @@ def _add_test_template(self): index += 1 template_name = '%s%i' % (base_name, index) return self.mcapi.templateAdd(name=template_name, html=html) + + +class TestExportAPI(TestMailChimp): + def setUp(self): + super(TestExportAPI, self).setUp() + self.export = MailSnake(MAILCHIMP_API_KEY, api='export') + self.export_stream = MailSnake(MAILCHIMP_API_KEY, api='export', + requests_opts={'prefetch': False}) + + def test_list(self): + member_list = self.export.list(id=MAILCHIMP_LIST_ID) + assert len(member_list) == 1 + self._subscribe() + member_list = self.export.list(id=MAILCHIMP_LIST_ID) + assert len(member_list) == 2 + member_list = self.export_stream.list(id=MAILCHIMP_LIST_ID) + lines = 0 + for list_member in member_list(): + if lines > 0: + assert isinstance(list_member, list) + lines += 1 From b95df151efc9402546281510669f3d09bd3b1d1e Mon Sep 17 00:00:00 2001 From: Brad Pitcher Date: Tue, 8 Jan 2013 22:14:09 -0800 Subject: [PATCH 10/24] more docs --- mailsnake/tests/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mailsnake/tests/tests.py b/mailsnake/tests/tests.py index 2a8bfae..cfbfba6 100644 --- a/mailsnake/tests/tests.py +++ b/mailsnake/tests/tests.py @@ -9,9 +9,10 @@ """ To run these tests, do the following: - Place a file called secret_keys.py under the mailsnake directory - containing the following mailchimp keys: + containing the following mailchimp related variables: * MAILCHIMP_API_KEY * MAILCHIMP_LIST_ID + * TEST_EMAIL (must be a valid email, i.e. not test@example.com) You must create a test list in MailChimp and get the ID for use here because the API does not have a method for creating lists. From ebb31098fee638d7cfae0636b78c2839a3fa5896 Mon Sep 17 00:00:00 2001 From: Gilbert Date: Sun, 30 Jun 2013 14:56:11 +0100 Subject: [PATCH 11/24] Updated to work with latest 'requests' version and be run with nosetests from mailsnake/tests (all tests now pass) --- mailsnake/tests/tests.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/mailsnake/tests/tests.py b/mailsnake/tests/tests.py index cfbfba6..bc663dd 100644 --- a/mailsnake/tests/tests.py +++ b/mailsnake/tests/tests.py @@ -38,6 +38,17 @@ def _unsubscribe(self, email=None): id=MAILCHIMP_LIST_ID, email_address=email if email else TEST_EMAIL, send_goodbye=False, send_notify=False) + def _set_wrong_api_key(self, dc=True, reset=False): + fake_key = "THISISNOTANAPIKEY" + fake_key += MAILCHIMP_API_KEY.split('-')[1] + # If dc is true then add the datacenter provided by the MAILCHIMP_API_KEY variable + if reset: + fake_key = MAILCHIMP_API_KEY + elif dc: + fake_key += "-" + MAILCHIMP_API_KEY.split('-')[1] + print(fake_key) + # self.mcapi = MailSnake("THISISNOTANAPIKEY") + class TestMailChimpAPI(TestMailChimp): # Helper Methods @@ -113,7 +124,7 @@ def test_templateAddDel(self): assert self.mcapi.templateDel(id=template_id) def _add_test_template(self): - html = open('mailsnake/tests/template.html', 'r').read() + html = open('./template.html', 'r').read() templates = self.mcapi.templates(inactives={'include': True}) template_names = [t['name'] for t in templates['user']] index = 0 @@ -130,7 +141,7 @@ def setUp(self): super(TestExportAPI, self).setUp() self.export = MailSnake(MAILCHIMP_API_KEY, api='export') self.export_stream = MailSnake(MAILCHIMP_API_KEY, api='export', - requests_opts={'prefetch': False}) + requests_opts={'stream': True}) def test_list(self): member_list = self.export.list(id=MAILCHIMP_LIST_ID) From 6369792374fb0b565b6164452af215f9f455e9d6 Mon Sep 17 00:00:00 2001 From: Gilbert Date: Sun, 30 Jun 2013 15:01:18 +0100 Subject: [PATCH 12/24] Revert "Updated to work with latest 'requests' version and be run with nosetests from mailsnake/tests (all tests now pass)" This reverts commit ebb31098fee638d7cfae0636b78c2839a3fa5896. --- mailsnake/tests/tests.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/mailsnake/tests/tests.py b/mailsnake/tests/tests.py index bc663dd..cfbfba6 100644 --- a/mailsnake/tests/tests.py +++ b/mailsnake/tests/tests.py @@ -38,17 +38,6 @@ def _unsubscribe(self, email=None): id=MAILCHIMP_LIST_ID, email_address=email if email else TEST_EMAIL, send_goodbye=False, send_notify=False) - def _set_wrong_api_key(self, dc=True, reset=False): - fake_key = "THISISNOTANAPIKEY" - fake_key += MAILCHIMP_API_KEY.split('-')[1] - # If dc is true then add the datacenter provided by the MAILCHIMP_API_KEY variable - if reset: - fake_key = MAILCHIMP_API_KEY - elif dc: - fake_key += "-" + MAILCHIMP_API_KEY.split('-')[1] - print(fake_key) - # self.mcapi = MailSnake("THISISNOTANAPIKEY") - class TestMailChimpAPI(TestMailChimp): # Helper Methods @@ -124,7 +113,7 @@ def test_templateAddDel(self): assert self.mcapi.templateDel(id=template_id) def _add_test_template(self): - html = open('./template.html', 'r').read() + html = open('mailsnake/tests/template.html', 'r').read() templates = self.mcapi.templates(inactives={'include': True}) template_names = [t['name'] for t in templates['user']] index = 0 @@ -141,7 +130,7 @@ def setUp(self): super(TestExportAPI, self).setUp() self.export = MailSnake(MAILCHIMP_API_KEY, api='export') self.export_stream = MailSnake(MAILCHIMP_API_KEY, api='export', - requests_opts={'stream': True}) + requests_opts={'prefetch': False}) def test_list(self): member_list = self.export.list(id=MAILCHIMP_LIST_ID) From bedbaed12a2f74aefefc296263073917374d1cb2 Mon Sep 17 00:00:00 2001 From: Gilbert Date: Sun, 30 Jun 2013 15:03:22 +0100 Subject: [PATCH 13/24] Updated to work with latest 'requests' version and be run with nosetests from mailsnake/tests (all tests now pass) --- mailsnake/tests/tests.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/mailsnake/tests/tests.py b/mailsnake/tests/tests.py index cfbfba6..26a891d 100644 --- a/mailsnake/tests/tests.py +++ b/mailsnake/tests/tests.py @@ -38,7 +38,6 @@ def _unsubscribe(self, email=None): id=MAILCHIMP_LIST_ID, email_address=email if email else TEST_EMAIL, send_goodbye=False, send_notify=False) - class TestMailChimpAPI(TestMailChimp): # Helper Methods @@ -91,6 +90,15 @@ def test_lists(self): assert 'total' in lists assert 'data' in lists + def test_lists_exception(self): + # Set apikey to wrong apikey + self._set_wrong_api_key + + self.assertRaises("Invalid Mailchimp API Key", self.mcapi.lists) + + # Reset apikey to correct apikey + self._set_wrong_api_key(reset=True) + def test_listActivity(self): activity = self.mcapi.listActivity(id=MAILCHIMP_LIST_ID) assert isinstance(activity, list) @@ -113,7 +121,7 @@ def test_templateAddDel(self): assert self.mcapi.templateDel(id=template_id) def _add_test_template(self): - html = open('mailsnake/tests/template.html', 'r').read() + html = open('./template.html', 'r').read() templates = self.mcapi.templates(inactives={'include': True}) template_names = [t['name'] for t in templates['user']] index = 0 @@ -130,7 +138,7 @@ def setUp(self): super(TestExportAPI, self).setUp() self.export = MailSnake(MAILCHIMP_API_KEY, api='export') self.export_stream = MailSnake(MAILCHIMP_API_KEY, api='export', - requests_opts={'prefetch': False}) + requests_opts={'stream': True}) def test_list(self): member_list = self.export.list(id=MAILCHIMP_LIST_ID) From 83308d2303f82e9ac41023512751be35f40f1663 Mon Sep 17 00:00:00 2001 From: Gilbert Date: Mon, 1 Jul 2013 23:13:56 +0100 Subject: [PATCH 14/24] Added tests to check lists method responds as expected to calls with incorrect apikeys --- mailsnake/tests/tests.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/mailsnake/tests/tests.py b/mailsnake/tests/tests.py index 26a891d..b8003ea 100644 --- a/mailsnake/tests/tests.py +++ b/mailsnake/tests/tests.py @@ -1,7 +1,7 @@ import unittest from collections import MutableSequence -from mailsnake import MailSnake +from mailsnake import MailSnake, exceptions from random import random from .secret_keys import MAILCHIMP_API_KEY, MAILCHIMP_LIST_ID, TEST_EMAIL @@ -38,6 +38,15 @@ def _unsubscribe(self, email=None): id=MAILCHIMP_LIST_ID, email_address=email if email else TEST_EMAIL, send_goodbye=False, send_notify=False) + def _set_api_key(self, apikey='', reset=False): + + # If dc is true then add the datacenter provided by the MAILCHIMP_API_KEY variable + if reset is True: + apikey = MAILCHIMP_API_KEY + + self.mcapi = MailSnake(apikey) + + class TestMailChimpAPI(TestMailChimp): # Helper Methods @@ -91,13 +100,18 @@ def test_lists(self): assert 'data' in lists def test_lists_exception(self): - # Set apikey to wrong apikey - self._set_wrong_api_key - self.assertRaises("Invalid Mailchimp API Key", self.mcapi.lists) + # Test with key including dc + self._set_api_key("WRONGKEY-us1") + self.assertRaises(exceptions.InvalidApiKeyException, self.mcapi.lists) + + # Test with key without dc + self._set_api_key("WRONGKEY") + self.assertRaises(exceptions.InvalidApiKeyException, self.mcapi.lists) + - # Reset apikey to correct apikey - self._set_wrong_api_key(reset=True) + # Reset apikey to MAILCHIMP_API_KEY + self._set_api_key(reset=True) def test_listActivity(self): activity = self.mcapi.listActivity(id=MAILCHIMP_LIST_ID) From 0cef57d00d1f96a647c425939871a07821e284b0 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Mon, 1 Jul 2013 20:37:59 -0400 Subject: [PATCH 15/24] Attempting to clean this lib up :) - Update LICENSE - Integrate Travis - PEP8 things - General code clean up --- .travis.yml | 16 +++ AUTHORS.rst | 15 +++ LICENSE | 2 +- README.md | 139 ----------------------- README.rst | 13 ++- mailsnake/.DS_Store | Bin 0 -> 6148 bytes mailsnake/__init__.py | 20 ++-- mailsnake/{mailsnake.py => api.py} | 98 +++++++++------- mailsnake/compat.py | 35 ++++++ requirements.txt | 3 + setup.py | 30 +++-- {mailsnake/tests => tests}/__init__.py | 0 tests/config.py | 5 + {mailsnake/tests => tests}/template.html | 0 {mailsnake/tests => tests}/tests.py | 16 +-- 15 files changed, 160 insertions(+), 232 deletions(-) create mode 100644 .travis.yml create mode 100644 AUTHORS.rst delete mode 100644 README.md create mode 100644 mailsnake/.DS_Store rename mailsnake/{mailsnake.py => api.py} (69%) create mode 100644 mailsnake/compat.py create mode 100644 requirements.txt rename {mailsnake/tests => tests}/__init__.py (100%) create mode 100644 tests/config.py rename {mailsnake/tests => tests}/template.html (100%) rename {mailsnake/tests => tests}/tests.py (90%) diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..846141b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: python +python: + - 2.6 + - 2.7 +env: + global: + - MAILCHIMP_API_KEY= + - MAILCHIMP_LIST_ID= + - TEST_EMAIL= +install: pip install -r requirements.txt +script: nosetests -v -w tests/ --logging-filter="mailsnake" --with-cov --cov twython --cov-config .coveragerc --cov-report term-missing +notifications: + email: false +branches: + only: + - master diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..7a76774 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,15 @@ +This project is forked from John-Kim Murphy's original MailSnake project (which he no longer actively maintains). + +Development Lead +```````````````` + +- Mike Helmick + + +Patches and Suggestions +```````````````````````` + +- `Brad Pitcher `_ +- `vlinhart `_ +- `starenka `_, +- `Ryan Tucker `_ diff --git a/LICENSE b/LICENSE index 06021bb..0fbe963 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2010-2012 John-Kim Murphy +Copyright (c) 2010-2013 John-Kim Murphy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md deleted file mode 100644 index 10b37e1..0000000 --- a/README.md +++ /dev/null @@ -1,139 +0,0 @@ -MailSnake -========= - -`MailSnake` is a Python wrapper for MailChimp's The API, STS API, Export API, and the -new Mandrill API. (Now with support for Python 3) - -Installation ------------- - pip install mailsnake - -Usage ------ - -```python -from mailsnake import MailSnake -from mailsnake.exceptions import * - -ms = MailSnake('YOUR MAILCHIMP API KEY') -try: - ms.ping() # returns "Everything's Chimpy!" -except MailSnakeException: - print 'An error occurred. :(' -``` - -You can also catch specific errors: - -```python -ms = MailSnake('my_wrong_mailchimp_api_key_that_does_not_exist') -try: - ms.ping() # returns "Everything's Chimpy!" -except InvalidApiKeyException: - print 'You have a bad API key, sorry.' -``` -The default API is MCAPI, but STS, Export, and Mandrill can be used by -supplying an api argument set to 'sts', 'export', or 'mandrill' -respectively. Here's an example: - -```python -mcsts = MailSnake('YOUR MAILCHIMP API KEY', api='sts') -mcsts.GetSendQuota() # returns something like {'Max24HourSend': '10000.0', 'SentLast24Hours': '0.0', 'MaxSendRate': '5.0'} -``` - -Since the Mandrill API is divided into sections, one must take that into -account when using it. Here's an example: - -```python -mapi = MailSnake('YOUR MANDRILL API KEY', api='mandrill') -mapi.users.ping() # returns 'PONG!' -``` - -or: - -```python -mapi_users = MailSnake('YOUR MANDRILL API KEY', api='mandrill', api_section='users') -mapi_users.ping() # returns 'PONG!' -``` - -Some Mandrill functions have a dash(-) in the name. Since Python -function names can't have dashes in them, use underscores(\_) instead: - -```python -mapi = MailSnake('YOUR MANDRILL API KEY', api='mandrill') -mapi.messages.send(message={'html':'email html', 'subject':'email subject', 'from_email':'from@example.com', 'from_name':'From Name', 'to':[{'email':'to@example.com', 'name':'To Name'}]}) # returns 'PONG!' -``` - -OAuth Tokens ------------- - -You can use a MailChimp OAuth token in place of an API key, but -because MailChimp's tokens don't include the datacenter as a suffix, -you must supply it yourself (substitute an actual number for X below): - -```python -ms = MailSnake('A MAILCHIMP OAUTH TOKEN', dc='usX') -ms.ping() # returns "Everything's Chimpy!" just like with an API key -``` - -If you completed the OAuth dance as described in the -[MailChimp OAuth2 documentation](http://apidocs.mailchimp.com/oauth2/), -you should have discovered the datacenter associated with the token -when making the metadata request. - -Additional Request Options --------------------------- - -MailSnake uses [Requests](http://docs.python-requests.org/en/v1.0.0/) for -HTTP. If you require more control over how your request is made, -you may supply a dictionary as the value of `requests_opts` when -constructing an instance of `MailSnake`. This will be passed through (as -kwargs) to `requests.post()`. See the next section for an example. - -Streamed Responses ------------------- - -Since responses from the MailChimp Export API can be quite large, it is -helpful to be able to consume them in a streamed fashion. If you supply -`requests_opts={'stream': True}` when calling MailSnake, a generator is -returned that deserializes and yields each line of the streamed response -as it arrives: - -```python -from mailsnake import MailSnake - -opts = {'stream': True} -export = MailSnake('YOURAPIKEY', api='export', requests_opts=opts) -resp = export.list(id='YOURLISTID') - -lines = 0 -for list_member in resp(): - if lines > 0: # skip header row - print list_member - lines += 1 -``` - -If you are using Requests < 1.0.0, supply `{'prefetch': False}` instead of -`{'stream': True}`. - -Note ----- - -API parameters must be passed by name. For example: - -```python -mcapi.listMemberInfo(id='YOUR LIST ID', email_address='name@example.com') -``` - -API Documentation ------------------ - -Note that in order to use the STS API or Mandrill you first need to -enable the Amazon Simple Email Service or the Mandrill -[integration](https://us4.admin.mailchimp.com/account/integrations/ "MailChimp Integrations") -in MailChimp. - -[MailChimp API v1.3 documentation](http://apidocs.mailchimp.com/api/1.3/ "MCAPI v1.3 Documentation") - -[MailChimp STS API v1.0 documentation](http://apidocs.mailchimp.com/sts/1.0/ "STS API v1.0 Documentation") - -[MailChimp Export API v1.0 documentation](http://apidocs.mailchimp.com/export/1.0/ "Export API v1.0") diff --git a/README.rst b/README.rst index c03ed27..13e431f 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,8 @@ MailSnake Installation ------------ -:: + +.. code-block::bash pip install mailsnake @@ -15,7 +16,7 @@ Usage Basic Ping ~~~~~~~~~~ -:: +.. code-block::python from mailsnake import MailSnake from mailsnake.exceptions import * @@ -29,7 +30,7 @@ Basic Ping Mandrill Ping ~~~~~~~~~~~~~ -:: +.. code-block::python mapi = MailSnake('YOUR MANDRILL API KEY', api='mandrill') mapi.users.ping() # returns "PONG!" @@ -38,7 +39,7 @@ Mandrill Ping STS Example ~~~~~~~~~~~ -:: +.. code-block::python mcsts = MailSnake('YOUR MAILCHIMP API KEY', api='sts') mcsts.GetSendQuota() # returns something like {'Max24HourSend': '10000.0', 'SentLast24Hours': '0.0', 'MaxSendRate': '5.0'} @@ -47,7 +48,7 @@ STS Example Catching Errors ~~~~~~~~~~~~~~~ -:: +.. code-block::python ms = MailSnake( 'my_wrong_mailchimp_api_key_that_does_not_exist') try: @@ -60,6 +61,6 @@ Note API parameters must be passed by name. For example: -:: +.. code-block::python ms.listMemberInfo(id='YOUR LIST ID', email_address='name@email.com') diff --git a/mailsnake/.DS_Store b/mailsnake/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0= 1.0.0) for response streaming - self.stream = requests_opts.get('stream', not prefetch) + self.requests_opts = requests_opts or {} + default_headers = {'User-Agent': 'MailSnake v' + __version__} + if not 'headers' in self.requests_opts: + # If they didn't set any headers, set our defaults for them + self.requests_opts['headers'] = default_headers + elif 'User-Agent' not in self.requests_opts['headers']: + # If they set headers, but didn't include User-Agent.. set it for them + self.requests_opts['headers'].update(default_headers) + + if self.api == 'api' or self.api == 'mandrill': + self.requests_opts['headers'].update({'content-type': 'application/json'}) + else: + self.requests_opts['headers'].update({'content-type': 'application/x-www-form-urlencoded'}) + + self.client = requests.Session() + # Make a copy of the client args and iterate over them + # Pop out all the acceptable args at this point because they will + # Never be used again. + requests_opts_copy = self.requests_opts.copy() + for k, v in requests_opts_copy.items(): + if k in ('cert', 'headers', 'hooks', 'max_redirects', 'proxies'): + setattr(self.client, k, v) + self.requests_opts.pop(k) # Pop, pop! def __repr__(self): if self.api == 'api': @@ -103,24 +115,21 @@ def call(self, method, params=None): data = json.dumps(params) if self.api == 'api': data = requests.utils.quote(data) - headers = {'content-type':'application/json'} + elif self.api == 'export': + data = flatten_data(params) else: data = params - headers = { - 'content-type': 'application/x-www-form-urlencoded' - } try: - if self.api == 'export': - req = requests.post(url, - params=flatten_data(data), - headers=headers, - **self.requests_opts) - else: - req = requests.post(url, - data=data, - headers=headers, - **self.requests_opts) + func = getattr(self.client, method) + + requests_args = {} + for k, v in self.requests_opts.items(): + # Maybe this should be set as a class variable and only done once? + if k in ('timeout', 'allow_redirects', 'stream', 'verify'): + requests_args[k] = v + + req = func(url, **requests_args) except requests.exceptions.RequestException as e: raise HTTPRequestException(e.message) @@ -162,6 +171,7 @@ def get(self, *args, **kwargs): return get.__get__(self) + def flatten_data(data, parent_key=''): items = [] for k, v in data.items(): diff --git a/mailsnake/compat.py b/mailsnake/compat.py new file mode 100644 index 0000000..2ebc1f9 --- /dev/null +++ b/mailsnake/compat.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +""" +mailsnake.compat +~~~~~~~~~~~~~~~~ + +This module contains imports and declarations for seamless Python 2 and +Python 3 compatibility. +""" + +import sys + +_ver = sys.version_info + +#: Python 2.x? +is_py2 = (_ver[0] == 2) + +#: Python 3.x? +is_py3 = (_ver[0] == 3) + +try: + import simplejson as json +except ImportError: + import json + +if is_py2: + str = unicode + basestring = basestring + numeric_types = (int, long, float) + + +elif is_py3: + str = str + basestring = (str, bytes) + numeric_types = (int, float) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6e65da1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +coverage==3.6.0 +requests==1.2.3 +nose-cov==1.6 diff --git a/setup.py b/setup.py index 2cbb6ac..c75f20f 100644 --- a/setup.py +++ b/setup.py @@ -1,28 +1,26 @@ #!/usr/bin/env python -import sys -from setuptools import setup, find_packages +from setuptools import setup -# dep sugar. -_ver = sys.version_info +__author__ = 'John-Kim Murphy' +__version__ = '2.0.0' -if _ver[0] == 2: - dep = ['simplejson', 'requests'] -elif _ver[0] == 3: - dep = ['requests'] +packages = [ + 'mailsnake', +] setup( name='mailsnake', - version='1.6.2', - description='MailChimp API v1.3, STS, Export, Mandrill wrapper for Python.', - long_description=open('README.rst').read(), - author='John-Kim Murphy', + version=__version__, + install_requires=['requests==1.2.3'], + author=__author__, + license=open('LICENSE').read(), url='https://github.com/michaelhelmick/python-mailsnake', - packages=find_packages(), - download_url='http://pypi.python.org/pypi/mailsnake/', keywords='mailsnake mailchimp api wrapper export mandrill sts 1.3 p3k', - zip_safe=True, - install_requires=dep, + description='MailChimp API v1.3, STS, Export, Mandrill wrapper for Python.', + long_description=open('README.rst').read(), + include_package_data=True, + packages=packages, py_modules=['mailsnake'], classifiers=[ 'Development Status :: 5 - Production/Stable', diff --git a/mailsnake/tests/__init__.py b/tests/__init__.py similarity index 100% rename from mailsnake/tests/__init__.py rename to tests/__init__.py diff --git a/tests/config.py b/tests/config.py new file mode 100644 index 0000000..8a0d3ef --- /dev/null +++ b/tests/config.py @@ -0,0 +1,5 @@ +import os + +MAILCHIMP_API_KEY = os.environ.get('MAILCHIMP_API_KEY') +MAILCHIMP_LIST_ID = os.environ.get('MAILCHIMP_LIST_ID') +TEST_EMAIL = os.environ.get('TEST_EMAIL') diff --git a/mailsnake/tests/template.html b/tests/template.html similarity index 100% rename from mailsnake/tests/template.html rename to tests/template.html diff --git a/mailsnake/tests/tests.py b/tests/tests.py similarity index 90% rename from mailsnake/tests/tests.py rename to tests/tests.py index b8003ea..a47da19 100644 --- a/mailsnake/tests/tests.py +++ b/tests/tests.py @@ -4,21 +4,7 @@ from mailsnake import MailSnake, exceptions from random import random -from .secret_keys import MAILCHIMP_API_KEY, MAILCHIMP_LIST_ID, TEST_EMAIL - -""" -To run these tests, do the following: -- Place a file called secret_keys.py under the mailsnake directory - containing the following mailchimp related variables: - * MAILCHIMP_API_KEY - * MAILCHIMP_LIST_ID - * TEST_EMAIL (must be a valid email, i.e. not test@example.com) - You must create a test list in MailChimp and get the ID for use here - because the API does not have a method for creating lists. - -- Install the python 'nose' library -- From the command-line, run 'nosetests' -""" +from .config import MAILCHIMP_API_KEY, MAILCHIMP_LIST_ID, TEST_EMAIL class TestMailChimp(unittest.TestCase): From 35a8d26d4f2a48ce7fc9f93f3e083e2256da0173 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Mon, 1 Jul 2013 20:44:18 -0400 Subject: [PATCH 16/24] Just trying to get a build for travis --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 846141b..01027a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,11 +4,11 @@ python: - 2.7 env: global: - - MAILCHIMP_API_KEY= - - MAILCHIMP_LIST_ID= - - TEST_EMAIL= + - MAILCHIMP_API_KEY=false + - MAILCHIMP_LIST_ID=false + - TEST_EMAIL=false install: pip install -r requirements.txt -script: nosetests -v -w tests/ --logging-filter="mailsnake" --with-cov --cov twython --cov-config .coveragerc --cov-report term-missing +script: nosetests -v -w tests/ --logging-filter="mailsnake" --with-cov --cov mailsnake --cov-config .coveragerc --cov-report term-missing notifications: email: false branches: From acb12c9e4baff9c2634c82bfbcc90f133aee6178 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Mon, 1 Jul 2013 21:00:56 -0400 Subject: [PATCH 17/24] Secure vars for mailchimp creds --- .travis.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 01027a5..990583f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,18 @@ python: - 2.7 env: global: - - MAILCHIMP_API_KEY=false - - MAILCHIMP_LIST_ID=false - - TEST_EMAIL=false + - secure: |- + iYEe6mMWrJbx4IhV8qFIpPCCGgaF++tH0BZEXIkmlTQ5aA0GzdMfO/rLMzlc + /WJNd5oW+JeoLQqSqr7L/1MJlY8rKwAepIWeR747GMOZqgLr5MQgZYCuQuBy + 0RQqc02lwfHt7+v7gDjU7q9/3Doftan3EIo3nHO9RLmHEZf5z2M= + - secure: |- + lJFqR4VUmFg91Gc7nDEKx9wEfxh4eNMM36iMgOQtK4IJo/J3UG9IUkiGknpM + pdDvdVVvOi/Vx4uPtntsTINHMNu2PiRLkE8t139aMA7TpCdZxyyBZHV8Kmcv + iIwUHUx+fury6ulrPDckT41O94tY6qWGEVgr3lyDBk9BnfCObfE= + - secure: |- + 26UppoJguiPcuyPX1x0+jWrmX/rViDBTT1ttqH9cestKtBE7NojS/X5jblMX + ZZngwgChHM85/H+CCN5A8IQQ3Ry5BGCFtyE7aJGhCt3M55i0m6SMa/XP3Rh+ + T1b4CnGi+wGbcxMD232SF5P9nJMRdgsLOyC7/8SVEKB5hbUOgiQ= install: pip install -r requirements.txt script: nosetests -v -w tests/ --logging-filter="mailsnake" --with-cov --cov mailsnake --cov-config .coveragerc --cov-report term-missing notifications: From 8e02d6731b7d5fdc5e12a200b28b7438f1ff6551 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Mon, 1 Jul 2013 21:03:04 -0400 Subject: [PATCH 18/24] Mandrill API key --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 990583f..dab469c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,10 @@ env: 26UppoJguiPcuyPX1x0+jWrmX/rViDBTT1ttqH9cestKtBE7NojS/X5jblMX ZZngwgChHM85/H+CCN5A8IQQ3Ry5BGCFtyE7aJGhCt3M55i0m6SMa/XP3Rh+ T1b4CnGi+wGbcxMD232SF5P9nJMRdgsLOyC7/8SVEKB5hbUOgiQ= + - secure: |- + faFGRraTG802mThDPSs4p2uoESKZ7lvahJD0mnqoB508cWq8ENhFocu+YTxP + S8F7Z314sSdVucLI7HP1XD1dabGlGpbKUBC3p62iKKcUsO9Xen6dfN52FDkS + 0dQR36zvYGRTCSBRjCbGcMJ5oJuoOSQx2qDNp39Nj5idxnmUGZA= install: pip install -r requirements.txt script: nosetests -v -w tests/ --logging-filter="mailsnake" --with-cov --cov mailsnake --cov-config .coveragerc --cov-report term-missing notifications: From a776bcab3f8e8d078a11b99851901da6728e6b07 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Mon, 1 Jul 2013 21:14:46 -0400 Subject: [PATCH 19/24] import __version__ --- mailsnake/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mailsnake/api.py b/mailsnake/api.py index 3386016..fe67f3f 100644 --- a/mailsnake/api.py +++ b/mailsnake/api.py @@ -13,6 +13,7 @@ import requests from .compat import json, basestring +from . import __version__ from .exceptions import * import collections From 7cad4fafd1091bb5117c2b825eca08abfeda9129 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Mon, 1 Jul 2013 21:18:09 -0400 Subject: [PATCH 20/24] Always a post, had the wrong "method" --- mailsnake/api.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mailsnake/api.py b/mailsnake/api.py index fe67f3f..c53d6df 100644 --- a/mailsnake/api.py +++ b/mailsnake/api.py @@ -122,15 +122,13 @@ def call(self, method, params=None): data = params try: - func = getattr(self.client, method) - requests_args = {} for k, v in self.requests_opts.items(): # Maybe this should be set as a class variable and only done once? if k in ('timeout', 'allow_redirects', 'stream', 'verify'): requests_args[k] = v - req = func(url, **requests_args) + req = self.client.post(url, **requests_args) except requests.exceptions.RequestException as e: raise HTTPRequestException(e.message) From 4f98ea05e82e42db449fdbb7f5f34058c223cfbc Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Wed, 24 Jul 2013 13:53:30 -0400 Subject: [PATCH 21/24] All current tests pass --- .gitignore | 2 + .travis.yml | 28 +++++++----- mailsnake/api.py | 3 +- tests/config.py | 3 +- tests/{tests.py => test_core.py} | 78 +++++++++++--------------------- tests/test_exceptions.py | 18 ++++++++ 6 files changed, 66 insertions(+), 66 deletions(-) rename tests/{tests.py => test_core.py} (65%) create mode 100644 tests/test_exceptions.py diff --git a/.gitignore b/.gitignore index 43660ca..8ca33f9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ dist/ *~ *.geany secret_keys.py + +.env diff --git a/.travis.yml b/.travis.yml index dab469c..930f55f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,22 +4,26 @@ python: - 2.7 env: global: +# MAILCHIMP_API_KEY - secure: |- - iYEe6mMWrJbx4IhV8qFIpPCCGgaF++tH0BZEXIkmlTQ5aA0GzdMfO/rLMzlc - /WJNd5oW+JeoLQqSqr7L/1MJlY8rKwAepIWeR747GMOZqgLr5MQgZYCuQuBy - 0RQqc02lwfHt7+v7gDjU7q9/3Doftan3EIo3nHO9RLmHEZf5z2M= + snHmkspOZFgByMfQh6C966JOtkqsT5spgb5Frhps9jLZTOJPzEi/UxPSPdxQ + b1iSNn30lYgBVarGao5OW5iOAFea2iQurBtHtuyhOiWl+zwm6fuuD8ZHmW4R + QzdPsB5TOG9RfDC5W5+km5vNsXvFV9GhWwUoTJ0gKxJkW+WQLjE= +# MAILCHIMP_LIST_ID - secure: |- - lJFqR4VUmFg91Gc7nDEKx9wEfxh4eNMM36iMgOQtK4IJo/J3UG9IUkiGknpM - pdDvdVVvOi/Vx4uPtntsTINHMNu2PiRLkE8t139aMA7TpCdZxyyBZHV8Kmcv - iIwUHUx+fury6ulrPDckT41O94tY6qWGEVgr3lyDBk9BnfCObfE= + GjWNYY0kSA5YQXdvymWcUQA8F0uNGLiwl06htyEbY/hG0DrdfJYzYNvos/CS + 8JgdIwvSopXmQaalOEjUxL6yAupZqRgeIzyR/VZY521/Wl9ZKaoTBnufJCH1 + wuGQdb49Cg/VkQ+LlO3+02S3qOq1x8Zf4572NVEriomKt8wP9nI= +# TEST_RECIPIENT_EMAIL - secure: |- - 26UppoJguiPcuyPX1x0+jWrmX/rViDBTT1ttqH9cestKtBE7NojS/X5jblMX - ZZngwgChHM85/H+CCN5A8IQQ3Ry5BGCFtyE7aJGhCt3M55i0m6SMa/XP3Rh+ - T1b4CnGi+wGbcxMD232SF5P9nJMRdgsLOyC7/8SVEKB5hbUOgiQ= + e3dE5DwzjvpEhLu2nO7pOIBstBe0ahS+Ugy6tzPX8kIlSLjzNIPg3GWokdw3 + rwOuyWd/iqkHOIUWPX4XrVzU08S90tMStHfbfswCnWG46+XuW9FHq7B/MpPJ + v0ePdq/BNIO2vMX5O6Ku+F/d6LWRFJkLcft9d14nV1960iVkeVA= +# MANDRILL_API_KEY - secure: |- - faFGRraTG802mThDPSs4p2uoESKZ7lvahJD0mnqoB508cWq8ENhFocu+YTxP - S8F7Z314sSdVucLI7HP1XD1dabGlGpbKUBC3p62iKKcUsO9Xen6dfN52FDkS - 0dQR36zvYGRTCSBRjCbGcMJ5oJuoOSQx2qDNp39Nj5idxnmUGZA= + KSME137xMK3VLxEpBbKoEpdUUUFlwnQglRM7hoxLgGjksQh5V++HclpvAXoq + hQ9arqmgt62Tv8W5m2vpHxoH7az6ErSP2JqykNynkoNfr8elFithBb93i9PP + 4CrSvdCV05vcDm5BRUZ2OqA2/t//DPpOxdO4emxTXMfeOz2KDTs= install: pip install -r requirements.txt script: nosetests -v -w tests/ --logging-filter="mailsnake" --with-cov --cov mailsnake --cov-config .coveragerc --cov-report term-missing notifications: diff --git a/mailsnake/api.py b/mailsnake/api.py index c53d6df..349c91f 100644 --- a/mailsnake/api.py +++ b/mailsnake/api.py @@ -128,13 +128,14 @@ def call(self, method, params=None): if k in ('timeout', 'allow_redirects', 'stream', 'verify'): requests_args[k] = v - req = self.client.post(url, **requests_args) + req = self.client.post(url, data=data, **requests_args) except requests.exceptions.RequestException as e: raise HTTPRequestException(e.message) if req.status_code != 200: raise HTTPRequestException(req.status_code) + self.stream = False try: if self.stream: def stream(): diff --git a/tests/config.py b/tests/config.py index 8a0d3ef..a44f7c0 100644 --- a/tests/config.py +++ b/tests/config.py @@ -2,4 +2,5 @@ MAILCHIMP_API_KEY = os.environ.get('MAILCHIMP_API_KEY') MAILCHIMP_LIST_ID = os.environ.get('MAILCHIMP_LIST_ID') -TEST_EMAIL = os.environ.get('TEST_EMAIL') +TEST_RECIPIENT_EMAIL = os.environ.get('TEST_RECIPIENT_EMAIL') +MANDRILL_API_KEY = os.environ.get('MANDRILL_API_KEY') diff --git a/tests/tests.py b/tests/test_core.py similarity index 65% rename from tests/tests.py rename to tests/test_core.py index a47da19..99dbd68 100644 --- a/tests/tests.py +++ b/tests/test_core.py @@ -4,42 +4,22 @@ from mailsnake import MailSnake, exceptions from random import random -from .config import MAILCHIMP_API_KEY, MAILCHIMP_LIST_ID, TEST_EMAIL +from .config import ( + MAILCHIMP_API_KEY, MAILCHIMP_LIST_ID, TEST_RECIPIENT_EMAIL, + MANDRILL_API_KEY +) -class TestMailChimp(unittest.TestCase): +class MailSnakeTestCase(unittest.TestCase): def setUp(self): self.mcapi = MailSnake(MAILCHIMP_API_KEY) - self.mcapi.listBatchUnsubscribe( - id=MAILCHIMP_LIST_ID, emails=[TEST_EMAIL], delete_member=False, - send_goodbye=False, send_notify=False) - - def _subscribe(self, email=None): - return self.mcapi.listSubscribe( - id=MAILCHIMP_LIST_ID, email_address=email if email else TEST_EMAIL, - double_optin=False, send_welcome=False) - - def _unsubscribe(self, email=None): - return self.mcapi.listUnsubscribe( - id=MAILCHIMP_LIST_ID, email_address=email if email else TEST_EMAIL, - send_goodbye=False, send_notify=False) - - def _set_api_key(self, apikey='', reset=False): - - # If dc is true then add the datacenter provided by the MAILCHIMP_API_KEY variable - if reset is True: - apikey = MAILCHIMP_API_KEY - - self.mcapi = MailSnake(apikey) - - -class TestMailChimpAPI(TestMailChimp): - # Helper Methods def test_ping(self): + """Testing ping to MailChimp succeeds""" + print self.mcapi.ping() assert self.mcapi.ping() == "Everything's Chimpy!" - def test_chimpChatter(self): + def test_chimp_chatter(self): chimp_chatter = self.mcapi.chimpChatter() # Check that the result is a list assert isinstance(chimp_chatter, MutableSequence) @@ -50,14 +30,19 @@ def test_chimpChatter(self): # Campaign Related Methods - def test_campaignCreateDelete(self): + def test_campaign_create_delete(self): template_id = self._add_test_template() from_email = self.mcapi.getAccountDetails()['contact']['email'] options = { - 'list_id': MAILCHIMP_LIST_ID, 'subject': 'testing', - 'from_email': from_email, 'from_name': 'Test From', - 'to_name': 'Test To', 'template_id': template_id, - 'inline_css': True, 'generate_text': True, 'title': 'testing' + 'list_id': MAILCHIMP_LIST_ID, + 'subject': 'testing', + 'from_email': from_email, + 'from_name': 'Test From', + 'to_name': 'Test To', + 'template_id': template_id, + 'inline_css': True, + 'generate_text': True, + 'title': 'testing' } test_content = '%f' % random() content = {'html_std_content00': test_content} @@ -85,27 +70,15 @@ def test_lists(self): assert 'total' in lists assert 'data' in lists - def test_lists_exception(self): - - # Test with key including dc - self._set_api_key("WRONGKEY-us1") - self.assertRaises(exceptions.InvalidApiKeyException, self.mcapi.lists) - - # Test with key without dc - self._set_api_key("WRONGKEY") - self.assertRaises(exceptions.InvalidApiKeyException, self.mcapi.lists) - - - # Reset apikey to MAILCHIMP_API_KEY - self._set_api_key(reset=True) - - def test_listActivity(self): + def test_list_activity(self): activity = self.mcapi.listActivity(id=MAILCHIMP_LIST_ID) assert isinstance(activity, list) - def test_listSubscribeUnsubscribe(self): - assert self._subscribe() - assert self._unsubscribe() + def test_list_subscribe_unsubscribe(self): + assert self.mcapi.listSubscribe(id=MAILCHIMP_LIST_ID, email_address=TEST_RECIPIENT_EMAIL, + double_optin=False, send_welcome=False) + assert self.mcapi.listUnsubscribe(id=MAILCHIMP_LIST_ID, email_address=TEST_RECIPIENT_EMAIL, + send_goodbye=False, send_notify=False) # Template Related Methods @@ -132,7 +105,7 @@ def _add_test_template(self): template_name = '%s%i' % (base_name, index) return self.mcapi.templateAdd(name=template_name, html=html) - +''' class TestExportAPI(TestMailChimp): def setUp(self): super(TestExportAPI, self).setUp() @@ -152,3 +125,4 @@ def test_list(self): if lines > 0: assert isinstance(list_member, list) lines += 1 +''' diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py new file mode 100644 index 0000000..fafb8db --- /dev/null +++ b/tests/test_exceptions.py @@ -0,0 +1,18 @@ +from mailsnake import MailSnake, exceptions + +from .config import ( + MAILCHIMP_API_KEY, MAILCHIMP_LIST_ID, TEST_RECIPIENT_EMAIL, + MANDRILL_API_KEY +) + +import unittest + + +class MailSnakeExceptionsTestCase(unittest.TestCase): + def setUp(self): + self.mcapi = MailSnake(MAILCHIMP_API_KEY) + self.bad_mcapi = MailSnake('WRONGKEY-us1') + + def test_invalid_apikey_exception(self): + """Test passing the wrong API Key will raise an InvalidApiKeyException""" + self.assertRaises(exceptions.InvalidApiKeyException, self.bad_mcapi.lists) From 0205dcc55b6f5f30f4865756ac760ca666d881d7 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Wed, 24 Jul 2013 13:59:38 -0400 Subject: [PATCH 22/24] Add some pretty images to README [ci skip] --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index 13e431f..c950724 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,13 @@ MailSnake ========= +.. image:: https://badge.fury.io/py/mailsnake.png + :target: http://badge.fury.io/py/mailsnake +.. image:: https://travis-ci.org/michaelhelmick/python-mailsnake.png?branch=master + :target: https://travis-ci.org/michaelhelmick/python-mailsnake +.. image:: https://pypip.in/d/mailsnake/badge.png + :target: https://crate.io/packages/mailsnake/ + ``MailSnake`` is a Python wrapper for `MailChimp API 1.3 `_ (as well as the `STS API `_, `Export API `_, and `Mandrill API `_) (Now with support for Python 3) Installation From 8ea2d6b1415a0061fc8913b8043a6a8c92384910 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Wed, 24 Jul 2013 14:02:24 -0400 Subject: [PATCH 23/24] Fix README [ci skip] --- README.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index c950724..b0c06d3 100644 --- a/README.rst +++ b/README.rst @@ -13,9 +13,9 @@ MailSnake Installation ------------ -.. code-block::bash +.. code-block:: bash - pip install mailsnake + $ pip install mailsnake Usage ----- @@ -23,7 +23,7 @@ Usage Basic Ping ~~~~~~~~~~ -.. code-block::python +.. code-block:: python from mailsnake import MailSnake from mailsnake.exceptions import * @@ -37,7 +37,7 @@ Basic Ping Mandrill Ping ~~~~~~~~~~~~~ -.. code-block::python +.. code-block:: python mapi = MailSnake('YOUR MANDRILL API KEY', api='mandrill') mapi.users.ping() # returns "PONG!" @@ -46,7 +46,7 @@ Mandrill Ping STS Example ~~~~~~~~~~~ -.. code-block::python +.. code-block:: python mcsts = MailSnake('YOUR MAILCHIMP API KEY', api='sts') mcsts.GetSendQuota() # returns something like {'Max24HourSend': '10000.0', 'SentLast24Hours': '0.0', 'MaxSendRate': '5.0'} @@ -55,7 +55,7 @@ STS Example Catching Errors ~~~~~~~~~~~~~~~ -.. code-block::python +.. code-block:: python ms = MailSnake( 'my_wrong_mailchimp_api_key_that_does_not_exist') try: @@ -68,6 +68,6 @@ Note API parameters must be passed by name. For example: -.. code-block::python +.. code-block:: python ms.listMemberInfo(id='YOUR LIST ID', email_address='name@email.com') From 0b3c4bc05bd249273f317033e76c1b16a79291da Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Sat, 27 Jul 2013 14:30:38 -0400 Subject: [PATCH 24/24] Fixes #7 [ci skip] --- mailsnake/api.py | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/mailsnake/api.py b/mailsnake/api.py index 349c91f..2a79cbf 100644 --- a/mailsnake/api.py +++ b/mailsnake/api.py @@ -27,13 +27,14 @@ def __init__(self, apikey='', extra_params=None, api='api', api_section='', requests are made, supply a dictionary for requests_opts. This will be passed through to requests.post() as kwargs. """ - self.apikey = apikey ACCEPTED_APIS = ('api', 'sts', 'export', 'mandrill') if not api in ACCEPTED_APIS: raise MailSnakeException('The API "%s" is not supported.') % api self.api = api + self.apikey = apikey + self.dc = self.apikey.split('-')[1] if '-' in self.apikey else dc self.default_params = {'apikey': apikey} extra_params = extra_params or {} @@ -49,10 +50,6 @@ def __init__(self, apikey='', extra_params=None, api='api', api_section='', api, x)) self.default_params.update(extra_params) - if dc: - self.dc = dc - elif '-' in self.apikey: - self.dc = self.apikey.split('-')[1] api_info = { 'api': (self.dc, '.api.', 'mailchimp', '1.3/?method='), 'sts': (self.dc, '.sts.', 'mailchimp', '1.0/'), @@ -135,22 +132,21 @@ def call(self, method, params=None): if req.status_code != 200: raise HTTPRequestException(req.status_code) - self.stream = False - try: - if self.stream: - def stream(): - for line in req.iter_lines(): - # Handle byte arrays in Python 3 - line = line.decode('utf-8') - if line: - yield json.loads(line) - rsp = stream - elif self.api == 'export' and req.text.find('\n') > -1: - rsp = [json.loads(i) for i in req.text.split('\n')[0:-1]] - else: - rsp = json.loads(req.text) - except ValueError as e: - raise ParseException(e.message) + try: + if 'stream' in requests_opts: + def stream(): + for line in req.iter_lines(): + # Handle byte arrays in Python 3 + line = line.decode('utf-8') + if line: + yield json.loads(line) + rsp = stream + elif self.api == 'export' and req.text.find('\n') > -1: + rsp = [json.loads(i) for i in req.text.split('\n')[0:-1]] + else: + rsp = json.loads(req.text) + except ValueError as e: + raise ParseException(e.message) types_ = int, bool, basestring, types.FunctionType if not isinstance(rsp, types_) and 'error' in rsp and 'code' in rsp: