Ticket #6572: 6572-0.patch

File 6572-0.patch, 5.2 KB (added by Kai Zhang, 8 years ago)

Add cancellation support to twisted.mail.smtp.sendmail. Add test for it.

  • twisted/mail/smtp.py

     
    18941894        p.timeout = self.timeout
    18951895        return p
    18961896
    1897 def sendmail(smtphost, from_addr, to_addrs, msg, senderDomainName=None, port=25):
     1897def _sendmail(_SMTPSenderFactory, smtphost, from_addr, to_addrs, msg,
     1898              senderDomainName=None, port=25):
    18981899    """Send an email
    18991900
    1900     This interface is intended to be a direct replacement for
    1901     smtplib.SMTP.sendmail() (with the obvious change that
    1902     you specify the smtphost as well). Also, ESMTP options
    1903     are not accepted, as we don't do ESMTP yet. I reserve the
    1904     right to implement the ESMTP options differently.
     1901    This interface should not be used directly.
     1902    Use L{twiste.mail.smtp.sendmail} instread.
    19051903
     1904    @param _SMTPSenderFactory: The factory used to send mail.
    19061905    @param smtphost: The host the message should be sent to
    19071906    @param from_addr: The (envelope) address sending this mail.
    19081907    @param to_addrs: A list of addresses to send this mail to.  A string will
     
    19341933        # It's not a file
    19351934        msg = StringIO(str(msg))
    19361935
    1937     d = defer.Deferred()
    1938     factory = SMTPSenderFactory(from_addr, to_addrs, msg, d)
     1936    def cancel(d):
     1937        """
     1938        Cancel the L{twisted.mail.smtp.sendmail} call, tell the
     1939        L{twisted.mail.smtp.SMTPSenderFactory} not to retry and disconnect the
     1940        connection.
     1941        """
     1942        factory.sendFinished = 1
     1943        connector.disconnect()
     1944    d = defer.Deferred(cancel)
     1945    factory = _SMTPSenderFactory(from_addr, to_addrs, msg, d)
    19391946
    19401947    if senderDomainName is not None:
    19411948        factory.domain = senderDomainName
    19421949
    1943     reactor.connectTCP(smtphost, port, factory)
     1950    connector = reactor.connectTCP(smtphost, port, factory)
    19441951
    19451952    return d
    19461953
     1954def sendmail(smtphost, from_addr, to_addrs, msg, senderDomainName=None, port=25):
     1955    """Send an email
    19471956
     1957    This interface is intended to be a direct replacement for
     1958    smtplib.SMTP.sendmail() (with the obvious change that
     1959    you specify the smtphost as well). Also, ESMTP options
     1960    are not accepted, as we don't do ESMTP yet. I reserve the
     1961    right to implement the ESMTP options differently.
    19481962
     1963    @param smtphost: The host the message should be sent to
     1964    @param from_addr: The (envelope) address sending this mail.
     1965    @param to_addrs: A list of addresses to send this mail to.  A string will
     1966        be treated as a list of one address
     1967    @param msg: The message, including headers, either as a file or a string.
     1968        File-like objects need to support read() and close(). Lines must be
     1969        delimited by '\\n'. If you pass something that doesn't look like a
     1970        file, we try to convert it to a string (so you should be able to
     1971        pass an email.Message directly, but doing the conversion with
     1972        email.Generator manually will give you more control over the
     1973        process).
     1974
     1975    @param senderDomainName: Name by which to identify.  If None, try
     1976    to pick something sane (but this depends on external configuration
     1977    and may not succeed).
     1978
     1979    @param port: Remote port to which to connect.
     1980
     1981    @rtype: L{Deferred}
     1982    @returns: A L{Deferred}, its callback will be called if a message is sent
     1983        to ANY address, the errback if no message is sent.
     1984
     1985        The callback will be called with a tuple (numOk, addresses) where numOk
     1986        is the number of successful recipient addresses and addresses is a list
     1987        of tuples (address, code, resp) giving the response to the RCPT command
     1988        for each address.
     1989    """
     1990    return _sendmail(SMTPSenderFactory, smtphost, from_addr, to_addr, msg,
     1991                     senderDomainName, port)
     1992
     1993
     1994
    19491995##
    19501996## Yerg.  Codecs!
    19511997##
  • twisted/mail/test/test_smtp.py

     
    16491649            warningsShown[0]['message'],
    16501650            "tlsMode attribute of twisted.mail.smtp.ESMTPClient "
    16511651            "is deprecated since Twisted 13.0")
     1652
     1653
     1654
     1655class SendmailTestCase(unittest.TestCase):
     1656    """
     1657    Tests for the L{twisted.mail.smtp.sendmail}.
     1658    """
     1659    def test_cancel(self):
     1660        """
     1661        A deferred returned by the L{twisted.mail.smtp.sendmail} can be
     1662        cancelled, telling the L{twisted.mail.smtp.SMTPSenderFactory} not to
     1663        retry and disconnecting the L{twisted.internet.interfaces.IConnector}.
     1664        """
     1665        class MockSMTPSenderFactory(smtp.SMTPSenderFactory):
     1666            def startedConnecting(self, connector):
     1667                self.connector = connector
     1668        def factory(*args, **kw):
     1669            factory.f = MockSMTPSenderFactory(*args, **kw)
     1670            return factory.f
     1671        d = smtp._sendmail(factory, "127.0.0.1",
     1672                           "source@address", "recipient@address", "message")
     1673        self.assertEqual(factory.f.connector.state, "connecting")
     1674        d.cancel()
     1675        self.assertEqual(factory.f.sendFinished, 1)
     1676        self.assertEqual(factory.f.connector.state, "disconnected")
     1677        d = self.assertFailure(d, defer.CancelledError)
     1678        return d