refactored MailSender to get rid of scrapy.conf singleton, also removed ill-designed scrapy.mail.mail_sent signal

This commit is contained in:
Pablo Hoffman 2012-09-11 16:27:19 -03:00
parent 1e2efe5664
commit 7ef593c5c2
6 changed files with 49 additions and 79 deletions

View File

@ -32,6 +32,7 @@ Scrapy changes:
- LogFormatter method now return dicts(instead of strings) to support lazy formatting (:issue:`164`, :commit:`dcef7b0`)
- downloader handlers (:setting:`DOWNLOAD_HANDLERS` setting) now receive settings as the first argument of the constructor
- replaced memory usage acounting with (more portable) `resource`_ module, removed ``scrapy.utils.memory`` module
- removed signal: ``scrapy.mail.mail_sent``
Scrapyd changes:

View File

@ -20,11 +20,19 @@ simple API for sending attachments and it's very easy to configure, with a few
Quick example
=============
Here's a quick example of how to send an e-mail (without attachments)::
There are two ways to instantiate the mail sender. You can instantiate it using
the standard constructor::
from scrapy.mail import MailSender
mailer = MailSender()
Or you can instantiate it passing a Scrapy settings object, which will respect
the :ref:`settings <topics-email-settings>`::
mailer = MailSender.from_settings(settings)
And here is how to use it to send an e-mail (without attachments)::
mailer.send(to=["someone@example.com"], subject="Some subject", body="Some body", cc=["another@example.com"])
MailSender class reference
@ -33,7 +41,7 @@ MailSender class reference
MailSender is the preferred class to use for sending emails from Scrapy, as it
uses `Twisted non-blocking IO`_, like the rest of the framework.
.. class:: MailSender(smtphost=None, mailfrom=None, smtpuser=None, smtppass=None, smtpport=None):
.. class:: MailSender(smtphost=None, mailfrom=None, smtpuser=None, smtppass=None, smtpport=None)
:param smtphost: the SMTP host to use for sending the emails. If omitted, the
:setting:`MAIL_HOST` setting will be used.
@ -54,9 +62,17 @@ uses `Twisted non-blocking IO`_, like the rest of the framework.
:param smtpport: the SMTP port to connect to
:type smtpport: int
.. classmethod:: from_settings(settings)
Instantiate using a Scrapy settings object, which will respect
:ref:`these Scrapy settings <topics-email-settings>`.
:param settings: the e-mail recipients
:type settings: :class:`scrapy.settings.Settings` object
.. method:: send(to, subject, body, cc=None, attachs=())
Send email to the given recipients. Emits the :signal:`mail_sent` signal.
Send email to the given recipients.
:param to: the e-mail recipients
:type to: list
@ -132,34 +148,3 @@ MAIL_PASS
Default: ``None``
Password to use for SMTP authentication, along with :setting:`MAIL_USER`.
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``

View File

@ -30,7 +30,7 @@ class MemoryUsage(object):
self.limit = crawler.settings.getint('MEMUSAGE_LIMIT_MB')*1024*1024
self.warning = crawler.settings.getint('MEMUSAGE_WARNING_MB')*1024*1024
self.report = crawler.settings.getbool('MEMUSAGE_REPORT')
self.mail = MailSender()
self.mail = MailSender.from_settings(crawler.settings)
crawler.signals.connect(self.engine_started, signal=signals.engine_started)
crawler.signals.connect(self.engine_stopped, signal=signals.engine_stopped)

View File

@ -10,23 +10,25 @@ from scrapy.exceptions import NotConfigured
class StatsMailer(object):
def __init__(self, stats, recipients):
def __init__(self, stats, recipients, mail):
self.stats = stats
self.recipients = recipients
self.mail = mail
@classmethod
def from_crawler(cls, crawler):
recipients = crawler.settings.getlist("STATSMAILER_RCPTS")
if not recipients:
raise NotConfigured
o = cls(crawler.stats, recipients)
crawler.connect(o.stats_spider_closed, signal=signals.stats_spider_closed)
mail = MailSender.from_settings(crawler.settings)
o = cls(crawler.stats, recipients, mail)
crawler.signals.connect(o.spider_closed, signal=signals.spider_closed)
return o
def stats_spider_closed(self, spider, spider_stats):
mail = MailSender()
def spider_closed(self, spider):
spider_stats = self.stats.get_stats(spider)
body = "Global stats\n\n"
body += "\n".join("%-50s : %s" % i for i in self.stats.get_stats().items())
body += "\n\n%s stats\n\n" % spider.name
body += "\n".join("%-50s : %s" % i for i in spider_stats.items())
mail.send(self.recipients, "Scrapy stats for: %s" % spider.name, body)
return self.mail.send(self.recipients, "Scrapy stats for: %s" % spider.name, body)

View File

@ -15,31 +15,24 @@ from twisted.internet import defer, reactor
from twisted.mail.smtp import ESMTPSenderFactory
from scrapy import log
from scrapy.exceptions import NotConfigured
from scrapy.conf import settings
# signal sent when message is sent
# args: to, subject, body, cc, attach, msg
mail_sent = object()
class MailSender(object):
def __init__(self, smtphost=None, mailfrom=None, smtpuser=None, smtppass=None, \
smtpport=None, debug=False, crawler=None):
self.smtphost = smtphost or settings['MAIL_HOST']
self.smtpport = smtpport or settings.getint('MAIL_PORT')
self.smtpuser = smtpuser or settings['MAIL_USER']
self.smtppass = smtppass or settings['MAIL_PASS']
self.mailfrom = mailfrom or settings['MAIL_FROM']
def __init__(self, smtphost='localhost', mailfrom='scrapy@localhost',
smtpuser=None, smtppass=None, smtpport=25, debug=False):
self.smtphost = smtphost
self.smtpport = smtpport
self.smtpuser = smtpuser
self.smtppass = smtppass
self.mailfrom = mailfrom
self.debug = debug
self.signals = crawler.signals if crawler else None
if not self.smtphost or not self.mailfrom:
raise NotConfigured("MAIL_HOST and MAIL_FROM settings are required")
@classmethod
def from_settings(cls, settings):
return cls(settings['MAIL_HOST'], settings['MAIL_FROM'], settings['MAIL_USER'],
settings['MAIL_PASS'], settings.getint('MAIL_PORT'))
def send(self, to, subject, body, cc=None, attachs=()):
def send(self, to, subject, body, cc=None, attachs=(), _callback=None):
if attachs:
msg = MIMEMultipart()
else:
@ -65,9 +58,8 @@ class MailSender(object):
else:
msg.set_payload(body)
if self.signals:
self.signals.send_catch_log(signal=mail_sent, to=to, subject=subject, body=body,
cc=cc, attach=attachs, msg=msg)
if _callback:
_callback(to=to, subject=subject, body=body, cc=cc, attach=attachs, msg=msg)
if self.debug:
log.msg(format='Debug mail sent OK: To=%(mailto)s Cc=%(mailcc)s Subject="%(mailsubject)s" Attachs=%(mailattachs)d',

View File

@ -1,23 +1,13 @@
from cStringIO import StringIO
import unittest
from scrapy.mail import MailSender, mail_sent
from scrapy.utils.test import get_crawler
from scrapy.mail import MailSender
class MailSenderTest(unittest.TestCase):
def setUp(self):
self.catched_msg = None
self.crawler = get_crawler()
self.crawler.signals.connect(self._catch_mail_sent, signal=mail_sent)
def tearDown(self):
self.crawler.signals.disconnect(self._catch_mail_sent, signal=mail_sent)
def test_send(self):
mailsender = MailSender(debug=True, crawler=self.crawler)
mailsender.send(to=['test@scrapy.org'], subject='subject', body='body')
mailsender = MailSender(debug=True)
mailsender.send(to=['test@scrapy.org'], subject='subject', body='body', _callback=self._catch_mail_sent)
assert self.catched_msg
@ -36,9 +26,9 @@ class MailSenderTest(unittest.TestCase):
attach.seek(0)
attachs = [('attachment', 'text/plain', attach)]
mailsender = MailSender(debug=True, crawler=self.crawler)
mailsender = MailSender(debug=True)
mailsender.send(to=['test@scrapy.org'], subject='subject', body='body',
attachs=attachs)
attachs=attachs, _callback=self._catch_mail_sent)
assert self.catched_msg
self.assertEqual(self.catched_msg['to'], ['test@scrapy.org'])