Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/boto/ses/connection.py @ 0:d30785e31577 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author | guerler |
---|---|
date | Fri, 31 Jul 2020 00:18:57 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:d30785e31577 |
---|---|
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 base64 | |
24 | |
25 from boto.compat import six, urllib | |
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, profile_name=None): | |
46 if not region: | |
47 region = RegionInfo(self, self.DefaultRegionName, | |
48 self.DefaultRegionEndpoint) | |
49 self.region = region | |
50 super(SESConnection, self).__init__(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 profile_name=profile_name) | |
58 | |
59 def _required_auth_capability(self): | |
60 return ['ses'] | |
61 | |
62 def _build_list_params(self, params, items, label): | |
63 """Add an AWS API-compatible parameter list to a dictionary. | |
64 | |
65 :type params: dict | |
66 :param params: The parameter dictionary | |
67 | |
68 :type items: list | |
69 :param items: Items to be included in the list | |
70 | |
71 :type label: string | |
72 :param label: The parameter list's name | |
73 """ | |
74 if isinstance(items, six.string_types): | |
75 items = [items] | |
76 for i in range(1, len(items) + 1): | |
77 params['%s.%d' % (label, i)] = items[i - 1] | |
78 | |
79 def _make_request(self, action, params=None): | |
80 """Make a call to the SES API. | |
81 | |
82 :type action: string | |
83 :param action: The API method to use (e.g. SendRawEmail) | |
84 | |
85 :type params: dict | |
86 :param params: Parameters that will be sent as POST data with the API | |
87 call. | |
88 """ | |
89 ct = 'application/x-www-form-urlencoded; charset=UTF-8' | |
90 headers = {'Content-Type': ct} | |
91 params = params or {} | |
92 params['Action'] = action | |
93 | |
94 for k, v in params.items(): | |
95 if isinstance(v, six.text_type): # UTF-8 encode only if it's Unicode | |
96 params[k] = v.encode('utf-8') | |
97 | |
98 response = super(SESConnection, self).make_request( | |
99 'POST', | |
100 '/', | |
101 headers=headers, | |
102 data=urllib.parse.urlencode(params) | |
103 ) | |
104 body = response.read().decode('utf-8') | |
105 if response.status == 200: | |
106 list_markers = ('VerifiedEmailAddresses', 'Identities', | |
107 'DkimTokens', 'DkimAttributes', | |
108 'VerificationAttributes', 'SendDataPoints') | |
109 item_markers = ('member', 'item', 'entry') | |
110 | |
111 e = boto.jsonresponse.Element(list_marker=list_markers, | |
112 item_marker=item_markers) | |
113 h = boto.jsonresponse.XmlHandler(e, None) | |
114 h.parse(body) | |
115 return e | |
116 else: | |
117 # HTTP codes other than 200 are considered errors. Go through | |
118 # some error handling to determine which exception gets raised, | |
119 self._handle_error(response, body) | |
120 | |
121 def _handle_error(self, response, body): | |
122 """ | |
123 Handle raising the correct exception, depending on the error. Many | |
124 errors share the same HTTP response code, meaning we have to get really | |
125 kludgey and do string searches to figure out what went wrong. | |
126 """ | |
127 boto.log.error('%s %s' % (response.status, response.reason)) | |
128 boto.log.error('%s' % body) | |
129 | |
130 if "Address blacklisted." in body: | |
131 # Delivery failures happened frequently enough with the recipient's | |
132 # email address for Amazon to blacklist it. After a day or three, | |
133 # they'll be automatically removed, and delivery can be attempted | |
134 # again (if you write the code to do so in your application). | |
135 ExceptionToRaise = ses_exceptions.SESAddressBlacklistedError | |
136 exc_reason = "Address blacklisted." | |
137 elif "Email address is not verified." in body: | |
138 # This error happens when the "Reply-To" value passed to | |
139 # send_email() hasn't been verified yet. | |
140 ExceptionToRaise = ses_exceptions.SESAddressNotVerifiedError | |
141 exc_reason = "Email address is not verified." | |
142 elif "Daily message quota exceeded." in body: | |
143 # Encountered when your account exceeds the maximum total number | |
144 # of emails per 24 hours. | |
145 ExceptionToRaise = ses_exceptions.SESDailyQuotaExceededError | |
146 exc_reason = "Daily message quota exceeded." | |
147 elif "Maximum sending rate exceeded." in body: | |
148 # Your account has sent above its allowed requests a second rate. | |
149 ExceptionToRaise = ses_exceptions.SESMaxSendingRateExceededError | |
150 exc_reason = "Maximum sending rate exceeded." | |
151 elif "Domain ends with dot." in body: | |
152 # Recipient address ends with a dot/period. This is invalid. | |
153 ExceptionToRaise = ses_exceptions.SESDomainEndsWithDotError | |
154 exc_reason = "Domain ends with dot." | |
155 elif "Local address contains control or whitespace" in body: | |
156 # I think this pertains to the recipient address. | |
157 ExceptionToRaise = ses_exceptions.SESLocalAddressCharacterError | |
158 exc_reason = "Local address contains control or whitespace." | |
159 elif "Illegal address" in body: | |
160 # A clearly mal-formed address. | |
161 ExceptionToRaise = ses_exceptions.SESIllegalAddressError | |
162 exc_reason = "Illegal address" | |
163 # The re.search is to distinguish from the | |
164 # SESAddressNotVerifiedError error above. | |
165 elif re.search('Identity.*is not verified', body): | |
166 ExceptionToRaise = ses_exceptions.SESIdentityNotVerifiedError | |
167 exc_reason = "Identity is not verified." | |
168 elif "ownership not confirmed" in body: | |
169 ExceptionToRaise = ses_exceptions.SESDomainNotConfirmedError | |
170 exc_reason = "Domain ownership is not confirmed." | |
171 else: | |
172 # This is either a common AWS error, or one that we don't devote | |
173 # its own exception to. | |
174 ExceptionToRaise = self.ResponseError | |
175 exc_reason = response.reason | |
176 | |
177 raise ExceptionToRaise(response.status, exc_reason, body) | |
178 | |
179 def send_email(self, source, subject, body, to_addresses, | |
180 cc_addresses=None, bcc_addresses=None, | |
181 format='text', reply_addresses=None, | |
182 return_path=None, text_body=None, html_body=None): | |
183 """Composes an email message based on input data, and then immediately | |
184 queues the message for sending. | |
185 | |
186 :type source: string | |
187 :param source: The sender's email address. | |
188 | |
189 :type subject: string | |
190 :param subject: The subject of the message: A short summary of the | |
191 content, which will appear in the recipient's inbox. | |
192 | |
193 :type body: string | |
194 :param body: The message body. | |
195 | |
196 :type to_addresses: list of strings or string | |
197 :param to_addresses: The To: field(s) of the message. | |
198 | |
199 :type cc_addresses: list of strings or string | |
200 :param cc_addresses: The CC: field(s) of the message. | |
201 | |
202 :type bcc_addresses: list of strings or string | |
203 :param bcc_addresses: The BCC: field(s) of the message. | |
204 | |
205 :type format: string | |
206 :param format: The format of the message's body, must be either "text" | |
207 or "html". | |
208 | |
209 :type reply_addresses: list of strings or string | |
210 :param reply_addresses: The reply-to email address(es) for the | |
211 message. If the recipient replies to the | |
212 message, each reply-to address will | |
213 receive the reply. | |
214 | |
215 :type return_path: string | |
216 :param return_path: The email address to which bounce notifications are | |
217 to be forwarded. If the message cannot be delivered | |
218 to the recipient, then an error message will be | |
219 returned from the recipient's ISP; this message | |
220 will then be forwarded to the email address | |
221 specified by the ReturnPath parameter. | |
222 | |
223 :type text_body: string | |
224 :param text_body: The text body to send with this email. | |
225 | |
226 :type html_body: string | |
227 :param html_body: The html body to send with this email. | |
228 | |
229 """ | |
230 format = format.lower().strip() | |
231 if body is not None: | |
232 if format == "text": | |
233 if text_body is not None: | |
234 raise Warning("You've passed in both a body and a " | |
235 "text_body; please choose one or the other.") | |
236 text_body = body | |
237 else: | |
238 if html_body is not None: | |
239 raise Warning("You've passed in both a body and an " | |
240 "html_body; please choose one or the other.") | |
241 html_body = body | |
242 | |
243 params = { | |
244 'Source': source, | |
245 'Message.Subject.Data': subject, | |
246 } | |
247 | |
248 if return_path: | |
249 params['ReturnPath'] = return_path | |
250 | |
251 if html_body is not None: | |
252 params['Message.Body.Html.Data'] = html_body | |
253 if text_body is not None: | |
254 params['Message.Body.Text.Data'] = text_body | |
255 | |
256 if(format not in ("text", "html")): | |
257 raise ValueError("'format' argument must be 'text' or 'html'") | |
258 | |
259 if(not (html_body or text_body)): | |
260 raise ValueError("No text or html body found for mail") | |
261 | |
262 self._build_list_params(params, to_addresses, | |
263 'Destination.ToAddresses.member') | |
264 if cc_addresses: | |
265 self._build_list_params(params, cc_addresses, | |
266 'Destination.CcAddresses.member') | |
267 | |
268 if bcc_addresses: | |
269 self._build_list_params(params, bcc_addresses, | |
270 'Destination.BccAddresses.member') | |
271 | |
272 if reply_addresses: | |
273 self._build_list_params(params, reply_addresses, | |
274 'ReplyToAddresses.member') | |
275 | |
276 return self._make_request('SendEmail', params) | |
277 | |
278 def send_raw_email(self, raw_message, source=None, destinations=None): | |
279 """Sends an email message, with header and content specified by the | |
280 client. The SendRawEmail action is useful for sending multipart MIME | |
281 emails, with attachments or inline content. The raw text of the message | |
282 must comply with Internet email standards; otherwise, the message | |
283 cannot be sent. | |
284 | |
285 :type source: string | |
286 :param source: The sender's email address. Amazon's docs say: | |
287 | |
288 If you specify the Source parameter, then bounce notifications and | |
289 complaints will be sent to this email address. This takes precedence | |
290 over any Return-Path header that you might include in the raw text of | |
291 the message. | |
292 | |
293 :type raw_message: string | |
294 :param raw_message: The raw text of the message. The client is | |
295 responsible for ensuring the following: | |
296 | |
297 - Message must contain a header and a body, separated by a blank line. | |
298 - All required header fields must be present. | |
299 - Each part of a multipart MIME message must be formatted properly. | |
300 - MIME content types must be among those supported by Amazon SES. | |
301 Refer to the Amazon SES Developer Guide for more details. | |
302 - Content must be base64-encoded, if MIME requires it. | |
303 | |
304 :type destinations: list of strings or string | |
305 :param destinations: A list of destinations for the message. | |
306 | |
307 """ | |
308 | |
309 if isinstance(raw_message, six.text_type): | |
310 raw_message = raw_message.encode('utf-8') | |
311 | |
312 params = { | |
313 'RawMessage.Data': base64.b64encode(raw_message), | |
314 } | |
315 | |
316 if source: | |
317 params['Source'] = source | |
318 | |
319 if destinations: | |
320 self._build_list_params(params, destinations, | |
321 'Destinations.member') | |
322 | |
323 return self._make_request('SendRawEmail', params) | |
324 | |
325 def list_verified_email_addresses(self): | |
326 """Fetch a list of the email addresses that have been verified. | |
327 | |
328 :rtype: dict | |
329 :returns: A ListVerifiedEmailAddressesResponse structure. Note that | |
330 keys must be unicode strings. | |
331 """ | |
332 return self._make_request('ListVerifiedEmailAddresses') | |
333 | |
334 def get_send_quota(self): | |
335 """Fetches the user's current activity limits. | |
336 | |
337 :rtype: dict | |
338 :returns: A GetSendQuotaResponse structure. Note that keys must be | |
339 unicode strings. | |
340 """ | |
341 return self._make_request('GetSendQuota') | |
342 | |
343 def get_send_statistics(self): | |
344 """Fetches the user's sending statistics. The result is a list of data | |
345 points, representing the last two weeks of sending activity. | |
346 | |
347 Each data point in the list contains statistics for a 15-minute | |
348 interval. | |
349 | |
350 :rtype: dict | |
351 :returns: A GetSendStatisticsResponse structure. Note that keys must be | |
352 unicode strings. | |
353 """ | |
354 return self._make_request('GetSendStatistics') | |
355 | |
356 def delete_verified_email_address(self, email_address): | |
357 """Deletes the specified email address from the list of verified | |
358 addresses. | |
359 | |
360 :type email_adddress: string | |
361 :param email_address: The email address to be removed from the list of | |
362 verified addreses. | |
363 | |
364 :rtype: dict | |
365 :returns: A DeleteVerifiedEmailAddressResponse structure. Note that | |
366 keys must be unicode strings. | |
367 """ | |
368 return self._make_request('DeleteVerifiedEmailAddress', { | |
369 'EmailAddress': email_address, | |
370 }) | |
371 | |
372 def verify_email_address(self, email_address): | |
373 """Verifies an email address. This action causes a confirmation email | |
374 message to be sent to the specified address. | |
375 | |
376 :type email_adddress: string | |
377 :param email_address: The email address to be verified. | |
378 | |
379 :rtype: dict | |
380 :returns: A VerifyEmailAddressResponse structure. Note that keys must | |
381 be unicode strings. | |
382 """ | |
383 return self._make_request('VerifyEmailAddress', { | |
384 'EmailAddress': email_address, | |
385 }) | |
386 | |
387 def verify_domain_dkim(self, domain): | |
388 """ | |
389 Returns a set of DNS records, or tokens, that must be published in the | |
390 domain name's DNS to complete the DKIM verification process. These | |
391 tokens are DNS ``CNAME`` records that point to DKIM public keys hosted | |
392 by Amazon SES. To complete the DKIM verification process, these tokens | |
393 must be published in the domain's DNS. The tokens must remain | |
394 published in order for Easy DKIM signing to function correctly. | |
395 | |
396 After the tokens are added to the domain's DNS, Amazon SES will be able | |
397 to DKIM-sign email originating from that domain. To enable or disable | |
398 Easy DKIM signing for a domain, use the ``SetIdentityDkimEnabled`` | |
399 action. For more information about Easy DKIM, go to the `Amazon SES | |
400 Developer Guide | |
401 <http://docs.amazonwebservices.com/ses/latest/DeveloperGuide>`_. | |
402 | |
403 :type domain: string | |
404 :param domain: The domain name. | |
405 | |
406 """ | |
407 return self._make_request('VerifyDomainDkim', { | |
408 'Domain': domain, | |
409 }) | |
410 | |
411 def set_identity_dkim_enabled(self, identity, dkim_enabled): | |
412 """Enables or disables DKIM signing of email sent from an identity. | |
413 | |
414 * If Easy DKIM signing is enabled for a domain name identity (e.g., | |
415 * ``example.com``), | |
416 then Amazon SES will DKIM-sign all email sent by addresses under that | |
417 domain name (e.g., ``user@example.com``) | |
418 * If Easy DKIM signing is enabled for an email address, then Amazon SES | |
419 will DKIM-sign all email sent by that email address. | |
420 | |
421 For email addresses (e.g., ``user@example.com``), you can only enable | |
422 Easy DKIM signing if the corresponding domain (e.g., ``example.com``) | |
423 has been set up for Easy DKIM using the AWS Console or the | |
424 ``VerifyDomainDkim`` action. | |
425 | |
426 :type identity: string | |
427 :param identity: An email address or domain name. | |
428 | |
429 :type dkim_enabled: bool | |
430 :param dkim_enabled: Specifies whether or not to enable DKIM signing. | |
431 | |
432 """ | |
433 return self._make_request('SetIdentityDkimEnabled', { | |
434 'Identity': identity, | |
435 'DkimEnabled': 'true' if dkim_enabled else 'false' | |
436 }) | |
437 | |
438 def get_identity_dkim_attributes(self, identities): | |
439 """Get attributes associated with a list of verified identities. | |
440 | |
441 Given a list of verified identities (email addresses and/or domains), | |
442 returns a structure describing identity notification attributes. | |
443 | |
444 :type identities: list | |
445 :param identities: A list of verified identities (email addresses | |
446 and/or domains). | |
447 | |
448 """ | |
449 params = {} | |
450 self._build_list_params(params, identities, 'Identities.member') | |
451 return self._make_request('GetIdentityDkimAttributes', params) | |
452 | |
453 def list_identities(self): | |
454 """Returns a list containing all of the identities (email addresses | |
455 and domains) for a specific AWS Account, regardless of | |
456 verification status. | |
457 | |
458 :rtype: dict | |
459 :returns: A ListIdentitiesResponse structure. Note that | |
460 keys must be unicode strings. | |
461 """ | |
462 return self._make_request('ListIdentities') | |
463 | |
464 def get_identity_verification_attributes(self, identities): | |
465 """Given a list of identities (email addresses and/or domains), | |
466 returns the verification status and (for domain identities) | |
467 the verification token for each identity. | |
468 | |
469 :type identities: list of strings or string | |
470 :param identities: List of identities. | |
471 | |
472 :rtype: dict | |
473 :returns: A GetIdentityVerificationAttributesResponse structure. | |
474 Note that keys must be unicode strings. | |
475 """ | |
476 params = {} | |
477 self._build_list_params(params, identities, | |
478 'Identities.member') | |
479 return self._make_request('GetIdentityVerificationAttributes', params) | |
480 | |
481 def verify_domain_identity(self, domain): | |
482 """Verifies a domain. | |
483 | |
484 :type domain: string | |
485 :param domain: The domain to be verified. | |
486 | |
487 :rtype: dict | |
488 :returns: A VerifyDomainIdentityResponse structure. Note that | |
489 keys must be unicode strings. | |
490 """ | |
491 return self._make_request('VerifyDomainIdentity', { | |
492 'Domain': domain, | |
493 }) | |
494 | |
495 def verify_email_identity(self, email_address): | |
496 """Verifies an email address. This action causes a confirmation | |
497 email message to be sent to the specified address. | |
498 | |
499 :type email_adddress: string | |
500 :param email_address: The email address to be verified. | |
501 | |
502 :rtype: dict | |
503 :returns: A VerifyEmailIdentityResponse structure. Note that keys must | |
504 be unicode strings. | |
505 """ | |
506 return self._make_request('VerifyEmailIdentity', { | |
507 'EmailAddress': email_address, | |
508 }) | |
509 | |
510 def delete_identity(self, identity): | |
511 """Deletes the specified identity (email address or domain) from | |
512 the list of verified identities. | |
513 | |
514 :type identity: string | |
515 :param identity: The identity to be deleted. | |
516 | |
517 :rtype: dict | |
518 :returns: A DeleteIdentityResponse structure. Note that keys must | |
519 be unicode strings. | |
520 """ | |
521 return self._make_request('DeleteIdentity', { | |
522 'Identity': identity, | |
523 }) | |
524 | |
525 def set_identity_notification_topic(self, identity, notification_type, sns_topic=None): | |
526 """Sets an SNS topic to publish bounce or complaint notifications for | |
527 emails sent with the given identity as the Source. Publishing to topics | |
528 may only be disabled when feedback forwarding is enabled. | |
529 | |
530 :type identity: string | |
531 :param identity: An email address or domain name. | |
532 | |
533 :type notification_type: string | |
534 :param notification_type: The type of feedback notifications that will | |
535 be published to the specified topic. | |
536 Valid Values: Bounce | Complaint | Delivery | |
537 | |
538 :type sns_topic: string or None | |
539 :param sns_topic: The Amazon Resource Name (ARN) of the Amazon Simple | |
540 Notification Service (Amazon SNS) topic. | |
541 """ | |
542 params = { | |
543 'Identity': identity, | |
544 'NotificationType': notification_type | |
545 } | |
546 if sns_topic: | |
547 params['SnsTopic'] = sns_topic | |
548 return self._make_request('SetIdentityNotificationTopic', params) | |
549 | |
550 def set_identity_feedback_forwarding_enabled(self, identity, forwarding_enabled=True): | |
551 """ | |
552 Enables or disables SES feedback notification via email. | |
553 Feedback forwarding may only be disabled when both complaint and | |
554 bounce topics are set. | |
555 | |
556 :type identity: string | |
557 :param identity: An email address or domain name. | |
558 | |
559 :type forwarding_enabled: bool | |
560 :param forwarding_enabled: Specifies whether or not to enable feedback forwarding. | |
561 """ | |
562 return self._make_request('SetIdentityFeedbackForwardingEnabled', { | |
563 'Identity': identity, | |
564 'ForwardingEnabled': 'true' if forwarding_enabled else 'false' | |
565 }) |