diff --git a/docs/topics/email.rst b/docs/topics/email.rst index 5dcf6dbc5..c372e2122 100644 --- a/docs/topics/email.rst +++ b/docs/topics/email.rst @@ -47,7 +47,10 @@ uses `Twisted non-blocking IO`_, like the rest of the framework. .. method:: send(to, subject, body, cc=None, attachs=()) - Send email to the given recipients + Send email to the given recipients. Emits the :signal:`mail_sent` signal. + + If :setting:`MAIL_DEBUG` is enabled the :signal:`mail_sent` signal will + be emmited and no actual email will be sent. :param to: the e-mail recipients :type to: list @@ -76,6 +79,37 @@ These settings define the default constructor values of the :class:`MailSender` class, and can be used to configure e-mail notifications in your project without writing any code (for those extensions that use the :class:`MailSender` class): +* :setting:`MAIL_DEBUG` * :setting:`MAIL_FROM` * :setting:`MAIL_HOST` + +Mail signals +============ + +.. signal:: mail_sent +.. function:: mail_sent(to, subject, body, cc, attachs, msg) + + Emitted by :meth:`MailSender.send` after an email has been sent. + + :param to: the e-mail recipients + :type to: list + + :param subject: the subject of the e-mail + :type subject: str + + :param cc: the e-mails to CC + :type cc: list + + :param body: the e-mail body + :type body: str + + :param attachs: an iterable of tuples ``(attach_name, mimetype, + file_object)`` where ``attach_name`` is a string with the name that will + appear on the e-mail's attachment, ``mimetype`` is the mimetype of the + attachment and ``file_object`` is a readable file object with the + contents of the attachment + :type attachs: iterable + + :param msg: the generated message + :type msg: ``MIMEMultipart`` or ``MIMENonMultipart`` diff --git a/docs/topics/settings.rst b/docs/topics/settings.rst index e64ee329b..8c06f0bd9 100644 --- a/docs/topics/settings.rst +++ b/docs/topics/settings.rst @@ -620,6 +620,16 @@ If ``True``, all standard output (and error) of your process will be redirected to the log. For example if you ``print 'hello'`` it will appear in the Scrapy log. +.. setting:: MAIL_DEBUG + +MAIL_DEBUG +---------- + +Default: ``False`` + +Whether to enable the debugging mode in the the :ref:`Scrapy e-mail sending +facility `. + .. setting:: MAIL_FROM MAIL_FROM diff --git a/scrapy/conf/default_settings.py b/scrapy/conf/default_settings.py index b80e89385..1bc2c8603 100644 --- a/scrapy/conf/default_settings.py +++ b/scrapy/conf/default_settings.py @@ -141,6 +141,7 @@ LOG_STDOUT = False LOG_LEVEL = 'DEBUG' LOG_FILE = None +MAIL_DEBUG = False MAIL_HOST = 'localhost' MAIL_FROM = 'scrapy@localhost' diff --git a/scrapy/mail.py b/scrapy/mail.py index 87825ed9c..8703a65c5 100644 --- a/scrapy/mail.py +++ b/scrapy/mail.py @@ -17,6 +17,13 @@ from twisted.mail.smtp import SMTPSenderFactory from scrapy import log from scrapy.core.exceptions import NotConfigured from scrapy.conf import settings +from scrapy.utils.signal import send_catch_log + + +# signal sent when message is sent +# args: to, subject, body, cc, attach, msg +mail_sent = object() + class MailSender(object): @@ -53,6 +60,14 @@ class MailSender(object): else: msg.set_payload(body) + send_catch_log(signal=mail_sent, to=to, subject=subject, body=body, + cc=cc, attach=attachs, msg=msg) + + if settings.getbool('MAIL_DEBUG'): + log.msg('Debug mail sent OK: To=%s Cc=%s Subject="%s" Attachs=%d' % \ + (to, cc, subject, len(attachs)), level=log.DEBUG) + return + dfd = self._sendmail(self.smtphost, self.mailfrom, rcpts, msg.as_string()) dfd.addCallbacks(self._sent_ok, self._sent_failed, callbackArgs=[to, cc, subject, len(attachs)], diff --git a/scrapy/tests/test_mail.py b/scrapy/tests/test_mail.py new file mode 100644 index 000000000..efc93abb5 --- /dev/null +++ b/scrapy/tests/test_mail.py @@ -0,0 +1,71 @@ +from cStringIO import StringIO +import unittest + +from scrapy.xlib.pydispatch import dispatcher + +from scrapy.conf import settings +from scrapy.mail import MailSender, mail_sent + + +class MailSenderTest(unittest.TestCase): + + def setUp(self): + settings.disabled = False + settings.overrides['MAIL_DEBUG'] = True + + self.catched_msg = None + + dispatcher.connect(self._catch_mail_sent, signal=mail_sent) + + def test_send(self): + mailsender = MailSender() + mailsender.send(to=['test@scrapy.org'], subject='subject', body='body') + + assert self.catched_msg + + self.assertEqual(self.catched_msg['to'], ['test@scrapy.org']) + self.assertEqual(self.catched_msg['subject'], 'subject') + self.assertEqual(self.catched_msg['body'], 'body') + + msg = self.catched_msg['msg'] + self.assertEqual(msg['to'], 'test@scrapy.org') + self.assertEqual(msg['subject'], 'subject') + self.assertEqual(msg.get_payload(), 'body') + + def test_send_attach(self): + attach = StringIO() + attach.write('content') + attach.seek(0) + attachs = [('attachment', 'text/plain', attach)] + + mailsender = MailSender() + mailsender.send(to=['test@scrapy.org'], subject='subject', body='body', + attachs=attachs) + + assert self.catched_msg + self.assertEqual(self.catched_msg['to'], ['test@scrapy.org']) + self.assertEqual(self.catched_msg['subject'], 'subject') + self.assertEqual(self.catched_msg['body'], 'body') + + msg = self.catched_msg['msg'] + self.assertEqual(msg['to'], 'test@scrapy.org') + self.assertEqual(msg['subject'], 'subject') + + payload = msg.get_payload() + assert isinstance(payload, list) + self.assertEqual(len(payload), 2) + + text, attach = payload + self.assertEqual(text.get_payload(decode=True), 'body') + self.assertEqual(attach.get_payload(decode=True), 'content') + + def tearDown(self): + del settings.overrides['MAIL_DEBUG'] + settings.disabled = True + + def _catch_mail_sent(self, **kwargs): + self.catched_msg = dict(**kwargs) + + +if __name__ == "__main__": + unittest.main()