Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1298)

Side by Side Diff: third_party/boto/ses/connection.py

Issue 12633019: Added boto/ to depot_tools/third_party (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Moved boto down by one Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/boto/ses/__init__.py ('k') | third_party/boto/ses/exceptions.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright (c) 2010 Mitch Garnaat http://garnaat.org/
2 # Copyright (c) 2011 Harry Marr http://hmarr.com/
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining a
5 # copy of this software and associated documentation files (the
6 # "Software"), to deal in the Software without restriction, including
7 # without limitation the rights to use, copy, modify, merge, publish, dis-
8 # tribute, sublicense, and/or sell copies of the Software, and to permit
9 # persons to whom the Software is furnished to do so, subject to the fol-
10 # lowing conditions:
11 #
12 # The above copyright notice and this permission notice shall be included
13 # in all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
17 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
18 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 # 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
21 # IN THE SOFTWARE.
22 import re
23 import urllib
24 import base64
25
26 from boto.connection import AWSAuthConnection
27 from boto.exception import BotoServerError
28 from boto.regioninfo import RegionInfo
29 import boto
30 import boto.jsonresponse
31 from boto.ses import exceptions as ses_exceptions
32
33
34 class SESConnection(AWSAuthConnection):
35
36 ResponseError = BotoServerError
37 DefaultRegionName = 'us-east-1'
38 DefaultRegionEndpoint = 'email.us-east-1.amazonaws.com'
39 APIVersion = '2010-12-01'
40
41 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None,
42 is_secure=True, port=None, proxy=None, proxy_port=None,
43 proxy_user=None, proxy_pass=None, debug=0,
44 https_connection_factory=None, region=None, path='/',
45 security_token=None, validate_certs=True):
46 if not region:
47 region = RegionInfo(self, self.DefaultRegionName,
48 self.DefaultRegionEndpoint)
49 self.region = region
50 AWSAuthConnection.__init__(self, self.region.endpoint,
51 aws_access_key_id, aws_secret_access_key,
52 is_secure, port, proxy, proxy_port,
53 proxy_user, proxy_pass, debug,
54 https_connection_factory, path,
55 security_token=security_token,
56 validate_certs=validate_certs)
57
58 def _required_auth_capability(self):
59 return ['ses']
60
61 def _build_list_params(self, params, items, label):
62 """Add an AWS API-compatible parameter list to a dictionary.
63
64 :type params: dict
65 :param params: The parameter dictionary
66
67 :type items: list
68 :param items: Items to be included in the list
69
70 :type label: string
71 :param label: The parameter list's name
72 """
73 if isinstance(items, basestring):
74 items = [items]
75 for i in range(1, len(items) + 1):
76 params['%s.%d' % (label, i)] = items[i - 1]
77
78 def _make_request(self, action, params=None):
79 """Make a call to the SES API.
80
81 :type action: string
82 :param action: The API method to use (e.g. SendRawEmail)
83
84 :type params: dict
85 :param params: Parameters that will be sent as POST data with the API
86 call.
87 """
88 ct = 'application/x-www-form-urlencoded; charset=UTF-8'
89 headers = {'Content-Type': ct}
90 params = params or {}
91 params['Action'] = action
92
93 for k, v in params.items():
94 if isinstance(v, unicode): # UTF-8 encode only if it's Unicode
95 params[k] = v.encode('utf-8')
96
97 response = super(SESConnection, self).make_request(
98 'POST',
99 '/',
100 headers=headers,
101 data=urllib.urlencode(params)
102 )
103 body = response.read()
104 if response.status == 200:
105 list_markers = ('VerifiedEmailAddresses', 'Identities',
106 'VerificationAttributes', 'SendDataPoints')
107 item_markers = ('member', 'item', 'entry')
108
109 e = boto.jsonresponse.Element(list_marker=list_markers,
110 item_marker=item_markers)
111 h = boto.jsonresponse.XmlHandler(e, None)
112 h.parse(body)
113 return e
114 else:
115 # HTTP codes other than 200 are considered errors. Go through
116 # some error handling to determine which exception gets raised,
117 self._handle_error(response, body)
118
119 def _handle_error(self, response, body):
120 """
121 Handle raising the correct exception, depending on the error. Many
122 errors share the same HTTP response code, meaning we have to get really
123 kludgey and do string searches to figure out what went wrong.
124 """
125 boto.log.error('%s %s' % (response.status, response.reason))
126 boto.log.error('%s' % body)
127
128 if "Address blacklisted." in body:
129 # Delivery failures happened frequently enough with the recipient's
130 # email address for Amazon to blacklist it. After a day or three,
131 # they'll be automatically removed, and delivery can be attempted
132 # again (if you write the code to do so in your application).
133 ExceptionToRaise = ses_exceptions.SESAddressBlacklistedError
134 exc_reason = "Address blacklisted."
135 elif "Email address is not verified." in body:
136 # This error happens when the "Reply-To" value passed to
137 # send_email() hasn't been verified yet.
138 ExceptionToRaise = ses_exceptions.SESAddressNotVerifiedError
139 exc_reason = "Email address is not verified."
140 elif "Daily message quota exceeded." in body:
141 # Encountered when your account exceeds the maximum total number
142 # of emails per 24 hours.
143 ExceptionToRaise = ses_exceptions.SESDailyQuotaExceededError
144 exc_reason = "Daily message quota exceeded."
145 elif "Maximum sending rate exceeded." in body:
146 # Your account has sent above its allowed requests a second rate.
147 ExceptionToRaise = ses_exceptions.SESMaxSendingRateExceededError
148 exc_reason = "Maximum sending rate exceeded."
149 elif "Domain ends with dot." in body:
150 # Recipient address ends with a dot/period. This is invalid.
151 ExceptionToRaise = ses_exceptions.SESDomainEndsWithDotError
152 exc_reason = "Domain ends with dot."
153 elif "Local address contains control or whitespace" in body:
154 # I think this pertains to the recipient address.
155 ExceptionToRaise = ses_exceptions.SESLocalAddressCharacterError
156 exc_reason = "Local address contains control or whitespace."
157 elif "Illegal address" in body:
158 # A clearly mal-formed address.
159 ExceptionToRaise = ses_exceptions.SESIllegalAddressError
160 exc_reason = "Illegal address"
161 # The re.search is to distinguish from the
162 # SESAddressNotVerifiedError error above.
163 elif re.search('Identity.*is not verified', body):
164 ExceptionToRaise = ses_exceptions.SESIdentityNotVerifiedError
165 exc_reason = "Identity is not verified."
166 elif "ownership not confirmed" in body:
167 ExceptionToRaise = ses_exceptions.SESDomainNotConfirmedError
168 exc_reason = "Domain ownership is not confirmed."
169 else:
170 # This is either a common AWS error, or one that we don't devote
171 # its own exception to.
172 ExceptionToRaise = self.ResponseError
173 exc_reason = response.reason
174
175 raise ExceptionToRaise(response.status, exc_reason, body)
176
177 def send_email(self, source, subject, body, to_addresses,
178 cc_addresses=None, bcc_addresses=None,
179 format='text', reply_addresses=None,
180 return_path=None, text_body=None, html_body=None):
181 """Composes an email message based on input data, and then immediately
182 queues the message for sending.
183
184 :type source: string
185 :param source: The sender's email address.
186
187 :type subject: string
188 :param subject: The subject of the message: A short summary of the
189 content, which will appear in the recipient's inbox.
190
191 :type body: string
192 :param body: The message body.
193
194 :type to_addresses: list of strings or string
195 :param to_addresses: The To: field(s) of the message.
196
197 :type cc_addresses: list of strings or string
198 :param cc_addresses: The CC: field(s) of the message.
199
200 :type bcc_addresses: list of strings or string
201 :param bcc_addresses: The BCC: field(s) of the message.
202
203 :type format: string
204 :param format: The format of the message's body, must be either "text"
205 or "html".
206
207 :type reply_addresses: list of strings or string
208 :param reply_addresses: The reply-to email address(es) for the
209 message. If the recipient replies to the
210 message, each reply-to address will
211 receive the reply.
212
213 :type return_path: string
214 :param return_path: The email address to which bounce notifications are
215 to be forwarded. If the message cannot be delivered
216 to the recipient, then an error message will be
217 returned from the recipient's ISP; this message
218 will then be forwarded to the email address
219 specified by the ReturnPath parameter.
220
221 :type text_body: string
222 :param text_body: The text body to send with this email.
223
224 :type html_body: string
225 :param html_body: The html body to send with this email.
226
227 """
228 format = format.lower().strip()
229 if body is not None:
230 if format == "text":
231 if text_body is not None:
232 raise Warning("You've passed in both a body and a "
233 "text_body; please choose one or the other.")
234 text_body = body
235 else:
236 if html_body is not None:
237 raise Warning("You've passed in both a body and an "
238 "html_body; please choose one or the other.")
239 html_body = body
240
241 params = {
242 'Source': source,
243 'Message.Subject.Data': subject,
244 }
245
246 if return_path:
247 params['ReturnPath'] = return_path
248
249 if html_body is not None:
250 params['Message.Body.Html.Data'] = html_body
251 if text_body is not None:
252 params['Message.Body.Text.Data'] = text_body
253
254 if(format not in ("text", "html")):
255 raise ValueError("'format' argument must be 'text' or 'html'")
256
257 if(not (html_body or text_body)):
258 raise ValueError("No text or html body found for mail")
259
260 self._build_list_params(params, to_addresses,
261 'Destination.ToAddresses.member')
262 if cc_addresses:
263 self._build_list_params(params, cc_addresses,
264 'Destination.CcAddresses.member')
265
266 if bcc_addresses:
267 self._build_list_params(params, bcc_addresses,
268 'Destination.BccAddresses.member')
269
270 if reply_addresses:
271 self._build_list_params(params, reply_addresses,
272 'ReplyToAddresses.member')
273
274 return self._make_request('SendEmail', params)
275
276 def send_raw_email(self, raw_message, source=None, destinations=None):
277 """Sends an email message, with header and content specified by the
278 client. The SendRawEmail action is useful for sending multipart MIME
279 emails, with attachments or inline content. The raw text of the message
280 must comply with Internet email standards; otherwise, the message
281 cannot be sent.
282
283 :type source: string
284 :param source: The sender's email address. Amazon's docs say:
285
286 If you specify the Source parameter, then bounce notifications and
287 complaints will be sent to this email address. This takes precedence
288 over any Return-Path header that you might include in the raw text of
289 the message.
290
291 :type raw_message: string
292 :param raw_message: The raw text of the message. The client is
293 responsible for ensuring the following:
294
295 - Message must contain a header and a body, separated by a blank line.
296 - All required header fields must be present.
297 - Each part of a multipart MIME message must be formatted properly.
298 - MIME content types must be among those supported by Amazon SES.
299 Refer to the Amazon SES Developer Guide for more details.
300 - Content must be base64-encoded, if MIME requires it.
301
302 :type destinations: list of strings or string
303 :param destinations: A list of destinations for the message.
304
305 """
306
307 if isinstance(raw_message, unicode):
308 raw_message = raw_message.encode('utf-8')
309
310 params = {
311 'RawMessage.Data': base64.b64encode(raw_message),
312 }
313
314 if source:
315 params['Source'] = source
316
317 if destinations:
318 self._build_list_params(params, destinations,
319 'Destinations.member')
320
321 return self._make_request('SendRawEmail', params)
322
323 def list_verified_email_addresses(self):
324 """Fetch a list of the email addresses that have been verified.
325
326 :rtype: dict
327 :returns: A ListVerifiedEmailAddressesResponse structure. Note that
328 keys must be unicode strings.
329 """
330 return self._make_request('ListVerifiedEmailAddresses')
331
332 def get_send_quota(self):
333 """Fetches the user's current activity limits.
334
335 :rtype: dict
336 :returns: A GetSendQuotaResponse structure. Note that keys must be
337 unicode strings.
338 """
339 return self._make_request('GetSendQuota')
340
341 def get_send_statistics(self):
342 """Fetches the user's sending statistics. The result is a list of data
343 points, representing the last two weeks of sending activity.
344
345 Each data point in the list contains statistics for a 15-minute
346 interval.
347
348 :rtype: dict
349 :returns: A GetSendStatisticsResponse structure. Note that keys must be
350 unicode strings.
351 """
352 return self._make_request('GetSendStatistics')
353
354 def delete_verified_email_address(self, email_address):
355 """Deletes the specified email address from the list of verified
356 addresses.
357
358 :type email_adddress: string
359 :param email_address: The email address to be removed from the list of
360 verified addreses.
361
362 :rtype: dict
363 :returns: A DeleteVerifiedEmailAddressResponse structure. Note that
364 keys must be unicode strings.
365 """
366 return self._make_request('DeleteVerifiedEmailAddress', {
367 'EmailAddress': email_address,
368 })
369
370 def verify_email_address(self, email_address):
371 """Verifies an email address. This action causes a confirmation email
372 message to be sent to the specified address.
373
374 :type email_adddress: string
375 :param email_address: The email address to be verified.
376
377 :rtype: dict
378 :returns: A VerifyEmailAddressResponse structure. Note that keys must
379 be unicode strings.
380 """
381 return self._make_request('VerifyEmailAddress', {
382 'EmailAddress': email_address,
383 })
384
385 def verify_domain_dkim(self, domain):
386 """
387 Returns a set of DNS records, or tokens, that must be published in the
388 domain name's DNS to complete the DKIM verification process. These
389 tokens are DNS ``CNAME`` records that point to DKIM public keys hosted
390 by Amazon SES. To complete the DKIM verification process, these tokens
391 must be published in the domain's DNS. The tokens must remain
392 published in order for Easy DKIM signing to function correctly.
393
394 After the tokens are added to the domain's DNS, Amazon SES will be able
395 to DKIM-sign email originating from that domain. To enable or disable
396 Easy DKIM signing for a domain, use the ``SetIdentityDkimEnabled``
397 action. For more information about Easy DKIM, go to the `Amazon SES
398 Developer Guide
399 <http://docs.amazonwebservices.com/ses/latest/DeveloperGuide>`_.
400
401 :type domain: string
402 :param domain: The domain name.
403
404 """
405 return self._make_request('VerifyDomainDkim', {
406 'Domain': domain,
407 })
408
409 def set_identity_dkim_enabled(self, identity, dkim_enabled):
410 """Enables or disables DKIM signing of email sent from an identity.
411
412 * If Easy DKIM signing is enabled for a domain name identity (e.g.,
413 * ``example.com``),
414 then Amazon SES will DKIM-sign all email sent by addresses under that
415 domain name (e.g., ``user@example.com``)
416 * If Easy DKIM signing is enabled for an email address, then Amazon SES
417 will DKIM-sign all email sent by that email address.
418
419 For email addresses (e.g., ``user@example.com``), you can only enable
420 Easy DKIM signing if the corresponding domain (e.g., ``example.com``)
421 has been set up for Easy DKIM using the AWS Console or the
422 ``VerifyDomainDkim`` action.
423
424 :type identity: string
425 :param identity: An email address or domain name.
426
427 :type dkim_enabled: bool
428 :param dkim_enabled: Specifies whether or not to enable DKIM signing.
429
430 """
431 return self._make_request('SetIdentityDkimEnabled', {
432 'Identity': identity,
433 'DkimEnabled': 'true' if dkim_enabled else 'false'
434 })
435
436 def get_identity_dkim_attributes(self, identities):
437 """Get attributes associated with a list of verified identities.
438
439 Given a list of verified identities (email addresses and/or domains),
440 returns a structure describing identity notification attributes.
441
442 :type identities: list
443 :param identities: A list of verified identities (email addresses
444 and/or domains).
445
446 """
447 params = {}
448 self._build_list_params(params, identities, 'Identities.member')
449 return self._make_request('GetIdentityDkimAttributes', params)
450
451 def list_identities(self):
452 """Returns a list containing all of the identities (email addresses
453 and domains) for a specific AWS Account, regardless of
454 verification status.
455
456 :rtype: dict
457 :returns: A ListIdentitiesResponse structure. Note that
458 keys must be unicode strings.
459 """
460 return self._make_request('ListIdentities')
461
462 def get_identity_verification_attributes(self, identities):
463 """Given a list of identities (email addresses and/or domains),
464 returns the verification status and (for domain identities)
465 the verification token for each identity.
466
467 :type identities: list of strings or string
468 :param identities: List of identities.
469
470 :rtype: dict
471 :returns: A GetIdentityVerificationAttributesResponse structure.
472 Note that keys must be unicode strings.
473 """
474 params = {}
475 self._build_list_params(params, identities,
476 'Identities.member')
477 return self._make_request('GetIdentityVerificationAttributes', params)
478
479 def verify_domain_identity(self, domain):
480 """Verifies a domain.
481
482 :type domain: string
483 :param domain: The domain to be verified.
484
485 :rtype: dict
486 :returns: A VerifyDomainIdentityResponse structure. Note that
487 keys must be unicode strings.
488 """
489 return self._make_request('VerifyDomainIdentity', {
490 'Domain': domain,
491 })
492
493 def verify_email_identity(self, email_address):
494 """Verifies an email address. This action causes a confirmation
495 email message to be sent to the specified address.
496
497 :type email_adddress: string
498 :param email_address: The email address to be verified.
499
500 :rtype: dict
501 :returns: A VerifyEmailIdentityResponse structure. Note that keys must
502 be unicode strings.
503 """
504 return self._make_request('VerifyEmailIdentity', {
505 'EmailAddress': email_address,
506 })
507
508 def delete_identity(self, identity):
509 """Deletes the specified identity (email address or domain) from
510 the list of verified identities.
511
512 :type identity: string
513 :param identity: The identity to be deleted.
514
515 :rtype: dict
516 :returns: A DeleteIdentityResponse structure. Note that keys must
517 be unicode strings.
518 """
519 return self._make_request('DeleteIdentity', {
520 'Identity': identity,
521 })
OLDNEW
« no previous file with comments | « third_party/boto/ses/__init__.py ('k') | third_party/boto/ses/exceptions.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698