From 8358b305d39dc7dbc9572fb826a03b09c59a556f Mon Sep 17 00:00:00 2001 From: "Serge S. Koval" Date: Thu, 18 Oct 2012 13:55:22 +0300 Subject: [PATCH 1/2] Added stack context wrapping --- setup.py | 2 +- tornadomail/backends/smtp.py | 23 ++++++++++++++++++++++- tornadomail/message.py | 7 ++++++- tornadomail/smtplib.py | 24 ++++++++++++------------ 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/setup.py b/setup.py index 9634062..19d3b74 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ import os -from distutils.core import setup +from setuptools import setup, find_packages version = '0.1.2' diff --git a/tornadomail/backends/smtp.py b/tornadomail/backends/smtp.py index cf57bd8..a1c2cd6 100644 --- a/tornadomail/backends/smtp.py +++ b/tornadomail/backends/smtp.py @@ -6,7 +6,7 @@ from ..utils import DNS_NAME from ..message import sanitize_address -from tornado import gen +from tornado import gen, stack_context class EmailBackend(BaseEmailBackend): @@ -36,20 +36,29 @@ def open(self, callback): if self.connection: # Nothing to do if the connection is already open. callback(False) + + # Capture stack context + callback = stack_context.wrap(callback) + try: # If local_hostname is not specified, socket.getfqdn() gets used. # For performance, we use the cached FQDN for local_hostname. self.connection = smtplib.SMTP(self.host, self.port, local_hostname=DNS_NAME.get_fqdn()) yield gen.Task(self.connection.connect, self.host, self.port) + if self.use_tls: yield gen.Task(self.connection.ehlo) yield gen.Task(self.connection.starttls) yield gen.Task(self.connection.ehlo) + if self.username and self.password: yield gen.Task(self.connection.login, self.username, self.password) + callback(True) except: + # TODO: Call callback + if not self.fail_silently: raise @@ -78,18 +87,25 @@ def send_messages(self, email_messages, callback=None): if not email_messages: return + callback = stack_context.wrap(callback) + new_conn_created = yield gen.Task(self.open) + if not self.connection: # We failed silently on open(). # Trying to send would be pointless. return + num_sent = 0 + for message in email_messages: sent = yield gen.Task(self._send, message) if sent: num_sent += 1 + if new_conn_created: self.close() + if callback: callback(num_sent) @@ -99,6 +115,9 @@ def _send(self, email_message, callback=None): if not email_message.recipients(): if callback: callback(False) + + callback = stack_context.wrap(callback) + from_email = sanitize_address(email_message.from_email, email_message.encoding) recipients = [sanitize_address(addr, email_message.encoding) for addr in email_message.recipients()] @@ -108,7 +127,9 @@ def _send(self, email_message, callback=None): except: if not self.fail_silently: raise + if callback: callback(False) + if callback: callback(True) diff --git a/tornadomail/message.py b/tornadomail/message.py index 154519f..2035ae8 100644 --- a/tornadomail/message.py +++ b/tornadomail/message.py @@ -16,7 +16,7 @@ from utils import DNS_NAME from encoding import smart_str, force_unicode -from tornado import gen +from tornado import gen, stack_context try: from cStringIO import StringIO @@ -254,7 +254,12 @@ def send(self, fail_silently=False, callback=None): if callback: callback(0) return + + # Capture stack context + callback = stack_context.wrap(callback) + result = yield gen.Task(self.get_connection(fail_silently).send_messages, [self]) + if callback: callback(result) diff --git a/tornadomail/smtplib.py b/tornadomail/smtplib.py index 40ae28c..895d979 100644 --- a/tornadomail/smtplib.py +++ b/tornadomail/smtplib.py @@ -256,7 +256,7 @@ def set_debuglevel(self, debuglevel): """ self.debuglevel = debuglevel - + def _get_socket(self, port, host, timeout, callback): # This makes it simpler for SMTP_SSL to use the SMTP connect code # and just alter the socket connection bit. @@ -797,7 +797,7 @@ def quit(self, callback=None): callback(res) # if _have_ssl: -# +# # class SMTP_SSL(SMTP): # """ This is a subclass derived from SMTP that connects over an SSL encrypted # socket (to use this class you need a socket module that was compiled with SSL @@ -813,45 +813,45 @@ def quit(self, callback=None): # self.certfile = certfile # SMTP.__init__(self, host, port, local_hostname, timeout) # self.default_port = SMTP_SSL_PORT -# +# # def _get_socket(self, host, port, timeout): # if self.debuglevel > 0: print>>stderr, 'connect:', (host, port) # new_socket = socket.create_connection((host, port), timeout) # new_socket = ssl.wrap_socket(new_socket, self.keyfile, self.certfile) # self.file = SSLFakeFile(new_socket) # return new_socket -# +# # __all__.append("SMTP_SSL") -# +# # # # # LMTP extension # # # LMTP_PORT = 2003 -# +# # class LMTP(SMTP): # """LMTP - Local Mail Transfer Protocol -# +# # The LMTP protocol, which is very similar to ESMTP, is heavily based # on the standard SMTP client. It's common to use Unix sockets for LMTP, # so our connect() method must support that as well as a regular # host:port server. To specify a Unix socket, you must use an absolute # path as the host, starting with a '/'. -# +# # Authentication is supported, using the regular SMTP mechanism. When # using a Unix socket, LMTP generally don't support or require any # authentication, but your mileage might vary.""" -# +# # ehlo_msg = "lhlo" -# +# # def __init__(self, host = '', port = LMTP_PORT, local_hostname = None): # """Initialize a new instance.""" # SMTP.__init__(self, host, port, local_hostname) -# +# # def connect(self, host = 'localhost', port = 0): # """Connect to the LMTP daemon, on either a Unix or a TCP socket.""" # if host[0] != '/': # return SMTP.connect(self, host, port) -# +# # # Handle Unix-domain sockets. # try: # self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) From 5357744de390a85d7c3e3461fa57b12844b639a3 Mon Sep 17 00:00:00 2001 From: "Serge S. Koval" Date: Thu, 18 Oct 2012 14:43:40 +0300 Subject: [PATCH 2/2] Debianized --- debian/changelog | 5 +++++ debian/compat | 1 + debian/control | 12 ++++++++++++ debian/copyright | 18 ++++++++++++++++++ debian/rules | 4 ++++ 5 files changed, 40 insertions(+) create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100755 debian/rules diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..6152e0e --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +python-tornadomail (0.0.1) unstable; urgency=low + + * Initial version + + -- Serge S. Koval Sat, 15 October 2012 13:13:14 +0100 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..95a8b32 --- /dev/null +++ b/debian/control @@ -0,0 +1,12 @@ +Source: python-tornadomail +Section: python +Priority: optional +Maintainer: Serge S. Koval +Build-Depends: debhelper (>= 7), python (>= 2.7) +Standards-Version: 3.9.2 +Homepage: http://github.com/socialabs/tornadomail + +Package: python-tornadomail +Architecture: all +Depends: python-tornado, ${misc:Depends}, ${python:Depends} +Description: Tornado mail framework diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..b68a7eb --- /dev/null +++ b/debian/copyright @@ -0,0 +1,18 @@ +Authors: + Anton Agafonov + Serge S. Koval +Download: http://github.com/socialabs/tornadomail/ + +Files: * +License: APACHE +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..c040149 --- /dev/null +++ b/debian/rules @@ -0,0 +1,4 @@ +#!/usr/bin/make -f + +%: + dh --with python2 $@