mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 11:51:05 -05:00
Use specific ESP name in error messages.
Change (e.g.,) "ESP API response 400" to "Mailgun API response 400".
This commit is contained in:
@@ -183,7 +183,8 @@ class AnymailBaseBackend(BaseEmailBackend):
|
|||||||
# Error if *all* recipients are invalid or refused
|
# Error if *all* recipients are invalid or refused
|
||||||
# (This behavior parallels smtplib.SMTPRecipientsRefused from Django's SMTP EmailBackend)
|
# (This behavior parallels smtplib.SMTPRecipientsRefused from Django's SMTP EmailBackend)
|
||||||
if anymail_status.status.issubset({"invalid", "rejected"}):
|
if anymail_status.status.issubset({"invalid", "rejected"}):
|
||||||
raise AnymailRecipientsRefused(email_message=message, payload=payload, response=response)
|
raise AnymailRecipientsRefused(email_message=message, payload=payload, response=response,
|
||||||
|
backend=self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def esp_name(self):
|
def esp_name(self):
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ class AnymailRequestsBackend(AnymailBaseBackend):
|
|||||||
parse_recipient_status)
|
parse_recipient_status)
|
||||||
"""
|
"""
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise AnymailRequestsAPIError(email_message=message, payload=payload, response=response)
|
raise AnymailRequestsAPIError(email_message=message, payload=payload, response=response,
|
||||||
|
backend=self)
|
||||||
|
|
||||||
def deserialize_json_response(self, response, payload, message):
|
def deserialize_json_response(self, response, payload, message):
|
||||||
"""Deserialize an ESP API response that's in json.
|
"""Deserialize an ESP API response that's in json.
|
||||||
@@ -96,7 +97,8 @@ class AnymailRequestsBackend(AnymailBaseBackend):
|
|||||||
return response.json()
|
return response.json()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise AnymailRequestsAPIError("Invalid JSON in %s API response" % self.esp_name,
|
raise AnymailRequestsAPIError("Invalid JSON in %s API response" % self.esp_name,
|
||||||
email_message=message, payload=payload, response=response)
|
email_message=message, payload=payload, response=response,
|
||||||
|
backend=self)
|
||||||
|
|
||||||
|
|
||||||
class RequestsPayload(BasePayload):
|
class RequestsPayload(BasePayload):
|
||||||
|
|||||||
@@ -46,10 +46,12 @@ class EmailBackend(AnymailRequestsBackend):
|
|||||||
mailgun_message = parsed_response["message"]
|
mailgun_message = parsed_response["message"]
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
raise AnymailRequestsAPIError("Invalid Mailgun API response format",
|
raise AnymailRequestsAPIError("Invalid Mailgun API response format",
|
||||||
email_message=message, payload=payload, response=response)
|
email_message=message, payload=payload, response=response,
|
||||||
|
backend=self)
|
||||||
if not mailgun_message.startswith("Queued"):
|
if not mailgun_message.startswith("Queued"):
|
||||||
raise AnymailRequestsAPIError("Unrecognized Mailgun API message '%s'" % mailgun_message,
|
raise AnymailRequestsAPIError("Unrecognized Mailgun API message '%s'" % mailgun_message,
|
||||||
email_message=message, payload=payload, response=response)
|
email_message=message, payload=payload, response=response,
|
||||||
|
backend=self)
|
||||||
# Simulate a per-recipient status of "queued":
|
# Simulate a per-recipient status of "queued":
|
||||||
status = AnymailRecipientStatus(message_id=message_id, status="queued")
|
status = AnymailRecipientStatus(message_id=message_id, status="queued")
|
||||||
return {recipient.email: status for recipient in payload.all_recipients}
|
return {recipient.email: status for recipient in payload.all_recipients}
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ class EmailBackend(AnymailRequestsBackend):
|
|||||||
recipient_status[email] = AnymailRecipientStatus(message_id=message_id, status=status)
|
recipient_status[email] = AnymailRecipientStatus(message_id=message_id, status=status)
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
raise AnymailRequestsAPIError("Invalid Mandrill API response format",
|
raise AnymailRequestsAPIError("Invalid Mandrill API response format",
|
||||||
email_message=message, payload=payload, response=response)
|
email_message=message, payload=payload, response=response,
|
||||||
|
backend=self)
|
||||||
return recipient_status
|
return recipient_status
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ class EmailBackend(AnymailRequestsBackend):
|
|||||||
msg = parsed_response["Message"]
|
msg = parsed_response["Message"]
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
raise AnymailRequestsAPIError("Invalid Postmark API response format",
|
raise AnymailRequestsAPIError("Invalid Postmark API response format",
|
||||||
email_message=message, payload=payload, response=response)
|
email_message=message, payload=payload, response=response,
|
||||||
|
backend=self)
|
||||||
|
|
||||||
message_id = parsed_response.get("MessageID", None)
|
message_id = parsed_response.get("MessageID", None)
|
||||||
rejected_emails = []
|
rejected_emails = []
|
||||||
@@ -51,7 +52,8 @@ class EmailBackend(AnymailRequestsBackend):
|
|||||||
# Either the From address or at least one recipient was invalid. Email not sent.
|
# Either the From address or at least one recipient was invalid. Email not sent.
|
||||||
if "'From' address" in msg:
|
if "'From' address" in msg:
|
||||||
# Normal error
|
# Normal error
|
||||||
raise AnymailRequestsAPIError(email_message=message, payload=payload, response=response)
|
raise AnymailRequestsAPIError(email_message=message, payload=payload, response=response,
|
||||||
|
backend=self)
|
||||||
else:
|
else:
|
||||||
# Use AnymailRecipientsRefused logic
|
# Use AnymailRecipientsRefused logic
|
||||||
default_status = 'invalid'
|
default_status = 'invalid'
|
||||||
@@ -64,7 +66,8 @@ class EmailBackend(AnymailRequestsBackend):
|
|||||||
default_status = 'sent'
|
default_status = 'sent'
|
||||||
rejected_emails = self.parse_inactive_recipients(msg)
|
rejected_emails = self.parse_inactive_recipients(msg)
|
||||||
else:
|
else:
|
||||||
raise AnymailRequestsAPIError(email_message=message, payload=payload, response=response)
|
raise AnymailRequestsAPIError(email_message=message, payload=payload, response=response,
|
||||||
|
backend=self)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
recipient.email: AnymailRecipientStatus(
|
recipient.email: AnymailRecipientStatus(
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ class EmailBackend(AnymailRequestsBackend):
|
|||||||
|
|
||||||
def raise_for_status(self, response, payload, message):
|
def raise_for_status(self, response, payload, message):
|
||||||
if response.status_code < 200 or response.status_code >= 300:
|
if response.status_code < 200 or response.status_code >= 300:
|
||||||
raise AnymailRequestsAPIError(email_message=message, payload=payload, response=response)
|
raise AnymailRequestsAPIError(email_message=message, payload=payload, response=response,
|
||||||
|
backend=self)
|
||||||
|
|
||||||
def parse_recipient_status(self, response, payload, message):
|
def parse_recipient_status(self, response, payload, message):
|
||||||
# If we get here, the send call was successful.
|
# If we get here, the send call was successful.
|
||||||
|
|||||||
@@ -54,11 +54,13 @@ class EmailBackend(AnymailRequestsBackend):
|
|||||||
sendgrid_message = parsed_response["message"]
|
sendgrid_message = parsed_response["message"]
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
raise AnymailRequestsAPIError("Invalid SendGrid API response format",
|
raise AnymailRequestsAPIError("Invalid SendGrid API response format",
|
||||||
email_message=message, payload=payload, response=response)
|
email_message=message, payload=payload, response=response,
|
||||||
|
backend=self)
|
||||||
if sendgrid_message != "success":
|
if sendgrid_message != "success":
|
||||||
errors = parsed_response.get("errors", [])
|
errors = parsed_response.get("errors", [])
|
||||||
raise AnymailRequestsAPIError("SendGrid send failed: '%s'" % "; ".join(errors),
|
raise AnymailRequestsAPIError("SendGrid send failed: '%s'" % "; ".join(errors),
|
||||||
email_message=message, payload=payload, response=response)
|
email_message=message, payload=payload, response=response,
|
||||||
|
backend=self)
|
||||||
# Simulate a per-recipient status of "queued":
|
# Simulate a per-recipient status of "queued":
|
||||||
status = AnymailRecipientStatus(message_id=payload.message_id, status="queued")
|
status = AnymailRecipientStatus(message_id=payload.message_id, status="queued")
|
||||||
return {recipient.email: status for recipient in payload.all_recipients}
|
return {recipient.email: status for recipient in payload.all_recipients}
|
||||||
|
|||||||
@@ -17,15 +17,19 @@ class AnymailError(Exception):
|
|||||||
Optional kwargs:
|
Optional kwargs:
|
||||||
email_message: the original EmailMessage being sent
|
email_message: the original EmailMessage being sent
|
||||||
status_code: HTTP status code of response to ESP send call
|
status_code: HTTP status code of response to ESP send call
|
||||||
|
backend: the backend instance involved
|
||||||
payload: data arg (*not* json-stringified) for the ESP send call
|
payload: data arg (*not* json-stringified) for the ESP send call
|
||||||
response: requests.Response from the send call
|
response: requests.Response from the send call
|
||||||
raised_from: original/wrapped Exception
|
raised_from: original/wrapped Exception
|
||||||
|
esp_name: what to call the ESP (read from backend if provided)
|
||||||
"""
|
"""
|
||||||
self.backend = kwargs.pop('backend', None)
|
self.backend = kwargs.pop('backend', None)
|
||||||
self.email_message = kwargs.pop('email_message', None)
|
self.email_message = kwargs.pop('email_message', None)
|
||||||
self.payload = kwargs.pop('payload', None)
|
self.payload = kwargs.pop('payload', None)
|
||||||
self.status_code = kwargs.pop('status_code', None)
|
self.status_code = kwargs.pop('status_code', None)
|
||||||
self.raised_from = kwargs.pop('raised_from', None)
|
self.raised_from = kwargs.pop('raised_from', None)
|
||||||
|
self.esp_name = kwargs.pop('esp_name',
|
||||||
|
self.backend.esp_name if self.backend else None)
|
||||||
if isinstance(self, HTTPError):
|
if isinstance(self, HTTPError):
|
||||||
# must leave response in kwargs for HTTPError
|
# must leave response in kwargs for HTTPError
|
||||||
self.response = kwargs.get('response', None)
|
self.response = kwargs.get('response', None)
|
||||||
@@ -61,7 +65,7 @@ class AnymailError(Exception):
|
|||||||
"""Return a formatted string of self.status_code and response, or None"""
|
"""Return a formatted string of self.status_code and response, or None"""
|
||||||
if self.status_code is None:
|
if self.status_code is None:
|
||||||
return None
|
return None
|
||||||
description = "ESP API response %d:" % self.status_code
|
description = "%s API response %d:" % (self.esp_name or "ESP", self.status_code)
|
||||||
try:
|
try:
|
||||||
json_response = self.response.json()
|
json_response = self.response.json()
|
||||||
description += "\n" + json.dumps(json_response, indent=2)
|
description += "\n" + json.dumps(json_response, indent=2)
|
||||||
@@ -131,7 +135,9 @@ class AnymailSerializationError(AnymailError, TypeError):
|
|||||||
|
|
||||||
def __init__(self, message=None, orig_err=None, *args, **kwargs):
|
def __init__(self, message=None, orig_err=None, *args, **kwargs):
|
||||||
if message is None:
|
if message is None:
|
||||||
esp_name = kwargs["backend"].esp_name if "backend" in kwargs else "the ESP"
|
# self.esp_name not set until super init, so duplicate logic to get esp_name
|
||||||
|
backend = kwargs.get('backend', None)
|
||||||
|
esp_name = kwargs.get('esp_name', backend.esp_name if backend else "the ESP")
|
||||||
message = "Don't know how to send this data to %s. " \
|
message = "Don't know how to send this data to %s. " \
|
||||||
"Try converting it to a string or number first." % esp_name
|
"Try converting it to a string or number first." % esp_name
|
||||||
if orig_err is not None:
|
if orig_err is not None:
|
||||||
|
|||||||
@@ -227,9 +227,8 @@ class MailgunBackendStandardEmailTests(MailgunBackendMockAPITestCase):
|
|||||||
|
|
||||||
def test_api_failure(self):
|
def test_api_failure(self):
|
||||||
self.set_mock_response(status_code=400)
|
self.set_mock_response(status_code=400)
|
||||||
with self.assertRaises(AnymailAPIError):
|
with self.assertRaisesMessage(AnymailAPIError, "Mailgun API response 400"):
|
||||||
sent = mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
|
mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
|
||||||
self.assertEqual(sent, 0)
|
|
||||||
|
|
||||||
# Make sure fail_silently is respected
|
# Make sure fail_silently is respected
|
||||||
self.set_mock_response(status_code=400)
|
self.set_mock_response(status_code=400)
|
||||||
|
|||||||
@@ -236,9 +236,8 @@ class MandrillBackendStandardEmailTests(MandrillBackendMockAPITestCase):
|
|||||||
|
|
||||||
def test_api_failure(self):
|
def test_api_failure(self):
|
||||||
self.set_mock_response(status_code=400)
|
self.set_mock_response(status_code=400)
|
||||||
with self.assertRaises(AnymailAPIError):
|
with self.assertRaisesMessage(AnymailAPIError, "Mandrill API response 400"):
|
||||||
sent = mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
|
mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
|
||||||
self.assertEqual(sent, 0)
|
|
||||||
|
|
||||||
# Make sure fail_silently is respected
|
# Make sure fail_silently is respected
|
||||||
self.set_mock_response(status_code=400)
|
self.set_mock_response(status_code=400)
|
||||||
|
|||||||
@@ -273,9 +273,8 @@ class PostmarkBackendStandardEmailTests(PostmarkBackendMockAPITestCase):
|
|||||||
|
|
||||||
def test_api_failure(self):
|
def test_api_failure(self):
|
||||||
self.set_mock_response(status_code=500)
|
self.set_mock_response(status_code=500)
|
||||||
with self.assertRaises(AnymailAPIError):
|
with self.assertRaisesMessage(AnymailAPIError, "Postmark API response 500"):
|
||||||
sent = mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
|
mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
|
||||||
self.assertEqual(sent, 0)
|
|
||||||
|
|
||||||
# Make sure fail_silently is respected
|
# Make sure fail_silently is respected
|
||||||
self.set_mock_response(status_code=500)
|
self.set_mock_response(status_code=500)
|
||||||
|
|||||||
@@ -294,9 +294,8 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
|
|||||||
|
|
||||||
def test_api_failure(self):
|
def test_api_failure(self):
|
||||||
self.set_mock_response(status_code=400)
|
self.set_mock_response(status_code=400)
|
||||||
with self.assertRaises(AnymailAPIError):
|
with self.assertRaisesMessage(AnymailAPIError, "SendGrid API response 400"):
|
||||||
sent = mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
|
mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
|
||||||
self.assertEqual(sent, 0)
|
|
||||||
|
|
||||||
# Make sure fail_silently is respected
|
# Make sure fail_silently is respected
|
||||||
self.set_mock_response(status_code=400)
|
self.set_mock_response(status_code=400)
|
||||||
|
|||||||
@@ -301,9 +301,8 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
|
|||||||
|
|
||||||
def test_api_failure(self):
|
def test_api_failure(self):
|
||||||
self.set_mock_response(status_code=400)
|
self.set_mock_response(status_code=400)
|
||||||
with self.assertRaises(AnymailAPIError):
|
with self.assertRaisesMessage(AnymailAPIError, "SendGrid API response 400"):
|
||||||
sent = mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
|
mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
|
||||||
self.assertEqual(sent, 0)
|
|
||||||
|
|
||||||
# Make sure fail_silently is respected
|
# Make sure fail_silently is respected
|
||||||
self.set_mock_response(status_code=400)
|
self.set_mock_response(status_code=400)
|
||||||
|
|||||||
@@ -289,12 +289,8 @@ class SparkPostBackendStandardEmailTests(SparkPostBackendMockAPITestCase):
|
|||||||
self.assertNotIn('recipients', params)
|
self.assertNotIn('recipients', params)
|
||||||
|
|
||||||
def test_api_failure(self):
|
def test_api_failure(self):
|
||||||
failure_response = b"""{ "errors": [ {
|
self.set_mock_failure(status_code=400)
|
||||||
"message": "Something went wrong",
|
with self.assertRaisesMessage(AnymailAPIError, "SparkPost API response 400"):
|
||||||
"description": "Helpful explanation from your ESP"
|
|
||||||
} ] }"""
|
|
||||||
self.set_mock_failure(raw=failure_response)
|
|
||||||
with self.assertRaisesMessage(AnymailAPIError, "Helpful explanation from your ESP"):
|
|
||||||
self.message.send()
|
self.message.send()
|
||||||
|
|
||||||
def test_api_failure_fail_silently(self):
|
def test_api_failure_fail_silently(self):
|
||||||
@@ -303,6 +299,16 @@ class SparkPostBackendStandardEmailTests(SparkPostBackendMockAPITestCase):
|
|||||||
sent = self.message.send(fail_silently=True)
|
sent = self.message.send(fail_silently=True)
|
||||||
self.assertEqual(sent, 0)
|
self.assertEqual(sent, 0)
|
||||||
|
|
||||||
|
def test_api_error_includes_details(self):
|
||||||
|
"""AnymailAPIError should include ESP's error message"""
|
||||||
|
failure_response = b"""{ "errors": [ {
|
||||||
|
"message": "Something went wrong",
|
||||||
|
"description": "Helpful explanation from your ESP"
|
||||||
|
} ] }"""
|
||||||
|
self.set_mock_failure(raw=failure_response)
|
||||||
|
with self.assertRaisesMessage(AnymailAPIError, "Helpful explanation from your ESP"):
|
||||||
|
self.message.send()
|
||||||
|
|
||||||
|
|
||||||
class SparkPostBackendAnymailFeatureTests(SparkPostBackendMockAPITestCase):
|
class SparkPostBackendAnymailFeatureTests(SparkPostBackendMockAPITestCase):
|
||||||
"""Test backend support for Anymail added features"""
|
"""Test backend support for Anymail added features"""
|
||||||
|
|||||||
Reference in New Issue
Block a user