Skip to content

Commit 34fda98

Browse files
committed
-
1 parent d4e7da5 commit 34fda98

File tree

3 files changed

+98
-20
lines changed

3 files changed

+98
-20
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,34 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright (c) 2013 Tomasz Wójcik <tomek@bthlabs.pl>
3+
#
4+
# Permission is hereby granted, free of charge, to any person obtaining a copy
5+
# of this software and associated documentation files (the "Software"), to deal
6+
# in the Software without restriction, including without limitation the rights
7+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
# copies of the Software, and to permit persons to whom the Software is
9+
# furnished to do so, subject to the following conditions:
10+
#
11+
# The above copyright notice and this permission notice shall be included in
12+
# all copies or substantial portions of the Software.
13+
#
14+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
# THE SOFTWARE.
21+
#
22+
23+
"""
24+
envelopes
25+
---------
26+
27+
Mailing for human beings.
28+
"""
29+
30+
__version__ = '0.4'
31+
32+
133
from .conn import *
234
from .envelope import Envelope

source_py2/python_toolbox/third_party/envelopes/conn.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,26 @@
2828
"""
2929

3030
import smtplib
31+
import socket
3132

32-
__all__ = ['SMTP', 'GMailSMTP', 'SendGridSMTP', 'MailcatcherSMTP']
33+
TimeoutException = socket.timeout
34+
35+
__all__ = ['SMTP', 'GMailSMTP', 'SendGridSMTP', 'MailcatcherSMTP',
36+
'TimeoutException']
3337

3438

3539
class SMTP(object):
3640
"""Wrapper around :py:class:`smtplib.SMTP` class."""
3741

38-
def __init__(self, host, port=25, login=None, password=None, tls=False):
42+
def __init__(self, host=None, port=25, login=None, password=None,
43+
tls=False, timeout=None):
3944
self._conn = None
4045
self._host = host
4146
self._port = port
4247
self._login = login
4348
self._password = password
4449
self._tls = tls
50+
self._timeout = timeout
4551

4652
@property
4753
def is_connected(self):
@@ -61,7 +67,11 @@ def _connect(self, replace_current=False):
6167
except (AttributeError, smtplib.SMTPServerDisconnected):
6268
pass
6369

64-
self._conn = smtplib.SMTP(self._host, self._port)
70+
if self._timeout:
71+
self._conn = smtplib.SMTP(self._host, self._port,
72+
timeout=self._timeout)
73+
else:
74+
self._conn = smtplib.SMTP(self._host, self._port)
6575

6676
if self._tls:
6777
self._conn.starttls()
@@ -75,7 +85,9 @@ def send(self, envelope):
7585
self._connect()
7686

7787
msg = envelope.to_mime_message()
78-
return self._conn.sendmail(msg['From'], msg['To'], msg.as_string())
88+
to_addrs = [envelope._addrs_to_header([addr]) for addr in envelope._to + envelope._cc + envelope._bcc]
89+
90+
return self._conn.sendmail(msg['From'], to_addrs, msg.as_string())
7991

8092

8193
class GMailSMTP(SMTP):
@@ -84,7 +96,7 @@ class GMailSMTP(SMTP):
8496
GMAIL_SMTP_HOST = 'smtp.googlemail.com'
8597
GMAIL_SMTP_TLS = True
8698

87-
def __init__(self, login, password):
99+
def __init__(self, login=None, password=None):
88100
super(GMailSMTP, self).__init__(
89101
self.GMAIL_SMTP_HOST, tls=self.GMAIL_SMTP_TLS, login=login,
90102
password=password
@@ -98,7 +110,7 @@ class SendGridSMTP(SMTP):
98110
SENDGRID_SMTP_PORT = 587
99111
SENDGRID_SMTP_TLS = False
100112

101-
def __init__(self, login, password):
113+
def __init__(self, login=None, password=None):
102114
super(SendGridSMTP, self).__init__(
103115
self.SENDGRID_SMTP_HOST, port=self.SENDGRID_SMTP_PORT,
104116
tls=self.SENDGRID_SMTP_TLS, login=login,

source_py2/python_toolbox/third_party/envelopes/envelope.py

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@
3434
elif sys.version_info[0] == 3:
3535
from email import encoders as email_encoders
3636
basestring = str
37-
37+
3838
def unicode(_str, _charset):
3939
return str(_str.encode(_charset), _charset)
4040
else:
4141
raise RuntimeError('Unsupported Python version: %d.%d.%d' % (
4242
sys.version_info[0], sys.version_info[1], sys.version_info[2]
4343
))
4444

45+
from email.header import Header
4546
from email.mime.base import MIMEBase
4647
from email.mime.multipart import MIMEMultipart
4748
from email.mime.application import MIMEApplication
@@ -50,6 +51,7 @@ def unicode(_str, _charset):
5051
from email.mime.text import MIMEText
5152
import mimetypes
5253
import os
54+
import re
5355

5456
from .conn import SMTP
5557
from .compat import encoded
@@ -78,13 +80,14 @@ class Envelope(object):
7880
:param subject: message subject
7981
:param html_body: optional HTML part of the message
8082
:param text_body: optional plain text part of the message
81-
:param cc_addrs: optional list of CC address
82-
:param bcc_addrs: optional list of BCC address
83+
:param cc_addr: optional single CC address or list of CC addresses
84+
:param bcc_addr: optional single BCC address or list of BCC addresses
8385
:param headers: optional dictionary of headers
8486
:param charset: message charset
8587
"""
8688

8789
ADDR_FORMAT = '%s <%s>'
90+
ADDR_REGEXP = re.compile(r'^(.*) <([^@]+@[^@]+)>$')
8891

8992
def __init__(self, to_addr=None, from_addr=None, subject=None,
9093
html_body=None, text_body=None, cc_addr=None, bcc_addr=None,
@@ -108,12 +111,18 @@ def __init__(self, to_addr=None, from_addr=None, subject=None,
108111
self._parts.append(('text/html', html_body, charset))
109112

110113
if cc_addr:
111-
self._cc = cc_addr
114+
if isinstance(cc_addr, list):
115+
self._cc = cc_addr
116+
else:
117+
self._cc = [cc_addr]
112118
else:
113119
self._cc = []
114120

115121
if bcc_addr:
116-
self._bcc = bcc_addr
122+
if isinstance(bcc_addr, list):
123+
self._bcc = bcc_addr
124+
else:
125+
self._bcc = [bcc_addr]
117126
else:
118127
self._bcc = []
119128

@@ -126,6 +135,13 @@ def __init__(self, to_addr=None, from_addr=None, subject=None,
126135

127136
self._addr_format = unicode(self.ADDR_FORMAT, charset)
128137

138+
def __repr__(self):
139+
return u'<Envelope from="%s" to="%s" subject="%s">' % (
140+
self._addrs_to_header([self._from]),
141+
self._addrs_to_header(self._to),
142+
self._subject
143+
)
144+
129145
@property
130146
def to_addr(self):
131147
"""List of ``To`` addresses."""
@@ -189,7 +205,7 @@ def _addr_tuple_to_addr(self, addr_tuple):
189205

190206
if len(addr_tuple) == 2 and addr_tuple[1]:
191207
addr = self._addr_format % (
192-
addr_tuple[1] or '',
208+
self._header(addr_tuple[1] or ''),
193209
addr_tuple[0] or ''
194210
)
195211
elif addr_tuple[0]:
@@ -217,40 +233,58 @@ def _addrs_to_header(self, addrs):
217233
continue
218234

219235
if isinstance(addr, basestring):
220-
_addrs.append(addr)
236+
if self._is_ascii(addr):
237+
_addrs.append(self._encoded(addr))
238+
else:
239+
# these headers need special care when encoding, see:
240+
# http://tools.ietf.org/html/rfc2047#section-8
241+
# Need to break apart the name from the address if there are
242+
# non-ascii chars
243+
m = self.ADDR_REGEXP.match(addr)
244+
if m:
245+
t = (m.group(2), m.group(1))
246+
_addrs.append(self._addr_tuple_to_addr(t))
247+
else:
248+
# What can we do? Just pass along what the user gave us and hope they did it right
249+
_addrs.append(self._encoded(addr))
221250
elif isinstance(addr, tuple):
222251
_addrs.append(self._addr_tuple_to_addr(addr))
223252
else:
224253
self._raise(MessageEncodeError,
225254
'%s is not a valid address' % str(addr))
226255

227-
_header = unicode(',', self._charset).join(_addrs)
256+
_header = ','.join(_addrs)
228257
return _header
229258

230259
def _raise(self, exc_class, message):
231260
raise exc_class(self._encoded(message))
232261

262+
def _header(self, _str):
263+
if self._is_ascii(_str):
264+
return _str
265+
return Header(_str, self._charset).encode()
266+
267+
def _is_ascii(self, _str):
268+
return all(ord(c) < 128 for c in _str)
269+
233270
def _encoded(self, _str):
234271
return encoded(_str, self._charset)
235272

236273
def to_mime_message(self):
237274
"""Returns the envelope as
238275
:py:class:`email.mime.multipart.MIMEMultipart`."""
239276
msg = MIMEMultipart('alternative')
240-
msg['Subject'] = self._encoded(self._subject or '')
277+
msg['Subject'] = self._header(self._subject or '')
241278

242279
msg['From'] = self._encoded(self._addrs_to_header([self._from]))
243280
msg['To'] = self._encoded(self._addrs_to_header(self._to))
244281

245282
if self._cc:
246-
msg['CC'] = self._encoded(self._addrs_to_header(self._cc))
247-
248-
if self._bcc:
249-
msg['BCC'] = self._encoded(self._addrs_to_header(self._bcc))
283+
msg['CC'] = self._addrs_to_header(self._cc)
250284

251285
if self._headers:
252286
for key, value in self._headers.items():
253-
msg[key] = self._encoded(value)
287+
msg[key] = self._header(value)
254288

255289
for part in self._parts:
256290
type_maj, type_min = part[0].split('/')

0 commit comments

Comments
 (0)