File tree 7 files changed +75
-4
lines changed
7 files changed +75
-4
lines changed Original file line number Diff line number Diff line change 33
33
Fixes
34
34
~~~~~
35
35
36
+ * Fix misleading error messages when sending with ``fail_silently=True ``
37
+ and session creation fails (e.g., with Amazon SES backend and missing
38
+ credentials). (Thanks to `@technolingo `_.)
39
+
36
40
* **Postmark: ** Fix spurious AnymailInvalidAddress in ``message.cc `` when
37
41
inbound message has no Cc recipients. (Thanks to `@Ecno92 `_.)
38
42
@@ -1455,6 +1459,7 @@ Features
1455
1459
.. _@slinkymanbyday : https://github.com/slinkymanbyday
1456
1460
.. _@swrobel : https://github.com/swrobel
1457
1461
.. _@tcourtqtm : https://github.com/tcourtqtm
1462
+ .. _@technolingo : https://github.com/technolingo
1458
1463
.. _@Thorbenl : https://github.com/Thorbenl
1459
1464
.. _@tiltec : https://github.com/tiltec
1460
1465
.. _@tim-schilling : https://github.com/tim-schilling
Original file line number Diff line number Diff line change @@ -59,7 +59,7 @@ def open(self):
59
59
self .client = boto3 .session .Session (** self .session_params ).client (
60
60
"ses" , ** self .client_params
61
61
)
62
- except BOTO_BASE_ERRORS :
62
+ except Exception :
63
63
if not self .fail_silently :
64
64
raise
65
65
else :
@@ -71,6 +71,22 @@ def close(self):
71
71
# self.client.close() # boto3 doesn't support (or require) client shutdown
72
72
self .client = None
73
73
74
+ def _send (self , message ):
75
+ if self .client :
76
+ return super ()._send (message )
77
+ elif self .fail_silently :
78
+ # (Probably missing boto3 credentials in open().)
79
+ return False
80
+ else :
81
+ class_name = self .__class__ .__name__
82
+ raise RuntimeError (
83
+ "boto3 Session has not been opened in {class_name}._send. "
84
+ "(This is either an implementation error in {class_name}, "
85
+ "or you are incorrectly calling _send directly.)" .format (
86
+ class_name = class_name
87
+ )
88
+ )
89
+
74
90
def build_message_payload (self , message , defaults ):
75
91
# The SES SendRawEmail and SendBulkTemplatedEmail calls have
76
92
# very different signatures, so use a custom payload for each
Original file line number Diff line number Diff line change @@ -63,7 +63,7 @@ def open(self):
63
63
self .client = boto3 .session .Session (** self .session_params ).client (
64
64
"sesv2" , ** self .client_params
65
65
)
66
- except BOTO_BASE_ERRORS :
66
+ except Exception :
67
67
if not self .fail_silently :
68
68
raise
69
69
else :
@@ -75,6 +75,22 @@ def close(self):
75
75
self .client .close ()
76
76
self .client = None
77
77
78
+ def _send (self , message ):
79
+ if self .client :
80
+ return super ()._send (message )
81
+ elif self .fail_silently :
82
+ # (Probably missing boto3 credentials in open().)
83
+ return False
84
+ else :
85
+ class_name = self .__class__ .__name__
86
+ raise RuntimeError (
87
+ "boto3 Session has not been opened in {class_name}._send. "
88
+ "(This is either an implementation error in {class_name}, "
89
+ "or you are incorrectly calling _send directly.)" .format (
90
+ class_name = class_name
91
+ )
92
+ )
93
+
78
94
def build_message_payload (self , message , defaults ):
79
95
if getattr (message , "template_id" , UNSET ) is not UNSET :
80
96
# For simplicity, use SESv2 SendBulkEmail for all templated messages
Original file line number Diff line number Diff line change @@ -47,7 +47,12 @@ def close(self):
47
47
self .session = None
48
48
49
49
def _send (self , message ):
50
- if self .session is None :
50
+ if self .session :
51
+ return super ()._send (message )
52
+ elif self .fail_silently :
53
+ # create_session failed
54
+ return False
55
+ else :
51
56
class_name = self .__class__ .__name__
52
57
raise RuntimeError (
53
58
"Session has not been opened in {class_name}._send. "
@@ -56,7 +61,6 @@ def _send(self, message):
56
61
class_name = class_name
57
62
)
58
63
)
59
- return super ()._send (message )
60
64
61
65
def create_session (self ):
62
66
"""Create the requests.Session object used by this instance of the backend.
Original file line number Diff line number Diff line change @@ -393,6 +393,16 @@ def test_api_failure_fail_silently(self):
393
393
sent = self .message .send (fail_silently = True )
394
394
self .assertEqual (sent , 0 )
395
395
396
+ def test_session_failure_fail_silently (self ):
397
+ # Make sure fail_silently is respected if boto3.Session creation fails
398
+ # (e.g., due to invalid or missing credentials)
399
+ from botocore .exceptions import NoCredentialsError
400
+
401
+ self .mock_session .side_effect = NoCredentialsError ()
402
+
403
+ sent = self .message .send (fail_silently = True )
404
+ self .assertEqual (sent , 0 )
405
+
396
406
def test_prevents_header_injection (self ):
397
407
# Since we build the raw MIME message, we're responsible for preventing header
398
408
# injection. django.core.mail.EmailMessage.message() implements most of that
Original file line number Diff line number Diff line change @@ -403,6 +403,16 @@ def test_api_failure_fail_silently(self):
403
403
sent = self .message .send (fail_silently = True )
404
404
self .assertEqual (sent , 0 )
405
405
406
+ def test_session_failure_fail_silently (self ):
407
+ # Make sure fail_silently is respected if boto3.Session creation fails
408
+ # (e.g., due to invalid or missing credentials)
409
+ from botocore .exceptions import NoCredentialsError
410
+
411
+ self .mock_session .side_effect = NoCredentialsError ()
412
+
413
+ sent = self .message .send (fail_silently = True )
414
+ self .assertEqual (sent , 0 )
415
+
406
416
def test_prevents_header_injection (self ):
407
417
# Since we build the raw MIME message, we're responsible for preventing header
408
418
# injection. django.core.mail.EmailMessage.message() implements most of that
Original file line number Diff line number Diff line change
1
+ from unittest import mock
2
+
1
3
from django .test import SimpleTestCase , override_settings , tag
2
4
3
5
from anymail .backends .base_requests import AnymailRequestsBackend , RequestsPayload
@@ -69,6 +71,14 @@ def test_timeout_setting(self):
69
71
timeout = self .get_api_call_arg ("timeout" )
70
72
self .assertEqual (timeout , 5 )
71
73
74
+ @mock .patch (f"{ __name__ } .MinimalRequestsBackend.create_session" )
75
+ def test_create_session_error_fail_silently (self , mock_create_session ):
76
+ # If create_session fails and fail_silently is True,
77
+ # make sure _send doesn't raise a misleading error.
78
+ mock_create_session .side_effect = ValueError ("couldn't create session" )
79
+ sent = self .message .send (fail_silently = True )
80
+ self .assertEqual (sent , 0 )
81
+
72
82
73
83
@tag ("live" )
74
84
@override_settings (EMAIL_BACKEND = "tests.test_base_backends.MinimalRequestsBackend" )
You can’t perform that action at this time.
0 commit comments