Rename EmailBackends for Django consistency

* **Future breaking change:**
  Rename all Anymail backends to just `EmailBackend`,
  matching Django's naming convention.
  (E.g., switch to "anymail.backends.mailgun.EmailBackend"
  rather than "anymail.backends.mailgun.MailgunBackend".)

  The old names still work, but will issue a DeprecationWarning
  and will be removed in some future release.

  (Apologies for this change; the old naming convention was
  a holdover from Djrill, and I wanted consistency with
  other Django EmailBackends before hitting 1.0.)

Fixes #49.
This commit is contained in:
medmunds
2017-01-20 15:47:37 -08:00
parent bff01b440a
commit 79288603fb
29 changed files with 190 additions and 71 deletions

View File

@@ -101,7 +101,7 @@ or SparkPost or any other supported ESP where you see "mailgun":
"MAILGUN_API_KEY": "<your Mailgun key>", "MAILGUN_API_KEY": "<your Mailgun key>",
"MAILGUN_SENDER_DOMAIN": 'mg.example.com', # your Mailgun domain, if needed "MAILGUN_SENDER_DOMAIN": 'mg.example.com', # your Mailgun domain, if needed
} }
EMAIL_BACKEND = "anymail.backends.mailgun.MailgunBackend" # or sendgrid.SendGridBackend, or... EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend" # or sendgrid.EmailBackend, or...
DEFAULT_FROM_EMAIL = "you@example.com" # if you don't already have this in settings DEFAULT_FROM_EMAIL = "you@example.com" # if you don't already have this in settings

View File

@@ -190,9 +190,12 @@ class AnymailBaseBackend(BaseEmailBackend):
""" """
Read-only name of the ESP for this backend. Read-only name of the ESP for this backend.
(E.g., MailgunBackend will return "Mailgun") Concrete backends must override with class attr. E.g.:
esp_name = "Postmark"
esp_name = "SendGrid" # (use ESP's preferred capitalization)
""" """
return self.__class__.__name__.replace("Backend", "") raise NotImplementedError("%s.%s must declare esp_name class attr" %
(self.__class__.__module__, self.__class__.__name__))
class BasePayload(object): class BasePayload(object):

View File

@@ -1,17 +1,20 @@
import warnings
from datetime import datetime from datetime import datetime
from ..exceptions import AnymailRequestsAPIError, AnymailError from ..exceptions import AnymailRequestsAPIError, AnymailError, AnymailDeprecationWarning
from ..message import AnymailRecipientStatus from ..message import AnymailRecipientStatus
from ..utils import get_anymail_setting, rfc2822date from ..utils import get_anymail_setting, rfc2822date
from .base_requests import AnymailRequestsBackend, RequestsPayload from .base_requests import AnymailRequestsBackend, RequestsPayload
class MailgunBackend(AnymailRequestsBackend): class EmailBackend(AnymailRequestsBackend):
""" """
Mailgun API Email Backend Mailgun API Email Backend
""" """
esp_name = "Mailgun"
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Init options from Django settings""" """Init options from Django settings"""
esp_name = self.esp_name esp_name = self.esp_name
@@ -22,7 +25,7 @@ class MailgunBackend(AnymailRequestsBackend):
default="https://api.mailgun.net/v3") default="https://api.mailgun.net/v3")
if not api_url.endswith("/"): if not api_url.endswith("/"):
api_url += "/" api_url += "/"
super(MailgunBackend, self).__init__(api_url, **kwargs) super(EmailBackend, self).__init__(api_url, **kwargs)
def build_message_payload(self, message, defaults): def build_message_payload(self, message, defaults):
return MailgunPayload(message, defaults, self) return MailgunPayload(message, defaults, self)
@@ -52,6 +55,15 @@ class MailgunBackend(AnymailRequestsBackend):
return {recipient.email: status for recipient in payload.all_recipients} return {recipient.email: status for recipient in payload.all_recipients}
# Pre-v0.8 naming (deprecated)
class MailgunBackend(EmailBackend):
def __init__(self, **kwargs):
warnings.warn(AnymailDeprecationWarning(
"Please update your EMAIL_BACKEND setting to "
"'anymail.backends.mailgun.EmailBackend'"))
super(MailgunBackend, self).__init__(**kwargs)
class MailgunPayload(RequestsPayload): class MailgunPayload(RequestsPayload):
def __init__(self, message, defaults, backend, *args, **kwargs): def __init__(self, message, defaults, backend, *args, **kwargs):

View File

@@ -1,18 +1,20 @@
import warnings import warnings
from datetime import datetime from datetime import datetime
from ..exceptions import AnymailRequestsAPIError, AnymailWarning from ..exceptions import AnymailRequestsAPIError, AnymailWarning, AnymailDeprecationWarning
from ..message import AnymailRecipientStatus, ANYMAIL_STATUSES from ..message import AnymailRecipientStatus, ANYMAIL_STATUSES
from ..utils import last, combine, get_anymail_setting from ..utils import last, combine, get_anymail_setting
from .base_requests import AnymailRequestsBackend, RequestsPayload from .base_requests import AnymailRequestsBackend, RequestsPayload
class MandrillBackend(AnymailRequestsBackend): class EmailBackend(AnymailRequestsBackend):
""" """
Mandrill API Email Backend Mandrill API Email Backend
""" """
esp_name = "Mandrill"
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Init options from Django settings""" """Init options from Django settings"""
esp_name = self.esp_name esp_name = self.esp_name
@@ -21,7 +23,7 @@ class MandrillBackend(AnymailRequestsBackend):
default="https://mandrillapp.com/api/1.0") default="https://mandrillapp.com/api/1.0")
if not api_url.endswith("/"): if not api_url.endswith("/"):
api_url += "/" api_url += "/"
super(MandrillBackend, self).__init__(api_url, **kwargs) super(EmailBackend, self).__init__(api_url, **kwargs)
def build_message_payload(self, message, defaults): def build_message_payload(self, message, defaults):
return MandrillPayload(message, defaults, self) return MandrillPayload(message, defaults, self)
@@ -44,6 +46,15 @@ class MandrillBackend(AnymailRequestsBackend):
return recipient_status return recipient_status
# Pre-v0.8 naming (deprecated)
class MandrillBackend(EmailBackend):
def __init__(self, **kwargs):
warnings.warn(AnymailDeprecationWarning(
"Please update your EMAIL_BACKEND setting to "
"'anymail.backends.mandrill.EmailBackend'"))
super(MandrillBackend, self).__init__(**kwargs)
class DjrillDeprecationWarning(AnymailWarning, DeprecationWarning): class DjrillDeprecationWarning(AnymailWarning, DeprecationWarning):
"""Warning for features carried over from Djrill that will be removed soon""" """Warning for features carried over from Djrill that will be removed soon"""

View File

@@ -1,19 +1,22 @@
import re import re
import warnings
from requests.structures import CaseInsensitiveDict from requests.structures import CaseInsensitiveDict
from ..exceptions import AnymailRequestsAPIError from ..exceptions import AnymailRequestsAPIError, AnymailDeprecationWarning
from ..message import AnymailRecipientStatus from ..message import AnymailRecipientStatus
from ..utils import get_anymail_setting from ..utils import get_anymail_setting
from .base_requests import AnymailRequestsBackend, RequestsPayload from .base_requests import AnymailRequestsBackend, RequestsPayload
class PostmarkBackend(AnymailRequestsBackend): class EmailBackend(AnymailRequestsBackend):
""" """
Postmark API Email Backend Postmark API Email Backend
""" """
esp_name = "Postmark"
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Init options from Django settings""" """Init options from Django settings"""
esp_name = self.esp_name esp_name = self.esp_name
@@ -22,7 +25,7 @@ class PostmarkBackend(AnymailRequestsBackend):
default="https://api.postmarkapp.com/") default="https://api.postmarkapp.com/")
if not api_url.endswith("/"): if not api_url.endswith("/"):
api_url += "/" api_url += "/"
super(PostmarkBackend, self).__init__(api_url, **kwargs) super(EmailBackend, self).__init__(api_url, **kwargs)
def build_message_payload(self, message, defaults): def build_message_payload(self, message, defaults):
return PostmarkPayload(message, defaults, self) return PostmarkPayload(message, defaults, self)
@@ -30,7 +33,7 @@ class PostmarkBackend(AnymailRequestsBackend):
def raise_for_status(self, response, payload, message): def raise_for_status(self, response, payload, message):
# We need to handle 422 responses in parse_recipient_status # We need to handle 422 responses in parse_recipient_status
if response.status_code != 422: if response.status_code != 422:
super(PostmarkBackend, self).raise_for_status(response, payload, message) super(EmailBackend, self).raise_for_status(response, payload, message)
def parse_recipient_status(self, response, payload, message): def parse_recipient_status(self, response, payload, message):
parsed_response = self.deserialize_json_response(response, payload, message) parsed_response = self.deserialize_json_response(response, payload, message)
@@ -89,6 +92,15 @@ class PostmarkBackend(AnymailRequestsBackend):
return [] return []
# Pre-v0.8 naming (deprecated)
class PostmarkBackend(EmailBackend):
def __init__(self, **kwargs):
warnings.warn(AnymailDeprecationWarning(
"Please update your EMAIL_BACKEND setting to "
"'anymail.backends.postmark.EmailBackend'"))
super(PostmarkBackend, self).__init__(**kwargs)
class PostmarkPayload(RequestsPayload): class PostmarkPayload(RequestsPayload):
def __init__(self, message, defaults, backend, *args, **kwargs): def __init__(self, message, defaults, backend, *args, **kwargs):

View File

@@ -5,16 +5,18 @@ from django.core.mail import make_msgid
from requests.structures import CaseInsensitiveDict from requests.structures import CaseInsensitiveDict
from .base_requests import AnymailRequestsBackend, RequestsPayload from .base_requests import AnymailRequestsBackend, RequestsPayload
from ..exceptions import AnymailConfigurationError, AnymailRequestsAPIError, AnymailWarning from ..exceptions import AnymailConfigurationError, AnymailRequestsAPIError, AnymailWarning, AnymailDeprecationWarning
from ..message import AnymailRecipientStatus from ..message import AnymailRecipientStatus
from ..utils import get_anymail_setting, timestamp, update_deep from ..utils import get_anymail_setting, timestamp, update_deep
class SendGridBackend(AnymailRequestsBackend): class EmailBackend(AnymailRequestsBackend):
""" """
SendGrid v3 API Email Backend SendGrid v3 API Email Backend
""" """
esp_name = "SendGrid"
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Init options from Django settings""" """Init options from Django settings"""
esp_name = self.esp_name esp_name = self.esp_name
@@ -46,7 +48,7 @@ class SendGridBackend(AnymailRequestsBackend):
default="https://api.sendgrid.com/v3/") default="https://api.sendgrid.com/v3/")
if not api_url.endswith("/"): if not api_url.endswith("/"):
api_url += "/" api_url += "/"
super(SendGridBackend, self).__init__(api_url, **kwargs) super(EmailBackend, self).__init__(api_url, **kwargs)
def build_message_payload(self, message, defaults): def build_message_payload(self, message, defaults):
return SendGridPayload(message, defaults, self) return SendGridPayload(message, defaults, self)
@@ -64,6 +66,15 @@ class SendGridBackend(AnymailRequestsBackend):
return {recipient.email: status for recipient in payload.all_recipients} return {recipient.email: status for recipient in payload.all_recipients}
# Pre-v0.8 naming (deprecated)
class SendGridBackend(EmailBackend):
def __init__(self, **kwargs):
warnings.warn(AnymailDeprecationWarning(
"Please update your EMAIL_BACKEND setting to "
"'anymail.backends.sendgrid.EmailBackend'"))
super(SendGridBackend, self).__init__(**kwargs)
class SendGridPayload(RequestsPayload): class SendGridPayload(RequestsPayload):
def __init__(self, message, defaults, backend, *args, **kwargs): def __init__(self, message, defaults, backend, *args, **kwargs):

View File

@@ -260,7 +260,7 @@ class SendGridPayload(RequestsPayload):
if files_field in self.files: if files_field in self.files:
# It's possible SendGrid could actually handle this case (needs testing), # It's possible SendGrid could actually handle this case (needs testing),
# but requests doesn't seem to accept a list of tuples for a files field. # but requests doesn't seem to accept a list of tuples for a files field.
# (See the MailgunBackend version for a different approach that might work.) # (See the Mailgun EmailBackend version for a different approach that might work.)
self.unsupported_feature( self.unsupported_feature(
"multiple attachments with the same filename ('%s')" % filename if filename "multiple attachments with the same filename ('%s')" % filename if filename
else "multiple unnamed attachments") else "multiple unnamed attachments")

View File

@@ -1,7 +1,10 @@
from __future__ import absolute_import # we want the sparkpost package, not our own module from __future__ import absolute_import # we want the sparkpost package, not our own module
import warnings
from .base import AnymailBaseBackend, BasePayload from .base import AnymailBaseBackend, BasePayload
from ..exceptions import AnymailAPIError, AnymailImproperlyInstalled, AnymailConfigurationError from ..exceptions import (AnymailAPIError, AnymailImproperlyInstalled,
AnymailConfigurationError, AnymailDeprecationWarning)
from ..message import AnymailRecipientStatus from ..message import AnymailRecipientStatus
from ..utils import get_anymail_setting from ..utils import get_anymail_setting
@@ -11,14 +14,16 @@ except ImportError:
raise AnymailImproperlyInstalled(missing_package='sparkpost', backend='sparkpost') raise AnymailImproperlyInstalled(missing_package='sparkpost', backend='sparkpost')
class SparkPostBackend(AnymailBaseBackend): class EmailBackend(AnymailBaseBackend):
""" """
SparkPost Email Backend (using python-sparkpost client) SparkPost Email Backend (using python-sparkpost client)
""" """
esp_name = "SparkPost"
def __init__(self, **kwargs): def __init__(self, **kwargs):
"""Init options from Django settings""" """Init options from Django settings"""
super(SparkPostBackend, self).__init__(**kwargs) super(EmailBackend, self).__init__(**kwargs)
# SPARKPOST_API_KEY is optional - library reads from env by default # SPARKPOST_API_KEY is optional - library reads from env by default
self.api_key = get_anymail_setting('api_key', esp_name=self.esp_name, self.api_key = get_anymail_setting('api_key', esp_name=self.esp_name,
kwargs=kwargs, allow_bare=True, default=None) kwargs=kwargs, allow_bare=True, default=None)
@@ -77,6 +82,15 @@ class SparkPostBackend(AnymailBaseBackend):
return {recipient.email: recipient_status for recipient in payload.all_recipients} return {recipient.email: recipient_status for recipient in payload.all_recipients}
# Pre-v0.8 naming (deprecated)
class SparkPostBackend(EmailBackend):
def __init__(self, **kwargs):
warnings.warn(AnymailDeprecationWarning(
"Please update your EMAIL_BACKEND setting to "
"'anymail.backends.sparkpost.EmailBackend'"))
super(SparkPostBackend, self).__init__(**kwargs)
class SparkPostPayload(BasePayload): class SparkPostPayload(BasePayload):
def init_payload(self): def init_payload(self):
self.params = {} self.params = {}

View File

@@ -5,13 +5,15 @@ from .base import AnymailBaseBackend, BasePayload
from ..utils import get_anymail_setting from ..utils import get_anymail_setting
class TestBackend(AnymailBaseBackend): class EmailBackend(AnymailBaseBackend):
""" """
Anymail backend that doesn't do anything. Anymail backend that doesn't do anything.
Used for testing Anymail common backend functionality. Used for testing Anymail common backend functionality.
""" """
esp_name = "Test"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Init options from Django settings # Init options from Django settings
esp_name = self.esp_name esp_name = self.esp_name
@@ -19,7 +21,7 @@ class TestBackend(AnymailBaseBackend):
kwargs=kwargs, allow_bare=True) kwargs=kwargs, allow_bare=True)
self.recorded_send_params = get_anymail_setting('recorded_send_params', default=[], self.recorded_send_params = get_anymail_setting('recorded_send_params', default=[],
esp_name=esp_name, kwargs=kwargs) esp_name=esp_name, kwargs=kwargs)
super(TestBackend, self).__init__(*args, **kwargs) super(EmailBackend, self).__init__(*args, **kwargs)
def build_message_payload(self, message, defaults): def build_message_payload(self, message, defaults):
return TestPayload(backend=self, message=message, defaults=defaults) return TestPayload(backend=self, message=message, defaults=defaults)
@@ -47,6 +49,14 @@ class TestBackend(AnymailBaseBackend):
raise AnymailAPIError('Unparsable test response') raise AnymailAPIError('Unparsable test response')
# Pre-v0.8 naming (immediately deprecated for this undocumented test feature)
class TestBackend(object):
def __init__(self, **kwargs):
raise NotImplementedError(
"Anymail's (undocumented) TestBackend has been renamed to "
"'anymail.backends.test.EmailBackend'")
class TestPayload(BasePayload): class TestPayload(BasePayload):
# For test purposes, just keep a dict of the params we've received. # For test purposes, just keep a dict of the params we've received.
# (This approach is also useful for native API backends -- think of # (This approach is also useful for native API backends -- think of

View File

@@ -175,3 +175,7 @@ class AnymailWarning(Warning):
class AnymailInsecureWebhookWarning(AnymailWarning): class AnymailInsecureWebhookWarning(AnymailWarning):
"""Warns when webhook configured without any validation""" """Warns when webhook configured without any validation"""
class AnymailDeprecationWarning(AnymailWarning, DeprecationWarning):
"""Warning for deprecated Anymail features"""

View File

@@ -17,10 +17,9 @@ To use Anymail's Mailgun backend, set:
.. code-block:: python .. code-block:: python
EMAIL_BACKEND = "anymail.backends.mailgun.MailgunBackend" EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend"
in your settings.py. (Watch your capitalization: Mailgun spells their name with a in your settings.py.
lowercase "g", so Anymail does too.)
.. setting:: ANYMAIL_MAILGUN_API_KEY .. setting:: ANYMAIL_MAILGUN_API_KEY

View File

@@ -33,7 +33,7 @@ To use Anymail's Mandrill backend, set:
.. code-block:: python .. code-block:: python
EMAIL_BACKEND = "anymail.backends.mandrill.MandrillBackend" EMAIL_BACKEND = "anymail.backends.mandrill.EmailBackend"
in your settings.py. in your settings.py.

View File

@@ -19,10 +19,9 @@ To use Anymail's Postmark backend, set:
.. code-block:: python .. code-block:: python
EMAIL_BACKEND = "anymail.backends.postmark.PostmarkBackend" EMAIL_BACKEND = "anymail.backends.postmark.EmailBackend"
in your settings.py. (Watch your capitalization: Postmark spells their name with a in your settings.py.
lowercase "m", so Anymail does too.)
.. setting:: ANYMAIL_POSTMARK_SERVER_TOKEN .. setting:: ANYMAIL_POSTMARK_SERVER_TOKEN

View File

@@ -34,10 +34,9 @@ To use Anymail's SendGrid backend, set:
.. code-block:: python .. code-block:: python
EMAIL_BACKEND = "anymail.backends.sendgrid.SendGridBackend" EMAIL_BACKEND = "anymail.backends.sendgrid.EmailBackend"
in your settings.py. (Watch your capitalization: SendGrid spells in your settings.py.
their name with an uppercase "G", so Anymail does too.)
.. setting:: ANYMAIL_SENDGRID_API_KEY .. setting:: ANYMAIL_SENDGRID_API_KEY

View File

@@ -33,10 +33,9 @@ To use Anymail's SparkPost backend, set:
.. code-block:: python .. code-block:: python
EMAIL_BACKEND = "anymail.backends.sparkpost.SparkPostBackend" EMAIL_BACKEND = "anymail.backends.sparkpost.EmailBackend"
in your settings.py. (Watch your capitalization: SparkPost spells in your settings.py.
their name with an inner capital "P", so Anymail does too.)
.. setting:: ANYMAIL_SPARKPOST_API_KEY .. setting:: ANYMAIL_SPARKPOST_API_KEY

View File

@@ -54,7 +54,7 @@ To use Anymail for sending email, edit your Django project's :file:`settings.py`
.. code-block:: python .. code-block:: python
EMAIL_BACKEND = "anymail.backends.mailgun.MailgunBackend" EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend"
(:setting:`EMAIL_BACKEND` sets Django's default for sending emails; you can also (:setting:`EMAIL_BACKEND` sets Django's default for sending emails; you can also
use :ref:`multiple Anymail backends <multiple-backends>` to send particular use :ref:`multiple Anymail backends <multiple-backends>` to send particular

View File

@@ -18,7 +18,7 @@ but send admin emails directly through an SMTP server:
from django.core.mail import send_mail, get_connection from django.core.mail import send_mail, get_connection
# send_mail connection defaults to the settings EMAIL_BACKEND, which # send_mail connection defaults to the settings EMAIL_BACKEND, which
# we've set to Anymail's MailgunBackend. This will be sent using Mailgun: # we've set to Anymail's Mailgun EmailBackend. This will be sent using Mailgun:
send_mail("Thanks", "We sent your order", "sales@example.com", ["customer@example.com"]) send_mail("Thanks", "We sent your order", "sales@example.com", ["customer@example.com"])
# Get a connection to an SMTP backend, and send using that instead: # Get a connection to an SMTP backend, and send using that instead:
@@ -27,13 +27,13 @@ but send admin emails directly through an SMTP server:
connection=smtp_backend) connection=smtp_backend)
# You can even use multiple Anymail backends in the same app: # You can even use multiple Anymail backends in the same app:
sendgrid_backend = get_connection('anymail.backends.sendgrid.SendGridBackend') sendgrid_backend = get_connection('anymail.backends.sendgrid.EmailBackend')
send_mail("Password reset", "Here you go", "noreply@example.com", ["user@example.com"], send_mail("Password reset", "Here you go", "noreply@example.com", ["user@example.com"],
connection=sendgrid_backend) connection=sendgrid_backend)
# You can override settings.py settings with kwargs to get_connection. # You can override settings.py settings with kwargs to get_connection.
# This example supplies credentials to use a SendGrid subuser acccount: # This example supplies credentials to use a SendGrid subuser acccount:
alt_sendgrid_backend = get_connection('anymail.backends.sendgrid.SendGridBackend', alt_sendgrid_backend = get_connection('anymail.backends.sendgrid.EmailBackend',
username='marketing_subuser', password='abc123') username='marketing_subuser', password='abc123')
send_mail("Here's that info", "you wanted", "marketing@example.com", ["prospect@example.com"], send_mail("Here's that info", "you wanted", "marketing@example.com", ["prospect@example.com"],
connection=alt_sendgrid_backend) connection=alt_sendgrid_backend)

View File

@@ -19,11 +19,11 @@ from .utils import AnymailTestMixin
recorded_send_params = [] recorded_send_params = []
@override_settings(EMAIL_BACKEND='anymail.backends.test.TestBackend', @override_settings(EMAIL_BACKEND='anymail.backends.test.EmailBackend',
ANYMAIL_TEST_SAMPLE_SETTING='sample', # required TestBackend setting ANYMAIL_TEST_SAMPLE_SETTING='sample', # required test EmailBackend setting
ANYMAIL_TEST_RECORDED_SEND_PARAMS=recorded_send_params) ANYMAIL_TEST_RECORDED_SEND_PARAMS=recorded_send_params)
class TestBackendTestCase(SimpleTestCase, AnymailTestMixin): class TestBackendTestCase(SimpleTestCase, AnymailTestMixin):
"""Base TestCase using Anymail's TestBackend""" """Base TestCase using Anymail's Test EmailBackend"""
def setUp(self): def setUp(self):
super(TestBackendTestCase, self).setUp() super(TestBackendTestCase, self).setUp()
@@ -42,8 +42,8 @@ class TestBackendTestCase(SimpleTestCase, AnymailTestMixin):
return recorded_send_params[-1] return recorded_send_params[-1]
@override_settings(EMAIL_BACKEND='anymail.backends.test.TestBackend') # but no ANYMAIL settings overrides @override_settings(EMAIL_BACKEND='anymail.backends.test.EmailBackend') # but no ANYMAIL settings overrides
class BackendSettingsTests(SimpleTestCase, AnymailTestMixin): # (so not TestBackendTestCase) class BackendSettingsTests(SimpleTestCase, AnymailTestMixin): # (so not TestBackendTestCase)
"""Test settings initializations for Anymail EmailBackends""" """Test settings initializations for Anymail EmailBackends"""
@override_settings(ANYMAIL={'TEST_SAMPLE_SETTING': 'setting_from_anymail_settings'}) @override_settings(ANYMAIL={'TEST_SAMPLE_SETTING': 'setting_from_anymail_settings'})
@@ -97,7 +97,7 @@ class UnsupportedFeatureTests(TestBackendTestCase):
def test_unsupported_feature(self): def test_unsupported_feature(self):
"""Unsupported features raise AnymailUnsupportedFeature""" """Unsupported features raise AnymailUnsupportedFeature"""
# TestBackend doesn't support non-HTML alternative parts # Test EmailBackend doesn't support non-HTML alternative parts
self.message.attach_alternative(b'FAKE_MP3_DATA', 'audio/mpeg') self.message.attach_alternative(b'FAKE_MP3_DATA', 'audio/mpeg')
with self.assertRaises(AnymailUnsupportedFeature): with self.assertRaises(AnymailUnsupportedFeature):
self.message.send() self.message.send()
@@ -135,10 +135,10 @@ class SendDefaultsTests(TestBackendTestCase):
self.assertEqual(params['tags'], ['globaltag']) self.assertEqual(params['tags'], ['globaltag'])
self.assertEqual(params['template_id'], 'my-template') self.assertEqual(params['template_id'], 'my-template')
self.assertEqual(params['track_clicks'], True) self.assertEqual(params['track_clicks'], True)
self.assertEqual(params['globalextra'], 'globalsetting') # TestBackend merges esp_extra into params self.assertEqual(params['globalextra'], 'globalsetting') # Test EmailBackend merges esp_extra into params
@override_settings(ANYMAIL={ @override_settings(ANYMAIL={
'TEST_SEND_DEFAULTS': { # "TEST" is the name of the TestBackend's ESP 'TEST_SEND_DEFAULTS': { # "TEST" is the name of the Test EmailBackend's ESP
'metadata': {'global': 'espvalue'}, 'metadata': {'global': 'espvalue'},
'tags': ['esptag'], 'tags': ['esptag'],
'track_opens': False, 'track_opens': False,
@@ -152,7 +152,7 @@ class SendDefaultsTests(TestBackendTestCase):
self.assertEqual(params['metadata'], {'global': 'espvalue'}) self.assertEqual(params['metadata'], {'global': 'espvalue'})
self.assertEqual(params['tags'], ['esptag']) self.assertEqual(params['tags'], ['esptag'])
self.assertEqual(params['track_opens'], False) self.assertEqual(params['track_opens'], False)
self.assertEqual(params['globalextra'], 'espsetting') # TestBackend merges esp_extra into params self.assertEqual(params['globalextra'], 'espsetting') # Test EmailBackend merges esp_extra into params
@override_settings(ANYMAIL={ @override_settings(ANYMAIL={
'SEND_DEFAULTS': { 'SEND_DEFAULTS': {
@@ -199,7 +199,7 @@ class SendDefaultsTests(TestBackendTestCase):
'template_id': 'global-template', 'template_id': 'global-template',
'esp_extra': {'globalextra': 'globalsetting'}, 'esp_extra': {'globalextra': 'globalsetting'},
}, },
'TEST_SEND_DEFAULTS': { # "TEST" is the name of the TestBackend's ESP 'TEST_SEND_DEFAULTS': { # "TEST" is the name of the Test EmailBackend's ESP
'merge_global_data': {'esp': 'espmerge'}, 'merge_global_data': {'esp': 'espmerge'},
'metadata': {'esp': 'espvalue'}, 'metadata': {'esp': 'espvalue'},
'tags': ['esptag'], 'tags': ['esptag'],

View File

@@ -17,7 +17,7 @@ from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharin
from .utils import sample_image_content, sample_image_path, SAMPLE_IMAGE_FILENAME, AnymailTestMixin from .utils import sample_image_content, sample_image_path, SAMPLE_IMAGE_FILENAME, AnymailTestMixin
@override_settings(EMAIL_BACKEND='anymail.backends.mailgun.MailgunBackend', @override_settings(EMAIL_BACKEND='anymail.backends.mailgun.EmailBackend',
ANYMAIL={'MAILGUN_API_KEY': 'test_api_key'}) ANYMAIL={'MAILGUN_API_KEY': 'test_api_key'})
class MailgunBackendMockAPITestCase(RequestsBackendMockAPITestCase): class MailgunBackendMockAPITestCase(RequestsBackendMockAPITestCase):
DEFAULT_RAW_RESPONSE = b"""{ DEFAULT_RAW_RESPONSE = b"""{
@@ -483,7 +483,7 @@ class MailgunBackendSessionSharingTestCase(SessionSharingTestCasesMixin, Mailgun
pass # tests are defined in the mixin pass # tests are defined in the mixin
@override_settings(EMAIL_BACKEND="anymail.backends.mailgun.MailgunBackend") @override_settings(EMAIL_BACKEND="anymail.backends.mailgun.EmailBackend")
class MailgunBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin): class MailgunBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin):
"""Test ESP backend without required settings in place""" """Test ESP backend without required settings in place"""
@@ -494,3 +494,13 @@ class MailgunBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin):
# Make sure the error mentions MAILGUN_API_KEY and ANYMAIL_MAILGUN_API_KEY # Make sure the error mentions MAILGUN_API_KEY and ANYMAIL_MAILGUN_API_KEY
self.assertRegex(errmsg, r'\bMAILGUN_API_KEY\b') self.assertRegex(errmsg, r'\bMAILGUN_API_KEY\b')
self.assertRegex(errmsg, r'\bANYMAIL_MAILGUN_API_KEY\b') self.assertRegex(errmsg, r'\bANYMAIL_MAILGUN_API_KEY\b')
class MailgunBackendDeprecationTests(MailgunBackendMockAPITestCase):
@override_settings(EMAIL_BACKEND='anymail.backends.mailgun.MailgunBackend')
def test_renamed_backend_warning(self):
# ...mailgun.MailgunBackend --> ...mailgun.EmailBackend
with self.assertWarnsRegex(DeprecationWarning,
r'anymail\.backends\.mailgun\.EmailBackend'):
self.message.send()

View File

@@ -24,7 +24,7 @@ MAILGUN_TEST_DOMAIN = os.getenv('MAILGUN_TEST_DOMAIN')
@override_settings(ANYMAIL={'MAILGUN_API_KEY': MAILGUN_TEST_API_KEY, @override_settings(ANYMAIL={'MAILGUN_API_KEY': MAILGUN_TEST_API_KEY,
'MAILGUN_SENDER_DOMAIN': MAILGUN_TEST_DOMAIN, 'MAILGUN_SENDER_DOMAIN': MAILGUN_TEST_DOMAIN,
'MAILGUN_SEND_DEFAULTS': {'esp_extra': {'o:testmode': 'yes'}}}, 'MAILGUN_SEND_DEFAULTS': {'esp_extra': {'o:testmode': 'yes'}}},
EMAIL_BACKEND="anymail.backends.mailgun.MailgunBackend") EMAIL_BACKEND="anymail.backends.mailgun.EmailBackend")
class MailgunBackendIntegrationTests(SimpleTestCase, AnymailTestMixin): class MailgunBackendIntegrationTests(SimpleTestCase, AnymailTestMixin):
"""Mailgun API integration tests """Mailgun API integration tests

View File

@@ -19,7 +19,7 @@ from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharin
from .utils import sample_image_content, sample_image_path, SAMPLE_IMAGE_FILENAME, AnymailTestMixin, decode_att from .utils import sample_image_content, sample_image_path, SAMPLE_IMAGE_FILENAME, AnymailTestMixin, decode_att
@override_settings(EMAIL_BACKEND='anymail.backends.mandrill.MandrillBackend', @override_settings(EMAIL_BACKEND='anymail.backends.mandrill.EmailBackend',
ANYMAIL={'MANDRILL_API_KEY': 'test_api_key'}) ANYMAIL={'MANDRILL_API_KEY': 'test_api_key'})
class MandrillBackendMockAPITestCase(RequestsBackendMockAPITestCase): class MandrillBackendMockAPITestCase(RequestsBackendMockAPITestCase):
DEFAULT_RAW_RESPONSE = b"""[{ DEFAULT_RAW_RESPONSE = b"""[{
@@ -588,7 +588,7 @@ class MandrillBackendSessionSharingTestCase(SessionSharingTestCasesMixin, Mandri
pass # tests are defined in the mixin pass # tests are defined in the mixin
@override_settings(EMAIL_BACKEND="anymail.backends.mandrill.MandrillBackend") @override_settings(EMAIL_BACKEND="anymail.backends.mandrill.EmailBackend")
class MandrillBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin): class MandrillBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin):
"""Test backend without required settings""" """Test backend without required settings"""
@@ -598,3 +598,12 @@ class MandrillBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin)
errmsg = str(cm.exception) errmsg = str(cm.exception)
self.assertRegex(errmsg, r'\bMANDRILL_API_KEY\b') self.assertRegex(errmsg, r'\bMANDRILL_API_KEY\b')
self.assertRegex(errmsg, r'\bANYMAIL_MANDRILL_API_KEY\b') self.assertRegex(errmsg, r'\bANYMAIL_MANDRILL_API_KEY\b')
class MandrillBackendDeprecationTests(MandrillBackendMockAPITestCase):
@override_settings(EMAIL_BACKEND='anymail.backends.mandrill.MandrillBackend')
def test_renamed_backend_warning(self):
# ...mandrill.MandrillBackend --> ...mandrill.EmailBackend
with self.assertWarnsRegex(DeprecationWarning,
r'anymail\.backends\.mandrill\.EmailBackend'):
self.message.send()

View File

@@ -17,7 +17,7 @@ MANDRILL_TEST_API_KEY = os.getenv('MANDRILL_TEST_API_KEY')
@unittest.skipUnless(MANDRILL_TEST_API_KEY, @unittest.skipUnless(MANDRILL_TEST_API_KEY,
"Set MANDRILL_TEST_API_KEY environment variable to run integration tests") "Set MANDRILL_TEST_API_KEY environment variable to run integration tests")
@override_settings(MANDRILL_API_KEY=MANDRILL_TEST_API_KEY, @override_settings(MANDRILL_API_KEY=MANDRILL_TEST_API_KEY,
EMAIL_BACKEND="anymail.backends.mandrill.MandrillBackend") EMAIL_BACKEND="anymail.backends.mandrill.EmailBackend")
class MandrillBackendIntegrationTests(SimpleTestCase, AnymailTestMixin): class MandrillBackendIntegrationTests(SimpleTestCase, AnymailTestMixin):
"""Mandrill API integration tests """Mandrill API integration tests

View File

@@ -18,7 +18,7 @@ from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharin
from .utils import sample_image_content, sample_image_path, SAMPLE_IMAGE_FILENAME, AnymailTestMixin, decode_att from .utils import sample_image_content, sample_image_path, SAMPLE_IMAGE_FILENAME, AnymailTestMixin, decode_att
@override_settings(EMAIL_BACKEND='anymail.backends.postmark.PostmarkBackend', @override_settings(EMAIL_BACKEND='anymail.backends.postmark.EmailBackend',
ANYMAIL={'POSTMARK_SERVER_TOKEN': 'test_server_token'}) ANYMAIL={'POSTMARK_SERVER_TOKEN': 'test_server_token'})
class PostmarkBackendMockAPITestCase(RequestsBackendMockAPITestCase): class PostmarkBackendMockAPITestCase(RequestsBackendMockAPITestCase):
DEFAULT_RAW_RESPONSE = b"""{ DEFAULT_RAW_RESPONSE = b"""{
@@ -560,7 +560,7 @@ class PostmarkBackendSessionSharingTestCase(SessionSharingTestCasesMixin, Postma
pass # tests are defined in the mixin pass # tests are defined in the mixin
@override_settings(EMAIL_BACKEND="anymail.backends.postmark.PostmarkBackend") @override_settings(EMAIL_BACKEND="anymail.backends.postmark.EmailBackend")
class PostmarkBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin): class PostmarkBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin):
"""Test ESP backend without required settings in place""" """Test ESP backend without required settings in place"""
@@ -570,3 +570,12 @@ class PostmarkBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin)
errmsg = str(cm.exception) errmsg = str(cm.exception)
self.assertRegex(errmsg, r'\bPOSTMARK_SERVER_TOKEN\b') self.assertRegex(errmsg, r'\bPOSTMARK_SERVER_TOKEN\b')
self.assertRegex(errmsg, r'\bANYMAIL_POSTMARK_SERVER_TOKEN\b') self.assertRegex(errmsg, r'\bANYMAIL_POSTMARK_SERVER_TOKEN\b')
class PostmarkBackendDeprecationTests(PostmarkBackendMockAPITestCase):
@override_settings(EMAIL_BACKEND='anymail.backends.postmark.PostmarkBackend')
def test_renamed_backend_warning(self):
# ...postmark.PostmarkBackend --> ...postmark.EmailBackend
with self.assertWarnsRegex(DeprecationWarning,
r'anymail\.backends\.postmark\.EmailBackend'):
self.message.send()

View File

@@ -11,7 +11,7 @@ from .utils import AnymailTestMixin, sample_image_path, RUN_LIVE_TESTS
@unittest.skipUnless(RUN_LIVE_TESTS, "RUN_LIVE_TESTS disabled in this environment") @unittest.skipUnless(RUN_LIVE_TESTS, "RUN_LIVE_TESTS disabled in this environment")
@override_settings(ANYMAIL_POSTMARK_SERVER_TOKEN="POSTMARK_API_TEST", @override_settings(ANYMAIL_POSTMARK_SERVER_TOKEN="POSTMARK_API_TEST",
EMAIL_BACKEND="anymail.backends.postmark.PostmarkBackend") EMAIL_BACKEND="anymail.backends.postmark.EmailBackend")
class PostmarkBackendIntegrationTests(SimpleTestCase, AnymailTestMixin): class PostmarkBackendIntegrationTests(SimpleTestCase, AnymailTestMixin):
"""Postmark API integration tests """Postmark API integration tests

View File

@@ -1,6 +1,6 @@
from django.dispatch import receiver from django.dispatch import receiver
from anymail.backends.test import TestBackend from anymail.backends.test import EmailBackend as TestEmailBackend
from anymail.exceptions import AnymailCancelSend, AnymailRecipientsRefused from anymail.exceptions import AnymailCancelSend, AnymailRecipientsRefused
from anymail.message import AnymailRecipientStatus from anymail.message import AnymailRecipientStatus
from anymail.signals import pre_send, post_send from anymail.signals import pre_send, post_send
@@ -16,9 +16,9 @@ class TestPreSendSignal(TestBackendTestCase):
@receiver(pre_send, weak=False) @receiver(pre_send, weak=False)
def handle_pre_send(sender, message, esp_name, **kwargs): def handle_pre_send(sender, message, esp_name, **kwargs):
self.assertEqual(self.get_send_count(), 0) # not sent yet self.assertEqual(self.get_send_count(), 0) # not sent yet
self.assertEqual(sender, TestBackend) self.assertEqual(sender, TestEmailBackend)
self.assertEqual(message, self.message) self.assertEqual(message, self.message)
self.assertEqual(esp_name, "Test") # the TestBackend's ESP is named "Test" self.assertEqual(esp_name, "Test") # the TestEmailBackend's ESP is named "Test"
self.receiver_called = True self.receiver_called = True
self.addCleanup(pre_send.disconnect, receiver=handle_pre_send) self.addCleanup(pre_send.disconnect, receiver=handle_pre_send)
@@ -62,13 +62,13 @@ class TestPostSendSignal(TestBackendTestCase):
@receiver(post_send, weak=False) @receiver(post_send, weak=False)
def handle_post_send(sender, message, status, esp_name, **kwargs): def handle_post_send(sender, message, status, esp_name, **kwargs):
self.assertEqual(self.get_send_count(), 1) # already sent self.assertEqual(self.get_send_count(), 1) # already sent
self.assertEqual(sender, TestBackend) self.assertEqual(sender, TestEmailBackend)
self.assertEqual(message, self.message) self.assertEqual(message, self.message)
self.assertEqual(status.status, {'sent'}) self.assertEqual(status.status, {'sent'})
self.assertEqual(status.message_id, 1) # TestBackend default message_id self.assertEqual(status.message_id, 1) # TestEmailBackend default message_id
self.assertEqual(status.recipients['to@example.com'].status, 'sent') self.assertEqual(status.recipients['to@example.com'].status, 'sent')
self.assertEqual(status.recipients['to@example.com'].message_id, 1) self.assertEqual(status.recipients['to@example.com'].message_id, 1)
self.assertEqual(esp_name, "Test") # the TestBackend's ESP is named "Test" self.assertEqual(esp_name, "Test") # the TestEmailBackend's ESP is named "Test"
self.receiver_called = True self.receiver_called = True
self.addCleanup(post_send.disconnect, receiver=handle_post_send) self.addCleanup(post_send.disconnect, receiver=handle_post_send)

View File

@@ -20,7 +20,7 @@ from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharin
from .utils import sample_image_content, sample_image_path, SAMPLE_IMAGE_FILENAME, AnymailTestMixin from .utils import sample_image_content, sample_image_path, SAMPLE_IMAGE_FILENAME, AnymailTestMixin
@override_settings(EMAIL_BACKEND='anymail.backends.sendgrid.SendGridBackend', @override_settings(EMAIL_BACKEND='anymail.backends.sendgrid.EmailBackend',
ANYMAIL={'SENDGRID_API_KEY': 'test_api_key'}) ANYMAIL={'SENDGRID_API_KEY': 'test_api_key'})
class SendGridBackendMockAPITestCase(RequestsBackendMockAPITestCase): class SendGridBackendMockAPITestCase(RequestsBackendMockAPITestCase):
DEFAULT_RAW_RESPONSE = b"" # SendGrid v3 success responses are empty DEFAULT_RAW_RESPONSE = b"" # SendGrid v3 success responses are empty
@@ -619,7 +619,7 @@ class SendGridBackendSessionSharingTestCase(SessionSharingTestCasesMixin, SendGr
pass # tests are defined in the mixin pass # tests are defined in the mixin
@override_settings(EMAIL_BACKEND="anymail.backends.sendgrid.SendGridBackend") @override_settings(EMAIL_BACKEND="anymail.backends.sendgrid.EmailBackend")
class SendGridBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin): class SendGridBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin):
"""Test ESP backend without required settings in place""" """Test ESP backend without required settings in place"""
@@ -628,7 +628,7 @@ class SendGridBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin)
mail.send_mail('Subject', 'Message', 'from@example.com', ['to@example.com']) mail.send_mail('Subject', 'Message', 'from@example.com', ['to@example.com'])
@override_settings(EMAIL_BACKEND="anymail.backends.sendgrid.SendGridBackend") @override_settings(EMAIL_BACKEND="anymail.backends.sendgrid.EmailBackend")
class SendGridBackendDisallowsV2Tests(SimpleTestCase, AnymailTestMixin): class SendGridBackendDisallowsV2Tests(SimpleTestCase, AnymailTestMixin):
"""Using v2-API-only features should cause errors with v3 backend""" """Using v2-API-only features should cause errors with v3 backend"""
@@ -645,3 +645,12 @@ class SendGridBackendDisallowsV2Tests(SimpleTestCase, AnymailTestMixin):
message.esp_extra = {'x-smtpapi': {'asm_group_id': 1}} message.esp_extra = {'x-smtpapi': {'asm_group_id': 1}}
with self.assertRaisesRegex(AnymailConfigurationError, r'\bsendgrid_v2\.EmailBackend\b'): with self.assertRaisesRegex(AnymailConfigurationError, r'\bsendgrid_v2\.EmailBackend\b'):
message.send() message.send()
class SendGridBackendDeprecationTests(SendGridBackendMockAPITestCase):
@override_settings(EMAIL_BACKEND='anymail.backends.sendgrid.SendGridBackend')
def test_renamed_backend_warning(self):
# ...sendgrid.SendGridBackend --> ...sendgrid.EmailBackend
with self.assertWarnsRegex(DeprecationWarning,
r'anymail\.backends\.sendgrid\.EmailBackend'):
self.message.send()

View File

@@ -22,7 +22,7 @@ SENDGRID_TEST_TEMPLATE_ID = os.getenv('SENDGRID_TEST_TEMPLATE_ID')
ANYMAIL_SENDGRID_SEND_DEFAULTS={"esp_extra": { ANYMAIL_SENDGRID_SEND_DEFAULTS={"esp_extra": {
"mail_settings": {"sandbox_mode": {"enable": True}}, "mail_settings": {"sandbox_mode": {"enable": True}},
}}, }},
EMAIL_BACKEND="anymail.backends.sendgrid.SendGridBackend") EMAIL_BACKEND="anymail.backends.sendgrid.EmailBackend")
class SendGridBackendIntegrationTests(SimpleTestCase, AnymailTestMixin): class SendGridBackendIntegrationTests(SimpleTestCase, AnymailTestMixin):
"""SendGrid v3 API integration tests """SendGrid v3 API integration tests

View File

@@ -21,7 +21,7 @@ from anymail.message import attach_inline_image_file
from .utils import AnymailTestMixin, decode_att, SAMPLE_IMAGE_FILENAME, sample_image_path, sample_image_content from .utils import AnymailTestMixin, decode_att, SAMPLE_IMAGE_FILENAME, sample_image_path, sample_image_content
@override_settings(EMAIL_BACKEND='anymail.backends.sparkpost.SparkPostBackend', @override_settings(EMAIL_BACKEND='anymail.backends.sparkpost.EmailBackend',
ANYMAIL={'SPARKPOST_API_KEY': 'test_api_key'}) ANYMAIL={'SPARKPOST_API_KEY': 'test_api_key'})
class SparkPostBackendMockAPITestCase(SimpleTestCase, AnymailTestMixin): class SparkPostBackendMockAPITestCase(SimpleTestCase, AnymailTestMixin):
"""TestCase that uses SparkPostEmailBackend with a mocked transmissions.send API""" """TestCase that uses SparkPostEmailBackend with a mocked transmissions.send API"""
@@ -559,7 +559,7 @@ class SparkPostBackendRecipientsRefusedTests(SparkPostBackendMockAPITestCase):
self.assertEqual(sent, 1) # refused message is included in sent count self.assertEqual(sent, 1) # refused message is included in sent count
@override_settings(EMAIL_BACKEND="anymail.backends.sparkpost.SparkPostBackend") @override_settings(EMAIL_BACKEND="anymail.backends.sparkpost.EmailBackend")
class SparkPostBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin): class SparkPostBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin):
"""Test ESP backend without required settings in place""" """Test ESP backend without required settings in place"""
@@ -580,3 +580,12 @@ class SparkPostBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin
# Poke into implementation details to verify: # Poke into implementation details to verify:
self.assertIsNone(conn.api_key) # Anymail prop self.assertIsNone(conn.api_key) # Anymail prop
self.assertEqual(conn.sp.api_key, 'key_from_environment') # SparkPost prop self.assertEqual(conn.sp.api_key, 'key_from_environment') # SparkPost prop
class SparkPostBackendDeprecationTests(SparkPostBackendMockAPITestCase):
@override_settings(EMAIL_BACKEND='anymail.backends.sparkpost.SparkPostBackend')
def test_renamed_backend_warning(self):
# ...sparkpost.SparkPostBackend --> ...sparkpost.EmailBackend
with self.assertWarnsRegex(DeprecationWarning,
r'anymail\.backends\.sparkpost\.EmailBackend'):
self.message.send()

View File

@@ -18,7 +18,7 @@ SPARKPOST_TEST_API_KEY = os.getenv('SPARKPOST_TEST_API_KEY')
"Set SPARKPOST_TEST_API_KEY environment variable " "Set SPARKPOST_TEST_API_KEY environment variable "
"to run SparkPost integration tests") "to run SparkPost integration tests")
@override_settings(ANYMAIL_SPARKPOST_API_KEY=SPARKPOST_TEST_API_KEY, @override_settings(ANYMAIL_SPARKPOST_API_KEY=SPARKPOST_TEST_API_KEY,
EMAIL_BACKEND="anymail.backends.sparkpost.SparkPostBackend") EMAIL_BACKEND="anymail.backends.sparkpost.EmailBackend")
class SparkPostBackendIntegrationTests(SimpleTestCase, AnymailTestMixin): class SparkPostBackendIntegrationTests(SimpleTestCase, AnymailTestMixin):
"""SparkPost API integration tests """SparkPost API integration tests