diff --git a/anymail/signals.py b/anymail/signals.py index 273b939..c5231a8 100644 --- a/anymail/signals.py +++ b/anymail/signals.py @@ -32,11 +32,11 @@ class AnymailTrackingEvent(AnymailEvent): self.click_url = kwargs.pop('click_url', None) # str self.description = kwargs.pop('description', None) # str, usually human-readable, not normalized self.message_id = kwargs.pop('message_id', None) # str, format may vary - self.metadata = kwargs.pop('metadata', None) # dict + self.metadata = kwargs.pop('metadata', {}) # dict self.mta_response = kwargs.pop('mta_response', None) # str, may include SMTP codes, not normalized self.recipient = kwargs.pop('recipient', None) # str email address (just the email portion; no name) self.reject_reason = kwargs.pop('reject_reason', None) # normalized to a RejectReason str - self.tags = kwargs.pop('tags', None) # list of str + self.tags = kwargs.pop('tags', []) # list of str self.user_agent = kwargs.pop('user_agent', None) # str diff --git a/anymail/webhooks/mailgun.py b/anymail/webhooks/mailgun.py index 94026ca..cd3de0f 100644 --- a/anymail/webhooks/mailgun.py +++ b/anymail/webhooks/mailgun.py @@ -113,7 +113,7 @@ class MailgunTrackingWebhookView(MailgunBaseWebhookView): try: headers = json.loads(esp_event['message-headers']) except (KeyError, ): - metadata = None + metadata = {} else: variables = [value for [field, value] in headers if field == 'X-Mailgun-Variables'] @@ -121,10 +121,10 @@ class MailgunTrackingWebhookView(MailgunBaseWebhookView): # Each X-Mailgun-Variables value is JSON. Parse and merge them all into single dict: metadata = combine(*[json.loads(value) for value in variables]) else: - metadata = None + metadata = {} # tags are sometimes delivered as X-Mailgun-Tag fields, sometimes as tag - tags = esp_event.getlist('tag', esp_event.getlist('X-Mailgun-Tag', None)) + tags = esp_event.getlist('tag', esp_event.getlist('X-Mailgun-Tag', [])) return AnymailTrackingEvent( event_type=event_type, diff --git a/anymail/webhooks/mandrill.py b/anymail/webhooks/mandrill.py index 0cd3685..3d5257a 100644 --- a/anymail/webhooks/mandrill.py +++ b/anymail/webhooks/mandrill.py @@ -128,12 +128,12 @@ class MandrillTrackingWebhookView(MandrillBaseWebhookView): try: metadata = esp_event['msg']['metadata'] except KeyError: - metadata = None + metadata = {} try: tags = esp_event['msg']['tags'] except KeyError: - tags = None + tags = [] return AnymailTrackingEvent( click_url=esp_event.get('url', None), diff --git a/anymail/webhooks/postmark.py b/anymail/webhooks/postmark.py index a63c181..672029c 100644 --- a/anymail/webhooks/postmark.py +++ b/anymail/webhooks/postmark.py @@ -89,7 +89,7 @@ class PostmarkTrackingWebhookView(PostmarkBaseWebhookView): try: tags = [esp_event['Tag']] except KeyError: - tags = None + tags = [] return AnymailTrackingEvent( description=esp_event.get('Description', None), diff --git a/anymail/webhooks/sendgrid.py b/anymail/webhooks/sendgrid.py index 9f52c97..0bd22d2 100644 --- a/anymail/webhooks/sendgrid.py +++ b/anymail/webhooks/sendgrid.py @@ -72,7 +72,7 @@ class SendGridTrackingWebhookView(SendGridBaseWebhookView): if len(metadata_keys) > 0: metadata = {key: esp_event[key] for key in metadata_keys} else: - metadata = None + metadata = {} return AnymailTrackingEvent( event_type=event_type, @@ -82,7 +82,7 @@ class SendGridTrackingWebhookView(SendGridBaseWebhookView): recipient=esp_event.get('email', None), reject_reason=reject_reason, mta_response=mta_response, - tags=esp_event.get('category', None), + tags=esp_event.get('category', []), metadata=metadata, click_url=esp_event.get('url', None), user_agent=esp_event.get('useragent', None), diff --git a/anymail/webhooks/sparkpost.py b/anymail/webhooks/sparkpost.py index aeb3b37..b958646 100644 --- a/anymail/webhooks/sparkpost.py +++ b/anymail/webhooks/sparkpost.py @@ -108,7 +108,7 @@ class SparkPostTrackingWebhookView(SparkPostBaseWebhookView): tag = event['campaign_id'] # not 'rcpt_tags' -- those don't come from sending a message tags = [tag] if tag else None except KeyError: - tags = None + tags = [] try: reject_reason = self.reject_reasons.get(event['bounce_class'], RejectReason.OTHER) @@ -129,7 +129,7 @@ class SparkPostTrackingWebhookView(SparkPostBaseWebhookView): mta_response=event.get('raw_reason', None), # description=???, tags=tags, - metadata=event.get('rcpt_meta', None) or None, # message + recipient metadata + metadata=event.get('rcpt_meta', None) or {}, # message + recipient metadata click_url=event.get('target_link_url', None), user_agent=event.get('user_agent', None), esp_event=raw_event, diff --git a/docs/sending/tracking.rst b/docs/sending/tracking.rst index 6eb842a..51edd81 100644 --- a/docs/sending/tracking.rst +++ b/docs/sending/tracking.rst @@ -139,12 +139,14 @@ Normalized tracking event .. attribute:: metadata - A `dict` of unique data attached to the message, or `None`. + A `dict` of unique data attached to the message. Will be empty if the ESP + doesn't provide metadata with its tracking events. (See :attr:`AnymailMessage.metadata `.) .. attribute:: tags - A `list` of `str` tags attached to the message, or `None`. + A `list` of `str` tags attached to the message. Will be empty if the ESP + doesn't provide tags with its tracking events. (See :attr:`AnymailMessage.tags `.) .. attribute:: reject_reason diff --git a/tests/test_mailgun_webhooks.py b/tests/test_mailgun_webhooks.py index be9917b..7a5fc44 100644 --- a/tests/test_mailgun_webhooks.py +++ b/tests/test_mailgun_webhooks.py @@ -109,6 +109,8 @@ class MailgunDeliveryTestCase(WebhookTestCase): self.assertEqual(event.event_id, "06c96bafc3f42a66b9edd546347a2fe18dc23461fe80dc52f0") self.assertEqual(event.recipient, "recipient@example.com") self.assertEqual(querydict_to_postdict(event.esp_event), raw_event) + self.assertEqual(event.tags, []) + self.assertEqual(event.metadata, {}) def test_dropped_bounce(self): raw_event = mailgun_sign({ diff --git a/tests/test_postmark_webhooks.py b/tests/test_postmark_webhooks.py index 99d6db5..1280f66 100644 --- a/tests/test_postmark_webhooks.py +++ b/tests/test_postmark_webhooks.py @@ -78,6 +78,7 @@ class PostmarkDeliveryTestCase(WebhookTestCase): self.assertEqual(event.message_id, "883953f4-6105-42a2-a16a-77a8eac79483") self.assertEqual(event.recipient, "recipient@example.com") self.assertEqual(event.tags, ["welcome-email"]) + self.assertEqual(event.metadata, {}) def test_open_event(self): raw_event = { @@ -106,3 +107,5 @@ class PostmarkDeliveryTestCase(WebhookTestCase): self.assertEqual(event.message_id, "f4830d10-9c35-4f0c-bca3-3d9b459821f8") self.assertEqual(event.recipient, "recipient@example.com") self.assertEqual(event.user_agent, "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0") + self.assertEqual(event.tags, []) + self.assertEqual(event.metadata, {}) diff --git a/tests/test_sendgrid_webhooks.py b/tests/test_sendgrid_webhooks.py index 8037b3d..ef3bb4a 100644 --- a/tests/test_sendgrid_webhooks.py +++ b/tests/test_sendgrid_webhooks.py @@ -73,8 +73,8 @@ class SendGridDeliveryTestCase(WebhookTestCase): self.assertEqual(event.event_id, "nOSv8m0eTQ-vxvwNwt3fZQ") self.assertEqual(event.recipient, "recipient@example.com") self.assertEqual(event.mta_response, "250 2.0.0 OK 1461095248 m143si2210036ioe.159 - gsmtp ") - self.assertEqual(event.tags, None) - self.assertEqual(event.metadata, None) + self.assertEqual(event.tags, []) + self.assertEqual(event.metadata, {}) def test_dropped_invalid_event(self): raw_events = [{ diff --git a/tests/test_sparkpost_webhooks.py b/tests/test_sparkpost_webhooks.py index a7f21ee..7be7a57 100644 --- a/tests/test_sparkpost_webhooks.py +++ b/tests/test_sparkpost_webhooks.py @@ -97,8 +97,8 @@ class SparkPostDeliveryTestCase(WebhookTestCase): self.assertIsInstance(event, AnymailTrackingEvent) self.assertEqual(event.event_type, "delivered") self.assertEqual(event.recipient, "Recipient@example.com") - self.assertEqual(event.tags, None) - self.assertEqual(event.metadata, None) + self.assertEqual(event.tags, []) + self.assertEqual(event.metadata, {}) def test_bounce_event(self): raw_events = [{