Skip to content

Commit 3415520

Browse files
author
childish-sambino
authored
feat: add support for dynamic template data to Email class (sendgrid#908)
Fixes sendgrid#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.
1 parent cb58667 commit 3415520

File tree

6 files changed

+148
-63
lines changed

6 files changed

+148
-63
lines changed

sendgrid/helpers/mail/dynamic_template_data.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ class DynamicTemplateData(object):
55

66
def __init__(self, dynamic_template_data=None, p=0):
77
"""Data for a transactional template.
8-
Should be JSON-serializeable structure.
8+
Should be JSON-serializable structure.
99
1010
:param dynamic_template_data: Data for a transactional template.
11-
:type dynamic_template_data: A JSON-serializeable structure
11+
:type dynamic_template_data: A JSON-serializable structure
1212
:param name: p is the Personalization object or Personalization object
1313
index
1414
:type name: Personalization, integer, optional
@@ -25,7 +25,7 @@ def __init__(self, dynamic_template_data=None, p=0):
2525
def dynamic_template_data(self):
2626
"""Data for a transactional template.
2727
28-
:rtype: A JSON-serializeable structure
28+
:rtype: A JSON-serializable structure
2929
"""
3030
return self._dynamic_template_data
3131

@@ -34,7 +34,7 @@ def dynamic_template_data(self, value):
3434
"""Data for a transactional template.
3535
3636
:param value: Data for a transactional template.
37-
:type value: A JSON-serializeable structure
37+
:type value: A JSON-serializable structure
3838
"""
3939
self._dynamic_template_data = value
4040

@@ -59,7 +59,7 @@ def personalization(self, value):
5959
def __str__(self):
6060
"""Get a JSON representation of this object.
6161
62-
:rtype: A JSON-serializeable structure
62+
:rtype: A JSON-serializable structure
6363
"""
6464
return str(self.get())
6565

@@ -68,6 +68,6 @@ def get(self):
6868
Get a JSON-ready representation of this DynamicTemplateData object.
6969
7070
:returns: Data for a transactional template.
71-
:rtype: A JSON-serializeable structure.
71+
:rtype: A JSON-serializable structure.
7272
"""
7373
return self.dynamic_template_data

sendgrid/helpers/mail/email.py

+33-16
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
html_entity_decode = __html_parser__.unescape
1919

2020
try:
21-
basestring = basestring
21+
basestring = basestring
2222
except NameError:
2323
# Define basestring when Python >= 3.0
2424
basestring = str
@@ -32,7 +32,8 @@ def __init__(self,
3232
name=None,
3333
substitutions=None,
3434
subject=None,
35-
p=0):
35+
p=0,
36+
dynamic_template_data=None):
3637
"""Create an Email with the given address and name.
3738
3839
Either fill the separate name and email fields, or pass all information
@@ -41,17 +42,19 @@ def __init__(self,
4142
:type email: string, optional
4243
:param name: Name for this sender or recipient.
4344
:type name: string, optional
45+
:param substitutions: String substitutions to be applied to the email.
46+
:type substitutions: list(Substitution), optional
4447
:param subject: Subject for this sender or recipient.
4548
:type subject: string, optional
4649
:param p: p is the Personalization object or Personalization object
4750
index
4851
:type p: Personalization, integer, optional
52+
:param dynamic_template_data: Data for a dynamic transactional template.
53+
:type dynamic_template_data: DynamicTemplateData, optional
4954
"""
5055
self._name = None
5156
self._email = None
52-
self._substitutions = None
53-
self._subject = None
54-
self._personalization = None
57+
self._personalization = p
5558

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

67-
if substitutions is not None:
68-
self.substitutions = substitutions
69-
70-
if subject is not None:
71-
self.subject = subject
72-
73-
if p is not None:
74-
self.personalization = p
70+
# Note that these only apply to To Emails (see Personalization.add_to)
71+
# and should be moved but have not been for compatibility.
72+
self._substitutions = substitutions
73+
self._dynamic_template_data = dynamic_template_data
74+
self._subject = subject
7575

7676
@property
7777
def name(self):
@@ -129,7 +129,7 @@ def email(self, value):
129129
@property
130130
def substitutions(self):
131131
"""A list of Substitution objects. These substitutions will apply to
132-
the text and html content of the body of your email, in addition
132+
the text and html content of the body of your email, in addition
133133
to the subject and reply-to parameters. The total collective size
134134
of your substitutions may not exceed 10,000 bytes per
135135
personalization object.
@@ -141,20 +141,37 @@ def substitutions(self):
141141
@substitutions.setter
142142
def substitutions(self, value):
143143
"""A list of Substitution objects. These substitutions will apply to
144-
the text and html content of the body of your email, in addition to
144+
the text and html content of the body of your email, in addition to
145145
the subject and reply-to parameters. The total collective size of
146146
your substitutions may not exceed 10,000 bytes per personalization
147147
object.
148148
149149
:param value: A list of Substitution objects. These substitutions will
150-
apply to the text and html content of the body of your email, in
150+
apply to the text and html content of the body of your email, in
151151
addition to the subject and reply-to parameters. The total collective
152152
size of your substitutions may not exceed 10,000 bytes per
153153
personalization object.
154154
:type value: list(Substitution)
155155
"""
156156
self._substitutions = value
157157

158+
@property
159+
def dynamic_template_data(self):
160+
"""Data for a dynamic transactional template.
161+
162+
:rtype: DynamicTemplateData
163+
"""
164+
return self._dynamic_template_data
165+
166+
@dynamic_template_data.setter
167+
def dynamic_template_data(self, value):
168+
"""Data for a dynamic transactional template.
169+
170+
:param value: DynamicTemplateData
171+
:type value: DynamicTemplateData
172+
"""
173+
self._dynamic_template_data = value
174+
158175
@property
159176
def subject(self):
160177
"""Subject for this sender or recipient.

sendgrid/helpers/mail/mail.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -185,17 +185,17 @@ def _set_emails(
185185

186186
@property
187187
def personalizations(self):
188-
"""A list of one or more Personaliztion objects
188+
"""A list of one or more Personalization objects
189189
190190
:rtype: list(Personalization)
191191
"""
192192
return self._personalizations
193193

194194
def add_personalization(self, personalization, index=0):
195-
"""Add a Personaliztion object
195+
"""Add a Personalization object
196196
197-
:param personalizations: Add a Personalization object
198-
:type personalizations: Personalization
197+
:param personalization: Add a Personalization object
198+
:type personalization: Personalization
199199
:param index: The index where to add the Personalization
200200
:type index: int
201201
"""
@@ -627,7 +627,7 @@ def dynamic_template_data(self, value):
627627
"""Data for a transactional template
628628
629629
:param value: Data for a transactional template
630-
:type value: DynamicTemplateData, a JSON-serializeable structure
630+
:type value: DynamicTemplateData, a JSON-serializable structure
631631
"""
632632
if not isinstance(value, DynamicTemplateData):
633633
value = DynamicTemplateData(value)

sendgrid/helpers/mail/personalization.py

+14-6
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,16 @@ def add_to(self, email):
5151
self.add_substitution(substitution)
5252
else:
5353
self.add_substitution(email.substitutions)
54+
55+
if email.dynamic_template_data:
56+
self.dynamic_template_data = email.dynamic_template_data
57+
5458
if email.subject:
5559
if isinstance(email.subject, str):
5660
self.subject = email.subject
5761
else:
5862
self.subject = email.subject.get()
63+
5964
self._tos.append(email.get())
6065

6166
@property
@@ -149,10 +154,10 @@ def add_substitution(self, substitution):
149154
150155
:type substitution: Substitution
151156
"""
152-
if isinstance(substitution, dict):
153-
self._substitutions.append(substitution)
154-
else:
155-
self._substitutions.append(substitution.get())
157+
if not isinstance(substitution, dict):
158+
substitution = substitution.get()
159+
160+
self._substitutions.append(substitution)
156161

157162
@property
158163
def custom_args(self):
@@ -190,14 +195,17 @@ def send_at(self, value):
190195
@property
191196
def dynamic_template_data(self):
192197
"""Data for dynamic transactional template.
193-
Should be JSON-serializeable structure.
198+
Should be JSON-serializable structure.
194199
195-
:rtype: JSON-serializeable structure
200+
:rtype: JSON-serializable structure
196201
"""
197202
return self._dynamic_template_data
198203

199204
@dynamic_template_data.setter
200205
def dynamic_template_data(self, value):
206+
if not isinstance(value, dict):
207+
value = value.get()
208+
201209
self._dynamic_template_data = value
202210

203211
def get(self):

test/test_mail_helpers.py

+80-17
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@
1010
EmailMessage = message.Message
1111

1212
from sendgrid.helpers.mail import (
13-
Asm, ApiKeyIncludedException, Attachment, BccSettings,
14-
BypassListManagement, Category, ClickTracking, Content, CustomArg,
15-
DynamicTemplateData, Email, FooterSettings, From, Ganalytics, Header,
16-
Mail, MailSettings, OpenTracking, Personalization, SandBoxMode, Section,
17-
SendGridException, SpamCheck, Subject, SubscriptionTracking, Substitution,
18-
TrackingSettings, To, ValidateApiKey
13+
Asm, Attachment,
14+
ClickTracking, Content,
15+
DynamicTemplateData, Email, From,
16+
Mail, Personalization,
17+
Subject, Substitution, To, TrackingSettings
1918
)
2019

2120

@@ -210,18 +209,18 @@ def test_multiple_emails_to_multiple_recipients(self):
210209

211210
to_emails = [
212211
To(email='[email protected]',
213-
name='Example Name 0',
214-
substitutions=[
215-
Substitution('-name-', 'Example Name Substitution 0'),
216-
Substitution('-github-', 'https://example.com/test0'),
217-
],
218-
subject=Subject('Override Global Subject')),
212+
name='Example Name 0',
213+
substitutions=[
214+
Substitution('-name-', 'Example Name Substitution 0'),
215+
Substitution('-github-', 'https://example.com/test0'),
216+
],
217+
subject=Subject('Override Global Subject')),
219218
To(email='[email protected]',
220-
name='Example Name 1',
221-
substitutions=[
222-
Substitution('-name-', 'Example Name Substitution 1'),
223-
Substitution('-github-', 'https://example.com/test1'),
224-
])
219+
name='Example Name 1',
220+
substitutions=[
221+
Substitution('-name-', 'Example Name Substitution 1'),
222+
Substitution('-github-', 'https://example.com/test1'),
223+
])
225224
]
226225
global_substitutions = Substitution('-time-', '2019-01-01 00:00:00')
227226
message = Mail(
@@ -285,6 +284,70 @@ def test_multiple_emails_to_multiple_recipients(self):
285284
}''')
286285
)
287286

287+
def test_dynamic_template_data(self):
288+
self.maxDiff = None
289+
290+
to_emails = [
291+
To(email='[email protected]',
292+
name='Example To 0 Name',
293+
dynamic_template_data=DynamicTemplateData({'name': 'Example 0 Name'})),
294+
To(email='[email protected]',
295+
name='Example To 1 Name',
296+
dynamic_template_data={'name': 'Example 1 Name'})
297+
]
298+
message = Mail(
299+
from_email=From('[email protected]', 'Example From Name'),
300+
to_emails=to_emails,
301+
subject=Subject('Hi!'),
302+
plain_text_content='Hello!',
303+
html_content='<strong>Hello!</strong>',
304+
is_multiple=True)
305+
306+
self.assertEqual(
307+
message.get(),
308+
json.loads(r'''{
309+
"content": [
310+
{
311+
"type": "text/plain",
312+
"value": "Hello!"
313+
},
314+
{
315+
"type": "text/html",
316+
"value": "<strong>Hello!</strong>"
317+
}
318+
],
319+
"from": {
320+
"email": "[email protected]",
321+
"name": "Example From Name"
322+
},
323+
"personalizations": [
324+
{
325+
"dynamic_template_data": {
326+
"name": "Example 1 Name"
327+
},
328+
"to": [
329+
{
330+
"email": "[email protected]",
331+
"name": "Example To 1 Name"
332+
}
333+
]
334+
},
335+
{
336+
"dynamic_template_data": {
337+
"name": "Example 0 Name"
338+
},
339+
"to": [
340+
{
341+
"email": "[email protected]",
342+
"name": "Example To 0 Name"
343+
}
344+
]
345+
}
346+
],
347+
"subject": "Hi!"
348+
}''')
349+
)
350+
288351
def test_kitchen_sink(self):
289352
from sendgrid.helpers.mail import (
290353
Mail, From, To, Cc, Bcc, Subject, Substitution, Header,

0 commit comments

Comments
 (0)