Skip to content

Commit

Permalink
feat: add support for dynamic template data to Email class (#908)
Browse files Browse the repository at this point in the history
Fixes #899

This is helpful when sending multiple emails to multiple recipients. You can now include the dynamic template data with the recipient which will then be included in the personalization.
  • Loading branch information
childish-sambino authored Jun 17, 2020
1 parent cb58667 commit 3415520
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 63 deletions.
12 changes: 6 additions & 6 deletions sendgrid/helpers/mail/dynamic_template_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ class DynamicTemplateData(object):

def __init__(self, dynamic_template_data=None, p=0):
"""Data for a transactional template.
Should be JSON-serializeable structure.
Should be JSON-serializable structure.
:param dynamic_template_data: Data for a transactional template.
:type dynamic_template_data: A JSON-serializeable structure
:type dynamic_template_data: A JSON-serializable structure
:param name: p is the Personalization object or Personalization object
index
:type name: Personalization, integer, optional
Expand All @@ -25,7 +25,7 @@ def __init__(self, dynamic_template_data=None, p=0):
def dynamic_template_data(self):
"""Data for a transactional template.
:rtype: A JSON-serializeable structure
:rtype: A JSON-serializable structure
"""
return self._dynamic_template_data

Expand All @@ -34,7 +34,7 @@ def dynamic_template_data(self, value):
"""Data for a transactional template.
:param value: Data for a transactional template.
:type value: A JSON-serializeable structure
:type value: A JSON-serializable structure
"""
self._dynamic_template_data = value

Expand All @@ -59,7 +59,7 @@ def personalization(self, value):
def __str__(self):
"""Get a JSON representation of this object.
:rtype: A JSON-serializeable structure
:rtype: A JSON-serializable structure
"""
return str(self.get())

Expand All @@ -68,6 +68,6 @@ def get(self):
Get a JSON-ready representation of this DynamicTemplateData object.
:returns: Data for a transactional template.
:rtype: A JSON-serializeable structure.
:rtype: A JSON-serializable structure.
"""
return self.dynamic_template_data
49 changes: 33 additions & 16 deletions sendgrid/helpers/mail/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
html_entity_decode = __html_parser__.unescape

try:
basestring = basestring
basestring = basestring
except NameError:
# Define basestring when Python >= 3.0
basestring = str
Expand All @@ -32,7 +32,8 @@ def __init__(self,
name=None,
substitutions=None,
subject=None,
p=0):
p=0,
dynamic_template_data=None):
"""Create an Email with the given address and name.
Either fill the separate name and email fields, or pass all information
Expand All @@ -41,17 +42,19 @@ def __init__(self,
:type email: string, optional
:param name: Name for this sender or recipient.
:type name: string, optional
:param substitutions: String substitutions to be applied to the email.
:type substitutions: list(Substitution), optional
:param subject: Subject for this sender or recipient.
:type subject: string, optional
:param p: p is the Personalization object or Personalization object
index
:type p: Personalization, integer, optional
:param dynamic_template_data: Data for a dynamic transactional template.
:type dynamic_template_data: DynamicTemplateData, optional
"""
self._name = None
self._email = None
self._substitutions = None
self._subject = None
self._personalization = None
self._personalization = p

if email and not name:
# allows passing emails as "Example Name <[email protected]>"
Expand All @@ -64,14 +67,11 @@ def __init__(self,
if name is not None:
self.name = name

if substitutions is not None:
self.substitutions = substitutions

if subject is not None:
self.subject = subject

if p is not None:
self.personalization = p
# Note that these only apply to To Emails (see Personalization.add_to)
# and should be moved but have not been for compatibility.
self._substitutions = substitutions
self._dynamic_template_data = dynamic_template_data
self._subject = subject

@property
def name(self):
Expand Down Expand Up @@ -129,7 +129,7 @@ def email(self, value):
@property
def substitutions(self):
"""A list of Substitution objects. These substitutions will apply to
the text and html content of the body of your email, in addition
the text and html content of the body of your email, in addition
to the subject and reply-to parameters. The total collective size
of your substitutions may not exceed 10,000 bytes per
personalization object.
Expand All @@ -141,20 +141,37 @@ def substitutions(self):
@substitutions.setter
def substitutions(self, value):
"""A list of Substitution objects. These substitutions will apply to
the text and html content of the body of your email, in addition to
the text and html content of the body of your email, in addition to
the subject and reply-to parameters. The total collective size of
your substitutions may not exceed 10,000 bytes per personalization
object.
:param value: A list of Substitution objects. These substitutions will
apply to the text and html content of the body of your email, in
apply to the text and html content of the body of your email, in
addition to the subject and reply-to parameters. The total collective
size of your substitutions may not exceed 10,000 bytes per
personalization object.
:type value: list(Substitution)
"""
self._substitutions = value

@property
def dynamic_template_data(self):
"""Data for a dynamic transactional template.
:rtype: DynamicTemplateData
"""
return self._dynamic_template_data

@dynamic_template_data.setter
def dynamic_template_data(self, value):
"""Data for a dynamic transactional template.
:param value: DynamicTemplateData
:type value: DynamicTemplateData
"""
self._dynamic_template_data = value

@property
def subject(self):
"""Subject for this sender or recipient.
Expand Down
10 changes: 5 additions & 5 deletions sendgrid/helpers/mail/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,17 +185,17 @@ def _set_emails(

@property
def personalizations(self):
"""A list of one or more Personaliztion objects
"""A list of one or more Personalization objects
:rtype: list(Personalization)
"""
return self._personalizations

def add_personalization(self, personalization, index=0):
"""Add a Personaliztion object
"""Add a Personalization object
:param personalizations: Add a Personalization object
:type personalizations: Personalization
:param personalization: Add a Personalization object
:type personalization: Personalization
:param index: The index where to add the Personalization
:type index: int
"""
Expand Down Expand Up @@ -627,7 +627,7 @@ def dynamic_template_data(self, value):
"""Data for a transactional template
:param value: Data for a transactional template
:type value: DynamicTemplateData, a JSON-serializeable structure
:type value: DynamicTemplateData, a JSON-serializable structure
"""
if not isinstance(value, DynamicTemplateData):
value = DynamicTemplateData(value)
Expand Down
20 changes: 14 additions & 6 deletions sendgrid/helpers/mail/personalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,16 @@ def add_to(self, email):
self.add_substitution(substitution)
else:
self.add_substitution(email.substitutions)

if email.dynamic_template_data:
self.dynamic_template_data = email.dynamic_template_data

if email.subject:
if isinstance(email.subject, str):
self.subject = email.subject
else:
self.subject = email.subject.get()

self._tos.append(email.get())

@property
Expand Down Expand Up @@ -149,10 +154,10 @@ def add_substitution(self, substitution):
:type substitution: Substitution
"""
if isinstance(substitution, dict):
self._substitutions.append(substitution)
else:
self._substitutions.append(substitution.get())
if not isinstance(substitution, dict):
substitution = substitution.get()

self._substitutions.append(substitution)

@property
def custom_args(self):
Expand Down Expand Up @@ -190,14 +195,17 @@ def send_at(self, value):
@property
def dynamic_template_data(self):
"""Data for dynamic transactional template.
Should be JSON-serializeable structure.
Should be JSON-serializable structure.
:rtype: JSON-serializeable structure
:rtype: JSON-serializable structure
"""
return self._dynamic_template_data

@dynamic_template_data.setter
def dynamic_template_data(self, value):
if not isinstance(value, dict):
value = value.get()

self._dynamic_template_data = value

def get(self):
Expand Down
97 changes: 80 additions & 17 deletions test/test_mail_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@
EmailMessage = message.Message

from sendgrid.helpers.mail import (
Asm, ApiKeyIncludedException, Attachment, BccSettings,
BypassListManagement, Category, ClickTracking, Content, CustomArg,
DynamicTemplateData, Email, FooterSettings, From, Ganalytics, Header,
Mail, MailSettings, OpenTracking, Personalization, SandBoxMode, Section,
SendGridException, SpamCheck, Subject, SubscriptionTracking, Substitution,
TrackingSettings, To, ValidateApiKey
Asm, Attachment,
ClickTracking, Content,
DynamicTemplateData, Email, From,
Mail, Personalization,
Subject, Substitution, To, TrackingSettings
)


Expand Down Expand Up @@ -210,18 +209,18 @@ def test_multiple_emails_to_multiple_recipients(self):

to_emails = [
To(email='[email protected]',
name='Example Name 0',
substitutions=[
Substitution('-name-', 'Example Name Substitution 0'),
Substitution('-github-', 'https://example.com/test0'),
],
subject=Subject('Override Global Subject')),
name='Example Name 0',
substitutions=[
Substitution('-name-', 'Example Name Substitution 0'),
Substitution('-github-', 'https://example.com/test0'),
],
subject=Subject('Override Global Subject')),
To(email='[email protected]',
name='Example Name 1',
substitutions=[
Substitution('-name-', 'Example Name Substitution 1'),
Substitution('-github-', 'https://example.com/test1'),
])
name='Example Name 1',
substitutions=[
Substitution('-name-', 'Example Name Substitution 1'),
Substitution('-github-', 'https://example.com/test1'),
])
]
global_substitutions = Substitution('-time-', '2019-01-01 00:00:00')
message = Mail(
Expand Down Expand Up @@ -285,6 +284,70 @@ def test_multiple_emails_to_multiple_recipients(self):
}''')
)

def test_dynamic_template_data(self):
self.maxDiff = None

to_emails = [
To(email='[email protected]',
name='Example To 0 Name',
dynamic_template_data=DynamicTemplateData({'name': 'Example 0 Name'})),
To(email='[email protected]',
name='Example To 1 Name',
dynamic_template_data={'name': 'Example 1 Name'})
]
message = Mail(
from_email=From('[email protected]', 'Example From Name'),
to_emails=to_emails,
subject=Subject('Hi!'),
plain_text_content='Hello!',
html_content='<strong>Hello!</strong>',
is_multiple=True)

self.assertEqual(
message.get(),
json.loads(r'''{
"content": [
{
"type": "text/plain",
"value": "Hello!"
},
{
"type": "text/html",
"value": "<strong>Hello!</strong>"
}
],
"from": {
"email": "[email protected]",
"name": "Example From Name"
},
"personalizations": [
{
"dynamic_template_data": {
"name": "Example 1 Name"
},
"to": [
{
"email": "[email protected]",
"name": "Example To 1 Name"
}
]
},
{
"dynamic_template_data": {
"name": "Example 0 Name"
},
"to": [
{
"email": "[email protected]",
"name": "Example To 0 Name"
}
]
}
],
"subject": "Hi!"
}''')
)

def test_kitchen_sink(self):
from sendgrid.helpers.mail import (
Mail, From, To, Cc, Bcc, Subject, Substitution, Header,
Expand Down
Loading

0 comments on commit 3415520

Please sign in to comment.