Package lamson :: Module mail
[hide private]
[frames] | no frames]

Source Code for Module lamson.mail

  1  """ 
  2  The lamson.mail module contains nothing more than wrappers around the big work 
  3  done in lamson.encoding.  These are the actual APIs that you'll interact with 
  4  when doing email, and they mostly replicate the lamson.encoding.MailBase  
  5  functionality. 
  6   
  7  The main design criteria is that MailRequest is mostly for reading email  
  8  that you've received, so it doesn't have functions for attaching files and such. 
  9  MailResponse is used when you are going to write an email, so it has the 
 10  APIs for doing attachments and such. 
 11  """ 
 12   
 13   
 14  import mimetypes 
 15  from lamson import encoding, bounce 
 16  from email.utils import parseaddr 
 17  import os 
 18  import warnings 
 19   
 20   
 21  # You can change this to 'Delivered-To' on servers that support it like Postfix 
 22  ROUTABLE_TO_HEADER='to' 
23 24 -def _decode_header_randomness(addr):
25 """ 26 This fixes the given address so that it is *always* a set() of 27 just email addresses suitable for routing. 28 """ 29 if not addr: 30 return set() 31 elif isinstance(addr, list): 32 return set(parseaddr(a.lower())[1] for a in addr) 33 elif isinstance(addr, basestring): 34 return set([parseaddr(addr.lower())[1]]) 35 else: 36 raise encoding.EncodingError("Address must be a string or a list not: %r", type(addr))
37
38 39 -class MailRequest(object):
40 """ 41 This is what's handed to your handlers for you to process. The information 42 you get out of this is *ALWAYS* in Python unicode and should be usable 43 by any API. Modifying this object will cause other handlers that deal 44 with it to get your modifications, but in general you don't want to do 45 more than maybe tag a few headers. 46 """
47 - def __init__(self, Peer, From, To, Data):
48 """ 49 Peer is the remote peer making the connection (sometimes the queue 50 name). From and To are what you think they are. Data is the raw 51 full email as received by the server. 52 53 NOTE: It does not handle multiple From headers, if that's even 54 possible. It will parse the From into a list and take the first 55 one. 56 """ 57 58 self.original = Data 59 self.base = encoding.from_string(Data) 60 self.Peer = Peer 61 self.From = From or self.base['from'] 62 self.To = To or self.base[ROUTABLE_TO_HEADER] 63 64 if 'from' not in self.base: 65 self.base['from'] = self.From 66 if 'to' not in self.base: 67 # do NOT use ROUTABLE_TO here 68 self.base['to'] = self.To 69 70 self.route_to = _decode_header_randomness(self.To) 71 self.route_from = _decode_header_randomness(self.From) 72 73 if self.route_from: 74 self.route_from = self.route_from.pop() 75 else: 76 self.route_from = None 77 78 self.bounce = None
79 80
81 - def all_parts(self):
82 """Returns all multipart mime parts. This could be an empty list.""" 83 return self.base.parts
84 85
86 - def body(self):
87 """ 88 Always returns a body if there is one. If the message 89 is multipart then it returns the first part's body, if 90 it's not then it just returns the body. If returns 91 None then this message has nothing for a body. 92 """ 93 if self.base.parts: 94 return self.base.parts[0].body 95 else: 96 return self.base.body
97 98
99 - def __contains__(self, key):
100 return self.base.__contains__(key)
101
102 - def __getitem__(self, name):
103 return self.base.__getitem__(name)
104
105 - def __setitem__(self, name, val):
106 self.base.__setitem__(name, val)
107
108 - def __delitem__(self, name):
109 del self.base[name]
110
111 - def __str__(self):
112 """ 113 Converts this to a string usable for storage into a queue or 114 transmission. 115 """ 116 return encoding.to_string(self.base)
117
118 - def __repr__(self):
119 return "From: %r" % [self.Peer, self.From, self.To]
120
121 - def keys(self):
122 return self.base.keys()
123
124 - def to_message(self):
125 """ 126 Converts this to a Python email message you can use to 127 interact with the python mail APIs. 128 """ 129 return encoding.to_message(self.base)
130
131 - def walk(self):
132 """Recursively walks all attached parts and their children.""" 133 for x in self.base.walk(): 134 yield x
135
136 - def is_bounce(self, threshold=0.3):
137 """ 138 Determines whether the message is a bounce message based on 139 lamson.bounce.BounceAnalzyer given threshold. 0.3 is a good 140 conservative base. 141 """ 142 if not self.bounce: 143 self.bounce = bounce.detect(self) 144 145 if self.bounce.score > threshold: 146 return True 147 else: 148 return False
149 150 @property
151 - def msg(self):
152 warnings.warn("The .msg attribute is deprecated, use .base instead. This will be gone in Lamson 1.0", 153 category=DeprecationWarning, stacklevel=2) 154 return self.base
155
156 157 158 -class MailResponse(object):
159 """ 160 You are given MailResponse objects from the lamson.view methods, and 161 whenever you want to generate an email to send to someone. It has 162 the same basic functionality as MailRequest, but it is designed to 163 be written to, rather than read from (although you can do both). 164 165 You can easily set a Body or Html during creation or after by 166 passing it as __init__ parameters, or by setting those attributes. 167 168 You can initially set the From, To, and Subject, but they are headers so 169 use the dict notation to change them: msg['From'] = 'joe@test.com'. 170 171 The message is not fully crafted until right when you convert it with 172 MailResponse.to_message. This lets you change it and work with it, then 173 send it out when it's ready. 174 """
175 - def __init__(self, To=None, From=None, Subject=None, Body=None, Html=None):
176 self.Body = Body 177 self.Html = Html 178 self.base = encoding.MailBase([('To', To), ('From', From), ('Subject', Subject)]) 179 self.multipart = self.Body and self.Html 180 self.attachments = []
181
182 - def __contains__(self, key):
183 return self.base.__contains__(key)
184
185 - def __getitem__(self, key):
186 return self.base.__getitem__(key)
187
188 - def __setitem__(self, key, val):
189 return self.base.__setitem__(key, val)
190
191 - def __delitem__(self, name):
192 del self.base[name]
193
194 - def attach(self, filename=None, content_type=None, data=None, disposition=None):
195 """ 196 Simplifies attaching files from disk or data as files. To attach simple 197 text simple give data and a content_type. To attach a file, give the 198 data/content_type/filename/disposition combination. 199 200 For convenience, if you don't give data and only a filename, then it 201 will read that file's contents when you call to_message() later. If you 202 give data and filename then it will assume you've filled data with what 203 the file's contents are and filename is just the name to use. 204 """ 205 assert filename or data, "You must give a filename or some data to attach." 206 assert data or os.path.exists(filename), "File doesn't exist, and no data given." 207 208 self.multipart = True 209 210 if filename and not content_type: 211 content_type, encoding = mimetypes.guess_type(filename) 212 213 assert content_type, "No content type given, and couldn't guess from the filename: %r" % filename 214 215 self.attachments.append({'filename': filename, 216 'content_type': content_type, 217 'data': data, 218 'disposition': disposition,})
219 - def attach_part(self, part):
220 """ 221 Attaches a raw MailBase part from a MailRequest (or anywhere) 222 so that you can copy it over. 223 """ 224 self.multipart = True 225 226 self.attachments.append({'filename': None, 227 'content_type': None, 228 'data': None, 229 'disposition': None, 230 'part': part, 231 })
232
233 - def attach_all_parts(self, mail_request):
234 """ 235 Used for copying the attachment parts of a mail.MailRequest 236 object for mailing lists that need to maintain attachments. 237 """ 238 for part in mail_request.all_parts(): 239 self.attach_part(part) 240 241 self.base.content_encoding = mail_request.base.content_encoding.copy()
242
243 - def clear(self):
244 """ 245 Clears out the attachments so you can redo them. Use this to keep the 246 headers for a series of different messages with different attachments. 247 """ 248 del self.attachments[:] 249 del self.base.parts[:] 250 self.multipart = False
251 252
253 - def update(self, message):
254 """ 255 Used to easily set a bunch of heading from another dict 256 like object. 257 """ 258 for k in message.keys(): 259 self.base[k] = message[k]
260
261 - def __str__(self):
262 """ 263 Converts to a string. 264 """ 265 return self.to_message().as_string()
266
267 - def _encode_attachment(self, filename=None, content_type=None, data=None, disposition=None, part=None):
268 """ 269 Used internally to take the attachments mentioned in self.attachments 270 and do the actual encoding in a lazy way when you call to_message. 271 """ 272 if part: 273 self.base.parts.append(part) 274 elif filename: 275 if not data: 276 data = open(filename).read() 277 278 self.base.attach_file(filename, data, content_type, disposition or 'attachment') 279 else: 280 self.base.attach_text(data, content_type) 281 282 ctype = self.base.content_encoding['Content-Type'][0] 283 284 if ctype and not ctype.startswith('multipart'): 285 self.base.content_encoding['Content-Type'] = ('multipart/mixed', {})
286
287 - def to_message(self):
288 """ 289 Figures out all the required steps to finally craft the 290 message you need and return it. The resulting message 291 is also available as a self.base attribute. 292 293 What is returned is a Python email API message you can 294 use with those APIs. The self.base attribute is the raw 295 lamson.encoding.MailBase. 296 """ 297 del self.base.parts[:] 298 299 if self.Body and self.Html: 300 self.multipart = True 301 self.base.content_encoding['Content-Type'] = ('multipart/alternative', {}) 302 303 if self.multipart: 304 self.base.body = None 305 if self.Body: 306 self.base.attach_text(self.Body, 'text/plain') 307 308 if self.Html: 309 self.base.attach_text(self.Html, 'text/html') 310 311 for args in self.attachments: 312 self._encode_attachment(**args) 313 314 elif self.Body: 315 self.base.body = self.Body 316 self.base.content_encoding['Content-Type'] = ('text/plain', {}) 317 318 elif self.Html: 319 self.base.body = self.Html 320 self.base.content_encoding['Content-Type'] = ('text/html', {}) 321 322 return encoding.to_message(self.base)
323
324 - def all_parts(self):
325 """ 326 Returns all the encoded parts. Only useful for debugging 327 or inspecting after calling to_message(). 328 """ 329 return self.base.parts
330
331 - def keys(self):
332 return self.base.keys()
333 334 @property
335 - def msg(self):
336 warnings.warn("The .msg attribute is deprecated, use .base instead. This will be gone in Lamson 1.0", 337 category=DeprecationWarning, stacklevel=2) 338 return self.base
339