Twisted

Event-driven networking engine in Python licensed under MIT license.

Why should you care?

History

Protocol implementations

Foreign Loop support

Main parts

flow / object lifetime - TCP Server

  1. Transport (with a Factory) listens on TCP socket
  2. When client connects, factory creates the Protocol object
  3. Transport delivers data into the Protocol
  4. Protocol handles the data
  5. When connection is terminated, protocol object is destroyed

Transport & protocol separation

Reactor Loop

Reactor Loop diagram

Reactor Loop

class NaiveReactor:
    def run(self):
        while self._run:
          for t in self.transports:
            t.read()
            sleep(0.001)

class NaiveTransport:
  def read(self):
    try:
      xs = read(self.fd, 512):
    except IOError:
        if e.errno != 11:
            self.protocol.connectionLost()
    else:
      self.protocol.dataReceived(xs)

IRC protocol example

class IRCClient(basic.LineReceiver):
    def userJoined(self, user, channel):
        """Called when I see another user joining a channel.
        """
        pass

    def topicUpdated(self, user, channel, newTopic):
        """In channel, user changed the topic to newTopic.

        Also called when first joining a channel.
        """
        pass

IRC protocol example

class YourIRCBot(IRCClient):
    def userJoined(self, user, channel):
      self.transport.write("Hello " + user)

class BotFactory(t.i.p.ReconnectingClientFactory):
  protol = YourIRCBot

reactor.connectTCP("irc.freenode.net", 6667, BotFactory())
reactor.run()
  

Testability

class Protocol:
  def dataReceived(self, data):
    pass

  def makeConnection(self, transport):
    self.transport = transport
def test_irc_hello():
    p = YourIRCBot()
    p.makeConnection(StringIO())
    p.lineReceived(":bob JOIN #foo")
    ok_(p.transport.getvalue(), "PRIVMSG #foo Hello bob")

Generating events - Deferred

def errback(reason):
    print "Oh noes: {0}".format(reason)
    sys.exit(1)

d = twisted.web.client.Agent().request('GET', url, Headers())
# Deferred returned instad of result
d.addCallback(lambda response: r.deliverBody(YourProtocol()))
# callback to execute once result arrives
d.addErrback(errback)
# or errback in case of failure
d.addBoth(lambda _: reactor.stop())
# and another callback regardless of success / fail

reactor.run()

Deferred callback chains

deferred proccess

Caveats

Database access

Caveats

Unit Testing - Trial

If you touch reactor in your test cases, errors can manifest in later testcases or hang forever

Caveats

Twisted as a platform

Footnotes

/

#