mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
SendGrid: convert long to str in headers, metadata
SendGrid requires extra headers and metadata values be strings. Anymail has always coerced int and float; this treats Python 2's `long` integer type the same. Fixes #74
This commit is contained in:
@@ -7,7 +7,7 @@ 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
|
||||||
from ..message import AnymailRecipientStatus
|
from ..message import AnymailRecipientStatus
|
||||||
from ..utils import get_anymail_setting, timestamp, update_deep, parse_address_list
|
from ..utils import BASIC_NUMERIC_TYPES, get_anymail_setting, timestamp, update_deep, parse_address_list
|
||||||
|
|
||||||
|
|
||||||
class EmailBackend(AnymailRequestsBackend):
|
class EmailBackend(AnymailRequestsBackend):
|
||||||
@@ -240,7 +240,7 @@ class SendGridPayload(RequestsPayload):
|
|||||||
# SendGrid requires header values to be strings -- not integers.
|
# SendGrid requires header values to be strings -- not integers.
|
||||||
# We'll stringify ints and floats; anything else is the caller's responsibility.
|
# We'll stringify ints and floats; anything else is the caller's responsibility.
|
||||||
self.data["headers"].update({
|
self.data["headers"].update({
|
||||||
k: str(v) if isinstance(v, (int, float)) else v
|
k: str(v) if isinstance(v, BASIC_NUMERIC_TYPES) else v
|
||||||
for k, v in headers.items()
|
for k, v in headers.items()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -290,7 +290,7 @@ class SendGridPayload(RequestsPayload):
|
|||||||
# if they're not.)
|
# if they're not.)
|
||||||
# We'll stringify ints and floats; anything else is the caller's responsibility.
|
# We'll stringify ints and floats; anything else is the caller's responsibility.
|
||||||
self.data["custom_args"] = {
|
self.data["custom_args"] = {
|
||||||
k: str(v) if isinstance(v, (int, float)) else v
|
k: str(v) if isinstance(v, BASIC_NUMERIC_TYPES) else v
|
||||||
for k, v in metadata.items()
|
for k, v in metadata.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from requests.structures import CaseInsensitiveDict
|
|||||||
|
|
||||||
from ..exceptions import AnymailConfigurationError, AnymailRequestsAPIError, AnymailWarning
|
from ..exceptions import AnymailConfigurationError, AnymailRequestsAPIError, AnymailWarning
|
||||||
from ..message import AnymailRecipientStatus
|
from ..message import AnymailRecipientStatus
|
||||||
from ..utils import get_anymail_setting, timestamp
|
from ..utils import BASIC_NUMERIC_TYPES, get_anymail_setting, timestamp
|
||||||
|
|
||||||
from .base_requests import AnymailRequestsBackend, RequestsPayload
|
from .base_requests import AnymailRequestsBackend, RequestsPayload
|
||||||
|
|
||||||
@@ -238,7 +238,7 @@ class SendGridPayload(RequestsPayload):
|
|||||||
# We'll stringify ints and floats; anything else is the caller's responsibility.
|
# We'll stringify ints and floats; anything else is the caller's responsibility.
|
||||||
# (This field gets converted to json in self.serialize_data)
|
# (This field gets converted to json in self.serialize_data)
|
||||||
self.data["headers"].update({
|
self.data["headers"].update({
|
||||||
k: str(v) if isinstance(v, (int, float)) else v
|
k: str(v) if isinstance(v, BASIC_NUMERIC_TYPES) else v
|
||||||
for k, v in headers.items()
|
for k, v in headers.items()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ from six.moves.urllib.parse import urlsplit, urlunsplit
|
|||||||
|
|
||||||
from .exceptions import AnymailConfigurationError, AnymailInvalidAddress
|
from .exceptions import AnymailConfigurationError, AnymailInvalidAddress
|
||||||
|
|
||||||
|
|
||||||
|
BASIC_NUMERIC_TYPES = six.integer_types + (float,) # int, float, and (on Python 2) long
|
||||||
|
|
||||||
|
|
||||||
UNSET = object() # Used as non-None default value
|
UNSET = object() # Used as non-None default value
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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
|
||||||
|
|
||||||
|
import six
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
@@ -19,6 +20,9 @@ from anymail.message import attach_inline_image_file
|
|||||||
from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharingTestCasesMixin
|
from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharingTestCasesMixin
|
||||||
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
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
longtype = int if six.PY3 else long
|
||||||
|
|
||||||
|
|
||||||
@override_settings(EMAIL_BACKEND='anymail.backends.sendgrid.EmailBackend',
|
@override_settings(EMAIL_BACKEND='anymail.backends.sendgrid.EmailBackend',
|
||||||
ANYMAIL={'SENDGRID_API_KEY': 'test_api_key'})
|
ANYMAIL={'SENDGRID_API_KEY': 'test_api_key'})
|
||||||
@@ -144,12 +148,13 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
|
|||||||
self.assertEqual(data['content'][0], {'type': "text/html", 'value': html_content})
|
self.assertEqual(data['content'][0], {'type': "text/html", 'value': html_content})
|
||||||
|
|
||||||
def test_extra_headers(self):
|
def test_extra_headers(self):
|
||||||
self.message.extra_headers = {'X-Custom': 'string', 'X-Num': 123,
|
self.message.extra_headers = {'X-Custom': 'string', 'X-Num': 123, 'X-Long': longtype(123),
|
||||||
'Reply-To': '"Do Not Reply" <noreply@example.com>'}
|
'Reply-To': '"Do Not Reply" <noreply@example.com>'}
|
||||||
self.message.send()
|
self.message.send()
|
||||||
data = self.get_api_call_json()
|
data = self.get_api_call_json()
|
||||||
self.assertEqual(data['headers']['X-Custom'], 'string')
|
self.assertEqual(data['headers']['X-Custom'], 'string')
|
||||||
self.assertEqual(data['headers']['X-Num'], '123') # converted to string (undoc'd SendGrid requirement)
|
self.assertEqual(data['headers']['X-Num'], '123') # converted to string (undoc'd SendGrid requirement)
|
||||||
|
self.assertEqual(data['headers']['X-Long'], '123') # converted to string (undoc'd SendGrid requirement)
|
||||||
# Reply-To must be moved to separate param
|
# Reply-To must be moved to separate param
|
||||||
self.assertNotIn('Reply-To', data['headers'])
|
self.assertNotIn('Reply-To', data['headers'])
|
||||||
self.assertEqual(data['reply_to'], {'name': "Do Not Reply", 'email': "noreply@example.com"})
|
self.assertEqual(data['reply_to'], {'name': "Do Not Reply", 'email': "noreply@example.com"})
|
||||||
@@ -331,12 +336,14 @@ class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase):
|
|||||||
"""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': 6}
|
self.message.metadata = {'user_id': "12345", 'items': 6, 'float': 98.6, 'long': longtype(123)}
|
||||||
self.message.send()
|
self.message.send()
|
||||||
data = self.get_api_call_json()
|
data = self.get_api_call_json()
|
||||||
data['custom_args'].pop('smtp-id', None) # remove Message-ID we added as tracking workaround
|
data['custom_args'].pop('smtp-id', None) # remove Message-ID we added as tracking workaround
|
||||||
self.assertEqual(data['custom_args'], {'user_id': "12345",
|
self.assertEqual(data['custom_args'], {'user_id': "12345",
|
||||||
'items': "6"}) # number converted to string
|
'items': "6", # int converted to a string,
|
||||||
|
'float': "98.6", # float converted to a string (watch binary rounding!)
|
||||||
|
'long': "123"}) # long converted to string
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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
|
||||||
|
|
||||||
|
import six
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
@@ -19,6 +20,9 @@ from anymail.message import attach_inline_image_file
|
|||||||
from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharingTestCasesMixin
|
from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharingTestCasesMixin
|
||||||
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
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
longtype = int if six.PY3 else long
|
||||||
|
|
||||||
|
|
||||||
@override_settings(EMAIL_BACKEND='anymail.backends.sendgrid_v2.EmailBackend',
|
@override_settings(EMAIL_BACKEND='anymail.backends.sendgrid_v2.EmailBackend',
|
||||||
ANYMAIL={'SENDGRID_API_KEY': 'test_api_key'})
|
ANYMAIL={'SENDGRID_API_KEY': 'test_api_key'})
|
||||||
@@ -154,12 +158,13 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
|
|||||||
self.assertEqual(data['html'], html_content)
|
self.assertEqual(data['html'], html_content)
|
||||||
|
|
||||||
def test_extra_headers(self):
|
def test_extra_headers(self):
|
||||||
self.message.extra_headers = {'X-Custom': 'string', 'X-Num': 123}
|
self.message.extra_headers = {'X-Custom': 'string', 'X-Num': 123, 'X-Long': longtype(123)}
|
||||||
self.message.send()
|
self.message.send()
|
||||||
data = self.get_api_call_data()
|
data = self.get_api_call_data()
|
||||||
headers = json.loads(data['headers'])
|
headers = json.loads(data['headers'])
|
||||||
self.assertEqual(headers['X-Custom'], 'string')
|
self.assertEqual(headers['X-Custom'], 'string')
|
||||||
self.assertEqual(headers['X-Num'], '123') # number converted to string (per SendGrid requirement)
|
self.assertEqual(headers['X-Num'], '123') # number converted to string (per SendGrid requirement)
|
||||||
|
self.assertEqual(headers['X-Long'], '123') # number converted to string (per SendGrid requirement)
|
||||||
|
|
||||||
def test_extra_headers_serialization_error(self):
|
def test_extra_headers_serialization_error(self):
|
||||||
self.message.extra_headers = {'X-Custom': Decimal(12.5)}
|
self.message.extra_headers = {'X-Custom': Decimal(12.5)}
|
||||||
|
|||||||
Reference in New Issue
Block a user