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
|
||||
"""
|
||||
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)
|
||||
return response
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
from traceback import format_exception_only
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
|
||||
from requests import HTTPError
|
||||
@@ -18,11 +19,13 @@ class AnymailError(Exception):
|
||||
status_code: HTTP status code of response to ESP send call
|
||||
payload: data arg (*not* json-stringified) for the ESP send call
|
||||
response: requests.Response from the send call
|
||||
raised_from: original/wrapped Exception
|
||||
"""
|
||||
self.backend = kwargs.pop('backend', None)
|
||||
self.email_message = kwargs.pop('email_message', None)
|
||||
self.payload = kwargs.pop('payload', None)
|
||||
self.status_code = kwargs.pop('status_code', None)
|
||||
self.raised_from = kwargs.pop('raised_from', None)
|
||||
if isinstance(self, HTTPError):
|
||||
# must leave response in kwargs for HTTPError
|
||||
self.response = kwargs.get('response', None)
|
||||
@@ -33,6 +36,7 @@ class AnymailError(Exception):
|
||||
def __str__(self):
|
||||
parts = [
|
||||
" ".join([str(arg) for arg in self.args]),
|
||||
self.describe_raised_from(),
|
||||
self.describe_send(),
|
||||
self.describe_response(),
|
||||
]
|
||||
@@ -68,6 +72,12 @@ class AnymailError(Exception):
|
||||
pass
|
||||
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):
|
||||
"""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.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 .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharingTestCasesMixin
|
||||
@@ -258,6 +258,20 @@ class MailgunBackendStandardEmailTests(MailgunBackendMockAPITestCase):
|
||||
with self.assertRaises(AnymailAPIError):
|
||||
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):
|
||||
"""Test backend support for Anymail added features"""
|
||||
|
||||
Reference in New Issue
Block a user