├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── README.rst ├── mailsnake ├── __init__.py ├── exceptions.py └── mailsnake.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build/ 3 | dist/ 4 | *.egg-info 5 | *~ 6 | *.geany 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2012 John-Kim Murphy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MailSnake 2 | ========= 3 | 4 | `MailSnake` is a Python wrapper for MailChimp's The API, STS API, Export API, and the 5 | new Mandrill API. (Now with support for Python 3) 6 | 7 | Installation 8 | ------------ 9 | pip install mailsnake 10 | 11 | Usage 12 | ----- 13 | 14 | ```python 15 | from mailsnake import MailSnake 16 | from mailsnake.exceptions import * 17 | 18 | ms = MailSnake('YOUR MAILCHIMP API KEY') 19 | try: 20 | ms.ping() # returns "Everything's Chimpy!" 21 | except MailSnakeException: 22 | print 'An error occurred. :(' 23 | ``` 24 | 25 | You can also catch specific errors: 26 | 27 | ```python 28 | ms = MailSnake('my_wrong_mailchimp_api_key_that_does_not_exist') 29 | try: 30 | ms.ping() # returns "Everything's Chimpy!" 31 | except InvalidApiKeyException: 32 | print 'You have a bad API key, sorry.' 33 | ``` 34 | The default API is MCAPI, but STS, Export, and Mandrill can be used by 35 | supplying an api argument set to 'sts', 'export', or 'mandrill' 36 | respectively. Here's an example: 37 | 38 | ```python 39 | mcsts = MailSnake('YOUR MAILCHIMP API KEY', api='sts') 40 | mcsts.GetSendQuota() # returns something like {'Max24HourSend': '10000.0', 'SentLast24Hours': '0.0', 'MaxSendRate': '5.0'} 41 | ``` 42 | 43 | Since the Mandrill API is divided into sections, one must take that into 44 | account when using it. Here's an example: 45 | 46 | ```python 47 | mapi = MailSnake('YOUR MANDRILL API KEY', api='mandrill') 48 | mapi.users.ping() # returns 'PONG!' 49 | ``` 50 | 51 | or: 52 | 53 | ```python 54 | mapi_users = MailSnake('YOUR MANDRILL API KEY', api='mandrill', api_section='users') 55 | mapi_users.ping() # returns 'PONG!' 56 | ``` 57 | 58 | Some Mandrill functions have a dash(-) in the name. Since Python 59 | function names can't have dashes in them, use underscores(\_) instead: 60 | 61 | ```python 62 | mapi = MailSnake('YOUR MANDRILL API KEY', api='mandrill') 63 | 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!' 64 | ``` 65 | 66 | OAuth Tokens 67 | ------------ 68 | 69 | You can use a MailChimp OAuth token in place of an API key, but 70 | because MailChimp's tokens don't include the datacenter as a suffix, 71 | you must supply it yourself (substitute an actual number for X below): 72 | 73 | ```python 74 | ms = MailSnake('A MAILCHIMP OAUTH TOKEN', dc='usX') 75 | ms.ping() # returns "Everything's Chimpy!" just like with an API key 76 | ``` 77 | 78 | If you completed the OAuth dance as described in the 79 | [MailChimp OAuth2 documentation](http://apidocs.mailchimp.com/oauth2/), 80 | you should have discovered the datacenter associated with the token 81 | when making the metadata request. 82 | 83 | Additional Request Options 84 | -------------------------- 85 | 86 | MailSnake uses [Requests](http://docs.python-requests.org/en/v1.0.0/) for 87 | HTTP. If you require more control over how your request is made, 88 | you may supply a dictionary as the value of `requests_opts` when 89 | constructing an instance of `MailSnake`. This will be passed through (as 90 | kwargs) to `requests.post()`. See the next section for an example. 91 | 92 | Streamed Responses 93 | ------------------ 94 | 95 | Since responses from the MailChimp Export API can be quite large, it is 96 | helpful to be able to consume them in a streamed fashion. If you supply 97 | `requests_opts={'stream': True}` when calling MailSnake, a generator is 98 | returned that deserializes and yields each line of the streamed response 99 | as it arrives: 100 | 101 | ```python 102 | from mailsnake import MailSnake 103 | 104 | opts = {'stream': True} 105 | export = MailSnake('YOURAPIKEY', api='export', requests_opts=opts) 106 | resp = export.list(id='YOURLISTID') 107 | 108 | lines = 0 109 | for list_member in resp(): 110 | if lines > 0: # skip header row 111 | print list_member 112 | lines += 1 113 | ``` 114 | 115 | If you are using Requests < 1.0.0, supply `{'prefetch': False}` instead of 116 | `{'stream': True}`. 117 | 118 | Note 119 | ---- 120 | 121 | API parameters must be passed by name. For example: 122 | 123 | ```python 124 | mcapi.listMemberInfo(id='YOUR LIST ID', email_address='name@example.com') 125 | ``` 126 | 127 | API Documentation 128 | ----------------- 129 | 130 | Note that in order to use the STS API or Mandrill you first need to 131 | enable the Amazon Simple Email Service or the Mandrill 132 | [integration](https://us4.admin.mailchimp.com/account/integrations/ "MailChimp Integrations") 133 | in MailChimp. 134 | 135 | [MailChimp API v1.3 documentation](http://apidocs.mailchimp.com/api/1.3/ "MCAPI v1.3 Documentation") 136 | 137 | [MailChimp STS API v1.0 documentation](http://apidocs.mailchimp.com/sts/1.0/ "STS API v1.0 Documentation") 138 | 139 | [MailChimp Export API v1.0 documentation](http://apidocs.mailchimp.com/export/1.0/ "Export API v1.0") 140 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | MailSnake 2 | ========= 3 | 4 | ``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) 5 | 6 | Installation 7 | ------------ 8 | :: 9 | 10 | pip install mailsnake 11 | 12 | Usage 13 | ----- 14 | 15 | Basic Ping 16 | ~~~~~~~~~~ 17 | 18 | :: 19 | 20 | from mailsnake import MailSnake 21 | from mailsnake.exceptions import * 22 | 23 | ms = MailSnake('YOUR MAILCHIMP API KEY') 24 | try: 25 | ms.ping() # returns "Everything's Chimpy!" 26 | except MailSnakeException: 27 | print 'An error occurred. :(' 28 | 29 | Mandrill Ping 30 | ~~~~~~~~~~~~~ 31 | 32 | :: 33 | 34 | mapi = MailSnake('YOUR MANDRILL API KEY', api='mandrill') 35 | mapi.users.ping() # returns "PONG!" 36 | 37 | 38 | STS Example 39 | ~~~~~~~~~~~ 40 | 41 | :: 42 | 43 | mcsts = MailSnake('YOUR MAILCHIMP API KEY', api='sts') 44 | mcsts.GetSendQuota() # returns something like {'Max24HourSend': '10000.0', 'SentLast24Hours': '0.0', 'MaxSendRate': '5.0'} 45 | 46 | 47 | Catching Errors 48 | ~~~~~~~~~~~~~~~ 49 | 50 | :: 51 | 52 | ms = MailSnake( 'my_wrong_mailchimp_api_key_that_does_not_exist') 53 | try: 54 | ms.ping() # returns "Everything's Chimpy!" 55 | except InvalidApiKeyException: 56 | print 'You have a bad API key, sorry.' 57 | 58 | Note 59 | ---- 60 | 61 | API parameters must be passed by name. For example: 62 | 63 | :: 64 | 65 | ms.listMemberInfo(id='YOUR LIST ID', email_address='name@email.com') 66 | -------------------------------------------------------------------------------- /mailsnake/__init__.py: -------------------------------------------------------------------------------- 1 | from .mailsnake import * 2 | 3 | __author__ = 'John-Kim Murphy' 4 | __credits__ = [ 5 | 'John-Kim Murphy', 6 | 'Michael Helmick', 7 | 'Brad Pitcher', 8 | 'vlinhart', 9 | 'starenka', 10 | 'Ryan Tucker' 11 | ] 12 | __version__ = '1.6.4' 13 | 14 | __all__ = ['MailSnake'] 15 | -------------------------------------------------------------------------------- /mailsnake/exceptions.py: -------------------------------------------------------------------------------- 1 | class MailSnakeException(Exception): pass 2 | 3 | class SystemException(MailSnakeException): pass 4 | class NetworkTimeoutException(SystemException): pass 5 | class HTTPRequestException(SystemException): pass 6 | class ParseException(SystemException): pass 7 | 8 | class UserException(MailSnakeException): pass 9 | class UserUnknownException(UserException): pass 10 | class UserDisabledException(UserException): pass 11 | class UserDoesNotExistException(UserException) :pass 12 | class UserNotApprovedException(UserException) :pass 13 | class InvalidApiKeyException(UserException) :pass 14 | class UserUnderMaintenanceException(UserException) :pass 15 | class InvalidAppKeyException(UserException) :pass 16 | class InvalidIPException(UserException) :pass 17 | class UserDoesExistException(UserException) :pass 18 | 19 | class UserActionException(MailSnakeException): pass 20 | class UserInvalidActionException(UserActionException): pass 21 | class UserMissingEmailException(UserActionException): pass 22 | class UserCannotSendCampaignException(UserActionException): pass 23 | class UserMissingModuleOutboxException(UserActionException): pass 24 | class UserModuleAlreadyPurchasedException(UserActionException): pass 25 | class UserModuleNotPurchasedException(UserActionException): pass 26 | class UserNotEnoughCreditException(UserActionException): pass 27 | class MCInvalidPaymentException(UserActionException): pass 28 | 29 | class ListException(MailSnakeException): pass 30 | class ListDoesNotExistException(ListException): pass 31 | 32 | class ListActionException(MailSnakeException): pass 33 | class ListInvalidInterestFieldTypeException(ListActionException): pass 34 | class ListInvalidOptionException(ListActionException): pass 35 | class ListInvalidUnsubMemberException(ListActionException): pass 36 | class ListInvalidBounceMemberException(ListActionException): pass 37 | class ListAlreadySubscribedException(ListActionException): pass 38 | class ListNotSubscribedException(ListActionException): pass 39 | 40 | class ListImportException(MailSnakeException): pass 41 | class ListInvalidImportException(ListImportException): pass 42 | class MCPastedListDuplicateException(ListImportException): pass 43 | class MCPastedListInvalidImportException(ListImportException): pass 44 | 45 | class ListEmailException(MailSnakeException): pass 46 | class EmailAlreadySubscribedException(ListEmailException): pass 47 | class EmailAlreadyUnsubscribedException(ListEmailException): pass 48 | class EmailNotExistsException(ListEmailException): pass 49 | class EmailNotSubscribedException(ListEmailException): pass 50 | 51 | class ListMergeException(MailSnakeException): pass 52 | class ListMergeFieldRequiredException(ListMergeException): pass 53 | class ListCannotRemoveEmailMergeException(ListMergeException): pass 54 | class ListMergeInvalidMergeIDException(ListMergeException): pass 55 | class ListTooManyMergeFieldsException(ListMergeException): pass 56 | class ListInvalidMergeFieldException(ListMergeException): pass 57 | 58 | class ListInterestGroupException(MailSnakeException): pass 59 | class ListInvalidInterestGroupException(ListInterestGroupException): pass 60 | class ListTooManyInterestGroupsException(ListInterestGroupException): pass 61 | 62 | class CampaignException(MailSnakeException): pass 63 | class CampaignDoesNotExistException(CampaignException): pass 64 | class CampaignStatsNotAvailableException(CampaignException): pass 65 | 66 | class CampaignOptionException(MailSnakeException): pass 67 | class CampaignInvalidAbsplitException(CampaignOptionException): pass 68 | class CampaignInvalidContentException(CampaignOptionException): pass 69 | class CampaignInvalidOptionException(CampaignOptionException): pass 70 | class CampaignInvalidStatusException(CampaignOptionException): pass 71 | class CampaignNotSavedException(CampaignOptionException): pass 72 | class CampaignInvalidSegmentException(CampaignOptionException): pass 73 | class CampaignInvalidRssException(CampaignOptionException): pass 74 | class CampaignInvalidAutoException(CampaignOptionException): pass 75 | class MCContentImportInvalidArchiveException(CampaignOptionException): pass 76 | class CampaignBounceMissingException(CampaignOptionException): pass 77 | 78 | class CampaignEcommException(MailSnakeException): pass 79 | class InvalidEcommOrderException(CampaignEcommException): pass 80 | 81 | class CampaignAbsplitException(MailSnakeException): pass 82 | class AbsplitUnknownErrorException(CampaignAbsplitException): pass 83 | class AbsplitUnknownSplitTestException(CampaignAbsplitException): pass 84 | class AbsplitUnknownTestTypeException(CampaignAbsplitException): pass 85 | class AbsplitUnknownWaitUnitException(CampaignAbsplitException): pass 86 | class AbsplitUnknownWinnerTypeException(CampaignAbsplitException): pass 87 | class AbsplitWinnerNotSelectedException(CampaignAbsplitException): pass 88 | 89 | class GenericValidationException(MailSnakeException): pass 90 | class InvalidAnalyticsException(GenericValidationException): pass 91 | class InvalidDateTimeException(GenericValidationException): pass 92 | class InvalidEmailException(GenericValidationException): pass 93 | class InvalidSendTypeException(GenericValidationException): pass 94 | class InvalidTemplateException(GenericValidationException): pass 95 | class InvalidTrackingOptionsException(GenericValidationException): pass 96 | class InvalidOptionsException(GenericValidationException): pass 97 | class InvalidFolderException(GenericValidationException): pass 98 | class InvalidURLException(GenericValidationException): pass 99 | 100 | class GenericUnknownException(MailSnakeException): pass 101 | class ModuleUnknownException(GenericUnknownException): pass 102 | class MonthlyPlanUnknownException(GenericUnknownException): pass 103 | class OrderTypeUnknownException(GenericUnknownException): pass 104 | class InvalidPagingLimitException(GenericUnknownException): pass 105 | class InvalidPagingStartException(GenericUnknownException): pass 106 | class MaxSizeReachedException(GenericUnknownException): pass 107 | 108 | _ERROR_MAP = { 109 | # System Related Errors 110 | -32601: SystemException, 111 | -32602: SystemException, 112 | -99: SystemException, 113 | -98: SystemException, 114 | -92: SystemException, 115 | -91: SystemException, 116 | -90: SystemException, 117 | -50: SystemException, 118 | 0: SystemException, 119 | 120 | # 100: User Related Errors 121 | 100: UserUnknownException, 122 | 101: UserDisabledException, 123 | 102: UserDoesNotExistException, 124 | 103: UserNotApprovedException, 125 | 104: InvalidApiKeyException, 126 | 105: UserUnderMaintenanceException, 127 | 106: InvalidAppKeyException, 128 | 107: InvalidIPException, 129 | 108: UserDoesExistException, 130 | 131 | # 120: User - Action Related Errors 132 | 120: UserInvalidActionException, 133 | 121: UserMissingEmailException, 134 | 122: UserCannotSendCampaignException, 135 | 123: UserMissingModuleOutboxException, 136 | 124: UserModuleAlreadyPurchasedException, 137 | 125: UserModuleNotPurchasedException, 138 | 126: UserNotEnoughCreditException, 139 | 127: MCInvalidPaymentException, 140 | 141 | # 200: List Error 142 | 200: ListDoesNotExistException, 143 | 144 | # 210: List - Basic Action Errors 145 | 210: ListInvalidInterestFieldTypeException, 146 | 211: ListInvalidOptionException, 147 | 212: ListInvalidUnsubMemberException, 148 | 213: ListInvalidBounceMemberException, 149 | 214: ListAlreadySubscribedException, 150 | 215: ListNotSubscribedException, 151 | 152 | # 220: List - Import Related Errors 153 | 220: ListInvalidImportException, 154 | 221: MCPastedListDuplicateException, 155 | 222: MCPastedListInvalidImportException, 156 | 157 | # 230: List - Email Related Errors 158 | 230: EmailAlreadySubscribedException, 159 | 231: EmailAlreadyUnsubscribedException, 160 | 232: EmailNotExistsException, 161 | 233: EmailNotSubscribedException, 162 | 163 | # 250: List - Merge Related Errors 164 | 250: ListMergeFieldRequiredException, 165 | 251: ListCannotRemoveEmailMergeException, 166 | 252: ListMergeInvalidMergeIDException, 167 | 253: ListTooManyMergeFieldsException, 168 | 254: ListInvalidMergeFieldException, 169 | 170 | # 270: List - Interest Group Related Errors 171 | 270: ListInvalidInterestGroupException, 172 | 271: ListTooManyInterestGroupsException, 173 | 174 | # 300: Campaign Related Errors 175 | 300: CampaignDoesNotExistException, 176 | 301: CampaignStatsNotAvailableException, 177 | 178 | # 310: Campaign - Option Related Errors 179 | 310: CampaignInvalidAbsplitException, 180 | 311: CampaignInvalidContentException, 181 | 312: CampaignInvalidOptionException, 182 | 313: CampaignInvalidStatusException, 183 | 314: CampaignNotSavedException, 184 | 315: CampaignInvalidSegmentException, 185 | 316: CampaignInvalidRssException, 186 | 317: CampaignInvalidAutoException, 187 | 318: MCContentImportInvalidArchiveException, 188 | 319: CampaignBounceMissingException, 189 | 190 | # 330: Campaign - Ecomm Errors 191 | 330: InvalidEcommOrderException, 192 | 193 | # 350: Campaign - Absplit Related Errors 194 | 350: AbsplitUnknownErrorException, 195 | 351: AbsplitUnknownSplitTestException, 196 | 352: AbsplitUnknownTestTypeException, 197 | 353: AbsplitUnknownWaitUnitException, 198 | 354: AbsplitUnknownWinnerTypeException, 199 | 355: AbsplitWinnerNotSelectedException, 200 | 201 | # 500: Generic Validation Errors 202 | 500: InvalidAnalyticsException, 203 | 501: InvalidDateTimeException, 204 | 502: InvalidEmailException, 205 | 503: InvalidSendTypeException, 206 | 504: InvalidTemplateException, 207 | 505: InvalidTrackingOptionsException, 208 | 506: InvalidOptionsException, 209 | 507: InvalidFolderException, 210 | 508: InvalidURLException, 211 | 212 | # 550: Generic Unknown Errors 213 | 550: ModuleUnknownException, 214 | 551: MonthlyPlanUnknownException, 215 | 552: OrderTypeUnknownException, 216 | 553: InvalidPagingLimitException, 217 | 554: InvalidPagingStartException, 218 | 555: MaxSizeReachedException, 219 | } 220 | 221 | 222 | def exception_for_code(code): 223 | return _ERROR_MAP[code] 224 | -------------------------------------------------------------------------------- /mailsnake/mailsnake.py: -------------------------------------------------------------------------------- 1 | """ MailSnake """ 2 | import collections 3 | import requests 4 | import types 5 | from requests.compat import basestring 6 | 7 | try: 8 | import simplejson as json 9 | except ImportError: 10 | try: 11 | import json 12 | except ImportError: 13 | try: 14 | from django.utils import simplejson as json 15 | except ImportError: 16 | raise ImportError('A json library is required to use ' + \ 17 | 'this python library. Lol, yay for ' + \ 18 | 'being verbose. ;)') 19 | 20 | from .exceptions import * 21 | 22 | 23 | class MailSnake(object): 24 | def __init__(self, 25 | apikey='', 26 | extra_params=None, 27 | api='api', 28 | api_section='', 29 | requests_opts={}, 30 | dc=None): 31 | """Cache API key and address. For additional control over how 32 | requests are made, supply a dictionary for requests_opts. This will 33 | be passed through to requests.post() as kwargs. 34 | """ 35 | self.apikey = apikey 36 | 37 | ACCEPTED_APIS = ('api', 'sts', 'export', 'mandrill') 38 | if not api in ACCEPTED_APIS: 39 | raise MailSnakeException('The API "%s" is not supported.') % api 40 | 41 | self.api = api 42 | 43 | self.default_params = {'apikey': apikey} 44 | extra_params = extra_params or {} 45 | if api == 'mandrill': 46 | self.default_params = {'key': apikey} 47 | if api_section != '': 48 | self.api_section = api_section 49 | else: 50 | # Mandrill divides the api into different sections 51 | for x in ['users', 'messages', 'tags', 'rejects', 52 | 'senders', 'urls', 'templates', 'webhooks']: 53 | setattr(self, x, MailSnake(apikey, extra_params, 54 | api, x)) 55 | self.default_params.update(extra_params) 56 | 57 | if dc: 58 | self.dc = dc 59 | elif '-' in self.apikey: 60 | self.dc = self.apikey.split('-')[1] 61 | api_info = { 62 | 'api': (self.dc, '.api.', 'mailchimp', '1.3/?method='), 63 | 'sts': (self.dc, '.sts.', 'mailchimp', '1.0/'), 64 | 'export': (self.dc, '.api.', 'mailchimp', 'export/1.0/'), 65 | 'mandrill': ('', '', 'mandrillapp', 'api/1.0/'), 66 | } 67 | self.api_url = 'https://%s%s%s.com/%s' % api_info[api] 68 | 69 | self.requests_opts = requests_opts 70 | # Handle both prefetch=False (Requests < 1.0.0) 71 | prefetch = requests_opts.get('prefetch', True) 72 | # and stream=True (Requests >= 1.0.0) for response streaming 73 | self.stream = requests_opts.get('stream', not prefetch) 74 | 75 | def __repr__(self): 76 | if self.api == 'api': 77 | api = 'API v3' 78 | elif self.api == 'sts': 79 | api = self.api.upper() + ' API' 80 | else: 81 | api = self.api.capitalize() + ' API' 82 | 83 | return '' % (api, self.apikey) 84 | 85 | def call(self, method, params=None): 86 | """Call the appropriate MailChimp API method with supplied 87 | params. If response streaming is enabled, return a generator 88 | that yields one line of deserialized JSON at a time, otherwise 89 | simply deserialize and return the entire JSON response body. 90 | """ 91 | url = self.api_url 92 | if self.api == 'mandrill': 93 | url += (self.api_section + '/' + method + '.json') 94 | elif self.api == 'sts': 95 | url += (method + '.json/') 96 | else: 97 | url += method 98 | 99 | params = params or {} 100 | params.update(self.default_params) 101 | 102 | if self.api == 'api' or self.api == 'mandrill': 103 | data = json.dumps(params) 104 | if self.api == 'api': 105 | data = requests.utils.quote(data) 106 | headers = {'content-type':'application/json'} 107 | else: 108 | data = params 109 | headers = { 110 | 'content-type': 'application/x-www-form-urlencoded' 111 | } 112 | 113 | try: 114 | if self.api == 'export': 115 | req = requests.post(url, 116 | params=flatten_data(data), 117 | headers=headers, 118 | **self.requests_opts) 119 | else: 120 | req = requests.post(url, 121 | data=data, 122 | headers=headers, 123 | **self.requests_opts) 124 | except requests.exceptions.RequestException as e: 125 | raise HTTPRequestException(e.message) 126 | 127 | if req.status_code != 200: 128 | raise HTTPRequestException(json.dumps({ 129 | 'status_code': req.status_code, 130 | 'reason': req.reason, 131 | 'url': url, 132 | })) 133 | 134 | try: 135 | if self.stream: 136 | def stream(): 137 | for line in req.iter_lines(): 138 | # Handle byte arrays in Python 3 139 | line = line.decode('utf-8') 140 | if line: 141 | yield json.loads(line) 142 | rsp = stream 143 | elif self.api == 'export' and req.text.find('\n') > -1: 144 | rsp = [json.loads(i) for i in req.text.split('\n')[0:-1]] 145 | else: 146 | rsp = json.loads(req.text) 147 | except ValueError as e: 148 | raise ParseException(e.message) 149 | 150 | types_ = int, bool, basestring, types.FunctionType 151 | if not isinstance(rsp, types_) and 'error' in rsp and 'code' in rsp: 152 | try: 153 | Err = exception_for_code(rsp['code']) 154 | except KeyError: 155 | raise SystemException(rsp['error']) 156 | raise Err(rsp['error']) 157 | 158 | return rsp 159 | 160 | def __getattr__(self, method_name): 161 | def get(self, *args, **kwargs): 162 | params = dict((i, j) for (i, j) in enumerate(args)) 163 | params.update(kwargs) 164 | # Some mandrill functions use - in the name 165 | return self.call(method_name.replace('_', '-'), params) 166 | 167 | return get.__get__(self) 168 | 169 | def flatten_data(data, parent_key=''): 170 | items = [] 171 | for k, v in data.items(): 172 | new_key = ('%s[%s]' % (parent_key, k)) if parent_key else k 173 | if isinstance(v, collections.MutableMapping): 174 | items.extend(flatten_data(v, new_key).items()) 175 | elif isinstance(v, collections.MutableSequence): 176 | new_v = [] 177 | for v_item in v: 178 | new_v.append((len(new_v), v_item)) 179 | items.extend(flatten_data(dict(new_v), new_key).items()) 180 | else: 181 | items.append((new_key, v)) 182 | return dict(items) 183 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | from setuptools import setup, find_packages 5 | 6 | # dep sugar. 7 | _ver = sys.version_info 8 | 9 | if _ver[0] == 2: 10 | dep = ['simplejson', 'requests'] 11 | elif _ver[0] == 3: 12 | dep = ['requests'] 13 | 14 | setup( 15 | name='mailsnake', 16 | version='1.6.4', 17 | description='MailChimp API v1.3, STS, Export, Mandrill wrapper for Python.', 18 | long_description=open('README.rst').read(), 19 | author='John-Kim Murphy', 20 | url='https://github.com/michaelhelmick/python-mailsnake', 21 | packages=find_packages(), 22 | download_url='http://pypi.python.org/pypi/mailsnake/', 23 | keywords='mailsnake mailchimp api wrapper export mandrill sts 1.3 p3k', 24 | zip_safe=True, 25 | install_requires=dep, 26 | py_modules=['mailsnake'], 27 | classifiers=[ 28 | 'Development Status :: 5 - Production/Stable', 29 | 'Intended Audience :: Developers', 30 | 'License :: OSI Approved :: MIT License', 31 | 'Natural Language :: English', 32 | 'Operating System :: OS Independent', 33 | 'Programming Language :: Python', 34 | 'Programming Language :: Python :: 2', 35 | 'Programming Language :: Python :: 3', 36 | 'Topic :: Software Development :: Libraries :: Python Modules' 37 | ] 38 | ) 39 | --------------------------------------------------------------------------------