[Twisted-Python] More IMAP4Client/Deferred fun

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:
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:
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:
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:
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
participants (1)
-
Jason Diamond