Cleanup: centralize Reply-To header handling; case-insensitive headers

Django allows setting the reply address with either message.reply_to
or message.extra_headers["Reply-To"]. If both are supplied, the extra
headers version takes precedence. (See EmailMessage.message().)

Several Anymail backends had duplicate logic to handle conflicting
properties. Move that logic into the base Payload.

(Also prepares for common handling of extra_headers['From'], later.)

Related changes:

* Use CaseInsensitiveDict for processing extra_headers.
  This is potentially a breaking change, but any code that was trying
  to send multiple headers differing only in case was likely already
  broken. (Email header field names are case-insensitive, per RFC-822.)

* Handle CaseInsensitiveDict in RequestsPayload.serialize_json().
  (Several backends had duplicate code for handling this, too.)

* Fixes SparkPost backend, which had been incorrectly treating
  message.reply_to and message.extra_headers['Reply-To'] differently.
This commit is contained in:
medmunds
2018-02-26 12:02:48 -08:00
parent ec0ee336a2
commit bd9d92f5a0
9 changed files with 75 additions and 33 deletions

View File

@@ -1,7 +1,7 @@
import json
import requests
# noinspection PyUnresolvedReferences
from requests.structures import CaseInsensitiveDict
from six.moves.urllib.parse import urljoin
from anymail.utils import get_anymail_setting
@@ -156,8 +156,16 @@ class RequestsPayload(BasePayload):
Useful for implementing serialize_data in a subclass,
"""
try:
return json.dumps(data)
return json.dumps(data, default=self._json_default)
except TypeError as err:
# Add some context to the "not JSON serializable" message
raise AnymailSerializationError(orig_err=err, email_message=self.message,
backend=self.backend, payload=self)
@staticmethod
def _json_default(o):
"""json.dump default function that handles some common Payload data types"""
if isinstance(o, CaseInsensitiveDict): # used for headers
return dict(o)
raise TypeError("Object of type '%s' is not JSON serializable" %
o.__class__.__name__)