mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
Don't require boto3 if Amazon SES webhooks aren't actually used
Delay raising AnymailImproperlyInstalled from webhooks.amazon_ses until an SES webhook view is instantiated. Allows anymail.urls to import webhooks.amazon_ses without error. Fixes #103
This commit is contained in:
@@ -177,9 +177,9 @@ class AnymailImproperlyInstalled(AnymailConfigurationError, ImportError):
|
||||
"""Exception for Anymail missing package dependencies"""
|
||||
|
||||
def __init__(self, missing_package, backend="<backend>"):
|
||||
message = "The %s package is required to use this backend, but isn't installed.\n" \
|
||||
message = "The %s package is required to use this ESP, but isn't installed.\n" \
|
||||
"(Be sure to use `pip install django-anymail[%s]` " \
|
||||
"with your desired backends)" % (missing_package, backend)
|
||||
"with your desired ESPs.)" % (missing_package, backend)
|
||||
super(AnymailImproperlyInstalled, self).__init__(message)
|
||||
|
||||
|
||||
@@ -195,3 +195,17 @@ class AnymailInsecureWebhookWarning(AnymailWarning):
|
||||
|
||||
class AnymailDeprecationWarning(AnymailWarning, DeprecationWarning):
|
||||
"""Warning for deprecated Anymail features"""
|
||||
|
||||
|
||||
# Helpers
|
||||
|
||||
class _LazyError(object):
|
||||
"""An object that sits inert unless/until used, then raises an error"""
|
||||
def __init__(self, error):
|
||||
self._error = error
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
raise self._error
|
||||
|
||||
def __getattr__(self, item):
|
||||
raise self._error
|
||||
|
||||
@@ -6,18 +6,23 @@ from django.http import HttpResponse
|
||||
from django.utils.dateparse import parse_datetime
|
||||
|
||||
from .base import AnymailBaseWebhookView
|
||||
from ..backends.amazon_ses import _get_anymail_boto3_params
|
||||
from ..exceptions import (
|
||||
AnymailAPIError, AnymailConfigurationError, AnymailImproperlyInstalled, AnymailWebhookValidationFailure)
|
||||
AnymailAPIError, AnymailConfigurationError, AnymailImproperlyInstalled, AnymailWebhookValidationFailure,
|
||||
_LazyError)
|
||||
from ..inbound import AnymailInboundMessage
|
||||
from ..signals import AnymailInboundEvent, AnymailTrackingEvent, EventType, RejectReason, inbound, tracking
|
||||
from ..utils import combine, get_anymail_setting, getfirst
|
||||
|
||||
try:
|
||||
import boto3
|
||||
import botocore.exceptions
|
||||
from botocore.exceptions import ClientError
|
||||
from ..backends.amazon_ses import _get_anymail_boto3_params
|
||||
except ImportError:
|
||||
raise AnymailImproperlyInstalled(missing_package='boto3', backend='amazon_ses')
|
||||
# This module gets imported by anymail.urls, so don't complain about boto3 missing
|
||||
# unless one of the Amazon SES webhook views is actually used and needs it
|
||||
boto3 = _LazyError(AnymailImproperlyInstalled(missing_package='boto3', backend='amazon_ses'))
|
||||
ClientError = object
|
||||
_get_anymail_boto3_params = _LazyError(AnymailImproperlyInstalled(missing_package='boto3', backend='amazon_ses'))
|
||||
|
||||
|
||||
class AmazonSESBaseWebhookView(AnymailBaseWebhookView):
|
||||
@@ -296,7 +301,7 @@ class AmazonSESInboundWebhookView(AmazonSESBaseWebhookView):
|
||||
s3.download_fileobj(bucket_name, object_key, content)
|
||||
content.seek(0)
|
||||
message = AnymailInboundMessage.parse_raw_mime_file(content)
|
||||
except botocore.exceptions.ClientError as err:
|
||||
except ClientError as err:
|
||||
# improve the botocore error message
|
||||
raise AnymailBotoClientAPIError(
|
||||
"Anymail AmazonSESInboundWebhookView couldn't download S3 object '{bucket_name}:{object_key}'"
|
||||
@@ -334,11 +339,11 @@ class AmazonSESInboundWebhookView(AmazonSESBaseWebhookView):
|
||||
)]
|
||||
|
||||
|
||||
class AnymailBotoClientAPIError(AnymailAPIError, botocore.exceptions.ClientError):
|
||||
class AnymailBotoClientAPIError(AnymailAPIError, ClientError):
|
||||
"""An AnymailAPIError that is also a Boto ClientError"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
raised_from = kwargs.pop('raised_from')
|
||||
assert isinstance(raised_from, botocore.exceptions.ClientError)
|
||||
assert isinstance(raised_from, ClientError)
|
||||
assert len(kwargs) == 0 # can't support other kwargs
|
||||
# init self as boto ClientError (which doesn't cooperatively subclass):
|
||||
super(AnymailBotoClientAPIError, self).__init__(
|
||||
|
||||
@@ -18,7 +18,7 @@ try:
|
||||
except ImportError:
|
||||
string_concat = None
|
||||
|
||||
from anymail.exceptions import AnymailInvalidAddress
|
||||
from anymail.exceptions import AnymailInvalidAddress, _LazyError
|
||||
from anymail.utils import (
|
||||
parse_address_list, parse_single_address, EmailAddress,
|
||||
is_lazy, force_non_lazy, force_non_lazy_dict, force_non_lazy_list,
|
||||
@@ -355,3 +355,16 @@ class ParseRFC2822DateTests(SimpleTestCase):
|
||||
self.assertIsNone(parse_rfc2822date("Tue, 24 Oct"))
|
||||
self.assertIsNone(parse_rfc2822date("Lug, 24 Nod 2017 10:11:35 +0000"))
|
||||
self.assertIsNone(parse_rfc2822date("Tue, 99 Oct 9999 99:99:99 +9999"))
|
||||
|
||||
|
||||
class LazyErrorTests(SimpleTestCase):
|
||||
def test_attr(self):
|
||||
lazy = _LazyError(ValueError("lazy failure")) # creating doesn't cause error
|
||||
lazy.some_prop = "foo" # setattr doesn't cause error
|
||||
with self.assertRaisesMessage(ValueError, "lazy failure"):
|
||||
self.unused = lazy.anything # getattr *does* cause error
|
||||
|
||||
def test_call(self):
|
||||
lazy = _LazyError(ValueError("lazy failure")) # creating doesn't cause error
|
||||
with self.assertRaisesMessage(ValueError, "lazy failure"):
|
||||
self.unused = lazy() # call *does* cause error
|
||||
|
||||
Reference in New Issue
Block a user