diff --git a/docs/news.rst b/docs/news.rst index 4a56abc8a..b4f2b5c5d 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -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: diff --git a/docs/topics/email.rst b/docs/topics/email.rst index a6a54445f..05003147e 100644 --- a/docs/topics/email.rst +++ b/docs/topics/email.rst @@ -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 `:: + + 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 `. + + :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`` diff --git a/scrapy/contrib/memusage.py b/scrapy/contrib/memusage.py index 8f315b4c7..4d4aae1c9 100644 --- a/scrapy/contrib/memusage.py +++ b/scrapy/contrib/memusage.py @@ -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) diff --git a/scrapy/contrib/statsmailer.py b/scrapy/contrib/statsmailer.py index 17ad2f377..6a982195d 100644 --- a/scrapy/contrib/statsmailer.py +++ b/scrapy/contrib/statsmailer.py @@ -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) diff --git a/scrapy/mail.py b/scrapy/mail.py index d65def22d..8af6efdc4 100644 --- a/scrapy/mail.py +++ b/scrapy/mail.py @@ -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', diff --git a/scrapy/tests/test_mail.py b/scrapy/tests/test_mail.py index f4e6a10d8..2a2cddabc 100644 --- a/scrapy/tests/test_mail.py +++ b/scrapy/tests/test_mail.py @@ -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'])