User Guide¶
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.
Overview¶
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.
Getting Started¶
Getting started with Pants is easy. Pants can be installed either from the Python Package Index or from source. Pants requires Python version 2.6 or 2.7.
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.
Tutorial¶
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 Stream
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
wrappers around 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
protocol.
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
it our Stream
subclass, Echo
.
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 Echo
class.
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()
The 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
handler method: 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 read_delimiter
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 on_read
in
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
features.
The read_delimiter
isn’t limited to being a number
of bytes, either. Here are some experiments for you to try:
- Set the
read_delimiter
to a short string.- Set the
read_delimiter
to a compiled regex object.- Set the
read_delimiter
toNone
.
Taking it to another level¶
Up until now we’ve been using Pants’ regular Server
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
by subclassing Server
:
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:
EchoLineToAllServer().listen(4040)
Engine.instance().start()
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[0])
def on_read(self, line):
self.server.write_to_all("%s: %s\r\n" % (self.remote_address[0], line))
def on_close(self):
self.server.write_to_all("Disconnected: %s\r\n" % self.remote_address[0])
As you can see, channels retain a reference to the server that they belong to.
In this case, we’re also using the remote_address
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_delimiter
values 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.