@@ -75,9 +75,9 @@ def __init__(self, message, defaults, backend, *args, **kwargs):
75
75
self .use_dynamic_template = False # how to represent merge_data
76
76
self .message_id = None # Message-ID -- assigned in serialize_data unless provided in headers
77
77
self .merge_field_format = backend .merge_field_format
78
- self .merge_data = None # late-bound per-recipient data
79
- self .merge_global_data = None
80
- self .merge_metadata = None
78
+ self .merge_data = {} # late-bound per-recipient data
79
+ self .merge_global_data = {}
80
+ self .merge_metadata = {}
81
81
82
82
http_headers = kwargs .pop ('headers' , {})
83
83
http_headers ['Authorization' ] = 'Bearer %s' % backend .api_key
@@ -101,6 +101,8 @@ def serialize_data(self):
101
101
102
102
if self .generate_message_id :
103
103
self .set_anymail_id ()
104
+ if self .is_batch ():
105
+ self .expand_personalizations_for_batch ()
104
106
self .build_merge_data ()
105
107
self .build_merge_metadata ()
106
108
@@ -115,118 +117,72 @@ def set_anymail_id(self):
115
117
self .message_id = str (uuid .uuid4 ())
116
118
self .data .setdefault ("custom_args" , {})["anymail_id" ] = self .message_id
117
119
118
- def build_merge_data (self ):
119
- if self .use_dynamic_template :
120
- self .build_merge_data_dynamic ()
121
- else :
122
- self .build_merge_data_legacy ()
123
-
124
- def build_merge_data_dynamic (self ):
125
- """Set personalizations[...]['dynamic_template_data']"""
126
- if self .merge_global_data is not None :
127
- assert len (self .data ["personalizations" ]) == 1
128
- self .data ["personalizations" ][0 ].setdefault (
129
- "dynamic_template_data" , {}).update (self .merge_global_data )
120
+ def expand_personalizations_for_batch (self ):
121
+ """Split data["personalizations"] into individual message for each recipient"""
122
+ assert len (self .data ["personalizations" ]) == 1
123
+ base_personalization = self .data ["personalizations" ].pop ()
124
+ to_list = base_personalization .pop ("to" ) # {email, name?} for each message.to
125
+ for recipient in to_list :
126
+ personalization = base_personalization .copy ()
127
+ personalization ["to" ] = [recipient ]
128
+ self .data ["personalizations" ].append (personalization )
130
129
131
- if self .merge_data is not None :
132
- # Burst apart each to-email in personalizations[0] into a separate
133
- # personalization, and add merge_data for that recipient
134
- assert len (self .data ["personalizations" ]) == 1
135
- base_personalizations = self .data ["personalizations" ].pop ()
136
- to_list = base_personalizations .pop ("to" ) # {email, name?} for each message.to
137
- for recipient in to_list :
138
- personalization = base_personalizations .copy () # captures cc, bcc, merge_global_data, esp_extra
139
- personalization ["to" ] = [recipient ]
140
- try :
141
- recipient_data = self .merge_data [recipient ["email" ]]
142
- except KeyError :
143
- pass # no merge_data for this recipient
144
- else :
145
- if "dynamic_template_data" in personalization :
146
- # merge per-recipient data into (copy of) merge_global_data
147
- personalization ["dynamic_template_data" ] = personalization ["dynamic_template_data" ].copy ()
148
- personalization ["dynamic_template_data" ].update (recipient_data )
149
- else :
150
- personalization ["dynamic_template_data" ] = recipient_data
151
- self .data ["personalizations" ].append (personalization )
152
-
153
- def build_merge_data_legacy (self ):
154
- """Set personalizations[...]['substitutions'] and data['sections']"""
130
+ def build_merge_data (self ):
131
+ if self .merge_data or self .merge_global_data :
132
+ # Always build dynamic_template_data first,
133
+ # then convert it to legacy template format if needed
134
+ for personalization in self .data ["personalizations" ]:
135
+ assert len (personalization ["to" ]) == 1
136
+ recipient_email = personalization ["to" ][0 ]["email" ]
137
+ dynamic_template_data = self .merge_global_data .copy ()
138
+ dynamic_template_data .update (self .merge_data .get (recipient_email , {}))
139
+ if dynamic_template_data :
140
+ personalization ["dynamic_template_data" ] = dynamic_template_data
141
+
142
+ if not self .use_dynamic_template :
143
+ self .convert_dynamic_template_data_to_legacy_substitutions ()
144
+
145
+ def convert_dynamic_template_data_to_legacy_substitutions (self ):
146
+ """Change personalizations[...]['dynamic_template_data'] to ...['substitutions]"""
155
147
merge_field_format = self .merge_field_format or '{}'
156
148
157
- if self .merge_data is not None :
158
- # Burst apart each to-email in personalizations[0] into a separate
159
- # personalization, and add merge_data for that recipient
160
- assert len (self .data ["personalizations" ]) == 1
161
- base_personalizations = self .data ["personalizations" ].pop ()
162
- to_list = base_personalizations .pop ("to" ) # {email, name?} for each message.to
163
- all_fields = set ()
164
- for recipient in to_list :
165
- personalization = base_personalizations .copy () # captures cc, bcc, and any esp_extra
166
- personalization ["to" ] = [recipient ]
167
- try :
168
- recipient_data = self .merge_data [recipient ["email" ]]
169
- personalization ["substitutions" ] = {merge_field_format .format (field ): data
170
- for field , data in recipient_data .items ()}
171
- all_fields = all_fields .union (recipient_data .keys ())
172
- except KeyError :
173
- pass # no merge_data for this recipient
174
- self .data ["personalizations" ].append (personalization )
175
-
176
- if self .merge_field_format is None and len (all_fields ) and all (field .isalnum () for field in all_fields ):
149
+ all_merge_fields = set ()
150
+ for personalization in self .data ["personalizations" ]:
151
+ try :
152
+ dynamic_template_data = personalization .pop ("dynamic_template_data" )
153
+ except KeyError :
154
+ pass # no substitutions for this recipient
155
+ else :
156
+ # Convert dynamic_template_data keys for substitutions, using merge_field_format
157
+ personalization ["substitutions" ] = {
158
+ merge_field_format .format (field ): data
159
+ for field , data in dynamic_template_data .items ()}
160
+ all_merge_fields .update (dynamic_template_data .keys ())
161
+
162
+ if self .merge_field_format is None :
163
+ if all_merge_fields and all (field .isalnum () for field in all_merge_fields ):
177
164
warnings .warn (
178
165
"Your SendGrid merge fields don't seem to have delimiters, "
179
166
"which can cause unexpected results with Anymail's merge_data. "
180
167
"Search SENDGRID_MERGE_FIELD_FORMAT in the Anymail docs for more info." ,
181
168
AnymailWarning )
182
169
183
- if self .merge_global_data is not None :
184
- # (merge into any existing 'sections' from esp_extra)
185
- self .data .setdefault ("sections" , {}).update ({
186
- merge_field_format .format (field ): data
187
- for field , data in self .merge_global_data .items ()
188
- })
189
-
190
- # Confusingly, "Section tags have to be contained within a Substitution tag"
191
- # (https://sendgrid.com/docs/API_Reference/SMTP_API/section_tags.html),
192
- # so we need to insert a "-field-": "-field-" identity fallback for each
193
- # missing global field in the recipient substitutions...
194
- global_fields = [merge_field_format .format (field )
195
- for field in self .merge_global_data .keys ()]
196
- for personalization in self .data ["personalizations" ]:
197
- substitutions = personalization .setdefault ("substitutions" , {})
198
- substitutions .update ({field : field for field in global_fields
199
- if field not in substitutions })
200
-
201
- if (self .merge_field_format is None and
202
- all (field .isalnum () for field in self .merge_global_data .keys ())):
170
+ if self .merge_global_data and all (field .isalnum () for field in self .merge_global_data .keys ()):
203
171
warnings .warn (
204
172
"Your SendGrid global merge fields don't seem to have delimiters, "
205
173
"which can cause unexpected results with Anymail's merge_data. "
206
174
"Search SENDGRID_MERGE_FIELD_FORMAT in the Anymail docs for more info." ,
207
175
AnymailWarning )
208
176
209
177
def build_merge_metadata (self ):
210
- if self .merge_metadata is None :
211
- return
212
-
213
- if self .merge_data is None :
214
- # Burst apart each to-email in personalizations[0] into a separate
215
- # personalization, and add merge_metadata for that recipient
216
- assert len (self .data ["personalizations" ]) == 1
217
- base_personalizations = self .data ["personalizations" ].pop ()
218
- to_list = base_personalizations .pop ("to" ) # {email, name?} for each message.to
219
- for recipient in to_list :
220
- personalization = base_personalizations .copy () # captures cc, bcc, and any esp_extra
221
- personalization ["to" ] = [recipient ]
222
- self .data ["personalizations" ].append (personalization )
223
-
224
- for personalization in self .data ["personalizations" ]:
225
- recipient_email = personalization ["to" ][0 ]["email" ]
226
- recipient_metadata = self .merge_metadata .get (recipient_email )
227
- if recipient_metadata :
228
- recipient_custom_args = self .transform_metadata (recipient_metadata )
229
- personalization ["custom_args" ] = recipient_custom_args
178
+ if self .merge_metadata :
179
+ for personalization in self .data ["personalizations" ]:
180
+ assert len (personalization ["to" ]) == 1
181
+ recipient_email = personalization ["to" ][0 ]["email" ]
182
+ recipient_metadata = self .merge_metadata .get (recipient_email )
183
+ if recipient_metadata :
184
+ recipient_custom_args = self .transform_metadata (recipient_metadata )
185
+ personalization ["custom_args" ] = recipient_custom_args
230
186
231
187
#
232
188
# Payload construction
0 commit comments