[Twisted-Python] More IMAP4Client/Deferred fun

Jason Diamond jason at injektilo.org
Tue May 18 03:51:08 EDT 2004


Hi.

I'm now trying to move beyond the simple exercise I did yesterday and
turn my IMAP4 "client" into something that could be reusable from
other classes.

This is my current script:

 >>>
from twisted.internet import reactor, protocol
from twisted.protocols import imap4

server = "xxx"
username = "yyy"
password = "zzz"

debug = 0

class MyIMAP4Client(imap4.IMAP4Client):

    def serverGreeting(self, caps):
        if debug: print "serverGreeting:", caps
        imap4.IMAP4Client.serverGreeting(self, caps)
        d = self.login(username, password)
        d.addCallback(self.loginCallback)

    def loginCallback(self, d):
        if debug: print "loginCallback:", d
        de = self.select("INBOX")
        de.addCallback(self.selectCallback)

    def selectCallback(self, d):
        if debug: print "selectCallback:", d
        print "I have %d messages in my INBOX." % d["EXISTS"]
        de = self.logout()
        de.addCallback(self.logoutCallback)

    def logoutCallback(self, d):
        if debug: print "logoutCallback:", d
        reactor.stop()

    def connectionLost(self, reason):
        if debug: print "connectionLost:", reason
        imap4.IMAP4Client.connectionLost(self)

    def sendLine(self, line):
        if debug: print "sendLine:", line
        imap4.IMAP4Client.sendLine(self, line)

    def lineReceived(self, line):
        if debug: print "lineReceived:", line
        imap4.IMAP4Client.lineReceived(self, line)

class MyIMAP4ClientFactory(protocol.ClientFactory):

    protocol = MyIMAP4Client

if __name__ == "__main__":
    f = MyIMAP4ClientFactory()
    reactor.connectTCP(server, 143, f)
    reactor.run()
<<<

(Since this is a learning exercise, I'm trying to keep things simple
by including everything in one script.)

The problem with the above is that it prints to stdout in
selectCallback and then stops the reactor in logoutCallback.

I want to use this class from a Resource's render method. But instead
of printing to stdout, I want to "return" the value so that the render
method can use that value while outputting HTML. It's probably also
not a good idea to stop the reactor while serving a request.

The reactor will already be started by the time render gets invoked on
my resorce so I won't be calling reactor.run, right? But I will need to
connect to my IMAP server. So I tried modifying the script to connect
*after* the reactor started:

 >>>
if __name__ == "__main__":
    def connect(f):
        reactor.connectTCP(server, 143, f)
    f = MyIMAP4ClientFactory()
    reactor.callLater(0, connect, f)
    reactor.run()
<<<

This works just like before but feels weird to me. Is that the correct
usage for reactor.callLater? This was the only way I could figure out
how to invoke a functon *after* calling reactor.run. But this might be
a moot point since reactor.connectTCP probably just adds some events
to the queue and doesn't do anything until the reactor starts anyways,
right?

Looking at the documentation for connectTCP, I found out that it
returns an IConnector implementation but I'm not saving it to a
variable and doing anything with it so I'm not sure what it would buy
me and none of the methods on it look helpful here.

Anyways, I need to *not* print to the console:

 >>>
    def selectCallback(self, d):
        if debug: print "selectCallback:", d
        self.factory.count = d["EXISTS"]
        de = self.logout()
        de.addCallback(self.logoutCallback)
<<<

I'm now saving the count on the factory that created the protocol
instead of printing it directly to stdout.

At some point, I'm going to have to notify somebody that I've
retrieved the count so I figure I'm going to need a Deferred for
that. I modify the factory to hold on to a Deferred for me and set it
up to call a function to use the count:

 >>>
if __name__ == "__main__":
    def printCount(count):
        print count
    reactor.stop()
    f = MyIMAP4ClientFactory()
    f.deferred = defer.Deferred()
    f.deferred.addCallback(printCount)
    reactor.connectTCP(server, 143, f)
    reactor.run()
<<<

But now I need to tell the Deferred to actually make that callback so
I modify logoutCallback and connectionLost:

 >>>
    def logoutCallback(self, d):
        if debug: print "logoutCallback:", d

    def connectionLost(self, reason):
        if debug: print "connectionLost:", reason
        imap4.IMAP4Client.connectionLost(self)
        self.factory.deferred.callback(self.factory.count)
<<<

Now logoutCallback is no longer stopping the reactor. And
connectionLost is using the factory's deferred object to inform some
function what the actual count is. This works but it seems messy.

First of all, it seems like it would be nice if my module could expose
a free function to do all this for me:

 >>>
def getCount():
    f = MyIMAP4ClientFactory()
    f.deferred = defer.Deferred()
    reactor.connectTCP(server, 143, f)
    return f.deferred

if __name__ == "__main__":
    def printCount(count):
        print count
        reactor.stop()
    getCount().addCallback(printCount)
    reactor.run()
<<<

That's the best I can do. It works but I still don't feel all that
stoked on it. What am I doing wrong?

Is using my ClientFactory to hold data like this the correct thing to
do? In this case, there will only be one Protocol instance for this
ClientFactory, right?

Just for easy reference, here's the complete script in all its glory:

 >>>
from twisted.internet import defer, reactor, protocol
from twisted.protocols import imap4

server = "xxx"
username = "yyy"
password = "zzz"

debug = 0

class MyIMAP4Client(imap4.IMAP4Client):

    def serverGreeting(self, caps):
        if debug: print "serverGreeting:", caps
        imap4.IMAP4Client.serverGreeting(self, caps)
        d = self.login(username, password)
        d.addCallback(self.loginCallback)

    def loginCallback(self, d):
        if debug: print "loginCallback:", d
        de = self.select("INBOX")
        de.addCallback(self.selectCallback)

    def selectCallback(self, d):
        if debug: print "selectCallback:", d
        self.factory.count = d["EXISTS"]
        de = self.logout()
        de.addCallback(self.logoutCallback)

    def logoutCallback(self, d):
        if debug: print "logoutCallback:", d

    def connectionLost(self, reason):
        if debug: print "connectionLost:", reason
        imap4.IMAP4Client.connectionLost(self)
        self.factory.deferred.callback(self.factory.count)

    def sendLine(self, line):
        if debug: print "sendLine:", line
        imap4.IMAP4Client.sendLine(self, line)

    def lineReceived(self, line):
        if debug: print "lineReceived:", line
        imap4.IMAP4Client.lineReceived(self, line)

class MyIMAP4ClientFactory(protocol.ClientFactory):

    protocol = MyIMAP4Client

def getCount():
    f = MyIMAP4ClientFactory()
    f.deferred = defer.Deferred()
    reactor.connectTCP(server, 143, f)
    return f.deferred

if __name__ == "__main__":
    def printCount(count):
        print count
        reactor.stop()
    getCount().addCallback(printCount)
    reactor.run()
<<<

Any suggestions for improvements or corrections to my thinking would
be greatly appreciated!

Thanks.

-- Jason




More information about the Twisted-Python mailing list