├── .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 |
--------------------------------------------------------------------------------