Pants is a network programming framework for Python. It is simple, fast and elegant. Pants provides the programmer with the basic tools they need to write responsive, high-throughput and highly-concurrent network applications. At its core, Pants consists of three things:
- Engines: efficient, asynchronous event loops.
- Channels: non-blocking wrappers around socket objects.
- Timers: non-blocking helpers for delayed execution of code.
All Pants applications share a similar architecture. Channels and timers are added to an engine. The engine runs an event loop and manages timer scheduling. As events are raised on sockets, the engine dispatches those events (read, write, close, etc.) to the relevant channel to be handled by user code. Timers are executed and possibly rescheduled by the engine as they expire. Writing a Pants application consists of defining your event-handling logic on custom channel classes, scheduling timers to be executed and starting the event loop. Pants makes writing efficient network applications simple through the use of elegant abstractions.
Pants is an asynchronous, callback-oriented framework. Being asynchronous, it is important that your code does not block the main process. Blocking code prevents Pants from efficiently polling for socket events, and has a significant, negative effect on performance. To eliminate the need for blocking code, Pants uses a callback-oriented design. Blocking operations like reading and writing data to a socket are performed in the background. When these operations complete, callback methods are invoked to notify user code. To get a better example of how Pants applications work, take a look at a few examples or read through the tutorial.
You can install Pants using your favourite Python package manager:
pip install pants
Or from source:
wget https://github.com/ecdavis/pants/tarball/pants-1.0.0-beta.3 tar xvfz pants-1.0.0-beta.3.tar.gz cd pants-1.0.0-beta.3 python setup.py install
Using the development version¶
Using the development version of Pants will give you access to the latest features as well as the latest bugs. If you’re interested in contributing code to Pants, this is the version you should work with. Otherwise, it’s suggested that you stick to a release version. You can clone the repository like so:
git clone git://github.com/ecdavis/pants
Many people also find it useful to add their repository directory to Python’s path, or to create a symbolic link from the repository directory to Python’s site-packages directory to allow them to import Pants in any Python script.
What follows is a simple tutorial designed to introduce you to the core parts of Pants’ API and demonstrate how to write simple Pants applications. This tutorial is by no means an exhaustive tour of Pants’ many features, but should serve as an excellent starting point for someone new to the framework or to asynchronous network programming in general.
Writing a simple server¶
We’re going to begin by writing an echo server. This is like the “Hello, World!” of networking frameworks, but it’s nonetheless a good place to start. Create a file containing the following code:
from pants import Engine, Server, Stream class Echo(Stream): def on_read(self, data): self.write(data) server = Server(ConnectionClass=Echo) server.listen(4040) Engine.instance().start()
Now run it and, in another terminal, connect to the server using telnet:
telnet localhost 4040
Try entering some data and you’ll find that it gets echoed right back to you. To get a better idea of what’s happening in this application, we’ll run through the code line by line:
class Echo(Stream): def on_read(self, data): self.write(data)
We begin by defining a class,
Echo, which subclasses Pants’
Stream class. Instances of
and its subclasses are what Pants calls ‘channels.’ They represent connections
from the local host to a remote host or vice-versa. Channels are basically just
socket objects that deal with all the
nitty-gritty, low-level stuff so that you don’t have to. You implement most of
your application’s logic by defining callback methods on your channel classes.
on_read is one such method. As the name suggests,
on_read will get
called any time data is read from the channel. The incoming data is passed to
the callback for use by your application. In this case, we’ve chosen to
immediately write it back to the channel, thereby implementing the echo
Having defined our application logic, we now need to get the server up and running:
server = Server(ConnectionClass=Echo) server.listen(4040) Engine.instance().start()
We create a new instance of Pants’
Server class and pass
Server instances are channels which represent sockets
that are listening for new connections to the local host. When a new connection
is made, the
Server will automatically wrap that
connection with an instance of its
ConnectionClass. In this case, new
connections will be wrapped with instances of our
After creating the server, we tell it to listen for new connections on port 4040 and then we start the global engine. All Pants applications have an engine at their core - it’s responsible for running a powerful event loop that listens for new events on sockets and dispatches those events to the appropriate channels.
We’ve only written 7 lines of code, but we’ve already covered a great deal of Pants’ core functionality. Before moving on, try messing around with the code a little bit and see what happens:
Kicking it up a notch¶
Now that we’ve covered the basics we can move on to something a little more interesting.
from pants import Engine, Server, Stream class BlockEcho(Stream): def on_connect(self): self.read_delimiter = 8 def on_read(self, block): self.write(block + '\r\n') server = Server(ConnectionClass=BlockEcho) server.listen(4040) Engine.instance().start()
on_read method is basically the same as before, we’ve just added a
newline to the end of the data before writing it. We’ve also added a new event
on_connect. As the name suggests, this gets called when the
channel’s connection is first established. In
on_connect we set the value
of the channel’s
read_delimiter attribute, and
this is where things get neat. The
changes the way Pants passes data to
on_read. Instead of being passed on as
soon as it arrives, data is internally buffered and passed to
blocks of 8 bytes. See what happens when you run this application and connect
to it as you did before. It’s a simple idea, but the
read_delimiter is one of Pants’ most powerful
read_delimiter isn’t limited to being a number
of bytes, either. Here are some experiments for you to try:
Taking it to another level¶
Up until now we’ve been using Pants’ regular
class and it’s suited our needs perfectly. There are times, however, where you
may need to define custom behaviour on your server channels. This is achieved
class EchoLineToAllServer(Server): ConnectionClass = EchoLineToAll def write_to_all(self, data): for channel in self.channels.itervalues(): if channel.connected: channel.write(data)
All very straight-forward. We defined a new method on the server that writes
data to all connected channels. We also overrode the default
ConnectionClass attribute, meaning that we’ll no longer need to pass in our
connection class to the constructor. Starting the server now looks like this:
For the sake of completeness, here’s the
EchoLineToAll connection class
used by the above server:
class EchoLineToAll(Stream): def on_connect(self): self.read_delimiter = '\r\n' self.server.write_to_all("Connected: %s\r\n" % self.remote_address) def on_read(self, line): self.server.write_to_all("%s: %s\r\n" % (self.remote_address, line)) def on_close(self): self.server.write_to_all("Disconnected: %s\r\n" % self.remote_address)
As you can see, channels retain a reference to the server that they belong to.
In this case, we’re also using the
property as a channel-specific identifier.
That’s it for the basic tutorial, but there’s plenty more you can do here:
- Experiment with different
read_delimitervalues to change the way connections process data. You might try implementing a packet-oriented protocol.
- Write a client for your server using Pants. You basically know how already, just take a look at
connect()and you’ll be good to go.
- We can’t have people communicating through unencrypted channels like this. Secure your chat server using Pants’ SSL support. Take a look at
startSSL()to get started.