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

@@ -190,9 +190,12 @@ class AnymailBaseBackend(BaseEmailBackend):
"""
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):

View File

@@ -1,17 +1,20 @@
import warnings
from datetime import datetime
from ..exceptions import AnymailRequestsAPIError, AnymailError
from ..exceptions import AnymailRequestsAPIError, AnymailError, AnymailDeprecationWarning
from ..message import AnymailRecipientStatus
from ..utils import get_anymail_setting, rfc2822date
from .base_requests import AnymailRequestsBackend, RequestsPayload
class MailgunBackend(AnymailRequestsBackend):
class EmailBackend(AnymailRequestsBackend):
"""
Mailgun API Email Backend
"""
esp_name = "Mailgun"
def __init__(self, **kwargs):
"""Init options from Django settings"""
esp_name = self.esp_name
@@ -22,7 +25,7 @@ class MailgunBackend(AnymailRequestsBackend):
default="https://api.mailgun.net/v3")
if not api_url.endswith("/"):
api_url += "/"
super(MailgunBackend, self).__init__(api_url, **kwargs)
super(EmailBackend, self).__init__(api_url, **kwargs)
def build_message_payload(self, message, defaults):
return MailgunPayload(message, defaults, self)
@@ -52,6 +55,15 @@ class MailgunBackend(AnymailRequestsBackend):
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):
def __init__(self, message, defaults, backend, *args, **kwargs):

View File

@@ -1,18 +1,20 @@
import warnings
from datetime import datetime
from ..exceptions import AnymailRequestsAPIError, AnymailWarning
from ..exceptions import AnymailRequestsAPIError, AnymailWarning, AnymailDeprecationWarning
from ..message import AnymailRecipientStatus, ANYMAIL_STATUSES
from ..utils import last, combine, get_anymail_setting
from .base_requests import AnymailRequestsBackend, RequestsPayload
class MandrillBackend(AnymailRequestsBackend):
class EmailBackend(AnymailRequestsBackend):
"""
Mandrill API Email Backend
"""
esp_name = "Mandrill"
def __init__(self, **kwargs):
"""Init options from Django settings"""
esp_name = self.esp_name
@@ -21,7 +23,7 @@ class MandrillBackend(AnymailRequestsBackend):
default="https://mandrillapp.com/api/1.0")
if not api_url.endswith("/"):
api_url += "/"
super(MandrillBackend, self).__init__(api_url, **kwargs)
super(EmailBackend, self).__init__(api_url, **kwargs)
def build_message_payload(self, message, defaults):
return MandrillPayload(message, defaults, self)
@@ -44,6 +46,15 @@ class MandrillBackend(AnymailRequestsBackend):
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):
"""Warning for features carried over from Djrill that will be removed soon"""

View File

@@ -1,19 +1,22 @@
import re
import warnings
from requests.structures import CaseInsensitiveDict
from ..exceptions import AnymailRequestsAPIError
from ..exceptions import AnymailRequestsAPIError, AnymailDeprecationWarning
from ..message import AnymailRecipientStatus
from ..utils import get_anymail_setting
from .base_requests import AnymailRequestsBackend, RequestsPayload
class PostmarkBackend(AnymailRequestsBackend):
class EmailBackend(AnymailRequestsBackend):
"""
Postmark API Email Backend
"""
esp_name = "Postmark"
def __init__(self, **kwargs):
"""Init options from Django settings"""
esp_name = self.esp_name
@@ -22,7 +25,7 @@ class PostmarkBackend(AnymailRequestsBackend):
default="https://api.postmarkapp.com/")
if not api_url.endswith("/"):
api_url += "/"
super(PostmarkBackend, self).__init__(api_url, **kwargs)
super(EmailBackend, self).__init__(api_url, **kwargs)
def build_message_payload(self, message, defaults):
return PostmarkPayload(message, defaults, self)
@@ -30,7 +33,7 @@ class PostmarkBackend(AnymailRequestsBackend):
def raise_for_status(self, response, payload, message):
# We need to handle 422 responses in parse_recipient_status
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):
parsed_response = self.deserialize_json_response(response, payload, message)
@@ -89,6 +92,15 @@ class PostmarkBackend(AnymailRequestsBackend):
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):
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 .base_requests import AnymailRequestsBackend, RequestsPayload
from ..exceptions import AnymailConfigurationError, AnymailRequestsAPIError, AnymailWarning
from ..exceptions import AnymailConfigurationError, AnymailRequestsAPIError, AnymailWarning, AnymailDeprecationWarning
from ..message import AnymailRecipientStatus
from ..utils import get_anymail_setting, timestamp, update_deep
class SendGridBackend(AnymailRequestsBackend):
class EmailBackend(AnymailRequestsBackend):
"""
SendGrid v3 API Email Backend
"""
esp_name = "SendGrid"
def __init__(self, **kwargs):
"""Init options from Django settings"""
esp_name = self.esp_name
@@ -46,7 +48,7 @@ class SendGridBackend(AnymailRequestsBackend):
default="https://api.sendgrid.com/v3/")
if not api_url.endswith("/"):
api_url += "/"
super(SendGridBackend, self).__init__(api_url, **kwargs)
super(EmailBackend, self).__init__(api_url, **kwargs)
def build_message_payload(self, message, defaults):
return SendGridPayload(message, defaults, self)
@@ -64,6 +66,15 @@ class SendGridBackend(AnymailRequestsBackend):
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):
def __init__(self, message, defaults, backend, *args, **kwargs):

View File

@@ -260,7 +260,7 @@ class SendGridPayload(RequestsPayload):
if files_field in self.files:
# 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.
# (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(
"multiple attachments with the same filename ('%s')" % filename if filename
else "multiple unnamed attachments")

View File

@@ -1,7 +1,10 @@
from __future__ import absolute_import # we want the sparkpost package, not our own module
import warnings
from .base import AnymailBaseBackend, BasePayload
from ..exceptions import AnymailAPIError, AnymailImproperlyInstalled, AnymailConfigurationError
from ..exceptions import (AnymailAPIError, AnymailImproperlyInstalled,
AnymailConfigurationError, AnymailDeprecationWarning)
from ..message import AnymailRecipientStatus
from ..utils import get_anymail_setting
@@ -11,14 +14,16 @@ except ImportError:
raise AnymailImproperlyInstalled(missing_package='sparkpost', backend='sparkpost')
class SparkPostBackend(AnymailBaseBackend):
class EmailBackend(AnymailBaseBackend):
"""
SparkPost Email Backend (using python-sparkpost client)
"""
esp_name = "SparkPost"
def __init__(self, **kwargs):
"""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
self.api_key = get_anymail_setting('api_key', esp_name=self.esp_name,
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}
# 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):
def init_payload(self):
self.params = {}

View File

@@ -5,13 +5,15 @@ from .base import AnymailBaseBackend, BasePayload
from ..utils import get_anymail_setting
class TestBackend(AnymailBaseBackend):
class EmailBackend(AnymailBaseBackend):
"""
Anymail backend that doesn't do anything.
Used for testing Anymail common backend functionality.
"""
esp_name = "Test"
def __init__(self, *args, **kwargs):
# Init options from Django settings
esp_name = self.esp_name
@@ -19,7 +21,7 @@ class TestBackend(AnymailBaseBackend):
kwargs=kwargs, allow_bare=True)
self.recorded_send_params = get_anymail_setting('recorded_send_params', default=[],
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):
return TestPayload(backend=self, message=message, defaults=defaults)
@@ -47,6 +49,14 @@ class TestBackend(AnymailBaseBackend):
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):
# For test purposes, just keep a dict of the params we've received.
# (This approach is also useful for native API backends -- think of

View File

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