Using Application

Motivation

What happens when you want to write a Twisted application? Well, if you come into Twisted thinking of it as a library, you may decide a useful way is to write a Python script. This Python script would probably take the following actions:

There are two problems with this methodology. It has a lot of boiler-plate code, and it introduces an inflexibility into the design. The usual way to solve this kind of problem is to write configuration files, and it is no different in Twisted.

At this point, the standard thing to do would be to write a long, tedious and subtly wrong manual describing the configuration language. Rest assured, like every other project, Twisted has those. But the language is secondary, and will be described later -- more important are the configuration objects.

Services, Persistence and Security, Oh My!

It is possible to think of any configuration language as a specially designed language to build a configuration object, which is then queried and acted upon by the program runtime. In Twisted, this is literally true internally -- and the master configuration object is twisted.application.service.Application. However, there is virtually nothing you can do with this object directly. This object is Componentized -- it has different, orthogonal, aspects. Access to this aspects is done by using interfaces. Interfaces, for our purposes, are just callables which return different aspects of the Application. Unlike other frameworks, like Qt or wxWindows, in Twisted you do not derive from Application -- you use methods to register your objects with it.

There are four interfaces supported, three of which are defined in twisted.application.service:

Constructing an application is done by calling it with a single argument -- its name. The name influences some things, among them the default file it will persist to (which is why it is mandatory).

from twisted.application import service
from twisted.persisted import sob
application = service.Application("myapplication")
s = service.IService(application)
sc = service.IServiceCollection(application)
proc = service.IProcess(application)
per = sob.IPersistable(application)

Services

There are two interfaces relevant to services -- IService and IServiceCollection. IService represents a state-aware container. That means the service is ready to be notified of application start-ups and shutdowns. Services can be named or unnamed. IServiceCollection holds other services. It is possible to get named services from it by name. All services can be gotten from it via either indexing or iteration.

Services can have a parent. Parents are set using setServiceParent. Services are detached from their parent with disownServiceParent. The parent must always be something that complies with the IServiceCollection interface.

Most services will inherit from Service. This class will set an attribute running to a true value in startService and to a false value in stopService. This attribute will always be false in just-unpersisted Services, without regards to its value at the time the Service was persisted.

MultiService implements both IService and IServiceCollection. It is used to keep the services in a hierarchy.

It is, of course, possible to write one's own services, but Twisted comes out of the box with several services which are useful in writing network applications. These are found in twisted.application.internet, including TCPServer, TCPClient, and TimerService.

To each reactor.listenFoo method corresponds a service named FooServer. The arguments to its constructor are the same as the arguments to the method. It calls the method on application start-up, and stops listening on application shut-down.

To each reactor.connectFoo methods corresponds a service named FooClient. The arguments to its constructor are the same as the arguments to the method. It calls the method on application start-up. It might, or might not, stop the connection on application shut-down. (This limitation will be removed at some point, and guaranteed disconnection will be implemented.)

The last service in twisted.application.internet is TimerService. The constructor takes a period, a callable and optionally arguments and keyword arguments. The service, when it is running, will make sure the callable will be called every time the period elapses.

String Ports

In Twisted, a ServerFactory does not care what kind of virtual reliable circuit it listens too -- SSL, UNIX domain sockets, TCP sockets or something else. However, the APIs for constructing the various *Server classes are different. When it is necessary for a less sophisticated user to direct construction of such a class, the twisted.application.strports module comes in handy. It contains a function service which accepts a description and a factory, and returns a service. The description is a string in a mini-language designed to specify ports. Full specifications are in the module docstrings.

Configuration

At some point, the objects for the configuration actually have to be constructed. The easiest and simplest way to do it is to use Python as the configuration mini-language. This format is called TAC and traditionally files with this format have the extension .tac.

TAC files need to be valid Python files, which construct a variable named application. This variable will be the configuration object. The full power of Python is available, of course.

Here's an example:

# Import modules
from twisted.application import service, internet
from twisted.protocols import wire
from twisted.internet import protocol

# Construct the application
application = service.Application("echo")

# Get the IServiceCollection interface
myService = service.IServiceCollection(application)

# Create the protocol factory
myFactory = protocol.ServerFactory()
myFactory.protocol = wire.Echo

# Create the (sole) server
# Normally, the echo protocol lives on port 7, but since that
# is a privileged port, for this example we'll use port 7001
myServer = internet.TCPServer(7001, myFactory)

# Tie the service to the application
myServer.setServiceParent(myService)

Note that setServiceParent will, in fact, automatically cast its argument to IServiceCollection. So, more succinctly, the above code can be written:

from twisted.application import service, internet
from twisted.protocols import wire
from twisted.internet import protocol
application = service.Application("echo")
myFactory = protocol.ServerFactory()
myFactory.protocol = wire.Echo
internet.TCPServer(7001, myFactory).setServiceParent(application)

TAC files are run with twistd -y or twistd --python. The twistd manpage has more information, but a common way to run is twistd -noy echo.tac. This tells twistd to not daemonize and not to try and save application on shutdown.