Get rid of magic JSON serialization for Mailgun metadata.

Treat Mailgun metadata like all other ESPs: simple
key-value dict, where values are strings. If you want
to store JSON in metadata, you should serialize and
deserialize it yourself.
This commit is contained in:
medmunds
2016-04-24 12:54:40 -07:00
parent 8e43f29944
commit ebe6710326
3 changed files with 11 additions and 31 deletions

View File

@@ -122,18 +122,8 @@ class MailgunPayload(RequestsPayload):
) )
def set_metadata(self, metadata): def set_metadata(self, metadata):
# The Mailgun docs are a little unclear on whether to send each var as a separate v: field,
# or to send a single 'v:my-custom-data' field with a json blob of all vars.
# (https://documentation.mailgun.com/user_manual.html#attaching-data-to-messages)
# From experimentation, it seems like the first option works:
for key, value in metadata.items(): for key, value in metadata.items():
# Ensure the value is json-serializable (for Mailgun storage) self.data["v:%s" % key] = value
json = self.serialize_json(value) # will raise AnymailSerializationError
# Special case: a single string value should be sent bare (without quotes),
# because Mailgun will add quotes when querying the value as json.
if json.startswith('"'): # only a single string could be serialized as "...
json = value
self.data["v:%s" % key] = json
def set_send_at(self, send_at): def set_send_at(self, send_at):
# Mailgun expects RFC-2822 format dates # Mailgun expects RFC-2822 format dates

View File

@@ -3,7 +3,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from datetime import date, datetime from datetime import date, datetime
from decimal import Decimal
from email.mime.base import MIMEBase from email.mime.base import MIMEBase
from email.mime.image import MIMEImage from email.mime.image import MIMEImage
@@ -13,7 +12,7 @@ from django.test import SimpleTestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils.timezone import get_fixed_timezone, override as override_current_timezone from django.utils.timezone import get_fixed_timezone, override as override_current_timezone
from anymail.exceptions import AnymailAPIError, AnymailSerializationError, AnymailUnsupportedFeature from anymail.exceptions import AnymailAPIError, AnymailUnsupportedFeature
from anymail.message import attach_inline_image_file from anymail.message import attach_inline_image_file
from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharingTestCasesMixin from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharingTestCasesMixin
@@ -270,12 +269,13 @@ class MailgunBackendAnymailFeatureTests(MailgunBackendMockAPITestCase):
"""Test backend support for Anymail added features""" """Test backend support for Anymail added features"""
def test_metadata(self): def test_metadata(self):
self.message.metadata = {'user_id': "12345", 'items': ['mail', 'gun']} # Each metadata value is just a string; you can serialize your own JSON if you'd like.
# (The Mailgun docs are a little confusing on this point.)
self.message.metadata = {'user_id': "12345", 'items': '["mail","gun"]'}
self.message.send() self.message.send()
data = self.get_api_call_data() data = self.get_api_call_data()
# note values get serialized to json: self.assertEqual(data['v:user_id'], '12345')
self.assertEqual(data['v:user_id'], '12345') # simple values are transmitted as-is self.assertEqual(data['v:items'], '["mail","gun"]')
self.assertEqual(data['v:items'], '["mail", "gun"]') # complex values get json-serialized
def test_send_at(self): def test_send_at(self):
utc_plus_6 = get_fixed_timezone(6 * 60) utc_plus_6 = get_fixed_timezone(6 * 60)
@@ -401,16 +401,8 @@ class MailgunBackendAnymailFeatureTests(MailgunBackendMockAPITestCase):
self.assertEqual(self.message.anymail_status.recipients, {}) self.assertEqual(self.message.anymail_status.recipients, {})
self.assertEqual(self.message.anymail_status.esp_response, mock_response) self.assertEqual(self.message.anymail_status.esp_response, mock_response)
def test_json_serialization_errors(self): # test_json_serialization_errors: Mailgun payload isn't JSON, so we don't test this.
"""Try to provide more information about non-json-serializable data""" # (Anything that requests can serialize as a form field will work with Mailgun)
self.message.metadata = {'total': Decimal('19.99')}
with self.assertRaises(AnymailSerializationError) as cm:
self.message.send()
print(self.get_api_call_data())
err = cm.exception
self.assertIsInstance(err, TypeError) # compatibility with json.dumps
self.assertIn("Don't know how to send this data to Mailgun", str(err)) # our added context
self.assertIn("Decimal('19.99') is not JSON serializable", str(err)) # original message
class MailgunBackendRecipientsRefusedTests(MailgunBackendMockAPITestCase): class MailgunBackendRecipientsRefusedTests(MailgunBackendMockAPITestCase):

View File

@@ -111,7 +111,7 @@ class MailgunBackendIntegrationTests(SimpleTestCase, AnymailTestMixin):
reply_to=["reply1@example.com", "Reply 2 <reply2@example.com>"], reply_to=["reply1@example.com", "Reply 2 <reply2@example.com>"],
headers={"X-Anymail-Test": "value"}, headers={"X-Anymail-Test": "value"},
metadata={"meta1": "simple string", "meta2": 2, "meta3": {"complex": "value"}}, metadata={"meta1": "simple string", "meta2": 2},
send_at=send_at, send_at=send_at,
tags=["tag 1", "tag 2"], tags=["tag 1", "tag 2"],
track_clicks=False, track_clicks=False,
@@ -136,9 +136,7 @@ class MailgunBackendIntegrationTests(SimpleTestCase, AnymailTestMixin):
event = events.pop() event = events.pop()
self.assertCountEqual(event["tags"], ["tag 1", "tag 2"]) # don't care about order self.assertCountEqual(event["tags"], ["tag 1", "tag 2"]) # don't care about order
self.assertEqual(event["user-variables"], self.assertEqual(event["user-variables"],
{"meta1": "simple string", {"meta1": "simple string", "meta2": "2"}) # all metadata values become strings
"meta2": "2", # numbers become strings
"meta3": '{"complex": "value"}'}) # complex values become json
self.assertEqual(event["message"]["scheduled-for"], send_at_timestamp) self.assertEqual(event["message"]["scheduled-for"], send_at_timestamp)
self.assertCountEqual(event["message"]["recipients"], self.assertCountEqual(event["message"]["recipients"],