mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
Upgrade requests exceptions to AnymailRequestsAPIError
Catch and re-raise requests.RequestException in AnymailRequestsBackend.post_to_esp. * AnymailRequestsAPIError is needed for proper fail_silently handling. * Retain original requests exception type, to avoid breaking existing code that might look for specific requests exceptions. Closes #16
This commit is contained in:
@@ -65,7 +65,15 @@ class AnymailRequestsBackend(AnymailBaseBackend):
|
|||||||
Can raise AnymailRequestsAPIError for HTTP errors in the post
|
Can raise AnymailRequestsAPIError for HTTP errors in the post
|
||||||
"""
|
"""
|
||||||
params = payload.get_request_params(self.api_url)
|
params = payload.get_request_params(self.api_url)
|
||||||
response = self.session.request(**params)
|
try:
|
||||||
|
response = self.session.request(**params)
|
||||||
|
except requests.RequestException as err:
|
||||||
|
# raise an exception that is both AnymailRequestsAPIError
|
||||||
|
# and the original requests exception type
|
||||||
|
exc_class = type('AnymailRequestsAPIError', (AnymailRequestsAPIError, type(err)), {})
|
||||||
|
raise exc_class(
|
||||||
|
"Error posting to %s:" % params.get('url', '<missing url>'),
|
||||||
|
raised_from=err, email_message=message, payload=payload)
|
||||||
self.raise_for_status(response, payload, message)
|
self.raise_for_status(response, payload, message)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
from traceback import format_exception_only
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
|
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
|
||||||
from requests import HTTPError
|
from requests import HTTPError
|
||||||
@@ -18,11 +19,13 @@ class AnymailError(Exception):
|
|||||||
status_code: HTTP status code of response to ESP send call
|
status_code: HTTP status code of response to ESP send call
|
||||||
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
|
||||||
"""
|
"""
|
||||||
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)
|
||||||
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)
|
||||||
@@ -33,6 +36,7 @@ class AnymailError(Exception):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
parts = [
|
parts = [
|
||||||
" ".join([str(arg) for arg in self.args]),
|
" ".join([str(arg) for arg in self.args]),
|
||||||
|
self.describe_raised_from(),
|
||||||
self.describe_send(),
|
self.describe_send(),
|
||||||
self.describe_response(),
|
self.describe_response(),
|
||||||
]
|
]
|
||||||
@@ -68,6 +72,12 @@ class AnymailError(Exception):
|
|||||||
pass
|
pass
|
||||||
return description
|
return description
|
||||||
|
|
||||||
|
def describe_raised_from(self):
|
||||||
|
"""Return the original exception"""
|
||||||
|
if self.raised_from is None:
|
||||||
|
return None
|
||||||
|
return ''.join(format_exception_only(type(self.raised_from), self.raised_from)).strip()
|
||||||
|
|
||||||
|
|
||||||
class AnymailAPIError(AnymailError):
|
class AnymailAPIError(AnymailError):
|
||||||
"""Exception for unsuccessful response from ESP's API."""
|
"""Exception for unsuccessful response from ESP's API."""
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from django.test import SimpleTestCase
|
|||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
from django.utils.timezone import get_fixed_timezone, override as override_current_timezone
|
from django.utils.timezone import get_fixed_timezone, override as override_current_timezone
|
||||||
|
|
||||||
from anymail.exceptions import AnymailAPIError, AnymailUnsupportedFeature
|
from anymail.exceptions import AnymailAPIError, AnymailRequestsAPIError, AnymailUnsupportedFeature
|
||||||
from anymail.message import attach_inline_image_file
|
from anymail.message import attach_inline_image_file
|
||||||
|
|
||||||
from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharingTestCasesMixin
|
from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharingTestCasesMixin
|
||||||
@@ -258,6 +258,20 @@ class MailgunBackendStandardEmailTests(MailgunBackendMockAPITestCase):
|
|||||||
with self.assertRaises(AnymailAPIError):
|
with self.assertRaises(AnymailAPIError):
|
||||||
self.message.send()
|
self.message.send()
|
||||||
|
|
||||||
|
def test_requests_exception(self):
|
||||||
|
"""Exception during API call should be AnymailAPIError"""
|
||||||
|
# (The post itself raises an error -- different from returning a failure response)
|
||||||
|
from requests.exceptions import SSLError # a low-level requests exception
|
||||||
|
self.mock_request.side_effect = SSLError("Something bad")
|
||||||
|
with self.assertRaisesMessage(AnymailRequestsAPIError, "Something bad") as cm:
|
||||||
|
self.message.send()
|
||||||
|
self.assertIsInstance(cm.exception, SSLError) # also retains specific requests exception class
|
||||||
|
|
||||||
|
# Make sure fail_silently is respected
|
||||||
|
self.mock_request.side_effect = SSLError("Something bad")
|
||||||
|
sent = mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'], fail_silently=True)
|
||||||
|
self.assertEqual(sent, 0)
|
||||||
|
|
||||||
|
|
||||||
class MailgunBackendAnymailFeatureTests(MailgunBackendMockAPITestCase):
|
class MailgunBackendAnymailFeatureTests(MailgunBackendMockAPITestCase):
|
||||||
"""Test backend support for Anymail added features"""
|
"""Test backend support for Anymail added features"""
|
||||||
|
|||||||
Reference in New Issue
Block a user