diff --git a/docs/historic/2002/ipc10/twisted-network-framework/errata.html b/docs/historic/2002/ipc10/twisted-network-framework/errata.html deleted file mode 100644 index 8388919e4bc..00000000000 --- a/docs/historic/2002/ipc10/twisted-network-framework/errata.html +++ /dev/null @@ -1,256 +0,0 @@ - - - - -
-Note: This document is relevant for the - version of Twisted that was current at IPC10. It has since been - superseded by many changes to the Python API. It is remaining - unchanged for historical reasons, but please refer to - documentation for the specific system you are looking for and - not these papers for current information.
- -Twisted has undergone several major revisions since Moshe - Zadka and I wrote the "The Twisted - Network Framework". Most of these changes have not deviated - from the central vision of the framework, but almost all of the - code listings have been re-visited and enhanced in some - way.
- -So, while the paper was correct at the time that it was - originally written, a few things have changed which have - invalidated portions of it.
- -Most significant is the fact that almost all methods which
- pass callbacks of some kind have been changed to take no
- callback or error-callback arguments, and instead return an
- instance of a twisted.python.defer.Deferred
. This means
- that an asynchronous function can be easily identified visually
- because it will be of the form: async_obj.asyncMethod("foo").addCallbacks(succeded,
- failed)
. There is also a utility method addCallback
which makes it more
- convenient to pass additional arguments to a callback function
- and omit special-case error handling.
While it is still backwards compatible, twisted.internet.passport
has been re-named
- to twisted.cred
, and the various
- classes in it have been split out into submodules of that
- package, and the various remote-object superclasses have been
- moved out of twisted.spread.pb and put into
- twisted.spread.flavors.
Application.listenOn
has been
- replaced with the more descripively named Application.listenTCP
, Application.listenUDP
, and Application.listenSSL
.
twisted.web.widgets
has progressed
- quite far since the paper was written! One description
- specifically given in the paper is no longer correct:
- The namespace for evaluating the template expressions is - obtained by scanning the class hierarchy for attributes, and - getting each of those attributes from the current instance. - This means that all methods will be bound methods, so - indicating "self" explicitly is not required. While it is - possible to override the method for creating namespaces, - using this default has the effect of associating all - presentation code for a particular widget in one class, along - with its template. If one is working with a non-programmer - designer, and the template is in an external file, it is - always very clear to the designer what functionality is - available to them in any given scope, because there is a list - of available methods for any given class. --
This is still possible to avoid breakages in old code, but
-after some experimentation, it became clear that simply passing
- self
was an easier method for
- creating the namespace, both for designers and programmers.
In addition, since the advent of Zope3, interoperability
- with Zope has become increasingly interesting possibility for
- the Twisted development team, since it would be desirable if
- Twisted could use their excellent strategy for
- content-management, while still maintaining Twisted's
- advantages in the arena of multi-protocol servers. Of
- particular interest has been Zope Presentation Templates, since
- they seem to be a truly robust solution for keeping design
- discrete from code, compatible with the event-based method in
- which twisted.web.widgets processes web requests. twisted.web.widgets.ZopePresentationTemplate
- may be opening soon in a theatre near you!
The following code examples are corrected or modernized - versions of the ones that appear in the paper.
- -- Listing 9: A remotely accessible object and accompanying call - -- --# Server Side -class MyObject(pb.Referenceable): - def remote_doIt(self): - return "did it" - -# Client Side - ... - def myCallback(result): - print result # result will be 'did it' - def myErrback(stacktrace): - print 'oh no, mr. bill!' - print stacktrace - myRemoteReference.doIt().addCallbacks(myCallback, - myErrback) --
- Listing 10: An object responding to its calling perspective -- - --# Server Side -class Greeter(pb.Viewable): - def view_greet(self, actor): - return "Hello %s!\n" % actor.perspectiveName - -# Client Side - ... - remoteGreeter.greet().addCallback(sys.stdout.write) - ... --
- Listing 12: A client for Echoer objects. -- --from twisted.spread import pb -from twisted.internet import main -def gotObject(object): - print "got object:",object - object.echo("hello network".addCallback(gotEcho) -def gotEcho(echo): - print 'server echoed:',echo - main.shutDown() -def gotNoObject(reason): - print "no object:",reason - main.shutDown() -pb.getObjectAt("localhost", 8789, gotObject, gotNoObject, 30) -main.run() --
- Listing 13: A PB server using twisted's "passport" - authentication. -- --from twisted.spread import pb -from twisted.internet import main -class SimplePerspective(pb.Perspective): - def perspective_echo(self, text): - print 'echoing',text - return text -class SimpleService(pb.Service): - def getPerspectiveNamed(self, name): - return SimplePerspective(name, self) -if __name__ == '__main__': - import pbecho - app = main.Application("pbecho") - pbecho.SimpleService("pbecho",app).getPerspectiveNamed("guest").makeIdentity("guest") - app.listenTCP(pb.portno, pb.BrokerFactory(pb.AuthRoot(app))) - app.save("start") --
- Listing 14: Connecting to an Authorized Service -- --from twisted.spread import pb -from twisted.internet import main -def success(message): - print "Message received:",message - main.shutDown() -def failure(error): - print "Failure...",error - main.shutDown() -def connected(perspective): - perspective.echo("hello world").addCallbacks(success, failure) - print "connected." - -pb.connect("localhost", pb.portno, "guest", "guest", - "pbecho", "guest", 30).addCallbacks(connected, - failure) -main.run() --
- Listing 15: A Twisted GUI application -- --from twisted.internet import main, ingtkernet -from twisted.spread.ui import gtkutil -import gtk -ingtkernet.install() -class EchoClient: - def __init__(self, echoer): - l.hide() - self.echoer = echoer - w = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL) - vb = gtk.GtkVBox(); b = gtk.GtkButton("Echo:") - self.entry = gtk.GtkEntry(); self.outry = gtk.GtkEntry() - w.add(vb) - map(vb.add, [b, self.entry, self.outry]) - b.connect('clicked', self.clicked) - w.connect('destroy', gtk.mainquit) - w.show_all() - def clicked(self, b): - txt = self.entry.get_text() - self.entry.set_text("") - self.echoer.echo(txt).addCallback(self.outry.set_text) -l = gtkutil.Login(EchoClient, None, initialService="pbecho") -l.show_all() -gtk.mainloop() --
- Listing 16: an event-based web widget. -- - - diff --git a/docs/historic/2002/ipc10/twisted-network-framework/index.html b/docs/historic/2002/ipc10/twisted-network-framework/index.html deleted file mode 100644 index 15368047565..00000000000 --- a/docs/historic/2002/ipc10/twisted-network-framework/index.html +++ /dev/null @@ -1,1568 +0,0 @@ - - - - - --from twisted.spread import pb -from twisted.python import defer -from twisted.web import widgets -class EchoDisplay(widgets.Gadget, widgets.Presentation): - template = """<H1>Welcome to my widget, displaying %%%%echotext%%%%.</h1> - <p>Here it is: %%%%getEchoPerspective()%%%%</p>""" - echotext = 'hello web!' - def getEchoPerspective(self): - return ['<b>', - pb.connect("localhost", pb.portno, - "guest", "guest", "pbecho", "guest", 1). - addCallbacks(self.makeListOf, self.formatTraceback) - ,'</b>'] - def makeListOf(self, echoer): - return [echoer.echo(self.echotext).addCallback(lambda x: [x])] -if __name__ == "__main__": - from twisted.web import server - from twisted.internet import main - a = main.Application("pbweb") - a.listenTCP(8080, server.Site(EchoDisplay())) - a.run() --
Note: This document is relevant for the - version of Twisted that were current previous to IPC10. Even at the time of - its release, there were errata - issued to make it current. It is remaining unaltered for - historical purposes but it is no longer accurate.
- -Twisted is a framework for writing asynchronous,
- event-driven networked programs in Python -- both clients and
- servers. In addition to abstractions for low-level system calls
- like select(2)
and socket(2)
, it also
- includes a large number of utility functions and classes, which
- make writing new servers easy. Twisted includes support for
- popular network protocols like HTTP and SMTP, support for GUI
- frameworks like GTK+
/GNOME
and
- Tk
and many other classes designed to make network
- programs easy. Whenever possible, Twisted uses Python's
- introspection facilities to save the client programmer as much
- work as possible. Even though Twisted is still work in
- progress, it is already usable for production systems -- it can
- be used to bring up a Web server, a mail server or an IRC
- server in a matter of minutes, and require almost no
- configuration.
Keywords: internet, network, framework, - event-based, asynchronous
- -Python lends itself to writing frameworks. Python has a - simple class model, which facilitates inheritance. It has - dynamic typing, which means code needs to assume less. Python - also has built-in memory management, which means application - code does not need to track ownership. Thus, when writing a new - application, a programmer often finds himself writing a - framework to make writing this kind of application easier. - Twisted evolved from the need to write high-performance - interoperable servers in Python, and making them easy to use - (and difficult to use incorrectly).
- -There are three ways to write network programs:
- -When dealing with many connections in one thread, the - scheduling is the responsibility of the application, not the - operating system, and is usually implemented by calling a - registered function when each connection is ready to for - reading or writing -- commonly known as event-driven, or - callback-based, programming.
- -Since multi-threaded programming is often tricky, even with
- high level abstractions, and since forking Python processes has
- many disadvantages, like Python's reference counting not
- playing well with copy-on-write and problems with shared state,
- it was felt the best option was an event-driven framework. A
- benefit of such approach is that by letting other event-driven
- frameworks take over the main loop, server and client code are
- essentially the same - making peer-to-peer a reality. While
- Twisted includes its own event loop, Twisted can already
- interoperate with GTK+
's and Tk
's
- mainloops, as well as provide an emulation of event-based I/O
- for Jython (specific support for the Swing toolkit is planned).
- Client code is never aware of the loop it is running under, as
- long as it is using Twisted's interface for registering for
- interesting events.
Some examples of programs which were written using the
- Twisted framework are twisted.web
(a web server),
- twisted.mail
(a mail server, supporting both SMTP
- and POP3, as well as relaying), twisted.words
(a
- chat application supporting integration between a variety of IM
- protocols, like IRC, AOL Instant Messenger's TOC and
- Perspective Broker, a remote-object protocol native to
- Twisted), im
(an instant messenger which connects
- to twisted.words) and faucet
(a GUI client for the
- twisted.reality
interactive-fiction framework).
- Twisted can be useful for any network or GUI application
- written in Python.
However, event-driven programming still contains some tricky - aspects. As each callback must be finished as soon as possible, - it is not possible to keep persistent state in function-local - variables. In addition, some programming techniques, such as - recursion, are impossible to use. Event-driven programming has - a reputation of being hard to use due to the frequent need to - write state machines. Twisted was built with the assumption - that with the right library, event-driven programming is easier - then multi-threaded programming. Twisted aims to be that - library.
- -Twisted includes both high-level and low-level support for - protocols. Most protocol implementation by twisted are in a - package which tries to implement "mechanisms, not policy". On - top of those implementations, Twisted includes usable - implementations of those protocols: for example, connecting the - abstract HTTP protocol handler to a concrete resource-tree, or - connecting the abstract mail protocol handler to deliver mail - to maildirs according to domains. Twisted tries to come with as - much functionality as possible out of the box, while not - constraining a programmer to a choice between using a - possibly-inappropriate class and rewriting the non-interesting - parts himself.
- -Twisted also includes Perspective Broker, a simple - remote-object framework, which allows Twisted servers to be - divided into separate processes as the end deployer (rather - then the original programmer) finds most convenient. This - allows, for example, Twisted web servers to pass requests for - specific URLs with co-operating servers so permissions are - granted according to the need of the specific application, - instead of being forced into giving all the applications all - permissions. The co-operation is truly symmetrical, although - typical deployments (such as the one which the Twisted web site - itself uses) use a master/slave relationship.
- -Twisted is not alone in the niche of a Python network - framework. One of the better known frameworks is Medusa. Medusa - is used, among other things, as Zope's native server serving - HTTP, FTP and other protocols. However, Medusa is no longer - under active development, and the Twisted development team had - a number of goals which would necessitate a rewrite of large - portions of Medusa. Twisted seperates protocols from the - underlying transport layer. This seperation has the advantages - of resuability (for example, using the same clients and servers - over SSL) and testability (because it is easy to test the - protocol with a much lighter test harness) among others. - Twisted also has a very flexible main-loop which can - interoperate with third-party main-loops, making it usable in - GUI programs too.
- -Python comes out of the box with "batteries included".
- However, it seems that many Python projects rewrite some basic
- parts: logging to files, parsing options and high level
- interfaces to reflection. When the Twisted project found itself
- rewriting those, it moved them into a separate subpackage,
- which does not depend on the rest of the twisted framework.
- Hopefully, people will use twisted.python
more and
- solve interesting problems instead. Indeed, it is one of
- Twisted's goals to serve as a repository for useful Python
- code.
One useful module is twisted.python.reflect
,
- which has methods like prefixedMethods
, which
- returns all methods with a specific prefix. Even though some
- modules in Python itself implement such functionality (notably,
- urllib2
), they do not expose it as a function
- usable by outside code. Another useful module is
- twisted.python.hook
, which can add pre-hooks and
- post-hooks to methods in classes.
-- --# Add all method names beginning with opt_ to the given -# dictionary. This cannot be done with dir(), since -# it does not search in superclasses -dct = {} -reflect.addMethodNamesToDict(self.__class__, dct, "opt_") - -# Sum up all lists, in the given class and superclasses, -# which have a given name. This gives us "different class -# semantics": attributes do not override, but rather append -flags = [] -reflect.accumulateClassList(self.__class__, 'optFlags', flags) - -# Add lock-acquire and lock-release to all methods which -# are not multi-thread safe -for methodName in klass.synchronized: - hook.addPre(klass, methodName, _synchPre) - hook.addPost(klass, methodName, _synchPost) - -- -Listing 1: Using
-twisted.python.reflect
and -twisted.python.hook
The twisted.python
subpackage also contains a
- high-level interface to getopt which supplies as much power as
- plain getopt while avoiding long
- if
/elif
chains and making many common
- cases easier to use. It uses the reflection interfaces in
- twisted.python.reflect
to find which options the
- class is interested in, and constructs the argument to
- getopt
. Since in the common case options' values
- are just saved in instance attributes, it is very easy to
- indicate interest in such options. However, for the cases
- custom code needs to be run for an option (for example,
- counting how many -v
options were given to
- indicate verbosity level), it will call a method which is named
- correctly.
-- --class ServerOptions(usage.Options): - # Those are (short and long) options which - # have no argument. The corresponding attribute - # will be true iff this option was given - optFlags = [['nodaemon','n'], - ['profile','p'], - ['threaded','t'], - ['quiet','q'], - ['no_save','o']] - # This are options which require an argument - # The default is used if no such option was given - # Note: since options can only have string arguments, - # putting a non-string here is a reliable way to detect - # whether the option was given - optStrings = [['logfile','l',None], - ['file','f','twistd.tap'], - ['python','y',''], - ['pidfile','','twistd.pid'], - ['rundir','d','.']] - - # For methods which can be called multiple times - # or have other unusual semantics, a method will be called - # Twisted assumes that the option needs an argument if and only if - # the method is defined to accept an argument. - def opt_plugin(self, pkgname): - pkg = __import__(pkgname) - self.python = os.path.join(os.path.dirname( - os.path.abspath(pkg.__file__)), 'config.tac') - - # Most long options based on methods are aliased to short - # options. If there is only one letter, Twisted knows it is a short - # option, so it is "-g", not "--g" - opt_g = opt_plugin - -try: - config = ServerOptions() - config.parseOptions() -except usage.error, ue: - print "%s: %s" % (sys.argv[0], ue) - sys.exit(1) -- -Listing 2:
-twistd
's Usage Code
Unlike getopt
, Twisted has a useful abstraction
- for the non-option arguments: they are passed as arguments to
- the parsedArgs
method. This means too many
- arguments, or too few, will cause a usage error, which will be
- flagged. If an unknown number of arguments is desired,
- explicitly using a tuple catch-all argument will work.
The formats of configuration files have shown two visible - trends over the years. On the one hand, more and more - programmability has been added, until sometimes they become a - new language. The extreme end of this trend is using a regular - programming language, such as Python, as the configuration - language. On the other hand, some configuration files became - more and more machine editable, until they become a miniature - database formates. The extreme end of that trend is using a - generic database tool.
- -Both trends stem from the same rationale -- the need to use - a powerful general purpose tool instead of hacking domain - specific languages. Domain specific languages are usually - ad-hoc and not well designed, having neither the power of - general purpose languages nor the predictable machine editable - format of generic databases.
- -Twisted combines these two trends. It can read the - configuration either from a Python file, or from a pickled - file. To some degree, it integrates the approaches by - auto-pickling state on shutdown, so the configuration files can - migrate from Python into pickles. Currently, there is no way to - go back from pickles to equivalent Python source, although it - is planned for the future. As a proof of concept, the RPG - framework Twisted Reality already has facilities for creating - Python source which evaluates into a given Python object.
- --- --from twisted.internet import main -from twisted.web import proxy, server -site = server.Site(proxy.ReverseProxyResource('www.yahoo.com', 80, '/')) -application = main.Application('web-proxy') -application.listenOn(8080, site) -- -Listing 3: The configuration file for a reverse web - proxy
-
Twisted's main program, twistd
, can receive
- either a pickled twisted.internet.main.Application
- or a Python file which defines a variable called
- application
. The application can be saved at any
- time by calling its save
method, which can take an
- optional argument to save to a different file name. It would be
- fairly easy, for example, to have a Twisted server which saves
- the application every few seconds to a file whose name depends
- on the time. Usually, however, one settles for the default
- behavior which saves to a shutdown
file. Then, if
- the shutdown configuration proves suitable, the regular pickle
- is replaced by the shutdown file. Hence, on the fly
- configuration changes, regardless of complexity, can always
- persist.
There are several client/server protocols which let a
- suitably privileged user to access to application variable and
- change it on the fly. The first, and least common denominator,
- is telnet. The administrator can telnet into twisted, and issue
- Python statements to her heart's content. For example, one can
- add ports to listen on to the application, reconfigure the web
- servers and various other ways by simple accessing
- __main__.application
. Some proof of concepts for a
- simple suite of command-line utilities to control a Twisted
- application were written, including commands which allow an
- administrator to shut down the server or save the current state
- to a tap file. These are especially useful on Microsoft
- Windows(tm) platforms, where the normal UNIX way of
- communicating shutdown requests via signals are less
- reliable.
If reconfiguration on the fly is not necessary, Python
- itself can be used as the configuration editor. Loading the
- application is as simple as unpickling it, and saving it is
- done by calling its save
method. It is quite easy
- to add more services or change existing ones from the Python
- interactive mode.
A more sophisticated way to reconfigure the application on
- the fly is via the manhole service. Manhole is a client/server
- protocol based on top of Perspective Broker, Twisted's
- translucent remote-object protocol which will be covered later.
- Manhole has a graphical client called gtkmanhole
- which can access the server and change its state. Since Twisted
- is modular, it is possible to write more services for user
- friendly configuration. For example, through-the-web
- configuration is planned for several services, notably
- mail.
For cases where a third party wants to distribute both the
- code for a server and a ready to run configuration file, there
- is the plugin configuration. Philosophically similar to the
- --python
option to twistd
, it
- simplifies the distribution process. A plugin is an archive
- which is ready to be unpacked into the Python module path. In
- order to keep a clean tree, twistd
extends the
- module path with some Twisted-specific paths, like the
- directory TwistedPlugins
in the user's home
- directory. When a plugin is unpacked, it should be a Python
- package which includes, alongside __init__.py
a
- file named config.tac
. This file should define a
- variable named application
, in a similar way to
- files loaded with --python
. The plugin way of
- distributing configurations is meant to reduce the temptation
- to put large amount of codes inside the configuration file
- itself.
Putting class and function definition inside the - configuration files would make the persistent servers which are - auto-generated on shutdown useless, since they would not have - access to the classes and functions defined inside the - configuration file. Thus, the plugin method is intended so - classes and functions can still be in regular, importable, - Python modules, but still allow third parties distribute - powerful configurations. Plugins are used by some of the - Twisted Reality virtual worlds.
- -Port
is the Twisted class which represents a
- socket listening on a port. Currently, twisted supports both
- internet and unix-domain sockets, and there are SSL classes
- with identical interface. A Port
is only
- responsible for handling the transfer layer. It calls
- accept
on the socket, checks that it actually
- wants to deal with the connection and asks its factory for a
- protocol. The factory is usually a subclass of
- twisted.protocols.protocol.Factory
, and its most
- important method is buildProtocol
. This should
- return something that adheres to the protocol interface, and is
- usually a subclass of
- twisted.protocols.protocol.Protocol
.
-- --from twisted.protocols import protocol -from twisted.internet import main, tcp - -class Echo(protocol.Protocol): - def dataReceived(self, data): - self.transport.write(data) - -factory = protocol.Factory() -factory.protocol = Echo -port = tcp.Port(8000, factory) -app = main.Application("echo") -app.addPort(port) -app.run() -- -Listing 4: A Simple Twisted Application
-
The factory is responsible for two tasks: creating new - protocols, and keeping global configuration and state. Since - the factory builds the new protocols, it usually makes sure the - protocols have a reference to it. This allows protocols to - access, and change, the configuration. Keeping state - information in the factory is the primary reason for keeping an - abstraction layer between ports and protocols. Examples of - configuration information is the root directory of a web server - or the user database of a telnet server. Note that it is - possible to use the same factory in two different Ports. This - can be used to run the same server bound to several different - addresses but not to all of them, or to run the same server on - a TCP socket and a UNIX domain sockets.
- -A protocol begins and ends its life with
- connectionMade
and connectionLost
;
- both are called with no arguments. connectionMade
- is called when a connection is first established. By then, the
- protocol has a transport
attribute. The
- transport
attribute is a Transport
-
- it supports write
and loseConnection
.
- Both these methods never block: write
actually
- buffers data which will be written only when the transport is
- signalled ready to for writing, and loseConnection
- marks the transport for closing as soon as there is no buffered
- data. Note that transports do not have a
- read
method: data arrives when it arrives, and the
- protocol must be ready for its dataReceived
- method, or its connectionLost
method, to be
- called. The transport also supports a getPeer
- method, which returns parameters about the other side of the
- transport. For TCP sockets, this includes the remote IP and
- port.
-- --# A tcp port-forwarder -# A StupidProtocol sends all data it gets to its peer. -# A StupidProtocolServer connects to the host/port, -# and initializes the client connection to be its peer -# and itself to be the client's peer -from twisted.protocols import protocol - -class StupidProtocol(protocol.Protocol): - def connectionLost(self): self.peer.loseConnection();del self.peer - def dataReceived(self, data): self.peer.write(data) - -class StupidProtocolServer(StupidProtocol): - def connectionMade(self): - clientProtocol = StupidProtocol() - clientProtocol.peer = self.transport - self.peer = tcp.Client(self.factory.host, self.factory.port, - clientProtocol) - -# Create a factory which creates StupidProtocolServers, and -# has the configuration information they assume -def makeStupidFactory(host, port): - factory = protocol.Factory() - factory.host, factory.port = host, port - factory.protocol = StupidProtocolServer - return factory -- -Listing 5: TCP forwarder code
-
While Twisted has the ability to let other event loops take
- over for integration with GUI toolkits, it usually uses its own
- event loop. The event loop code uses global variables to
- maintain interested readers and writers, and uses Python's
- select()
function, which can accept any object
- which has a fileno()
method, not only raw file
- descriptors. Objects can use the event loop interface to
- indicate interest in either reading to or writing from a given
- file descriptor. In addition, for those cases where time-based
- events are needed (for example, queue flushing or periodic POP3
- downloads), Twisted has a mechanism for repeating events at
- known delays. While far from being real-time, this is enough
- for most programs' needs.
Unfortunately, handling arbitrary data chunks is a hard way
- to code a server. This is why twisted has many classes sitting
- in submodules of the twisted.protocols package which give
- higher level interface to the data. For line oriented
- protocols, LineReceiver
translates the low-level
- dataReceived
events into lineReceived
- events. However, the first naive implementation of
- LineReceiver
proved to be too simple. Protocols
- like HTTP/1.1 or Freenet have packets which begin with header
- lines that include length information, and then byte streams.
- LineReceiver
was rewritten to have a simple
- interface for switching at the protocol layer between
- line-oriented parts and byte-stream parts.
Another format which is gathering popularity is Dan J.
- Bernstein's netstring format. This format keeps ASCII text as
- ASCII, but allows arbitrary bytes (including nulls and
- newlines) to be passed freely. However, netstrings were never
- designed to be used in event-based protocols where over-reading
- is unavoidable. Twisted makes sure no user will have to deal
- with the subtle problems handling netstrings in event-driven
- programs by providing NetstringReceiver
.
For even higher levels, there are the protocol-specific - protocol classes. These translate low-level chunks into - high-level events such as "HTTP request received" (for web - servers), "approve destination address" (for mail servers) or - "get user information" (for finger servers). Many RFCs have - been thus implemented for Twisted (at latest count, more then - 12 RFCs have been implemented). One of Twisted's goals is to be - a repository of event-driven implementations for various - protocols in Python.
- --- --class DomainSMTP(SMTP): - - def validateTo(self, helo, destination): - try: - user, domain = string.split(destination, '@', 1) - except ValueError: - return 0 - if domain not in self.factory.domains: - return 0 - if not self.factory.domains[domain].exists(user, domain, self): - return 0 - return 1 - - def handleMessage(self, helo, origin, recipients, message): - # No need to check for existence -- only recipients which - # we approved at the validateTo stage are passed here - for recipient in recipients: - user, domain = string.split(recipient, '@', 1) - self.factory.domains[domain].saveMessage(origin, user, message, - domain) -- -Listing 6: Implementation of virtual domains using the - SMTP protocol class
-
Copious documentation on writing new protocol abstraction - exists, since this is the largest amount of code written -- - much like most operating system code is device drivers. Since - many different protocols have already been implemented, there - are also plenty of examples to draw on. Usually implementing - the client-side of a protocol is particularly challenging, - since protocol designers tend to assume much more state kept on - the client side of a connection then on the server side.
- -twisted.tap
Package and
- mktap
Since one of Twisted's configuration formats are pickles,
- which are tricky to edit by hand, Twisted evolved a framework
- for creating such pickles. This framework is contained in the
- twisted.tap
package and the mktap
- script. New servers, or new ways to configure existing servers,
- can easily participate in the twisted.tap framework by creating
- a twisted.tap
submodule.
All twisted.tap
submodules must conform to a
- rigid interface. The interface defines functions to accept the
- command line parameters, and functions to take the processed
- command line parameters and add servers to
- twisted.main.internet.Application
. Existing
- twisted.tap
submodules use
- twisted.python.usage
, so the command line format
- is consistent between different modules.
The mktap
utility gets some generic options,
- and then the name of the server to build. It imports a
- same-named twisted.tap
submodule, and lets it
- process the rest of the options and parameters. This makes sure
- that the process configuring the main.Application
- is agnostic for where it is used. This allowed
- mktap
to grow the --append
option,
- which appends to an existing pickle rather then creating a new
- one. This option is frequently used to post-add a telnet server
- to an application, for net-based on the fly configuration
- later.
When running mktap
under UNIX, it saves the
- user id and group id inside the tap. Then, when feeding this
- tap into twistd
, it changes to this user/group id
- after binding the ports. Such a feature is necessary in any
- production-grade server, since ports below 1024 require root
- privileges to use on UNIX -- but applications should not run as
- root. In case changing to the specified user causes difficulty
- in the build environment, it is also possible to give those
- arguments to mktap
explicitly.
-- --from twisted.internet import tcp, stupidproxy -from twisted.python import usage - -usage_message = """ -usage: mktap stupid [OPTIONS] - -Options are as follows: - --port <#>, -p: set the port number to <#>. - --host <host>, -h: set the host to <host> - --dest_port <#>, -d: set the destination port to <#> -""" - -class Options(usage.Options): - optStrings = [["port", "p", 6666], - ["host", "h", "localhost"], - ["dest_port", "d", 6665]] - -def getPorts(app, config): - s = stupidproxy.makeStupidFactory(config.host, int(config.dest_port)) - return [(int(config.port), s)] -- -Listing 7:
-twisted.tap.stupid
The twisted.tap
framework is one of the reasons
- servers can be set up with little knowledge and time. Simply
- running mktap
with arguments can bring up a web
- server, a mail server or an integrated chat server -- with
- hardly any need for maintainance. As a working
- proof-on-concept, the tap2deb
utility exists to
- wrap up tap files in Debian packages, which include scripts for
- running and stopping the server and interact with
- init(8)
to make sure servers are automatically run
- on start-up. Such programs can also be written to interface
- with the Red Hat Package Manager or the FreeBSD package
- management systems.
-- --% mktap --uid 33 --gid 33 web --static /var/www --port 80 -% tap2deb -t web.tap -m 'Moshe Zadka <moshez@debian.org>' -% su -password: -# dpkg -i .build/twisted-web_1.0_all.deb -- -Listing 8: Bringing up a web server on a Debian - system
-
Sometimes, threads are unavoidable or hard to avoid. Many
- legacy programs which use threads want to use Twisted, and some
- vendor APIs have no non-blocking version -- for example, most
- database systems' API. Twisted can work with threads, although
- it supports only one thread in which the main select loop is
- running. It can use other threads to simulate non-blocking API
- over a blocking API -- it spawns a thread to call the blocking
- API, and when it returns, the thread calls a callback in the
- main thread. Threads can call callbacks in the main thread
- safely by adding those callbacks to a list of pending events.
- When the main thread is between select calls, it searches
- through the list of pending events, and executes them. This is
- used in the twisted.enterprise
package to supply
- an event driven interfaces to databases, which uses Python's DB
- API.
Twisted tries to optimize for the common case -- no threads.
- If there is need for threads, a special call must be made to
- inform the twisted.python.threadable
module that
- threads will be used. This module is implemented differently
- depending on whether threads will be used or not. The decision
- must be made before importing any modules which use threadable,
- and so is usually done in the main application. For example,
- twistd
has a command line option to initialize
- threads.
Twisted also supplies a module which supports a threadpool, - so the common task of implementing non-blocking APIs above - blocking APIs will be both easy and efficient. Threads are kept - in a pool, and dispatch requests are done by threads which are - not working. The pool supports a maximum amount of threads, and - will throw exceptions when there are more requests than - allowable threads.
- -One of the difficulties about multi-threaded systems is
- using locks to avoid race conditions. Twisted uses a mechanism
- similar to Java's synchronized methods. A class can declare a
- list of methods which cannot safely be called at the same time
- from two different threads. A function in threadable then uses
- twisted.python.hook
to transparently add
- lock/unlock around these methods. This allows Twisted classes
- to be written without thought about threading, except for one
- localized declaration which does not entail any performance
- penalty for the single-threaded case.
Mail servers have a history of security flaws. Sendmail is - by now the poster boy of security holes, but no mail servers, - bar maybe qmail, are free of them. Like Dan Bernstein of qmail - fame said, mail cannot be simply turned off -- even the - simplest organization needs a mail server. Since Twisted is - written in a high-level language, many problems which plague - other mail servers, notably buffer overflows, simply do not - exist. Other holes are avoidable with correct design. Twisted - Mail is a project trying to see if it is possible to write a - high quality high performance mail server entirely in - Python.
- -Twisted Mail is built on the SMTP server and client protocol - classes. While these present a level of abstraction from the - specific SMTP line semantics, they do not contain any message - storage code. The SMTP server class does know how to divide - responsibility between domains. When a message arrives, it - analyzes the recipient's address, tries matching it with one of - the registered domain, and then passes validation of the - address and saving the message to the correct domain, or - refuses to handle the message if it cannot handle the domain. - It is possible to specify a catch-all domain, which will - usually be responsible for relaying mails outwards.
- -While correct relaying is planned for the future, at the - moment we have only so-called "smarthost" relaying. All e-mail - not recognized by a local domain is relayed to a single outside - upstream server, which is supposed to relay the mail further. - This is the configuration for most home machines, which are - Twisted Mail's current target audience.
- -Since the people involved in Twisted's development were - reluctant to run code that runs as a super user, or with any - special privileges, it had to be considered how delivery of - mail to users is possible. The solution decided upon was to - have Twisted deliver to its own directory, which should have - very strict permissions, and have users pull the mail using - some remote mail access protocol like POP3. This means only a - user would write to his own mail box, so no security holes in - Twisted would be able to adversely affect a user.
- -Future plans are to use a Perspective Broker-based service - to hand mail to users to a personal server using a UNIX domain - socket, as well as to add some more conventional delivery - methods, as scary as they may be.
- -Because the default configuration of Twisted Mail is to be - an integrated POP3/SMTP servers, it is ideally suited for the - so-called POP toaster configuration, where there are a - multitude of virtual users and domains, all using the same IP - address and computer to send and receive mails. It is fairly - easy to configure Twisted as a POP toaster. There are a number - of deployment choices: one can append a telnet server to the - tap for remote configuration, or simple scripts can add and - remove users from the user database. The user database is saved - as a directory, where file names are keys and file contents are - values, so concurrency is not usually a problem.
- --- --% mktap mail -d foobar.com=$HOME/Maildir/ -u postmaster=secret -b \ - -p 110 -s 25 -% twistd -f mail.tap - -- -Bringing up a simple mail-server
-
Twisted's native mail storage format is Maildir, a format
- that requires no locking and is safe and atomic. Twisted
- supports a number of standardized extensions to Maildir,
- commonly known as Maildir++. Most importantly, it supports
- deletion as simply moving to a subfolder named
- Trash
, so mail is recoverable if accessed through
- a protocol which allows multiple folders, like IMAP. However,
- Twisted itself currently does not support any such protocol
- yet.
Twisted was originally designed to support multi-player - games; a simulated "real world" environment. Experience with - game systems of that type is enlightening as to the nature of - computing on the whole. Almost all services on a computer are - modeled after some simulated real-world activity. For example, - e-"mail", or "document publishing" on the web. Even - "object-oriented" programming is based around the notion that - data structures in a computer simulate some analogous - real-world objects.
- -All such networked simulations have a few things in common. - They each represent a service provided by software, and there - is usually some object where "global" state is kept. Such a - service must provide an authentication mechanism. Often, there - is a representation of the authenticated user within the - context of the simulation, and there are also objects aside - from the user and the simulation itself that can be - accessed.
- -For most existing protocols, Twisted provides these
- abstractions through twisted.internet.passport
.
- This is so named because the most important common
- functionality it provides is authentication. A simulation
- "world" as described above -- such as an e-mail system,
- document publishing archive, or online video game -- is
- represented by subclass of Service
, the
- authentication mechanism by an Authorizer
(which
- is a set of Identities
), and the user of the
- simulation by a Perspective
. Other objects in the
- simulation may be represented by arbitrary python objects,
- depending upon the implementation of the given protocol.
New problem domains, however, often require new protocols, - and re-implementing these abstractions each time can be - tedious, especially when it's not necessary. Many efforts have - been made in recent years to create generic "remote object" or - "remote procedure call" protocols, but in developing Twisted, - these protocols were found to require too much overhead in - development, be too inefficient at runtime, or both.
- -Perspective Broker is a new remote-object protocol designed
- to be lightweight and impose minimal constraints upon the
- development process and use Python's dynamic nature to good
- effect, but still relatively efficient in terms of bandwidth
- and CPU utilization. twisted.spread.pb
serves as a
- reference implementation of the protocol, but implementation of
- Perspective Broker in other languages is already underway.
- spread
is the twisted
subpackage
- dealing with remote calls and objects, and has nothing to do
- with the spread
toolkit.
Perspective Broker extends
- twisted.internet.passport
's abstractions to be
- concrete objects rather than design patterns. Rather than
- having a Protocol
implementation translate between
- sequences of bytes and specifically named methods (as in the
- other Twisted Protocols
), Perspective Broker
- defines a direct mapping between network messages and
- quasi-arbitrary method calls.
In a server application where a large number of clients may
- be interacting at once, it is not feasible to have an
- arbitrarily large number of OS threads blocking and waiting for
- remote method calls to return. Additionally, the ability for
- any client to call any method of an object would present a
- significant security risk. Therefore, rather than attempting to
- provide a transparent interface to remote objects,
- twisted.spread.pb
is "translucent", meaning that
- while remote method calls have different semantics than local
- ones, the similarities in semantics are mirrored by
- similarities in the syntax. Remote method calls impose as
- little overhead as possible in terms of volume of code, but "as
- little as possible" is unfortunately not "nothing".
twisted.spread.pb
defines a method naming
- standard for each type of remotely accessible object. For
- example, if a client requests a method call with an expression
- such as myPerspective.doThisAction()
, the remote
- version of myPerspective
would be sent the message
- perspective_doThisAction
. Depending on the manner
- in which an object is accessed, other method prefixes may be
- observe_
, view_
, or
- remote_
. Any method present on a remotely
- accessible object, and named appropriately, is considered to be
- published -- since this is accomplished with
- getattr
, the definition of "present" is not just
- limited to methods defined on the class, but instances may have
- arbitrary callable objects associated with them as long as the
- name is correct -- similarly to normal python objects.
Remote method calls are made on remote reference objects
- (instances of pb.RemoteReference
) by calling a
- method with an appropriate name. However, that call will not
- block -- if you need the result from a remote method call, you
- pass in one of the two special keyword arguments to that method
- -- pbcallback
or pberrback
.
- pbcallback
is a callable object which will be
- called when the result is available, and pberrback
- is a callable object which will be called if there was an
- exception thrown either in transmission of the call or on the
- remote side.
In the case that neither pberrback
or
- pbcallback
is provided,
- twisted.spread.pb
will optimize network usage by
- not sending confirmations of messages.
-- --# Server Side -class MyObject(pb.Referenceable): - def remote_doIt(self): - return "did it" - -# Client Side - ... - def myCallback(result): - print result # result will be 'did it' - def myErrback(stacktrace): - print 'oh no, mr. bill!' - print stacktrace - myRemoteReference.doIt(pbcallback=myCallback, - pberrback=myErrback) -- -Listing 9: A remotely accessible object and accompanying - call
-
Considering the problem of remote object access in terms of - a simulation demonstrates a requirement for the knowledge of an - actor with certain actions or requests. Often, when processing - message, it is useful to know who sent it, since different - results may be required depending on the permissions or state - of the caller.
- -A simple example is a game where certain an object is - invisible, but players with the "Heightened Perception" - enchantment can see it. When answering the question "What - objects are here?" it is important for the room to know who is - asking, to determine which objects they can see. Parallels to - the differences between "administrators" and "users" on an - average multi-user system are obvious.
- -Perspective Broker is named for the fact that it does not
- broker only objects, but views of objects. As a user of the
- twisted.spread.pb
module, it is quite easy to
- determine the caller of a method. All you have to do is
- subclass Viewable
.
-- Before any arguments sent by the client, the actor - (specifically, the Perspective instance through which this - object was retrieved) will be passed as the first argument to - any-# Server Side -class Greeter(pb.Viewable): - def view_greet(self, actor): - return "Hello %s!\n" % actor.perspectiveName - -# Client Side - ... - remoteGreeter.greet(pbcallback=sys.stdout.write) - ... -- -Listing 10: An object responding to its calling - perspective
-
view_xxx
methods.
-
- In a simulation of any decent complexity, client and server - will wish to share structured data. Perspective Broker provides - a mechanism for both transferring (copying) and sharing - (caching) that state.
- -Whenever an object is passed as an argument to or returned
- from a remote method call, that object is serialized using
- twisted.spread.jelly
; a serializer similar in some
- ways to Python's native pickle
. Originally,
- pickle
itself was going to be used, but there were
- several security issues with the pickle
code as it
- stands. It is on these issues of security that
- pickle
and twisted.spread.jelly
part
- ways.
While twisted.spread.jelly
handles a few basic
- types such as strings, lists, dictionaries and numbers
- automatically, all user-defined types must be registered both
- for serialization and unserialization. This registration
- process is necessary on the sending side in order to determine
- if a particular object is shared, and whether it is shared as
- state or behavior. On the receiving end, it's necessary to
- prevent arbitrary code from being run when an object is
- unserialized -- a significant security hole in
- pickle
for networked applications.
On the sending side, the registration is accomplished by
- making the object you want to serialize a subclass of one of
- the "flavors" of object that are handled by Perspective Broker.
- A class may be Referenceable
,
- Viewable
, Copyable
or
- Cacheable
. These four classes correspond to
- different ways that the object will be seen remotely.
- Serialization flavors are mutually exclusive -- these 4 classes
- may not be mixed in with each other.
Referenceable
: The remote side will refer to
- this object directly. Methods with the prefix
- remote_
will be callable on it. No state will be
- transferred.Viewable
: The remote side will refer to a
- proxy for this object, which indicates what perspective
- accessed this; as discussed above. Methods with the prefix
- view_
will be callable on it, and have an
- additional first argument inserted (the perspective that
- called the method). No state will be transferred.Copyable
: Each time this object is
- serialized, its state will be copied and sent. No methods are
- remotely callable on it. By default, the state sent will be
- the instance's __dict__
, but a method
- getStateToCopyFor(perspective)
may be defined
- which returns an arbitrary serializable object for
- state.Cacheable
: The first time this object is
- serialized, its state will be copied and sent. Each
- subsequent time, however, a reference to the original object
- will be sent to the receiver. No methods will be remotely
- callable on this object. By default, again, the state sent
- will be the instance's __dict__
but a method
- getStateToCacheAndObserveFor(perspective,
- observer)
may be defined to return alternative state.
- Since the state for this object is only sent once, the
- observer
argument is an object representative of
- the receiver's representation of the Cacheable
- after unserialization -- method calls to this object will be
- resolved to methods prefixed with observe_
,
- on the receiver's RemoteCache
of this
- object. This may be used to keep the receiver's cache
- up-to-date as relevant portions of the Cacheable
- object change.The previous samples of code have shown how an individual - object will interact over a previously-established PB - connection. In order to get to that connection, you need to do - some set-up work on both the client and server side; PB - attempts to minimize this effort.
- -There are two different approaches for setting up a PB - server, depending on your application's needs. In the simplest - case, where your application does not deal with the - abstractions above -- services, identities, and perspectives -- - you can simply publish an object on a particular port.
- --- --from twisted.spread import pb -from twisted.internet import main -class Echoer(pb.Root): - def remote_echo(self, st): - print 'echoing:', st - return st -if __name__ == '__main__': - app = main.Application("pbsimple") - app.listenOn(8789, pb.BrokerFactory(Echoer())) - app.run() -- -Listing 11: Creating a simple PB server
-
Listing 11 shows how to publish a simple object which - responds to a single message, "echo", and returns whatever - argument is sent to it. There is very little to explain: the - "Echoer" class is a pb.Root, which is a small subclass of - Referenceable designed to be used for objects published by a - BrokerFactory, so Echoer follows the same rule for remote - access that Referenceable does. Connecting to this service is - almost equally simple.
- --- --from twisted.spread import pb -from twisted.internet import main -def gotObject(object): - print "got object:",object - object.echo("hello network", pbcallback=gotEcho) -def gotEcho(echo): - print 'server echoed:',echo - main.shutDown() -def gotNoObject(reason): - print "no object:",reason - main.shutDown() -pb.getObjectAt("localhost", 8789, gotObject, gotNoObject, 30) -main.run() -- -Listing 12: A client for Echoer objects.
-
The utility function pb.getObjectAt
retrieves
- the root object from a hostname/port-number pair and makes a
- callback (in this case, gotObject
) if it can
- connect and retrieve the object reference successfully, and an
- error callback (gotNoObject
) if it cannot connect
- or the connection times out.
gotObject
receives the remote reference, and
- sends the echo
message to it. This call is
- visually noticeable as a remote method invocation by the
- distinctive pbcallback
keyword argument. When the
- result from that call is received, gotEcho
will be
- called, notifying us that in fact, the server echoed our input
- ("hello network").
While this setup might be useful for certain simple types of - applications where there is no notion of a "user", the - additional complexity necessary for authentication and service - segregation is worth it. In particular, re-use of server code - for things like chat (twisted.words) is a lot easier with a - unified notion of users and authentication.
- --- --from twisted.spread import pb -from twisted.internet import main -class SimplePerspective(pb.Perspective): - def perspective_echo(self, text): - print 'echoing',text - return text -class SimpleService(pb.Service): - def getPerspectiveNamed(self, name): - return SimplePerspective(name, self) -if __name__ == '__main__': - import pbecho - app = main.Application("pbecho") - pbecho.SimpleService("pbecho",app).getPerspectiveNamed("guest")\ - .makeIdentity("guest") - app.listenOn(pb.portno, pb.BrokerFactory(pb.AuthRoot(app))) - app.save("start") -- -Listing 13: A PB server using twisted's "passport" - authentication.
-
In terms of the "functionality" it offers, this server is - identical. It provides a method which will echo some simple - object sent to it. However, this server provides it in a manner - which will allow it to cooperate with multiple other - authenticated services running on the same connection, because - it uses the central Authorizer for the application.
- -On the line that creates the SimpleService
,
- several things happen.
Application
instance.getPerspectiveNamed
method.SimplePerspective
has an
- Identity
generated for it, and persistently
- added to the Application
's
- Authorizer
. The created identity will have the
- same name as the perspective ("guest"), and the password
- supplied (also, "guest"). It will also have a reference to
- the service "pbecho" and a perspective named "guest", by
- name. The Perspective.makeIdentity
utility
- method prevents having to deal with the intricacies of the
- passport Authorizer
system when one doesn't
- require strongly separate Identity
s and
- Perspective
s.Also, this server does not run itself, but instead persists - to a file which can be run with twistd, offering all the usual - amenities of daemonization, logging, etc. Once the server is - run, connecting to it is similar to the previous example.
- ----from twisted.spread import pb -from twisted.internet import main -def success(message): - print "Message received:",message - main.shutDown() -def failure(error): - print "Failure...",error - main.shutDown() -def connected(perspective): - perspective.echo("hello world", - pbcallback=success, - pberrback=failure) - print "connected." -pb.connect(connected, failure, "localhost", pb.portno, - "guest", "guest", "pbecho", "guest", 30) -main.run() -- -Listing 14: Connecting to an Authorized Service
-
This introduces a new utility -- pb.connect
.
- This function takes a long list of arguments and manages the
- handshaking and challenge/response aspects of connecting to a
- PB service perspective, eventually calling back to indicate
- either success or failure. In this particular example, we are
- connecting to localhost on the default PB port (8787),
- authenticating to the identity "guest" with the password
- "guest", requesting the perspective "guest" from the service
- "pbecho". If this can't be done within 30 seconds, the
- connection will abort.
In these examples, I've attempted to show how Twisted makes
- event-based scripting easier; this facilitates the ability to
- run short scripts as part of a long-running process. However,
- event-based programming is not natural to procedural scripts;
- it is more generally accepted that GUI programs will be
- event-driven whereas scripts will be blocking. An alternative
- client to our SimpleService
using GTK illustrates
- the seamless meshing of Twisted and GTK.
-- --from twisted.internet import main, ingtkernet -from twisted.spread.ui import gtkutil -import gtk -ingtkernet.install() -class EchoClient: - def __init__(self, echoer): - l.hide() - self.echoer = echoer - w = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL) - vb = gtk.GtkVBox(); b = gtk.GtkButton("Echo:") - self.entry = gtk.GtkEntry(); self.outry = gtk.GtkEntry() - w.add(vb) - map(vb.add, [b, self.entry, self.outry]) - b.connect('clicked', self.clicked) - w.connect('destroy', gtk.mainquit) - w.show_all() - def clicked(self, b): - txt = self.entry.get_text() - self.entry.set_text("") - self.echoer.echo(txt, pbcallback=self.outry.set_text) -l = gtkutil.Login(EchoClient, None, initialService="pbecho") -l.show_all() -gtk.mainloop() -- -Listing 15: A Twisted GUI application
-
Although PB will be interesting to those people who wish to
- write custom clients for their networked applications, many
- prefer or require a web-based front end. Twisted's built-in web
- server has been designed to accommodate this desire, and the
- presentation framework that one would use to write such an
- application is twisted.web.widgets
. Web.Widgets
- has been designed to work in an event-based manner, without
- adding overhead to the designer or the developer's
- work-flow.
Surprisingly, asynchronous web interfaces fit very well into - the normal uses of purpose-built web toolkits such as PHP. Any - experienced PHP, Zope, or WebWare developer will tell you that - separation of presentation, content, and logic is very - important. In practice, this results in a "header" block of - code which sets up various functions which are called - throughout the page, some of which load blocks of content to - display. While PHP does not enforce this, it is certainly - idiomatic. Zope enforces it to a limited degree, although it - still allows control structures and other programmatic elements - in the body of the content.
- -In Web.Widgets, strict enforcement of this principle - coincides very neatly with a "hands-free" event-based - integration, where much of the work of declaring callbacks is - implicit. A "Presentation" has a very simple structure for - evaluating Python expressions and giving them a context to - operate in. The "header" block which is common to many - templating systems becomes a class, which represents an - enumeration of events that the template may generate, each of - which may be responded to either immediately or latently.
- -For the sake of simplicity, as well as maintaining
- compatibility for potential document formats other than HTML,
- Presentation widgets do not attempt to parse their template as
- HTML tags. The structure of the template is "HTML Text
- %%%%python_expression()%%%% more HTML Text"
. Every set
- of 4 percent signs (%%%%) switches back and forth between
- evaluation and printing.
No control structures are allowed in the template. This was - originally thought to be a potentially major inconvenience, but - with use of the Web.Widgets code to develop a few small sites, - it has seemed trivial to encapsulate any table-formatting code - within a method; especially since those methods can take string - arguments if there's a need to customize the table's - appearance.
- -The namespace for evaluating the template expressions is - obtained by scanning the class hierarchy for attributes, and - getting each of those attributes from the current instance. - This means that all methods will be bound methods, so - indicating "self" explicitly is not required. While it is - possible to override the method for creating namespaces, using - this default has the effect of associating all presentation - code for a particular widget in one class, along with its - template. If one is working with a non-programmer designer, and - the template is in an external file, it is always very clear to - the designer what functionality is available to them in any - given scope, because there is a list of available methods for - any given class.
- -A convenient event to register for would be a response from
- the PB service that we just implemented. We can use the
- Deferred
class in order to indicate to the widgets
- framework that certain work has to be done later. This is a
- Twisted convention which one can currently use in PB as well as
- webwidgets; any framework which needs the ability to defer a
- return value until later should use this facility. Elements of
- the page will be rendered from top to bottom as data becomes
- available, so the page will not be blocked on rendering until
- all deferred elements have been completed.
-- --from twisted.spread import pb -from twisted.python import defer -from twisted.web import widgets -class EchoDisplay(widgets.Presentation): - template = """<H1>Welcome to my widget, displaying %%%%echotext%%%%.</h1> - <p>Here it is: %%%%getEchoPerspective()%%%%</p>""" - echotext = 'hello web!' - def getEchoPerspective(self): - d = defer.Deferred() - pb.connect(d.callback, d.errback, "localhost", pb.portno, - "guest", "guest", "pbecho", "guest", 1) - d.addCallbacks(self.makeListOf, self.formatTraceback) - return ['<b>',d,'</b>'] - def makeListOf(self, echoer): - d = defer.Deferred() - echoer.echo(self.echotext, pbcallback=d.callback, pberrback=d.errback) - d.addCallbacks(widgets.listify, self.formatTraceback) - return [d] -if __name__ == "__main__": - from twisted.web import server - from twisted.internet import main - a = main.Application("pbweb") - gdgt = widgets.Gadget() - gdgt.widgets['index'] = EchoDisplay() - a.listenOn(8080, server.Site(gdgt)) - a.run() -- -Listing 16: an event-based web widget.
-
Each time a Deferred is returned as part of the page, the
- page will pause rendering until the deferred's
- callback
method is invoked. When that callback is
- made, it is inserted at the point in the page where rendering
- left off.
If necessary, there are options within web.widgets to allow - a widget to postpone or cease rendering of the entire page -- - for example, it is possible to write a FileDownload widget, - which will override the rendering of the entire page and - replace it with a file download.
- -The final goal of web.widgets is to provide a framework - which encourages the development of usable library code. Too - much web-based code is thrown away due to its particular - environment requirements or stylistic preconceptions it carries - with it. The goal is to combine the fast-and-loose iterative - development cycle of PHP with the ease of installation and use - of Zope's "Product" plugins.
- -It is unfortunately well beyond the scope of this paper to - cover all the functionality that Twisted provides, but it - serves as a good overview. It may seem as though twisted does - anything and everything, but there are certain features we - never plan to implement because they are simply outside the - scope of the project.
- -Despite the multiple ways to publish and access objects, - Twisted does not have or support an interface definition - language. Some developers on the Twisted project have - experience with remote object interfaces that require explicit - specification of all datatypes during the design of an object's - interface. We feel that such interfaces are in the spirit of - statically-typed languages, and are therefore suited to the - domain of problems where statically-typed languages excel. - Twisted has no plans to implement a protocol schema or static - type-checking mechanism, as the efficiency gained by such an - approach would be quickly lost again by requiring the type - conversion between Python's dynamic types and the protocol's - static ones. Since one of the key advantages of Python is its - extremely flexible dynamic type system, we felt that a - dynamically typed approach to protocol design would share some - of those advantages.
- -Twisted does not assume that all data is stored in a
- relational database, or even an efficient object database.
- Currently, Twisted's configuration state is all stored in
- memory at run-time, and the persistent parts of it are pickled
- at one go. There are no plans to move the configuration objects
- into a "real" database, as we feel it is easier to keep a naive
- form of persistence for the default case and let
- application-specific persistence mechanisms handle persistence.
- Consequently, there is no object-relational mapping in Twisted;
- twisted.enterprise
is an interface to the
- relational paradigm, not an object-oriented layer over it.
There are other things that Twisted will not do as well, but - these have been frequently discussed as possibilities for it. - The general rule of thumb is that if something will increase - the required installation overhead, then Twisted will probably - not do it. Optional additions that enhance integration with - external systems are always welcome: for example, database - drivers for Twisted or a CORBA IDL for PB objects.
- -Twisted is still a work in progress. The number of protocols - in the world is infinite for all practical purposes, and it - would be nice to have a central repository of event-based - protocol implementations. Better integration with frameworks - and operating systems is also a goal. Examples for integration - opportunities are automatic creation of installer for "tap" - files (for Red Hat Packager-based distributions, FreeBSD's - package management system or Microsoft Windows(tm) installers), - and integration with other event-dispatch mechanisms, such as - win32's native message dispatch.
- -A still-nascent feature of Twisted, which this paper only
- touches briefly upon, is twisted.enterprise
: it is
- planned that Twisted will have first-class database support
- some time in the near future. In particular, integration
- between twisted.web and twisted.enterprise to allow developers
- to have SQL conveniences that they are used to from other
- frameworks.
Another direction that we hope Twisted will progress in is - standardization and porting of PB as a messaging protocol. Some - progress has already been made in that direction, with XEmacs - integration nearly ready for release as of this writing.
- -Tighter integration of protocols is also a future goal, such - an FTP server that can serve the same resources as a web - server, or a web server that allows users to change their POP3 - password. While Twisted is already a very tightly integrated - framework, there is always room for more integration. Of - course, all this should be done in a flexible way, so the - end-user will choose which components to use -- and have those - components work well together.
- -As shown, Twisted provides a lot of functionality to the
- Python network programmer, while trying to be in his way as
- little as possible. Twisted gives good tools for both someone
- trying to implement a new protocol, or someone trying to use an
- existing protocol. Twisted allows developers to prototype and
- develop object communication models with PB, without designing
- a byte-level protocol. Twisted tries to have an easy way to
- record useful deployment options, via the
- twisted.tap
and plugin mechanisms, while making it
- easy to generate new forms of deployment. And last but not
- least, even Twisted is written in a high-level language and
- uses its dynamic facilities to give an easy API, it has
- performance which is good enough for most situations -- for
- example, the web server can easily saturate a T1 line serving
- dynamic requests on low-end machines.
While still an active project, Twisted can already used for - production programs. Twisted can be downloaded from the main - Twisted site (http://www.twistedmatrix.com) where there is also - documentation for using and programming Twisted.
- -We wish to thank Sean Riley, Allen Short, Chris Armstrong, - Paul Swartz, Jürgen Hermann, Benjamin Bruheim, Travis B. - Hartwell, and Itamar Shtull-Trauring for being a part of the - Twisted development team with us.
- -Thanks also to Jason Asbahr, Tommi Virtanen, Gavin Cooper, - Erno Kuusela, Nick Moffit, Jeremy Fincher, Jerry Hebert, Keith - Zaback, Matthew Walker, and Dan Moniz, for providing insight, - commentary, bandwidth, crazy ideas, and bug-fixes (in no - particular order) to the Twisted team.
- --try: - f = opne("file") -except BaseException: - sys.exit("no such file") -- -
-def readlinesfromfile(file): - try: - return open(file).readlines() - except IOError: - pass # do what? --
-try: - fp = open("file") -except IOError, OSError: - print "could not open file" --
-try: - fp = open("file") -except (IOError, OSError): - print "could not open file" --
-try: - import foo -except ImportError: - pass - -try: - foo.Function() -except NameError: - pass # some replacement --
-try: - import foo -except ImportError: - foo = None - -if foo is not None: - foo.Function() -else: - pass # some replacement --
-# file: hello.py -import hello -class Foo: pass --
-from os import * - -fp = open("file") # works -fp.readline() # fails with a weird error...? --
-# Extra newline -r = 1 \ - -+2 - -# Missing backslash in long series -r = 1 \ -+2 \ -+3 \ -+4 -+5 \ -+6 --
-# Extra newline -r = (1 - -+2) - -# Long series -r = (1 -+2 -+3 -+4 -+5 -+6) -- -
-class Foo: - def __init__(self, i): - if type(i) is types.StringType: - self.content = open(i).readlines() - elif type(i) is types.ListType: - self.content = i --
- -class Foo: - pass - -class FooFromFile(Foo): - - def __init__(self, filename): - self.content = open(filename).readlines() - -class FooFromList(Foo): - - def __init__(self, list): - self.content = list -- -
-def foo(l=[]): - l.append(5);return l -- -
-def foo(l=None): - if l is None: l=[] - l.append(5);return l --
-<html> -<head><title>Title</title></head> -<body><h1>Title</h1></body> -</html>- -
<a class="python-listing" href="/usr/lib/python2.2/os.py">os.py</a>
-<p>paragraph</p>
-<em>emphasis</em>
-<strong>strong emphasis</strong>
-<img src="http://example.com/img.png" />
-<a name="label-name" />
-<a href="#label-name">reference text</a>
<a href="file-name#label-name">reference text</a>
<a href="http://example.com">reference text</a>
<code class="API">urllib</code>
-<code base="urllib" class="API">urlencode</code>
-<code base="twisted" class="API">copyright.version</code>
--<pre class="python"> -def foo(): - return forbnicate(4) -</pre>-
-# blinker/html.py -from twisted.lore import tree -from twisted.web import microdom, domhelpers - -def doBlink(document): - for node in domhelpers.findElementsWithAttribute(document, 'class', - 'blink'): - newNode = microdom.Element('blink') - newNode.children = node.children - node.parentNode.replaceChild(newNode, node) - -def doFile(fn, docsdir, ext, url, templ, linkrel=''): - doc = tree.parseFileAndReport(fn) - doBlink(doc) - cn = templ.cloneNode(1) - tree.munge(doc, cn, linkrel, docsdir, fn, ext, url) - cn.writexml(open(os.path.splitext(fn)[0]+ext, 'wb')) -- -
-# blinker/latex.py -class BlinkerLatexSpitter(latex.LatexSpitter): - - def visitNode_span_blink(self, node): - self.writer('{\sc ') - self.visitNodeDefault(node) - self.writer('}') -- -
-# blinker/factory.py -from blinker import html, latex -from twisted.lore import default - -class ProcessingFunctionFactory(default.ProcessingFunctionFactory): - - doFile = [doFile] - - latexSpitters = {None: latex.BlinkLatexSpitter} - - def getLintChecker(self): - checker = lint.getDefaultChecker() - checker.allowedClasses = checker.allowedClasses.copy() - oldSpan = checker.allowedClasses['span'] - checker.allowedClasses['span'] = (lambda x:oldSpan(x) or - x=='blink') - return checker - -factory = ProcessingFunctionFactory() --
-# blinker/plugins.tml -register("Blink-Lore", - "blinker.factory", - description="Lore format with blink", - type="lore", - tapname="blinklore") --
...and that's it!
- --% mktap --uid=33 --gid=33 web --path=/var/www/htdocs --port=80 -% sudo twistd -f web.tap --
-from twisted.web import static, twcgi - -class PerlScript(twcgi.FilteredScript): - filter = '/usr/bin/perl' # Points to the perl parser --
-from twisted.web import resource as resourcelib - -class MyGreatResource(resourcelib.Resource): - def render(self, request): - return "<html>foo</html>" - -resource = MyGreatResource() --
-from twisted.internet import app -from twisted.web import static, server - -application = app.Application('web') -application.listenTCP(80, - server.Site(static.File("/var/www/htdocs"))) --
-resource = proxy.ReverseProxyResource('localhost', 81, '/') --
-from twisted.internet import app -from twisted.cred import authorizer -from twisted.web import server -from bannerfish import service - -application = app.Application("bannerfish") -auth = authorizer.DefaultAuthorizer(app) -svc = service.BannerService('/var/bannerfish', - "bannerfish", application, auth) -site = server.Site(svc.buildResource(None, None)) -application.listenTCP(80, site) --
-from twisted.cred import authorizer -from bannerfish import service - -auth = authorizer.DefaultAuthorizer(app) -svc = service.BannerService('/var/bannerfish', - "bannerfish", application, auth) -resource = svc.buildResource(None, None) -default.addChild("bannerfish", resource) --
-from twisted.cred import authorizer -from twisted.internet import app -from bannerfish import service - -application = registry.getComponent(app.Application) -auth = authorizer.DefaultAuthorizer(application) -svc = service.BannerService('/var/bannerfish', - "bannerfish", application, auth) -resource = svc.buildResource(None, None) --
-from twisted.internet import application -from twisted.cred import authorizer -from twisted.web import server -from bannerfish import service -application = app.Application("bannerfish") -auth = authorizer.DefaultAuthorizer(application) -svc = service.BannerService('/var/bannerfish', - "bannerfish", application, auth) -site = server.Site(svc.buildResource(None, None)) -fact = pb.BrokerFactory(site) -site = server.Site(root) -application.listenUNIX('/var/run/bannerfish', fact) --
-from twisted.web import distrib - -resource = distrib.ResourceSubscription('unix', - '/var/run/bannerfish') --
- -from twisted.internet import app -from twisted.web import static, server -from twisted.web import twcgi - -root = static.File("/var/www") -root.processors = {".cgi": twcgi.CGIScript} -application = app.Application('web') -application.listenTCP(80, server.Site(root)) --
- -root = static.File("/var/www") -root.indices = ['index.rpy', 'index.html'] --
- -from twisted.web import vhost -default = static.File("/var/www") -foo = static.File("/var/foo") -root = vhost.NamedVirtualHost(default) -root.addHost('foo.com', foo) --
- -from twisted.internet import app -from twisted.web import static, server, vhost, script - -default = static.File("/var/www") -default.processors = {".rpy", script.ResourceScript} -root = vhost.NamedVirtualHost(default -foo = static.File("/var/foo") -foo.indices = ['index.xhtml', 'index.html'] -root.addHost('foo.com', foo) -site = server.Site(root) -application = app.Application('web') -application.listenTCP(80, site, interface='127.0.0.1') --
-from twisted.web import proxy - -root.putChild('foo', - proxy.ReverseProxyResource('localhost', - 81, '/foo/')) --
-site.getChild('foo', request - ).getChild('bar', request - ).getChild('baz', request - ).render(request) --
-site.getChild('foo', request - ).getChild('bar', request - ).getChild('baz', request - ).getChild('', request - ).render(request) --
-from twisted.internet import app, protocol -from twisted.web import server, distrib, static -from twisted.spread import pb - -application = app.Application("silly-web") -# The "master" server -site = server.Site(distrib.ResourceSubscription('unix', '.rp')) -application.listenTCP(19988, site) -# The "slave" server -fact = pb.BrokerFactory(distrib.ResourcePublisher( - server.Site(static.File('static')))) -application.listenUNIX('./.rp', fact) --
-from twisted.internet import app, protocol -from twisted.web import server, distrib, static, vhost -from twisted.spread import pb - -application = app.Application("ping-web") - -default = static.File("/var/www/foo") -root = vhost.NamedVirtualHost(default) -root.addVhost("foo.com", default) -bar = distrib.ResourceSubscription('unix', '.bar') -root.addVhost("bar.com", bar) - -fact = pb.BrokerFactory(static.Site(default)) -site = server.Site(root) -application.listenTCP(19988, site) -application.listenUNIX('./.foo', fact) --
-from twisted.internet import app, protocol -from twisted.web import server, distrib, static, vhost -from twisted.spread import pb - -application = app.Application("pong-web") - -foo = distrib.ResourceSubscription('unix', '.foo') -root = vhost.NamedVirtualHost(foo) -root.addVhost("foo.com", foo) -bar = static.File("/var/www/bar") -root.addVhost("bar.com", bar) - -fact = pb.BrokerFactory(static.Site(bar)) -site = server.Site(root) -application.listenTCP(19989, site) -application.listenUNIX('./.bar', fact) --
-from twisted.internet import app -from twisted.web import static, server, distrib - -root = static.File("/var/www") -root.putChild("users", distrib.UserDirectory()) -site = server.Site(root) -application = app.Application('web') -application.listenTCP(80, site) --
-from twisted.internet import app -from twisted.web import static, server, distrib -from twisted.spread import pb - -root = static.File("/home/moshez/twistd") -site = server.Site(root) - -fact = pb.BrokerFactory(distrib.ResourcePublisher(site)) -application.listenUNIX('/home/moshez/.twisted-web-pb', fact) --
-from twisted.web import static -import os - -vhostDir = '/var/www/vhost/' - -for file in os.listdir(vhostDir): - root.addHost(file, static.File(os.path.join(vhostDir, file))) --
-from twisted.web import script, static - -default.processors['.rpy'] = script.ResourceScript -default.ignoreExt('rpy') --
-from twisted.web import vhost - -default.putChild('vhost', vhost.VHostMonsterResource()) --
-... -indexNames = ['index', 'index.html', 'index.xhtml', 'index.rpy','index.cgi'] -... -root.putChild('mailman', twcgi.CGIDirectory('/usr/lib/cgi-bin')) -root.putChild('users', distrib.UserDirectory()) -root.putChild('cgi-bin', twcgi.CGIDirectory('/usr/lib/cgi-bin')) -root.putChild('doc', static.File('/usr/share/doc')) -... -uid = pwd.getpwnam('www-data')[2] -gid = grp.getgrnam('www-data')[2] -... -top = rewrite.RewriterResource(root, rewrite.tildeToUsers) -... -application = app.Application("web", uid=uid, gid=gid) --
- -root = static.File("/var/www") -root.putChild("users", distrib.UserDirectory()) -root = rewrite.RewriterResource(root, rewrite.tildeToUsers) --
-import pwd, os -from twisted.web import resource, error, distrib -from twisted.protocols import http - -class UserNameVirtualHost(resource.Resource): - - def __init__(self, default, tail): - resource.Resource.__init__(self) - self.default = default - self.tail = tail - self.users = {} - - def _getResourceForRequest(self, request): - host=request.getHeader('host') - if host.endswith(tail): - username = host[:-len(tail)] - else: - username = default - if username in self.users: - return self.users[username] - try: - (pw_name, pw_passwd, pw_uid, pw_gid, pw_gecos, pw_dir, - pw_shell) = pwd.getpwnam(username) - except KeyError: - return error.ErrorPage(http.NOT_FOUND, - "No Such User", - "The user %s was not found on this system." % - repr(username)) - twistdsock = os.path.join(pw_dir, ".twistd-web-pb") - rs = distrib.ResourceSubscription('unix',twistdsock) - self.users[username] = rs - return rs - - def render(self, request): - resrc = self._getResourceForRequest(request) - return resrc.render(request) - - def getChild(self, path, request): - resrc = self._getResourceForRequest(request) - request.path=request.path[:-1] - request.postpath=request.uri.split('/')[1:] - print request, request.path, request.postpath - return resrc.getChildForRequest(request) --
-from twisted.internet import app -from twisted.web import server -import uservhost - -root = UserNameVirtualHost("www", "example.com") -site = server.Site(root) -application = app.Application('web') -application.listenTCP(80, site) --
- -from twisted.web import distrib - -resource = registry.getComponent(distrib.UserDirectory) -if not resource: - resource = distrib.UserDirectory() - registry.setComponent(distrib.UserDirectory, resource) --
-<?xml version="1.0"?> - -<instance class="twisted.internet.app.Application" reference="1"> - <dictionary> -... - <string role="key" value="tcpPorts" /> - <list> - <tuple> - <int value="80" /> - <instance class="twisted.web.server.Site"> - <dictionary> -... - <string role="key" value="resource" /> - <instance class="twisted.web.static.File"> - <dictionary> -... - <string role="key" value="path" /> - <string value="/var/www" /> -... - </dictionary> - </instance> -... - </dictionary> - </instance> -... - </tuple> - </list> -... - </dictionary> -</instance> --
-app=Ref(1, - Instance('twisted.internet.app.Application',{ -... - 'tcpPorts':[ - ( - 80, - Instance('twisted.web.server.Site', -... - resource=Instance('twisted.web.static.File',{ -... - 'path':'/var/www', -... - ), - ], -... - })) --
- -<glyphAtWork> the http server was so we could say "Web!" if we ever did - a freshmeat announcement -<glyphAtWork> this makes people excited --
-from twisted.protocols import basic - -class FingerClient(basic.LineReceiver): - - # This will be called when the connection is made - def connectionMade(self): self.sendLine(self.factory.user) - - # This will be called when the server sends us a line. - # IMPORTANT: line *without "\n" at end. - # Yes, this means empty line does not mean EOF - def lineReceived(self, line): print line - - # This will be called when the connection is terminated - def connectionLost(self, _): print "-"*40 --
-from twisted.internet import protocol - -class FingerFactory(protocol.ClientFactory): - protocol = FingerProtocol - - def __init__(self, user): self.user = user - - def clientConnectionFailed(self, _, reason): - print "error", reason.value -- -
-from twisted.internet import reactor -import sys - -user, host = sys.argv[1].split('@') -port = 79 -reactor.connectTCP(host, port, FingerFactory(port)) -reactor.run() --
-from twisted.protocols import basic -from twisted.internet import protocol, defer -import sys - -class FingerClient(basic.LineReceiver): - - def connectionMade(self): - self.transport.write(self.factory.user+"\n") - - def lineReceived(self, line): - self.factory.gotLine(line) -- - -
-class FingerFactory(protocol.ClientFactory): - protocol = FingerProtocol - - def __init__(self, user): - self.user, self.d = user, defer.Deferred() - - def gotLine(self, line): print line - - def clientConnectionLost(self, _, why): self.d.callback(None) - - def clientConnectionFailed(self, _, why): self.d.errback(why) -- -
-if __name__ == '__main__': - from twisted.internet import reactor - from twisted.python import util - user, host = sys.argv[1].split('@') - f = FingerFactory(user) - port = 79 - reactor.connectTCP(host, port, FingerFactory(port)) - f.d.addCallback(lambda _: reactor.stop()) - f.d.addErrback(lambda _: (util.println("could not connect"), - reactor.stop())) - reactor.run() --
-class FingerServer(basic.LineReceiver): - - def lineReceived(self, line): - self.transport.write(self.factory.getUser(line)) - self.transport.loseConnection() --
-class FingerServerFactory(protocol.Factory): - - protocol = FingerServer - - def __init__(self): - self.users = {} - self.message = "No such user\n" - - def getUser(self, name): - return self.users.get(name, self.message) - - def setUser(self, user, status): - self.users[user] = status --
-factory = FingerServerFactory() -factory.setUser("moshez", "Online - Sitting at computer\n") -factory.setUser("spiv", "Offline - Surfing the waves\n") - -reactor.listenTCP(79, factory) --
-class FingerServer(basic.LineReceiver): - - def lineReceived(self, line): - d = self.factory.getUser(line) - d.addCallback(self.writeResponse) - d.addErrback(self.writeError) - - def writeResponse(self, response): - self.transport.write(response) - self.transport.loseConnection() - - def writeError(self, error): - self.transport.write("Server error -- try later\n") - self.transport.loseConnection() --
-class FingerServerFactory(protocol.Factory): - - protocol = FingerServer - - def __init__(self): - self.users = {} - self.message = "No such user\n" - - def getUser(self, name): - return defer.succeed(self.users.get(name, self.message)) - - def setUser(self, user, status): - self.users[user] = status --
-from twisted.web import client - -class FingerWebFactory(protocol.Factory): - protocol = FingerServer - - def getUser(self, name): - url = "http://example.com/~%s/online" % name - d = client.getPage(url) - d.addErrback(lambda _: "No such user\n") - return d --
-# File: finger.tpy -from twisted.internet import app -import fingerserver - -factory = fingerserver.FingerServerFactory() -factory.setUser("moshez", "Online - Sitting at computer\n") -factory.setUser("spiv", "Offline - Surfing the waves\n") -application = app.Application("finger") -application.listenTCP(79, factory) -- -
-# File finger/tap.py -from twisted.python import usage - -class Options(usage.Options): - synopsis = "Usage: mktap finger [options]" - optParameters = [["port", "p", 6666,"Set the port number."]] - longdesc = 'Finger Server' - users = () - - def opt_user(self, user): - if not '=' in user: status = "Online" - else: user, status = user.split('=', 1) - self.users += ((user, status+"\n"),) -- - -
-def updateApplication(app, config): - f = FingerFactory() - for (user, status) in config.users: - f.setUser(user, status) - app.listenTCP(int(config.opts['port']), s) --
-# File finger/plugins.tml -register("Finger", - "finger.tap", - description="Finger Server", - type='tap', - tapname="finger") --
-def calculateFact(cur, acc=1, d=None): - d = d or defer.Deferred() - if cur<=1: d.callback(acc) - else: reactor.callLater(0, calculateFact, acc*cur, cur-1, d) - -calculateFact(10 -).addCallback(lambda n: (util.println(n), reactor.stop())) -reactor.run() --
-from OpenSSL import SSL - -class ServerContextFactory: - - def getContext(self): - ctx = SSL.Context(SSL.SSLv23_METHOD) - ctx.use_certificate_file('server.pem') - ctx.use_privatekey_file('server.pem') - return ctx - -reactor.listenSSL(111, FingerWebFactory(), ServerContextFactory()) -- -
-class Advertizer(protocol.ProcessProtocol): - def outReceived(self, data): print "out", `data` - - def errReceived(self, data): print "error", `data` - - def processEnded(self, reason): print "ended", reason - -reactor.spawnProcess(Advertizer(), - "echo", ["echo", "hello", "world"]) --
-from twisted.spread import pb - -class FingerSetter(pb.Root): - - def __init__(self, ff): self.ff = ff - - def remote_setUser(self, name, status): - self.ff.setUser(name, status+"\n") - -ff = FingerServerFactory() -setter = FingerSetter(ff) -reactor.listenUNIX("/var/run/finger.control", - pb.BrokerFactory(setter)) --
-from twisted.spread import pb -from twisted.internet import reactor -import sys - -def failed(reason): - print "failed:", reason.value;reactor.stop() - -pb.getObjectAt("unix", "/var/run/finger.control", 30 -).addCallback(lambda o: o.callRemote("setUser", *sys.argv[1:3], -).addCallbacks(lambda _: reactor.stop(), failed) - -reactor.run() --
-# For lists -import htmllib, formatter - -h = htmllib.HTMLParser(formatter.NullFormatter()) -h.feed(htmlString) -print h.anchorlist -- -
-import htmllib, formatter - -class IMGFinder(htmllib.HTMLParser): - - def __init__(self, *args, **kw): - htmllib.HTMLParser.__init__(self, *args, **kw) - self.ims = [] - - def handle_image(self, src, *args): self.ims.append(src) - -h = IMGFinder(formatter.NullFormatter()) -h.feed(htmlString) -print h.ims -- -
-GET /foo/bar.html HTTP/1.0 -Host: www.example.org -<blank line> -- -
-HTTP/1.0 200 OK -Content-Type: text/html - -<html><body>lalalala</body></html> -- -
->>> import httplib ->>> h=httplib.HTTP("moshez.org") ->>> h.putrequest('GET', '/') ->>> h.putheader('Host', 'moshez.org') ->>> h.endheaders() ->>> h.getreply() -(200, 'OK', <mimetools.Message instance at 0x81220dc>) ->>> h.getfile().read(10) -"<HTML>\n<HE" --
-import urllib2, re - -URL = 'http://www.dilbert.com/' -f = urllib2.urlopen(URL) -s = f.read() -href = re.compile('<a href="(/comics/.*?/dilbert.*?gif)">') -m = href.search(value) -f = urllib2.urlretrieve(urlparse.urljoin(URL, m.group(1)), - "dilbert.gif") --
-import urllib2, htmllib, formatter, posixpath -URL="http://www.darkangelfan.com/episode/" -LINK_RE = re.compile('/trans_[0-9]+\.shtml$') -s = urllib2.urlopen(URL).read() -h = htmllib.HTMLParser(formatter.NullFormatter()) -h.feed(s) -links = [urlparse.urljoin(URL, link) - for link in h.anchorlist if LINK_RE.search(link)] -### -- really download -- -for link in links: - urllib2.urlretrieve(link, posixpath.basename(link)) -- -
-class Downloader: - - def __init__(self, fin, fout): - self.fin, self.fout, self.fileno = fin, fout, fin.fileno - - def read(self): - buf = self.fin.read(4096) - if not buf: - for f in [self.fout, self.fin]: f.close() - return 1 - self.fout.write(buf) --
-downloaders = [Downloader(urllib2.urlopen(link), - open(posixpath.basename(link), 'wb')) - for link in links] -while downloaders: - toRead = select.select(None, [downloaders], [], []) - for downloader in toRead: - if downloader.read(): - downloaders.remove(downloader) --
-import threading - -for link in links: - Thread(target=urllib2.urlretrieve, - args=(link,posixpath.basename(link))) --
-from twisted.web import client -from twisted.internet import import reactor, defer - -defer.DeferredList( -[client.downloadPage(link, posixpath.basename(link)) - for link in links]).addBoth(lambda _: reactor.stop()) -reactor.run() --
-user = 'moshez' -password = 's3kr1t' -import httplib -h=httplib.HTTP("localhost") -h.putrequest('GET', '/protected/stuff.html') -h.putheader('Authorization', - base64.encodestring(user+":"+password).strip()) -h.endheaders() -h.getreply() -print h.getfile().read() --
- -import urllib2 - -u = urllib2.urlopen("http://advogato.org/acct/loginsub.html", - urllib2.urlencode({'u': 'moshez', - 'pass': 'not my real pass'}) -cookie = u.info()['set-cookie'] -cookie = cookie[:cookie.find(';')] -r = Request('http://advogato.org/diary/post.html', - urllib2.urlencode( - {'entry': open('entry').read(), 'post': 'Post'}), - {'Cookie': cookie}) -urllib2.urlopen(r).read() -- -
-import robotparser -rp = robotparser.RobotFileParser() -rp.set_url('http://www.example.com/robots.txt') -rp.read() -if not rp.can_fetch('', 'http://www.example.com/'): - sys.exit(1) -- -
-from twisted.internet import reactor -reactor.run() -- -
Here, we just run the reactor. Nothing at all will happen, -until we interrupt the program. It will not consume (almost) -no CPU resources. Not very useful, perhaps -- but this -is the skeleton inside which the Twisted program -will grow.
- --from twisted.internet import protocol, reactor -class FingerProtocol(protocol.Protocol): - pass -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol -reactor.listenTCP(1079, FingerFactory()) -reactor.run() -- -
Here, we start listening on port 1079 [which is supposed to be -a reminder that eventually, we want to run on port 79, the port -the finger server is supposed to run on. We define a protocol which -does not respond to any events. Thus, connections to 1079 will -be accepted, but the input ignored.
- --from twisted.internet import protocol, reactor -class FingerProtocol(protocol.Protocol): - def connectionMade(self): - self.transport.loseConnection() -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol -reactor.listenTCP(1079, FingerFactory()) -reactor.run() -- -
Here we add to the protocol the ability to respond to the -event of beginning a connection -- by terminating it. -Perhaps not an interesting behaviour, but it is already -not that far from behaving according to the letter of the -protocol. After all, there is no requirement to send any -data to the remote connection in the standard, is there. -The only technical problem is that we terminate the connection -too soon. A client which is slow enough will see his send() -of the username result in an error.
- --from twisted.internet import protocol, reactor -from twisted.protocols import basic -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.transport.loseConnection() -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol -reactor.listenTCP(1079, FingerFactory()) -reactor.run() -- -
Here we make FingerProtocol
inherit from
-LineReceiver
, so that we get data-based events
-on a line-by-line basis. We respond to the event of receiving
-the line with shutting down the connection. Congratulations,
-this is the first standard-compliant version of the code.
-However, usually people actually expect some data about
-users to be transmitted.
-from twisted.internet import protocol, reactor -from twisted.protocols import basic -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.transport.write("No such user\r\n") - self.transport.loseConnection() -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol -reactor.listenTCP(1079, FingerFactory()) -reactor.run() -- -
Finally, a useful version. Granted, the usefulness is somewhat -limited by the fact that this version only prints out a no such -user message. It could be used for devestating effect in honeypots, -of course :)
- --# Read username, output from empty factory, drop connections -from twisted.internet import protocol, reactor -from twisted.protocols import basic -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.transport.write(self.factory.getUser(user)+"\r\n") - self.transport.loseConnection() -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol - def getUser(self, user): return "No such user" -reactor.listenTCP(1079, FingerFactory()) -reactor.run() -- -
The same behaviour, but finally we see what usefuleness the -factory has: as something that does not get constructed for -every connection, it can be in charge of the user database. -In particular, we won't have to change the protocol if -the user database backend changes.
- --# Read username, output from non-empty factory, drop connections -from twisted.internet import protocol, reactor -from twisted.protocols import basic -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.transport.write(self.factory.getUser(user)+"\r\n") - self.transport.loseConnection() -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol - def __init__(self, **kwargs): self.users = kwargs - def getUser(self, user): - return self.users.get(user, "No such user") -reactor.listenTCP(1079, FingerFactory(moshez='Happy and well')) -reactor.run() -- -
Finally, a really useful finger database. While it does not -supply information about logged in users, it could be used to -distribute things like office locations and internal office -numbers. As hinted above, the factory is in charge of keeping -the user database: note that the protocol instance has not -changed. This is starting to look good: we really won't have -to keep tweaking our protocol.
- --# Read username, output from non-empty factory, drop connections -# Use deferreds, to minimize synchronicity assumptions -from twisted.internet import protocol, reactor, defer -from twisted.protocols import basic -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.factory.getUser(user - ).addErrback(lambda _: "Internal error in server" - ).addCallback(lambda m: - (self.transport.write(m+"\r\n"),self.transport.loseConnection())) -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol - def __init__(self, **kwargs): self.users = kwargs - def getUser(self, user): - return defer.succeed(self.users.get(user, "No such user")) -reactor.listenTCP(1079, FingerFactory(moshez='Happy and well')) -reactor.run() -- -
But, here we tweak it just for the hell of it. Yes, while the -previous version worked, it did assume the result of getUser is -always immediately available. But what if instead of an in memory -database, we would have to fetch result from a remote Oracle? -Or from the web? Or, or...
- --# Read username, output from factory interfacing to OS, drop connections -from twisted.internet import protocol, reactor, defer, utils -from twisted.protocols import basic -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.factory.getUser(user - ).addErrback(lambda _: "Internal error in server" - ).addCallback(lambda m: - (self.transport.write(m+"\r\n"),self.transport.loseConnection())) -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol - def getUser(self, user): - return utils.getProcessOutput("finger", [user]) -reactor.listenTCP(1079, FingerFactory()) -reactor.run() -- -
...from running a local command? Yes, this version (safely!) runs -finger locally with whatever arguments it is given, and returns the -standard output. This will do exactly what the standard version -of the finger server does -- without the need for any remote buffer -overflows, as the networking is done safely.
- --# Read username, output from factory interfacing to web, drop connections -from twisted.internet import protocol, reactor, defer, utils -from twisted.protocols import basic -from twisted.web import client -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.factory.getUser(user - ).addErrback(lambda _: "Internal error in server" - ).addCallback(lambda m: - (self.transport.write(m+"\r\n"),self.transport.loseConnection())) -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol - def __init__(self, prefix): self.prefix=prefix - def getUser(self, user): - return client.getPage(self.prefix+user) -reactor.listenTCP(1079, FingerFactory(prefix='http://livejournal.com/~')) -reactor.run() -- -
The web. That invention which has infiltrated homes around the -world finally gets through to our invention. Here we use the built-in -Twisted web client, which also returns a deferred. Finally, we manage -to have examples of three different database backends, which do -not change the protocol class. In fact, we will not have to change -the protocol again until the end of this talk: we have achieved, -here, one truly usable class.
- - --# Read username, output from non-empty factory, drop connections -# Use deferreds, to minimize synchronicity assumptions -# Write application. Save in 'finger.tpy' -from twisted.internet import protocol, reactor, defer, app -from twisted.protocols import basic -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.factory.getUser(user - ).addErrback(lambda _: "Internal error in server" - ).addCallback(lambda m: - (self.transport.write(m+"\r\n"),self.transport.loseConnection())) -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol - def __init__(self, **kwargs): self.users = kwargs - def getUser(self, user): - return defer.succeed(self.users.get(user, "No such user")) -application = app.Application('finger', uid=1, gid=1) -application.listenTCP(79, FingerFactory(moshez='Happy and well')) -- -
Up until now, we faked. We kept using port 1079, because really, -who wants to run a finger server with root privileges? Well, the -common solution is "privilege shedding": after binding to the network, -become a different, less privileged user. We could have done it ourselves, -but Twisted has a builtin way to do it. Create a snippet as above, -defining an application object. That object will have uid and gid -attributes. When running it (later we will see how) it will bind -to ports, shed privileges and then run.
- --root% twistd -ny finger.tpy # just like before -root% twistd -y finger.tpy # daemonize, keep pid in twistd.pid -root% twistd -y finger.tpy --pidfile=finger.pid -root% twistd -y finger.tpy --rundir=/ -root% twistd -y finger.tpy --chroot=/var -root% twistd -y finger.tpy -l /var/log/finger.log -root% twistd -y finger.tpy --syslog # just log to syslog -root% twistd -y finger.tpy --syslog --prefix=twistedfinger # use given prefix -- -
This is how to run "Twisted Applications" -- files which define an -'application'. twistd (TWISTed Daemonizer) does everything a daemon -can be expected to -- shuts down stdin/stdout/stderr, disconnects -from the terminal and can even change runtime directory, or even -the root filesystems. In short, it does everything so the Twisted -application developer can concentrate on writing his networking code.
- --# But let's try and fix setting away messages, shall we? -from twisted.internet import protocol, reactor, defer, app -from twisted.protocols import basic -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.factory.getUser(user - ).addErrback(lambda _: "Internal error in server" - ).addCallback(lambda m: - (self.transport.write(m+"\r\n"),self.transport.loseConnection())) -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol - def __init__(self, **kwargs): self.users = kwargs - def getUser(self, user): - return defer.succeed(self.users.get(user, "No such user")) -class FingerSetterProtocol(basic.LineReceiver): - def connectionMade(self): self.lines = [] - def lineReceived(self, line): self.lines.append(line) - def connectionLost(self): self.factory.setUser(*self.lines) -class FingerSetterFactory(protocol.ServerFactory): - def __init__(self, ff): self.setUser = self.ff.users.__setitem__ -ff = FingerFactory(moshez='Happy and well') -fsf = FingerSetterFactory(ff) -application = app.Application('finger', uid=1, gid=1) -application.listenTCP(79, ff) -application.listenTCP(1079, fsf, interface='127.0.0.1') -- -
Now that port 1079 is free, maybe we can run on it a different -server, one which will let people set their messages. It does -no access control, so anyone who can login to the machine can -set any message. We assume this is the desired behaviour in -our case. Testing it can be done by simply: -
- --% nc localhost 1079 -moshez -Giving a talk now, sorry! -^D -- -
-# Fix asymmetry -from twisted.internet import protocol, reactor, defer, app -from twisted.protocols import basic -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.factory.getUser(user - ).addErrback(lambda _: "Internal error in server" - ).addCallback(lambda m: - (self.transport.write(m+"\r\n"),self.transport.loseConnection())) -class FingerSetterProtocol(basic.LineReceiver): - def connectionMade(self): self.lines = [] - def lineReceived(self, line): self.lines.append(line) - def connectionLost(self): self.factory.setUser(*self.lines) -class FingerService(app.ApplicationService): - def __init__(self, *args, **kwargs): - app.ApplicationService.__init__(self, *args) - self.users = kwargs - def getUser(self, user): - return defer.succeed(self.users.get(u, "No such user")) - def getFingerFactory(self): - f = protocol.ServerFactory() - f.protocol, f.getUser = FingerProtocol, self.getUser - return f - def getFingerSetterFactory(self): - f = protocol.ServerFactory() - f.protocol, f.setUser = FingerSetterProtocol, self.users.__setitem__ - return f -application = app.Application('finger', uid=1, gid=1) -f = FingerService(application, 'finger', moshez='Happy and well') -application.listenTCP(79, f.getFingerFactory()) -application.listenTCP(1079, f.getFingerSetterFactory(), interface='127.0.0.1') -- -
The previous version had the setter poke at the innards of the -finger factory. It's usually not a good idea: this version makes -both factories symmetric by making them both look at a single -object. Services are useful for when an object is needed which is -not related to a specific network server. Here, we moved all responsibility -for manufacturing factories into the service. Note that we stopped -subclassing: the service simply puts useful methods and attributes -inside the factories. We are getting better at protocol design: -none of our protocol classes had to be changed, and neither will -have to change until the end of the talk.
- --# Read from file -from twisted.internet import protocol, reactor, defer, app -from twisted.protocols import basic -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.factory.getUser(user - ).addErrback(lambda _: "Internal error in server" - ).addCallback(lambda m: - (self.transport.write(m+"\r\n"),self.transport.loseConnection())) -class FingerSetterProtocol(basic.LineReceiver): - def connectionMade(self): self.lines = [] - def lineReceived(self, line): self.lines.append(line) - def connectionLost(self): self.factory.setUser(*self.lines) -class FingerService(app.ApplicationService): - def __init__(self, file, *args, **kwargs): - app.ApplicationService.__init__(self, *args, **kwargs) - self.file = file - def startService(self): - app.ApplicationService.startService(self) - self._read() - def _read(self): - self.users = {} - for line in file(self.file): - user, status = line.split(':', 1) - self.users[user] = status - self.call = reactor.callLater(30, self._read) - def stopService(self): - app.ApplicationService.stopService(self) - self.call.cancel() - def getUser(self, user): - return defer.succeed(self.users.get(u, "No such user")) - def getFingerFactory(self): - f = protocol.ServerFactory() - f.protocol, f.getUser = FingerProtocol, self.getUser - return f -application = app.Application('finger', uid=1, gid=1) -f = FingerService('/etc/users', application, 'finger') -application.listenTCP(79, f.getFingerFactory()) -- -
This version shows how, instead of just letting users set their -messages, we can read those from a centrally managed file. We cache -results, and every 30 seconds we refresh it. Services are useful -for such scheduled tasks.
- --# Read from file, announce on the web! -from twisted.internet import protocol, reactor, defer, app -from twisted.protocols import basic -from twisted.web import resource, server, static -import cgi -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.factory.getUser(user - ).addErrback(lambda _: "Internal error in server" - ).addCallback(lambda m: - (self.transport.write(m+"\r\n"),self.transport.loseConnection())) -class FingerSetterProtocol(basic.LineReceiver): - def connectionMade(self): self.lines = [] - def lineReceived(self, line): self.lines.append(line) - def connectionLost(self): self.factory.setUser(*self.lines) -class FingerService(app.ApplicationService): - def __init__(self, file, *args, **kwargs): - app.ApplicationService.__init__(self, *args, **kwargs) - self.file = file - def startService(self): - app.ApplicationService.startService(self) - self._read() - def _read(self): - self.users = {} - for line in file(self.file): - user, status = line.split(':', 1) - self.users[user] = status - self.call = reactor.callLater(30, self._read) - def stopService(self): - app.ApplicationService.stopService(self) - self.call.cancel() - def getUser(self, user): - return defer.succeed(self.users.get(u, "No such user")) - def getFingerFactory(self): - f = protocol.ServerFactory() - f.protocol, f.getUser = FingerProtocol, self.getUser - return f - def getResource(self): - r = resource.Resource() - r.getChild = (lambda path, request: - static.Data('text/html', - '<h1>%s</h1><p>%s</p>' % - tuple(map(cgi.escape, - [path,self.users.get(path, "No such user")])))) -application = app.Application('finger', uid=1, gid=1) -f = FingerService('/etc/users', application, 'finger') -application.listenTCP(79, f.getFingerFactory()) -application.listenTCP(80, server.Site(f.getResource())) -- -
The same kind of service can also produce things useful for -other protocols. For example, in twisted.web, the factory -itself (the site) is almost never subclassed -- instead, -it is given a resource, which represents the tree of resources -available via URLs. That hierarchy is navigated by site, -and overriding it dynamically is possible with getChild.
- --# Read from file, announce on the web, irc -from twisted.internet import protocol, reactor, defer, app -from twisted.protocols import basic, irc -from twisted.web import resource, server, static -import cgi -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.factory.getUser(user - ).addErrback(lambda _: "Internal error in server" - ).addCallback(lambda m: - (self.transport.write(m+"\r\n"),self.transport.loseConnection())) -class FingerSetterProtocol(basic.LineReceiver): - def connectionMade(self): self.lines = [] - def lineReceived(self, line): self.lines.append(line) - def connectionLost(self): self.factory.setUser(*self.lines) -class IRCReplyBot(irc.IRCClient): - def connectionMade(self): - self.nickname = self.factory.nickname - irc.IRCClient.connectionMade(self) - def privmsg(self, user, channel, msg): - if user.lower() == channel.lower(): - self.factory.getUser(msg - ).addErrback(lambda _: "Internal error in server" - ).addCallback(lambda m: self.msg(user, m)) -class FingerService(app.ApplicationService): - def __init__(self, file, *args, **kwargs): - app.ApplicationService.__init__(self, *args, **kwargs) - self.file = file - def startService(self): - app.ApplicationService.startService(self) - self._read() - def _read(self): - self.users = {} - for line in file(self.file): - user, status = line.split(':', 1) - self.users[user] = status - self.call = reactor.callLater(30, self._read) - def stopService(self): - app.ApplicationService.stopService(self) - self.call.cancel() - def getUser(self, user): - return defer.succeed(self.users.get(u, "No such user")) - def getFingerFactory(self): - f = protocol.ServerFactory() - f.protocol, f.getUser = FingerProtocol, self.getUser - return f - def getResource(self): - r = resource.Resource() - r.getChild = (lambda path, request: - static.Data('text/html', - '<h1>%s</h1><p>%s</p>' % - tuple(map(cgi.escape, - [path,self.users.get(path, "No such user")])))) - def getIRCBot(self, nickname): - f = protocol.ReconnectingClientFactory() - f.protocol,f.nickname,f.getUser = IRCReplyBot,nickname,self.getUser - return f -application = app.Application('finger', uid=1, gid=1) -f = FingerService('/etc/users', application, 'finger') -application.listenTCP(79, f.getFingerFactory()) -application.listenTCP(80, server.Site(f.getResource())) -application.connectTCP('irc.freenode.org', 6667, f.getIRCBot('finger-bot')) -- -
This is the first time there is client code. IRC clients often -act a lot like servers: responding to events form the network. -The reconnecting client factory will make sure that severed links -will get re-established, with intelligent tweaked exponential -backoff algorithms. The irc client itself is simple: the only -real hack is getting the nickname from the factory in connectionMade.
- - - --# Read from file, announce on the web, irc, xml-rpc -from twisted.internet import protocol, reactor, defer, app -from twisted.protocols import basic, irc -from twisted.web import resource, server, static, xmlrpc -import cgi -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.factory.getUser(user - ).addErrback(lambda _: "Internal error in server" - ).addCallback(lambda m: - (self.transport.write(m+"\r\n"),self.transport.loseConnection())) -class FingerSetterProtocol(basic.LineReceiver): - def connectionMade(self): self.lines = [] - def lineReceived(self, line): self.lines.append(line) - def connectionLost(self): self.factory.setUser(*self.lines) -class IRCReplyBot(irc.IRCClient): - def connectionMade(self): - self.nickname = self.factory.nickname - irc.IRCClient.connectionMade(self) - def privmsg(self, user, channel, msg): - if user.lower() == channel.lower(): - self.factory.getUser(msg - ).addErrback(lambda _: "Internal error in server" - ).addCallback(lambda m: self.msg(user, m)) -class FingerService(app.ApplicationService): - def __init__(self, file, *args, **kwargs): - app.ApplicationService.__init__(self, *args, **kwargs) - self.file = file - def startService(self): - app.ApplicationService.startService(self) - self._read() - def _read(self): - self.users = {} - for line in file(self.file): - user, status = line.split(':', 1) - self.users[user] = status - self.call = reactor.callLater(30, self._read) - def stopService(self): - app.ApplicationService.stopService(self) - self.call.cancel() - def getUser(self, user): - return defer.succeed(self.users.get(u, "No such user")) - def getFingerFactory(self): - f = protocol.ServerFactory() - f.protocol, f.getUser = FingerProtocol, self.getUser - return f - def getResource(self): - r = resource.Resource() - r.getChild = (lambda path, request: - static.Data('text/html', - '<h1>%s</h1><p>%s</p>' % - tuple(map(cgi.escape, - [path,self.users.get(path, "No such user")])))) - x = xmlrpc.XMLRPRC() - x.xmlrpc_getUser = self.getUser - r.putChild('RPC2.0', x) - return r - def getIRCBot(self, nickname): - f = protocol.ReconnectingClientFactory() - f.protocol,f.nickname,f.getUser = IRCReplyBot,nickname,self.getUser - return f -application = app.Application('finger', uid=1, gid=1) -f = FingerService('/etc/users', application, 'finger') -application.listenTCP(79, f.getFingerFactory()) -application.listenTCP(80, server.Site(f.getResource())) -application.connectTCP('irc.freenode.org', 6667, f.getIRCBot('finger-bot')) -- -
In Twisted, XML-RPC support is handled just as though it was -another resource. That resource will still support GET calls normally -through render(), but that is usually left unimplemented. Note -that it is possible to return deferreds from XML-RPC methods. -The client, of course, will not get the answer until the deferred -is triggered.
- - --# Do everything properly -from twisted.internet import protocol, reactor, defer, app -from twisted.protocols import basic, irc -from twisted.web import resource, server, static, xmlrpc -import cgi - -def catchError(err): - return "Internal error in server" - -class FingerProtocol(basic.LineReceiver): - - def lineReceived(self, user): - d = self.factory.getUser(user) - d.addErrback(catchError) - def writeValue(value): - self.transport.write(value) - self.transport.loseConnection() - d.addCallback(writeValue) - - -class FingerSetterProtocol(basic.LineReceiver): - - def connectionMade(self): - self.lines = [] - - def lineReceived(self, line): - self.lines.append(line) - - def connectionLost(self): - if len(self.lines) == 2: - self.factory.setUser(*self.lines) - - -class IRCReplyBot(irc.IRCClient): - - def connectionMade(self): - self.nickname = self.factory.nickname - irc.IRCClient.connectionMade(self) - - def privmsg(self, user, channel, msg): - if user.lower() == channel.lower(): - d = self.factory.getUser(msg) - d.addErrback(catchError) - d.addCallback(lambda m: "Status of %s: %s" % (user, m)) - d.addCallback(lambda m: self.msg(user, m)) - - -class UserStatusTree(resource.Resource): - - def __init__(self, service): - resource.Resource.__init__(self): - self.service = service - - def render(self, request): - d = self.service.getUsers() - def formatUsers(users): - l = ["<li><a href="%s">%s</a></li> % (user, user) - for user in users] - return '<ul>'+''.join(l)+'</ul>' - d.addCallback(formatUsers) - d.addCallback(request.write) - d.addCallback(lambda _: request.finish()) - return server.NOT_DONE_YET - - def getChild(self, path, request): - return UserStatus(path, self.service) - - -class UserStatus(resource.Resource): - - def __init__(self, user, service): - resource.Resource.__init__(self): - self.user = user - self.service = service - - def render(self, request): - d = self.service.getUser(self.user) - d.addCallback(cgi.escape) - d.addCallback(lambda m: - '<h1>%s</h1>'%self.user+'<p>%s</p>'%m) - d.addCallback(request.write) - d.addCallback(lambda _: request.finish()) - return server.NOT_DONE_YET - - -class UserStatusXR(xmlrpc.XMLPRC): - - def __init__(self, service): - xmlrpc.XMLRPC.__init__(self) - self.service = service - - def xmlrpc_getUser(self, user): - return self.service.getUser(user) - - -class FingerService(app.ApplicationService): - - def __init__(self, file, *args, **kwargs): - app.ApplicationService.__init__(self, *args, **kwargs) - self.file = file - - def startService(self): - app.ApplicationService.startService(self) - self._read() - - def _read(self): - self.users = {} - for line in file(self.file): - user, status = line.split(':', 1) - self.users[user] = status - self.call = reactor.callLater(30, self._read) - - def stopService(self): - app.ApplicationService.stopService(self) - self.call.cancel() - - def getUser(self, user): - return defer.succeed(self.users.get(u, "No such user")) - - def getUsers(self): - return defer.succeed(self.users.keys()) - - def getFingerFactory(self): - f = protocol.ServerFactory() - f.protocol = FingerProtocol - f.getUser = self.getUser - return f - - def getResource(self): - r = UserStatusTree(self) - x = UserStatusXR(self) - r.putChild('RPC2.0', x) - return r - - def getIRCBot(self, nickname): - f = protocol.ReconnectingClientFactory() - f.protocol = IRCReplyBot - f.nickname = nickname - f.getUser = self.getUser - return f - -application = app.Application('finger', uid=1, gid=1) -f = FingerService('/etc/users', application, 'finger') -application.listenTCP(79, f.getFingerFactory()) -application.listenTCP(80, server.Site(f.getResource())) -application.connectTCP('irc.freenode.org', 6667, f.getIRCBot('finger-bot')) -- -
The last version of the application had a lot of hacks. We avoided -subclassing, did not support things like user listings in the web -support, and removed all blank lines -- all in the interest of code -which is shorter. Here we take a step back, subclass what is more -naturally a subclass, make things which should take multiple lines -take them, etc. This shows a much better style of developing Twisted -applications, though the hacks in the previous stages are sometimes -used in throw-away prototypes.
- --# Do everything properly, and componentize -from twisted.internet import protocol, reactor, defer, app -from twisted.protocols import basic, irc -from twisted.python import components -from twisted.web import resource, server, static, xmlrpc -import cgi - -class IFingerService(components.Interface): - - def getUser(self, user): - '''Return a deferred returning a string''' - - def getUsers(self): - '''Return a deferred returning a list of strings''' - -class IFingerSettingService(components.Interface): - - def setUser(self, user, status): - '''Set the user's status to something''' - -def catchError(err): - return "Internal error in server" - - -class FingerProtocol(basic.LineReceiver): - - def lineReceived(self, user): - d = self.factory.getUser(user) - d.addErrback(catchError) - def writeValue(value): - self.transport.write(value) - self.transport.loseConnection() - d.addCallback(writeValue) - - -class IFingerFactory(components.Interface): - - def getUser(self, user): - """Return a deferred returning a string"""" - - def buildProtocol(self, addr): - """Return a protocol returning a string"""" - - -class FingerFactoryFromService(protocol.ServerFactory): - - __implements__ = IFingerFactory, - - protocol = FingerProtocol - - def __init__(self, service): - self.service = service - - def getUser(self, user): - return self.service.getUser(user) - -components.registerAdapter(FingerFactoryFromService, IFingerService) - - -class FingerSetterProtocol(basic.LineReceiver): - - def connectionMade(self): - self.lines = [] - - def lineReceived(self, line): - self.lines.append(line) - - def connectionLost(self): - if len(self.lines) == 2: - self.factory.setUser(*self.lines) - - -class IFingerSetterFactory(components.Interface): - - def setUser(self, user, status): - """Return a deferred returning a string""" - - def buildProtocol(self, addr): - """Return a protocol returning a string""" - - -class FingerSetterFactoryFromService(protocol.ServerFactory): - - __implements__ = IFingerSetterFactory, - - protocol = FingerSetterProtocol - - def __init__(self, service): - self.service = service - - def setUser(self, user, status): - self.service.setUser(user, status) - - -components.registerAdapter(FingerSetterFactoryFromService, - IFingerSettingService) - -class IRCReplyBot(irc.IRCClient): - - def connectionMade(self): - self.nickname = self.factory.nickname - irc.IRCClient.connectionMade(self) - - def privmsg(self, user, channel, msg): - if user.lower() == channel.lower(): - d = self.factory.getUser(msg) - d.addErrback(catchError) - d.addCallback(lambda m: "Status of %s: %s" % (user, m)) - d.addCallback(lambda m: self.msg(user, m)) - - -class IIRCClientFactory(components.Interface): - - ''' - @ivar nickname - ''' - - def getUser(self, user): - """Return a deferred returning a string""" - - def buildProtocol(self, addr): - """Return a protocol""" - - -class IRCClientFactoryFromService(protocol.ClientFactory): - - __implements__ = IIRCClientFactory, - - protocol = IRCReplyBot - nickname = None - - def __init__(self, service): - self.service = service - - def getUser(self, user): - return self.service.getUser() - -components.registerAdapter(IRCClientFactoryFromService, IFingerService) - -class UserStatusTree(resource.Resource): - - def __init__(self, service): - resource.Resource.__init__(self): - self.putChild('RPC2.0', UserStatusXR(self.service)) - self.service = service - - def render(self, request): - d = self.service.getUsers() - def formatUsers(users): - l = ["<li><a href="%s">%s</a></li> % (user, user) - for user in users] - return '<ul>'+''.join(l)+'</ul>' - d.addCallback(formatUsers) - d.addCallback(request.write) - d.addCallback(lambda _: request.finish()) - return server.NOT_DONE_YET - - def getChild(self, path, request): - return UserStatus(path, self.service) - -components.registerAdapter(UserStatusTree, IFingerService) - -class UserStatus(resource.Resource): - - def __init__(self, user, service): - resource.Resource.__init__(self): - self.user = user - self.service = service - - def render(self, request): - d = self.service.getUser(self.user) - d.addCallback(cgi.escape) - d.addCallback(lambda m: - '<h1>%s</h1>'%self.user+'<p>%s</p>'%m) - d.addCallback(request.write) - d.addCallback(lambda _: request.finish()) - return server.NOT_DONE_YET - - -class UserStatusXR(xmlrpc.XMLPRC): - - def __init__(self, service): - xmlrpc.XMLRPC.__init__(self) - self.service = service - - def xmlrpc_getUser(self, user): - return self.service.getUser(user) - - -class FingerService(app.ApplicationService): - - __implements__ = IFingerService, - - def __init__(self, file, *args, **kwargs): - app.ApplicationService.__init__(self, *args, **kwargs) - self.file = file - - def startService(self): - app.ApplicationService.startService(self) - self._read() - - def _read(self): - self.users = {} - for line in file(self.file): - user, status = line.split(':', 1) - self.users[user] = status - self.call = reactor.callLater(30, self._read) - - def stopService(self): - app.ApplicationService.stopService(self) - self.call.cancel() - - def getUser(self, user): - return defer.succeed(self.users.get(u, "No such user")) - - def getUsers(self): - return defer.succeed(self.users.keys()) - - -application = app.Application('finger', uid=1, gid=1) -f = FingerService('/etc/users', application, 'finger') -application.listenTCP(79, IFingerFactory(f)) -application.listenTCP(80, server.Site(resource.IResource(f))) -i = IIRCClientFactory(f) -i.nickname = 'fingerbot' -application.connectTCP('irc.freenode.org', 6667, i) -- -
In the last version, the service class was three times longer than -any other class, and was hard to understand. This was because it turned -out to have multiple responsibilities. It had to know how to access -user information, by scheduling a reread of the file ever half minute, -but also how to display itself in a myriad of protocols. Here, we -used the component-based architecture that Twisted provides to achieve -a separation of concerns. All the service is responsible for, now, -is supporting getUser/getUsers. It declares its support via the -__implements__ keyword. Then, adapters are used to make this service -look like an appropriate class for various things: for supplying -a finger factory to listenTCP, for supplying a resource to site's -constructor, and to provide an IRC client factory for connectTCP. -All the adapters use are the methods in FingerService they are -declared to use: getUser/getUsers. We could, of course, -skipped the interfaces and let the configuration code use -things like FingerFactoryFromService(f) directly. However, using -interfaces provides the same flexibility inheritance gives: future -subclasses can override the adapters.
- - - --class MemoryFingerService(app.ApplicationService): - __implements__ = IFingerService, IFingerSetterService - - def __init__(self, *args, **kwargs): - app.ApplicationService.__init__(self, *args) - self.users = kwargs - - def getUser(self, user): - return defer.succeed(self.users.get(u, "No such user")) - - def getUsers(self): - return defer.succeed(self.users.keys()) - - def setUser(self, user, status): - self.users[user] = status - -application = app.Application('finger', uid=1, gid=1) -# New constructor call -f = MemoryFingerService(application, 'finger', moshez='Happy and well') -application.listenTCP(79, IFingerFactory(f)) -application.listenTCP(80, server.Site(resource.IResource(f))) -i = IIRCClientFactory(f) -i.nickname = 'fingerbot' -application.connectTCP('irc.freenode.org', 6667, i) -# New: run setter too -application.listenTCP(1079, IFingerSetterFactory(f), interface='127.0.0.1') -- -
Here we show just how convenient it is to implement new backends -when we move to a component based architecture. Note that here -we also use an interface we previously wrote, FingerSetterFactory, -by supporting one single method. We manage to preserve the service's -ignorance of the network.
- --class LocalFingerService(app.ApplicationService): - __implements__ = IFingerService - - def getUser(self, user): - return utils.getProcessOutput("finger", [user]) - - def getUsers(self): - return defer.succeed([]) - -application = app.Application('finger', uid=1, gid=1) -f = LocalFingerService(application, 'finger') -application.listenTCP(79, IFingerFactory(f)) -application.listenTCP(80, server.Site(resource.IResource(f))) -i = IIRCClientFactory(f) -i.nickname = 'fingerbot' -application.connectTCP('irc.freenode.org', 6667, i) -- -
We have already wrote this, but now we get more for less work: -the network code is completely separate from the backend.
- --import pwd - -class LocalFingerService(app.ApplicationService): - __implements__ = IFingerService - - def getUser(self, user): - try: - entry = pwd.getpwnam(user) - except KeyError: - return "No such user" - try: - f=file(os.path.join(entry[5],'.plan')) - except (IOError, OSError): - return "No such user" - data = f.read() - f.close() - return data - - def getUsers(self): - return defer.succeed([]) - -application = app.Application('finger', uid=1, gid=1) -f = LocalFingerService(application, 'finger') -application.listenTCP(79, IFingerFactory(f)) -application.listenTCP(80, server.Site(resource.IResource(f))) -i = IIRCClientFactory(f) -i.nickname = 'fingerbot' -application.connectTCP('irc.freenode.org', 6667, i) -- -
Not much to say about that, except to indicate that by now we -can be churning out backends like crazy. Feel like doing a backend -for advogato, for example? Dig out the XML-RPC client support Twisted -has, and get to work!
- - --# Do everything properly, and componentize -from twisted.internet import protocol, reactor, defer, app -from twisted.protocols import basic, irc -from twisted.python import components -from twisted.web import resource, server, static, xmlrpc, microdom -from twisted.web.woven import page, widget -import cgi - -class IFingerService(components.Interface): - - def getUser(self, user): - '''Return a deferred returning a string''' - - def getUsers(self): - '''Return a deferred returning a list of strings''' - -class IFingerSettingService(components.Interface): - - def setUser(self, user, status): - '''Set the user's status to something''' - -def catchError(err): - return "Internal error in server" - - -class FingerProtocol(basic.LineReceiver): - - def lineReceived(self, user): - d = self.factory.getUser(user) - d.addErrback(catchError) - def writeValue(value): - self.transport.write(value) - self.transport.loseConnection() - d.addCallback(writeValue) - - -class IFingerFactory(components.Interface): - - def getUser(self, user): - """Return a deferred returning a string"""" - - def buildProtocol(self, addr): - """Return a protocol returning a string"""" - - -class FingerFactoryFromService(protocol.ServerFactory): - - __implements__ = IFingerFactory, - - protocol = FingerProtocol - - def __init__(self, service): - self.service = service - - def getUser(self, user): - return self.service.getUser(user) - -components.registerAdapter(FingerFactoryFromService, IFingerService) - - -class FingerSetterProtocol(basic.LineReceiver): - - def connectionMade(self): - self.lines = [] - - def lineReceived(self, line): - self.lines.append(line) - - def connectionLost(self): - if len(self.lines) == 2: - self.factory.setUser(*self.lines) - - -class IFingerSetterFactory(components.Interface): - - def setUser(self, user, status): - """Return a deferred returning a string""" - - def buildProtocol(self, addr): - """Return a protocol returning a string""" - - -class FingerSetterFactoryFromService(protocol.ServerFactory): - - __implements__ = IFingerSetterFactory, - - protocol = FingerSetterProtocol - - def __init__(self, service): - self.service = service - - def setUser(self, user, status): - self.service.setUser(user, status) - - -components.registerAdapter(FingerSetterFactoryFromService, - IFingerSettingService) - -class IRCReplyBot(irc.IRCClient): - - def connectionMade(self): - self.nickname = self.factory.nickname - irc.IRCClient.connectionMade(self) - - def privmsg(self, user, channel, msg): - if user.lower() == channel.lower(): - d = self.factory.getUser(msg) - d.addErrback(catchError) - d.addCallback(lambda m: "Status of %s: %s" % (user, m)) - d.addCallback(lambda m: self.msg(user, m)) - - -class IIRCClientFactory(components.Interface): - - ''' - @ivar nickname - ''' - - def getUser(self, user): - """Return a deferred returning a string""" - - def buildProtocol(self, addr): - """Return a protocol""" - - -class IRCClientFactoryFromService(protocol.ClientFactory): - - __implements__ = IIRCClientFactory, - - protocol = IRCReplyBot - nickname = None - - def __init__(self, service): - self.service = service - - def getUser(self, user): - return self.service.getUser() - -components.registerAdapter(IRCClientFactoryFromService, IFingerService) - - -class UsersModel(model.MethodModel): - - def __init__(self, service): - self.service = service - - def wmfactory_users(self): - return self.service.getUsers() - -components.registerAdapter(UsersModel, IFingerService) - -class UserStatusTree(page.Page): - - template = """<html><head><title>Users</title><head><body> - <h1>Users</h1> - <ul model="users" view="List"> - <li pattern="listItem" /><a view="Link" model="." - href="dummy"><span model="." view="Text" /></a> - </ul></body></html>""" - - def initialize(self, **kwargs): - self.putChild('RPC2.0', UserStatusXR(self.model.service)) - - def getDynamicChild(self, path, request): - return UserStatus(user=path, service=self.model.service) - -components.registerAdapter(UserStatusTree, IFingerService) - - -class UserStatus(page.Page): - - template='''<html><head><title view="Text" model="user"/></heaD> - <body><h1 view="Text" model="user"/> - <p mode="status" view="Text" /> - </body></html>''' - - def initialize(self, **kwargs): - self.user = kwargs['user'] - self.service = kwargs['service'] - - def wmfactory_user(self): - return self.user - - def wmfactory_status(self): - return self.service.getUser(self.user) - -class UserStatusXR(xmlrpc.XMLPRC): - - def __init__(self, service): - xmlrpc.XMLRPC.__init__(self) - self.service = service - - def xmlrpc_getUser(self, user): - return self.service.getUser(user) - - -class FingerService(app.ApplicationService): - - __implements__ = IFingerService, - - def __init__(self, file, *args, **kwargs): - app.ApplicationService.__init__(self, *args, **kwargs) - self.file = file - - def startService(self): - app.ApplicationService.startService(self) - self._read() - - def _read(self): - self.users = {} - for line in file(self.file): - user, status = line.split(':', 1) - self.users[user] = status - self.call = reactor.callLater(30, self._read) - - def stopService(self): - app.ApplicationService.stopService(self) - self.call.cancel() - - def getUser(self, user): - return defer.succeed(self.users.get(u, "No such user")) - - def getUsers(self): - return defer.succeed(self.users.keys()) - - -application = app.Application('finger', uid=1, gid=1) -f = FingerService('/etc/users', application, 'finger') -application.listenTCP(79, IFingerFactory(f)) -application.listenTCP(80, server.Site(resource.IResource(f))) -i = IIRCClientFactory(f) -i.nickname = 'fingerbot' -application.connectTCP('irc.freenode.org', 6667, i) -- -
Here we convert to using Woven, instead of manually -constructing HTML snippets. Woven is a sophisticated web templating -system. Its main features are to disallow any code inside the HTML, -and transparent integration with deferred results.
- --# Do everything properly, and componentize -from twisted.internet import protocol, reactor, defer, app -from twisted.protocols import basic, irc -from twisted.python import components -from twisted.web import resource, server, static, xmlrpc, microdom -from twisted.web.woven import page, widget -from twisted.spread import pb -import cgi - -class IFingerService(components.Interface): - - def getUser(self, user): - '''Return a deferred returning a string''' - - def getUsers(self): - '''Return a deferred returning a list of strings''' - -class IFingerSettingService(components.Interface): - - def setUser(self, user, status): - '''Set the user's status to something''' - -def catchError(err): - return "Internal error in server" - - -class FingerProtocol(basic.LineReceiver): - - def lineReceived(self, user): - d = self.factory.getUser(user) - d.addErrback(catchError) - def writeValue(value): - self.transport.write(value) - self.transport.loseConnection() - d.addCallback(writeValue) - - -class IFingerFactory(components.Interface): - - def getUser(self, user): - """Return a deferred returning a string"""" - - def buildProtocol(self, addr): - """Return a protocol returning a string"""" - - -class FingerFactoryFromService(protocol.ServerFactory): - - __implements__ = IFingerFactory, - - protocol = FingerProtocol - - def __init__(self, service): - self.service = service - - def getUser(self, user): - return self.service.getUser(user) - -components.registerAdapter(FingerFactoryFromService, IFingerService) - - -class FingerSetterProtocol(basic.LineReceiver): - - def connectionMade(self): - self.lines = [] - - def lineReceived(self, line): - self.lines.append(line) - - def connectionLost(self): - if len(self.lines) == 2: - self.factory.setUser(*self.lines) - - -class IFingerSetterFactory(components.Interface): - - def setUser(self, user, status): - """Return a deferred returning a string""" - - def buildProtocol(self, addr): - """Return a protocol returning a string""" - - -class FingerSetterFactoryFromService(protocol.ServerFactory): - - __implements__ = IFingerSetterFactory, - - protocol = FingerSetterProtocol - - def __init__(self, service): - self.service = service - - def setUser(self, user, status): - self.service.setUser(user, status) - - -components.registerAdapter(FingerSetterFactoryFromService, - IFingerSettingService) - -class IRCReplyBot(irc.IRCClient): - - def connectionMade(self): - self.nickname = self.factory.nickname - irc.IRCClient.connectionMade(self) - - def privmsg(self, user, channel, msg): - if user.lower() == channel.lower(): - d = self.factory.getUser(msg) - d.addErrback(catchError) - d.addCallback(lambda m: "Status of %s: %s" % (user, m)) - d.addCallback(lambda m: self.msg(user, m)) - - -class IIRCClientFactory(components.Interface): - - ''' - @ivar nickname - ''' - - def getUser(self, user): - """Return a deferred returning a string""" - - def buildProtocol(self, addr): - """Return a protocol""" - - -class IRCClientFactoryFromService(protocol.ClientFactory): - - __implements__ = IIRCClientFactory, - - protocol = IRCReplyBot - nickname = None - - def __init__(self, service): - self.service = service - - def getUser(self, user): - return self.service.getUser() - -components.registerAdapter(IRCClientFactoryFromService, IFingerService) - - -class UsersModel(model.MethodModel): - - def __init__(self, service): - self.service = service - - def wmfactory_users(self): - return self.service.getUsers() - -components.registerAdapter(UsersModel, IFingerService) - -class UserStatusTree(page.Page): - - template = """<html><head><title>Users</title><head><body> - <h1>Users</h1> - <ul model="users" view="List"> - <li pattern="listItem" /><a view="Link" model="." - href="dummy"><span model="." view="Text" /></a> - </ul></body></html>""" - - def initialize(self, **kwargs): - self.putChild('RPC2.0', UserStatusXR(self.model.service)) - - def getDynamicChild(self, path, request): - return UserStatus(user=path, service=self.model.service) - -components.registerAdapter(UserStatusTree, IFingerService) - - -class UserStatus(page.Page): - - template='''<html><head><<title view="Text" model="user"/></heaD> - <body><h1 view="Text" model="user"/> - <p mode="status" view="Text" /> - </body></html>''' - - def initialize(self, **kwargs): - self.user = kwargs['user'] - self.service = kwargs['service'] - - def wmfactory_user(self): - return self.user - - def wmfactory_status(self): - return self.service.getUser(self.user) - -class UserStatusXR(xmlrpc.XMLPRC): - - def __init__(self, service): - xmlrpc.XMLRPC.__init__(self) - self.service = service - - def xmlrpc_getUser(self, user): - return self.service.getUser(user) - - -class IPerspectiveFinger(components.Interface): - - def remote_getUser(self, username): - """return a user's status""" - - def remote_getUsers(self): - """return a user's status""" - - -class PerspectiveFingerFromService(pb.Root): - - __implements__ = IPerspectiveFinger, - - def __init__(self, service): - self.service = service - - def remote_getUser(self, username): - return self.service.getUser(username) - - def remote_getUsers(self): - return self.service.getUsers() - -components.registerAdapter(PerspectiveFingerFromService, IFingerService) - - -class FingerService(app.ApplicationService): - - __implements__ = IFingerService, - - def __init__(self, file, *args, **kwargs): - app.ApplicationService.__init__(self, *args, **kwargs) - self.file = file - - def startService(self): - app.ApplicationService.startService(self) - self._read() - - def _read(self): - self.users = {} - for line in file(self.file): - user, status = line.split(':', 1) - self.users[user] = status - self.call = reactor.callLater(30, self._read) - - def stopService(self): - app.ApplicationService.stopService(self) - self.call.cancel() - - def getUser(self, user): - return defer.succeed(self.users.get(u, "No such user")) - - def getUsers(self): - return defer.succeed(self.users.keys()) - - -application = app.Application('finger', uid=1, gid=1) -f = FingerService('/etc/users', application, 'finger') -application.listenTCP(79, IFingerFactory(f)) -application.listenTCP(80, server.Site(resource.IResource(f))) -i = IIRCClientFactory(f) -i.nickname = 'fingerbot' -application.connectTCP('irc.freenode.org', 6667, i) -application.listenTCP(8889, pb.BrokerFactory(IPerspectiveFinger(f)) -- -
We add support for perspective broker, Twisted's native remote -object protocol. Now, Twisted clients will not have to go through -XML-RPCish contortions to get information about users.
- --# Do everything properly, and componentize -from twisted.internet import protocol, reactor, defer, app -from twisted.protocols import basic, irc -from twisted.python import components -from twisted.web import resource, server, static, xmlrpc, microdom -from twisted.web.woven import page, widget -from twisted.spread import pb -from OpenSSL import SSL -import cgi - -class IFingerService(components.Interface): - - def getUser(self, user): - '''Return a deferred returning a string''' - - def getUsers(self): - '''Return a deferred returning a list of strings''' - -class IFingerSettingService(components.Interface): - - def setUser(self, user, status): - '''Set the user's status to something''' - -def catchError(err): - return "Internal error in server" - - -class FingerProtocol(basic.LineReceiver): - - def lineReceived(self, user): - d = self.factory.getUser(user) - d.addErrback(catchError) - def writeValue(value): - self.transport.write(value) - self.transport.loseConnection() - d.addCallback(writeValue) - - -class IFingerFactory(components.Interface): - - def getUser(self, user): - """Return a deferred returning a string"""" - - def buildProtocol(self, addr): - """Return a protocol returning a string"""" - - -class FingerFactoryFromService(protocol.ServerFactory): - - __implements__ = IFingerFactory, - - protocol = FingerProtocol - - def __init__(self, service): - self.service = service - - def getUser(self, user): - return self.service.getUser(user) - -components.registerAdapter(FingerFactoryFromService, IFingerService) - - -class FingerSetterProtocol(basic.LineReceiver): - - def connectionMade(self): - self.lines = [] - - def lineReceived(self, line): - self.lines.append(line) - - def connectionLost(self): - if len(self.lines) == 2: - self.factory.setUser(*self.lines) - - -class IFingerSetterFactory(components.Interface): - - def setUser(self, user, status): - """Return a deferred returning a string""" - - def buildProtocol(self, addr): - """Return a protocol returning a string""" - - -class FingerSetterFactoryFromService(protocol.ServerFactory): - - __implements__ = IFingerSetterFactory, - - protocol = FingerSetterProtocol - - def __init__(self, service): - self.service = service - - def setUser(self, user, status): - self.service.setUser(user, status) - - -components.registerAdapter(FingerSetterFactoryFromService, - IFingerSettingService) - -class IRCReplyBot(irc.IRCClient): - - def connectionMade(self): - self.nickname = self.factory.nickname - irc.IRCClient.connectionMade(self) - - def privmsg(self, user, channel, msg): - if user.lower() == channel.lower(): - d = self.factory.getUser(msg) - d.addErrback(catchError) - d.addCallback(lambda m: "Status of %s: %s" % (user, m)) - d.addCallback(lambda m: self.msg(user, m)) - - -class IIRCClientFactory(components.Interface): - - ''' - @ivar nickname - ''' - - def getUser(self, user): - """Return a deferred returning a string""" - - def buildProtocol(self, addr): - """Return a protocol""" - - -class IRCClientFactoryFromService(protocol.ClientFactory): - - __implements__ = IIRCClientFactory, - - protocol = IRCReplyBot - nickname = None - - def __init__(self, service): - self.service = service - - def getUser(self, user): - return self.service.getUser() - -components.registerAdapter(IRCClientFactoryFromService, IFingerService) - - -class UsersModel(model.MethodModel): - - def __init__(self, service): - self.service = service - - def wmfactory_users(self): - return self.service.getUsers() - -components.registerAdapter(UsersModel, IFingerService) - -class UserStatusTree(page.Page): - - template = """<html><head><title>Users</title><head><body> - <h1>Users</h1> - <ul model="users" view="List"> - <li pattern="listItem" /><a view="Link" model="." - href="dummy"><span model="." view="Text" /></a> - </ul></body></html>""" - - def initialize(self, **kwargs): - self.putChild('RPC2.0', UserStatusXR(self.model.service)) - - def getDynamicChild(self, path, request): - return UserStatus(user=path, service=self.model.service) - -components.registerAdapter(UserStatusTree, IFingerService) - -class UserStatus(page.Page): - - template='''<html><head><title view="Text" model="user"/></heaD> - <body><h1 view="Text" model="user"/> - <p mode="status" view="Text" /> - </body></html>''' - - def initialize(self, **kwargs): - self.user = kwargs['user'] - self.service = kwargs['service'] - - def wmfactory_user(self): - return self.user - - def wmfactory_status(self): - return self.service.getUser(self.user) - -class UserStatusXR(xmlrpc.XMLPRC): - - def __init__(self, service): - xmlrpc.XMLRPC.__init__(self) - self.service = service - - def xmlrpc_getUser(self, user): - return self.service.getUser(user) - - -class IPerspectiveFinger(components.Interface): - - def remote_getUser(self, username): - """return a user's status""" - - def remote_getUsers(self): - """return a user's status""" - - -class PerspectiveFingerFromService(pb.Root): - - __implements__ = IPerspectiveFinger, - - def __init__(self, service): - self.service = service - - def remote_getUser(self, username): - return self.service.getUser(username) - - def remote_getUsers(self): - return self.service.getUsers() - -components.registerAdapter(PerspectiveFingerFromService, IFingerService) - - -class FingerService(app.ApplicationService): - - __implements__ = IFingerService, - - def __init__(self, file, *args, **kwargs): - app.ApplicationService.__init__(self, *args, **kwargs) - self.file = file - - def startService(self): - app.ApplicationService.startService(self) - self._read() - - def _read(self): - self.users = {} - for line in file(self.file): - user, status = line.split(':', 1) - self.users[user] = status - self.call = reactor.callLater(30, self._read) - - def stopService(self): - app.ApplicationService.stopService(self) - self.call.cancel() - - def getUser(self, user): - return defer.succeed(self.users.get(u, "No such user")) - - def getUsers(self): - return defer.succeed(self.users.keys()) - - -class ServerContextFactory: - - def getContext(self): - """Create an SSL context. - - This is a sample implementation that loads a certificate from a file - called 'server.pem'.""" - ctx = SSL.Context(SSL.SSLv23_METHOD) - ctx.use_certificate_file('server.pem') - ctx.use_privatekey_file('server.pem') - return ctx - - -application = app.Application('finger', uid=1, gid=1) -f = FingerService('/etc/users', application, 'finger') -application.listenTCP(79, IFingerFactory(f)) -site = server.Site(resource.IResource(f)) -application.listenTCP(80, site) -application.listenSSL(443, site, ServerContextFactory()) -i = IIRCClientFactory(f) -i.nickname = 'fingerbot' -application.connectTCP('irc.freenode.org', 6667, i) -application.listenTCP(8889, pb.BrokerFactory(IPerspectiveFinger(f)) -- -
All we need to do to code an HTTPS site is just write a context -factory (in this case, which loads the certificate from a certain file) -and then use the listenSSL method. Note that one factory (in this -case, a site) can listen on multiple ports with multiple protocols.
- --class FingerClient(protocol.Protocol): - - def connectionMade(self): - self.transport.write(self.factory.user+"\r\n") - self.buf = [] - - def dataReceived(self, data): - self.buf.append(data) - - def connectionLost(self): - self.factory.gotData(''.join(self.buf)) - - -class FingerClientFactory(protocol.ClientFactory): - - protocol = FingerClient - - def __init__(self, user): - self.user = user - self.d = defer.Deferred() - - def clientConnectionFailed(self, _, reason): - self.d.errback(reason) - - def gotData(self, data): - self.d.callback(data) - - -def finger(user, host, port=79): - f = FingerClientFactory(user) - reactor.connectTCP(host, port, f) - return f.d - -class ProxyFingerService(app.ApplicationService): - __implements__ = IFingerService - - def getUser(self, user): - user, host = user.split('@', 1) - ret = finger(user, host) - ret.addErrback(lambda _: "Could not connect to remote host") - return ret - - def getUsers(self): - return defer.succeed([]) - -application = app.Application('finger', uid=1, gid=1) -f = ProxyFingerService(application, 'finger') -application.listenTCP(79, IFingerFactory(f)) -- -
Writing new clients with Twisted is much like writing new servers. -We implement the protocol, which just gathers up all the data, and -give it to the factory. The factory keeps a deferred which is triggered -if the connection either fails or succeeds. When we use the client, -we first make sure the deferred will never fail, by producing a message -in that case. Implementing a wrapper around client which just returns -the deferred is a common pattern. While being less flexible than -using the factory directly, it is also more convenient.
- -application=
- line.-from twisted.internet import app -from finger import FingerService, IIRCclient, ServerContextFactory, \ - IFingerFactory, IPerspectiveFinger -from twisted.web import resource, server -from twisted.spread import pb - -application = app.Application('finger', uid=1, gid=1) -f = FingerService('/etc/users', application, 'finger') -application.listenTCP(79, IFingerFactory(f)) -r = resource.IResource(f) -r.templateDirectory = '/usr/share/finger/templates/' -site = server.Site(r) -application.listenTCP(80, site) -application.listenSSL(443, site, ServerContextFactory()) -i = IIRCClientFactory(f) -i.nickname = 'fingerbot' -application.connectTCP('irc.freenode.org', 6667, i) -application.listenTCP(8889, pb.BrokerFactory(IPerspectiveFinger(f)) -- -
We can also supply easy configuration for common cases
- --# in finger.py moudle -def updateApplication(app, **kwargs): - f = FingerService(kwargs['users'], application, 'finger') - application.listenTCP(79, IFingerFactory(f)) - r = resource.IResource(f) - r.templateDirectory = kwargs['templates'] - site = server.Site(r) - app.listenTCP(80, site) - if kwargs.get('ssl'): - app.listenSSL(443, site, ServerContextFactory()) - if 'ircnick' in kwargs: - i = IIRCClientFactory(f) - i.nickname = kwargs['ircnick'] - ircServer = kwargs['ircserver'] - application.connectTCP(ircserver, 6667, i) - if 'pbport' in kwargs: - application.listenTCP(int(kwargs['pbport']), - pb.BrokerFactory(IPerspectiveFinger(f)) -- -
And we can write simpler files now:
- --# simple-finger.tpy -from twisted.internet import app -import finger - -application = app.Application('finger', uid=1, gid=1) -finger.updateApplication(application, - users='/etc/users', - templatesDirectory='/usr/share/finger/templates', - ssl=1, - ircnick='fingerbot', - ircserver='irc.freenode.net', - pbport=8889 -) -- -
Note: the finger user still has ultimate power: he can use -updateApplication, or he can use the lower-level interface if he has -specific needs (maybe an ircserver on some other port? maybe we -want the non-ssl webserver to listen only locally? etc. etc.) -This is an important design principle: never force a layer of abstraction: -allow usage of layers of abstractions.
- -The pasta theory of design:
- -So far, the user had to be somewhat of a programmer to use this. -Maybe we can eliminate even that? Move old code to -"finger/service.py", put empty "__init__.py" and...
- --# finger/tap.py -from twisted.python import usage -from finger import service - -class Options(usage.Options): - - optParams = [ - ['users', 'u', '/etc/users'], - ['templatesDirectory', 't', '/usr/share/finger/templates'], - ['ircnick', 'n', 'fingerbot'], - ['ircserver', None, 'irc.freenode.net'], - ['pbport', 'p', 8889], - ] - - optFlags = [['ssl', 's']] - -def updateApplication(app, config): - service.updateApplication(app, **config) -- -
And register it all:
- --#finger/plugins.tml -register('Finger', 'finger.tap', type='tap', tapname='finger') -- -
And now, the following works
- --% mktap finger --users=/usr/local/etc/users --ircnick=moshez-finger -% sudo twistd -f finger.tap -- -
If we already have the "finger" package installed, we can achieve -easy integration:
- -on Debian--
- --% tap2deb --unsigned -m "Foo- -" --type=python finger.tpy -% sudo dpkg -i .build/*.deb -
On Red Hat [or Mandrake]
- --% tap2rpm --type=python finger.tpy #[maybe other options needed] -% sudo rpm -i .build/*.rpm -- -
Will properly register configuration files, init.d sripts, etc. etc.
- -If it doesn't work on your favourite OS: patches accepted!
- -[translated roughly from Hebrew]
- -{self.content}' - - -class Raw: - def __init__(self, content): - self.content = content - - def toHTML(self): - return self.content + "\n" - - -lecture = Lecture( - "Applications of Twisted", - Slide( - "Twisted.names", - Bullet( - "Domain Name Server", - SubBullet( - Bullet("Authoritative"), - Bullet("Caching"), - Bullet("Other!"), - ), - ), - Bullet("Domain Name Client"), - ), - Slide( - "Mostly Functional", - Bullet( - "All common records support; 22 supported total", - SubBullet( - Bullet("A, NS, CNAME, SOA, PTR, HINFO, MX, TXT"), - Bullet("IPv6 records AAAA and A6"), - ), - ), - Bullet("No DNSSEC support"), - Bullet("Server and Client functionality"), - ), - Slide( - "Rapidly Developed", - Bullet( - "One month initial development period", - SubBullet( - Bullet("Python is good for rapid development"), - Bullet("Twisted handles all the boring network details"), - ), - ), - Bullet( - "Easily extended", - SubBullet( - Bullet("Doesn't choke on unrecognized record types"), - Bullet( - "Support for a new record type can be added in " - "just a few minutes" - ), - Bullet( - PythonSource( - """\ -from twisted.protocols import dns - -class Record_A: - __implements__ = (dns.IEncodable,) - TYPE = dns.QUERY_TYPES['A'] = 1 - - def __init__(self, address = '0.0.0.0'): - self.address = socket.inet_aton(address) - - def encode(self, strio, compDict = None): - strio.write(self.address) - - def decode(self, strio, length = None): - self.address = readPrecisely(strio, 4) -""" - ) - ), - ), - ), - ), - Slide( - "Easily Configured", - Bullet("BIND zonefile syntax"), - Bullet( - "Python source", - SubBullet( - Bullet( - PythonSource( - """\ -zone = [ - AAAA('intarweb.us', '3ffe:b80:1886:1::1'), - SRV('_http._tcp.www.intarweb.us', 0, 0, 8080, 'intarweb.us'), - MX('intarweb.us', 10, 'mail.intarweb.us') -] -""" - ) - ), - ), - ), - Bullet( - "Twisted's mktap and twistd tools", - SubBullet( - PRE("mktap dns --pyzone a.domain.zonefile --recursive --cache"), - PRE("twistd -f dns.tap"), - ), - ), - ), - Slide( - "Client API", - Bullet( - "Asynchronous", - SubBullet( - Bullet("All lookup functions return Deferred objects"), - Bullet( - PythonSource( - """\ -import random -from twisted.names import client - -def addressFor(service, protocol, domain): - d = client.theResolver.lookupService( - '_%s._%s.%s' % (service, protocol, domain) - ) - - def grabPayload((answers, authority, additional)): - return [r.payload for r in answers] - - def randomAnswer(results): - if len(results) == 1 and results[0] == '.': - raise RuntimeException, "No service records found" - return random.choice(results) - - return d.addCallback(grabPayload).addCallback(randomAnswer) -""" - ) - ), - ), - ), - ), - Slide( - "Uses", - Bullet( - "Service Records", - SubBullet( - Bullet("Potential to simplify user experience"), - Bullet("Not widely accessible"), - Bullet("Names' client API makes accessing them trivial"), - ), - ), - ), - Slide( - "Pynfo: A Network Information 'Bot", - Bullet( - "A 'bot with the goal of integrating access to miscellaneous data inputs" - ), - ), - Slide( - "Architecture", - Bullet( - "Factories", - SubBullet( - Bullet("Takes care of connecting to different services"), - Bullet("Acts as a central storage for shared data"), - Bullet("Currently only IRC is supported"), - Bullet("Planned support for web, IM, and PB interfaces"), - ), - ), - Bullet( - "Protocols", - SubBullet( - Bullet("Created by Factories"), - Bullet("Handles all service-specific interaction"), - Bullet("Refers back to the factory for shared data"), - Bullet("Current support for IRC only"), - ), - ), - Image("pynfo-chart.png"), - Bullet( - "Separation of Factory and Protocols", - SubBullet( - Bullet("Per-protocol data separate, per-robot data shared"), - Bullet( - "Protocols destroyed on disconnect, factory manage reconnecting" - ), - ), - ), - ), - Slide( - "Plugins", - Bullet( - "Plugins are modules plus some metadata", - SubBullet( - Bullet("A plugin name"), - Bullet("A plugin description"), - Bullet("A plugin type"), - Bullet("Any other data appropriate for the type"), - ), - ), - Bullet("Initialization / Finalization hooks"), - Bullet("Input filtering, for behaviors like ignore"), - Bullet( - "An example", - SubBullet( - Bullet( - PythonSource( - """ -from twisted.names import client -def info_LOOKUP(bot, user, channel, query): - def tellUserResponse((ans, auth, add)): - bot.reply(user, "%s: %s" % (query, [str(a.payload) for a in ans])) - - def tellUserError(failure): - bot.reply(user, "Host lookup failed.") - - client.lookupAddress(query).addCallbacks( - tellUserResponse, tellUserError - ) -""" - ) - ), - ), - ), - ), - Slide( - "But where do they come from?", - Bullet( - "Twisted's plugin module", - SubBullet( - Bullet( - PythonSource( - """\ -from twisted.python import plugin -class InfoBotFactory: - ... - def loadPlugins(self): - ... - p = plugin.getPlugIns('infobot') - .... -""" - ) - ), - ), - ), - ), - Slide( - "Persistence", - Bullet("Addresses to connect to and protocols to use"), - Bullet("Administrators, passwords, keys"), - Bullet("Connection statistics"), - Bullet("Plugins can store objects for later retrieval"), - ), - Slide( - "Components", - Bullet( - "Shared and pluggable behavior is implemented as Adapters for Interfaces", - SubBullet( - Bullet("IScheduler, IStorage, IAuthenticator"), - ), - ), - Bullet( - "Plugins can register their own adapters for the factory", - SubBullet( - Bullet("Gracefully add new capabilities without __class__ hacks"), - Bullet("Share capabilities with other plugins"), - Bullet("Avoids namespace collisions"), - ), - ), - ), - Slide( - "Interaction", - Bullet("Commands and responses are issued through normal protocol actions"), - Bullet('Three levels of command "security"'), - Bullet( - "Access to some commands is unrestricted", - Raw("
"),
- SubBullet(" pyn: networks"),
- SubBullet(
- " Connected to: oftc -> ('irc.oftc.net', 6667), fn -> ('irc.freenode.net', 6667)"
- ),
- Raw("
"),
- ),
- Bullet(
- "Access to others is granted via an ACL",
- Raw(""),
- SubBullet(" pyn: rebuild"),
- SubBullet(" You aren't allowed to do that."),
- Raw("
"),
- ),
- Bullet(
- "Still further access is granted by the possession of a secret key",
- Raw(""),
- SubBullet(" pyn: spill self.transport.getHost()"),
- SubBullet(" Command queued. Challenge: BBKSkYCRCGQETx4kTmceUg==%"),
- SubBullet(" pyn: respond 2lwcgSVnJPzrW6Yvq7sg+g==%"),
- SubBullet(" ('INET', '192.168.123.137', 45539)"),
- Raw("
"),
- ),
- ),
- Slide(
- "Network Bridging",
- Bullet(
- "Pass messages between networks",
- Raw(""),
- SubBullet(" Yosomono (~fake@hostmask) has joined on efnet"),
- SubBullet(" hello"),
- Raw("
"),
- ),
- Bullet(
- "Requesting user information across networks",
- Raw(""),
- SubBullet(" pyn: whois Yosomono@efnet"),
- SubBullet(" Hostmask: Yosomono!fake@hostmask"),
- SubBullet(" Channels: @#python"),
- Raw("
"),
- ),
- ),
- Slide(
- "Conversation Logging",
- Bullet(
- "The 'conversation' command",
- SubBullet(
- Raw(""),
- SubBullet(
- " pyn: conversation begin PyCon example conversation"
- ),
- SubBullet(
- " Beginning tagged conversation 'PyCon example conversation'."
- ),
- SubBullet(" Hello, PyCon"),
- SubBullet(" Enjoy the example!"),
- SubBullet(
- " pyn: conversation end PyCon example conversation"
- ),
- SubBullet(
- " Ended tagged conversation 'PyCon example conversation'."
- ),
- Raw("
"),
- ),
- ),
- Bullet(
- "Web interface",
- SubBullet(
- URL(
- "http://c.intarweb.us:8008/%23tanstaafl/PyCon%20example%20conversation"
- ),
- ),
- ),
- Bullet("Search previous logs and add conversation tags"),
- ),
- Slide(
- "Various other plugins",
- Bullet("PyPI monitor and querying"),
- Bullet("Network specific operations - IRC operator module"),
- Bullet("Freshmeat and Google querying"),
- Bullet("Link shortener"),
- Bullet("PyMetar plugin"),
- Bullet("Manhole"),
- ),
- Slide("Questions?"),
-)
-
-lecture.renderHTML(".", "applications-%d.html", css="stylesheet.css")
diff --git a/docs/historic/2003/pycon/applications/applications.html b/docs/historic/2003/pycon/applications/applications.html
deleted file mode 100644
index f357d7f33ec..00000000000
--- a/docs/historic/2003/pycon/applications/applications.html
+++ /dev/null
@@ -1,343 +0,0 @@
-
-
-
-
-
- Jp Calderone
-exarkun@twistedmatrix.com
- -Two projects developed using the Twisted framework are described; - one, Twisted.names, which is included as part of the Twisted - distribution, a domain name server and client API, and one, Pynfo, which - is packaged separately, a network information robot.
- -The field of domain name servers is well explored and numerous - strong, widely-deployed implementations of the protocol exist. DNSSEC, - IPv6, service location, geographical location, and many of the other DNS - extension proposals all have high quality support in BIND, djbdns, - maradns, and others. From a client's perspective, though, the landscape - looks a little different. APIs to perform arbitrary domain name lookups - are sparse. In contrast, Twisted.names presents a richly featured, - asynchronous client API.
- -Names is capable of operating as a fully functional domain - name server. It implements caching, recursive lookups, and can act as - the authority for an arbitrary number of domains. It is not, however, a - finely tuned performance machine. Responding to queries can take about - twice the time other domain name servers might need. It has not been - investigated whether this is a design limitation or merely the result of - an unoptimized implementation.
- -As a client, Names provides an easy interface to every type of - record supported by. Looking up the MX records for a host, for example, - might look like this:
- -- def _cbMailExchange(results): - # Callback for MX query - answers = results[0] - print 'Mail Exchange is: ', answers - - def _ebMailExchange(failure): - # Error callback for MX query - print 'Lookup failed:' - failure.printTraceback() - - from twisted.names import client - d = client.lookupMailExchange('example-domain.com') - d.addCallbacks(_cbMailExchange, _ebMailExchange) -- -
Looking up other record types is as simple as calling a different
- lookup*
function.
As with most network software written using Twisted, the first step - in developing Names was to write the protocol support. In this - case, the protocol was DNS, and support was partially implemented. - However, it attempted to merge support for both UDP and TCP, and ended - up with less than optimal results. Much of this code was discarded, - though some of the lowest level encoding and decoding code worked well - and was re-used.
- -With the two protocol classes, DNSDatagramProtocol and DNSProtocol
- (the TCP version) implemented, the next step was to write classes which
- created the proper behavior for a domain name server. This logic was
- put in the twisted.names.server.DNSServerFactory
class,
- which in turn relies on several different kind of Resolver
s
- to find the appropriate response to queries it receives from the
- protocol instance.
The chain of execution, then, is this: a packet is received by the
- protocol object (a DNSDatagramProtocol
or
- DNSProtocol
instance); the packet is decoded by
- twisted.protocols.dns.RRHeader
in cooperation with one of
- the record classes (twisted.protocols.dns.Record_A
for
- example); the decoded twisted.protocols.dns.Query
object is
- passed up to the twisted.names.server.DNSServerFactory
,
- which determines the query type and invokes the appropriate lookup
- method on each of its resolver objects in turn; if an answer is found,
- it is passed back down to the protocol instance (otherwise the
- appropriate bit for an error condition is set), where it is encoded and
- transmitted back to the client.
There are four kinds of resolvers in the current implementation. The - first three are authorities, caches, and recursive resolvers. They are - generally queried, in this order, using the fourth resolver, the "chain" - resolver, which simply queries the resolvers it knows about, moving on - to the next when any given resolver fails to produce a response, and - generating the proper exception when the last resolver has failed.
- -There are several aspects of Twisted Names that might preclude its - use in "production" software. These issues stem mainly from its - immaturity, it being less than six months old at the writing of this - paper.
- -Possibly of foremost interest to those who might use it in a - high-load environment, it has somewhat poor runtime performance - characteristics. One potential reason for this is the extensive use of - exceptions to signal the relatively common case of a resolver lookup - failing. Solutions to this problem are apparent, but an implementation - change has not been attempted. Until this area of its development is - more fully examined, it will likely not be of use in anything other than - for low- to mid-load tasks, or with more hardware available to it than - might seem reasonable.
Pynfo was originally begun as a learning project to become acquainted - with the Twisted framework. After a brief initial development period - and an extended period of non-development, Pynfo was picked up again to - serve as a replacement for several existing robots, each with fragile - code bases and with designs not intended for future integration with - other services. After it subsumed the functions of network relaying and - Google searches, other desired features, which enhanced the IRC medium - and had not previously been considered due to the difficulty of - extending existing robots, were added to Pynfo, prompting the development - of an elementary plug-in system to further facilitate the integration - process.
- -Pynfo performs such simple tasks as noting the last time an - individual spoke and querying the Google search engine, as well as - several more complex operations like relaying traffic between different - IRC networks and publishing channel logs through an HTTP interface.
- -Toward these ends, it is useful to abstract the functionality into - several different layers:
- -The factory: All shared data, such as the channels a given user is - known to be in, the plugins currently loaded, and the addresses of servers - to connect to, is aggregated here. When it is necessary to make a - connection, the factory creates an instance of the appropriate Protocol - subclass, in a manner similar to this: - -
- def buildProtocol(self, address): - for net in self.data['networks'].values(): - if net.address == address: - break - - proto = IRCProtocol(net) - self.allBots[net.alias] = proto - proto.factory = self - return proto -- - The factory instance is created only once, and that instance persists - through the entire time a particular Pynfo bot operates. -
The protocol: Each kind of service Pynfo can connect to has a - Protocol class associated with it, a class which handles the specifics - of communicating over this protocol. Unlike the factory, protocols - instances can be short lived and are created and destroyed as many times - as network connectivity demands. When a Pynfo robot shuts down and is - serialized to disk, all Protocol instances are destroyed and discarded, - to be created anew when the robot is restarted.
-Plugins: These give Pynfo most of its functionality. From the - very simple logging module, which does no more than write strings to - disk, to the esoteric lookup module, which translates hostnames into - dotted-quads, to the informative dictionary module, which queries an online dictionary, plugins come in all shapes - and sizes, and can be written to fill almost any niche.
-Twisted provides a component system which Pynfo relies on to - split up useful functionality used in different areas of the code. The - Interface class is the primary element in the component system, and is - used as a location for a semi-format definition of an API, as well as - documentation. Classes declare that they implement an Interface by - including it in their __implements__ tuple attribute. Interfaces can - also be added to classes by third parties using the registerAdapter() - function. This takes an Adapter type in addition to the interface being - registered and the type it is being registered for. Adapters are a - objects which can store additional state information and implement - functionality without being part of the classes that are "actually" - being operated upon. They, as their name suggests, adapt components to - conform to interfaces.
- -Components can implement interfaces themselves, or maintain a cache - of adapter objects for each interfaces that is requested of them. These - persist like any other attribute, and so state stored in adapters - remains associated with the component as long as that component exists, or - until the adapter is explicitly removed.
- -Pynfo's Factory class uses two adapters to implement two basic - Interfaces that many plugins find useful. The first is the IStorage - interface. - -
- class IStorage(components.Interface): - - def store(self, key, version, value): - """ - Store any pickleable object - """ - - def retrieve(self, key, version): - """ - Retrieve the previously stored object associated with key and - version - """ -- An example usage of this interface is the PyPI plugin, which polls the - Python Package Index and reports updates to a configurable list of - outputs: - -
- def init(factory): - global notifyChannels - store = factory.getComponent(interfaces.IStorage) - try: - notifyChannels = store.retrieve('pypi', __version__) - except error.RetrievalError: - notifyChannels = [] - --
The module requests the component of factory which implements - IStorage, then attempts to load any previously stored version of - "notifyChannels". If none is found, it defaults to none. In the - finalizer below, this global is stored, using the same interfaced, to be - retrieved when the module is next initialized.
- -- def fini(factory): - s = factory.getComponent(interfaces.IStorage) - s.store('pypi', __version__, notifyChannels) -- - The second interface allows low granularity scheduling of events: - -
- class IScheduler(components.Interface): - MINUTELY = 60 - HOURLY = 60 * 60 - DAILY = 60 * 60 * 24 - WEEKLY = 60 * 60 * 24 * 7 - - - def schedule(self, period, fn, *args, **kw): - """ - Cause a function to be invoked at regular intervals with the given - arguments. - """ -- The Adapter which implements this interface is just as simple: -
- class SchedulerAdapter(components.Adapter): - __implements__ = (interfaces.IScheduler,) - - def schedule(self, period, fn, *args, **kw): - from twisted.internet import reactor - def cycle(): - fn(*args, **kw) - reactor.callLater(period, cycle) - reactor.callLater(period, cycle) -- - -
Implementing these interfaces as adapters using the component system - has two primary advantages over a simple inheritance or mixins approach. - First, it allows plugins to add completely new behavior to the system - without complex and fragile manipulation of the factory's __class__ - attribute. This is a big win when it comes to plugins that want to - share new functionality with other plugins. For example, the "ignore" - plugin adds an IDiscriminating interface and an adapter which implements - it. Once this plugin is loaded, any other plugin can request the - component for IDiscriminating and add users to or remove users from the - ignore list.
- -Before a module can be loaded and initialized as a plugin, it must be
- located. This could be done with a simple use of
- os.listdir()
, or __all__
could be set to include
- each new plugin added. Twisted provides another way, though.
The twisted.python.plugin
provides the most high-level
- interface to the plugin system, a function called
- getPlugIns
. It usually takes one argument, a plugin type,
- which is an arbitrary string used to categorize the different kinds of
- plugins available on a system. Twisted's own "mktap" tool uses the
- "tap" plugin type. For Pynfo, I have elected to use the "infobot"
- string. getPlugIns("infobot")
searches the system (by way
- of PYTHONPATH) for files named "plugins.tml". These files contain
- python source, and are run as such; a function, "register" is placed in
- their namespace, and the most common action for them is to invoke this
- function one or more times, providing information about a plugin. Here
- is a snippet from one which Pynfo uses:
- register( - "Weather", - "Pynfo.plugins.weather", - description="Commands to check the weather at " - "various places around the world.", - type="infobot" - ) -- -
Any number of plugin.tml files may exist in the filesystem, allowing - per-user and even per-robot plugins to be installed, all without - modifying the Pynfo installation itself. - - The second argument indicates the module which may be imported to get - this plugin. Pynfo traverses the resulting list, importing these modules, - and initializing them if necessary.
- - - diff --git a/docs/historic/2003/pycon/applications/pynfo-chart.png b/docs/historic/2003/pycon/applications/pynfo-chart.png deleted file mode 100644 index 7318b154587..00000000000 Binary files a/docs/historic/2003/pycon/applications/pynfo-chart.png and /dev/null differ diff --git a/docs/historic/2003/pycon/conch/conch b/docs/historic/2003/pycon/conch/conch deleted file mode 100755 index fa656105dce..00000000000 --- a/docs/historic/2003/pycon/conch/conch +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/python -from slides import URL, Bullet, Slide, SubBullet -from twslides import Lecture - -lecture = Lecture( - "Twisted Conch: SSH in Python", - Slide( - "Introduction", - ), - Slide( - "Other implementations (servers)", - Bullet( - "OpenSSH", - SubBullet(URL("http://www.openssh.org")), - ), - Bullet( - "FSecure SSH", - SubBullet(URL("http://www.f-secure.com/products/ssh/")), - ), - Bullet( - "LSH", - SubBullet(URL("http://www.lysator.liu.se/~nisse/lsh/")), - ), - ), - Slide( - "Other implementations (clients)", - Bullet( - "PuTTY", - SubBullet(URL("http://www.chiark.greenend.org.uk/~sgtatham/putty/")), - ), - Bullet( - "TeraTerm", - SubBullet(URL("http://www.ayera.com/teraterm/")), - ), - Bullet( - "MindTerm", - SubBullet(URL("http://www.appgate.com/mindterm/")), - ), - ), - Slide( - "Why Twisted?", - Bullet("Asynchronous"), - Bullet("Python"), - Bullet("High-Level"), - ), - Slide( - "No Forking or Threads", - Bullet("Forking is expensive"), - Bullet("Threads are complicated/expensive, esp. in Python"), - Bullet("Asynch means no worrying about any of that"), - Bullet("Makes running a session 2x as fast in Conch as in OpenSSH"), - ), - Slide( - "Security - No Pointers", - SubBullet("No buffer overflows"), - SubBullet("No off-by-1 errors"), - SubBullet("No malloc/free bugs"), - SubBullet("No arbitrary code execution"), - ), - Slide( - "Security - High Level", - Bullet("Strong built-in library"), - Bullet("Exceptions"), - ), - Slide( - "Security - Not Root", - Bullet("Limits vulnerablity in a compromise"), - Bullet("Allows use of process limits/etc."), - ), - Slide( - "Interfacing with other software", - Bullet( - "OpenSSH interacts only through separate processes", - SubBullet("Expensive"), - SubBullet("Complicated"), - ), - Bullet( - "Conch can interact in-process", - SubBullet("Faster"), - SubBullet("Easy integration to other Twisted and Python libraries"), - ), - ), - Slide( - "Speed", - Bullet("C is faster than Python"), - Bullet("Interpreter cost is high for the client"), - Bullet("FSH-style connection caching helps a bit"), - Bullet("Psyco helps as well"), - ), - Slide( - "Age", - Bullet( - "Conch is new", - SubBullet("First commit was July 15, 2002"), - ), - Bullet("Hasn't had a security aduit"), - Bullet("Shouldn't be used in security-critical systems"), - ), - Slide( - "Applications with Conch", - Bullet("Reality: MUD framework"), - Bullet("Insults: async. replacement for curses in Conch apps"), - ), - Slide( - "Future Directions", - Bullet("Generic authentication forwarding"), - Bullet("Work on applications"), - Bullet("Auditing of the code"), - Bullet("Increase speed"), - Bullet("SFTP/SCP"), - Bullet("Key Agent"), - Bullet("DNSSEC"), - ), - Slide( - "Conclusion", - Bullet("Working implementation in Python"), - Bullet("Much room for improvement"), - ), -) - -lecture.renderHTML(".", "conch-%d.html", css="main.css") diff --git a/docs/historic/2003/pycon/conch/conch.html b/docs/historic/2003/pycon/conch/conch.html deleted file mode 100644 index 0faab7ea57e..00000000000 --- a/docs/historic/2003/pycon/conch/conch.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - -Although it is a newcomer on the Secure Shell stage, Twisted Conch has quickly -caught up with the two most popular free *nix implementations and the most -popular free Windows implementation in terms of functionality. This rapid -development time, as well as the stability and other advantages, owes much to -Python and the Twisted networking framework.
- -Other than Conch, there are three popular server implementations. OpenSSH -works with versions 1 and 2 of the protocol, and is the most popular on *nix -systems. FSecure is more popular on Windows servers, and also works with both -versions. LSH is newer, and implements version 2. All three are written in C, -with LSH having some supporting Scheme code to generate C files.
- -On *nix, the SSH clients are provided by the server implementations (OpenSSH -and LSH). On Windows, there are a couple of separate clients. PuTTY is the -most popular and supports Telnet along with SSH1 and 2. TeraTerm recently -incorporated SSH into the core: before it had been an extension module. -MindTerm is the only implementation in this list to be written in a language -other than C. It runs as a Java applet, allowing SSH to run on any computer -with a JVM.
- -Why is Twisted ideal for this type of project? Firstly, it is an asynchronous -library, meaning there are no worries about threading or concurrency issues. -This means more developer time can be devoted to making the code work well, -rather than just work. Second, Python lends itself to this kind of -development: the code is easy to read and easy to write. Third, the Twisted -library is high-level, so developers do not need to worry about select loops or -callbacks. Twisted handles all of that and allows developers to concentrate on -the code.
- -Unlike OpenSSH, the Conch server does not fork a process for each incoming -connection. Instead, it uses the Twisted reactor to multiplex the connections. -The only fork done is to execute a process such as a shell, but running a shell -is not necessary, in which case the entire protocol would be run in-process. -One of the initial features of the server was an in-process Python interpreter -which allowed a user to interact with the server as it was running. (It is -currently disabled for security reasons.) Threads are only used to interface -with synchronous libraries, such as PyPAM (Pluggable Authentication Modules -support) or PyME (GPGME support). By not using forks or threads, the time it -takes for the Conch server to start an SSH session is roughly half of the time -it takes for OpenSSH. However, this does require that code in Conch be -non-blocking, which is an obstacle for programmers not used to that style.
- -OpenSSH, LSH, and PuTTY are all written in C. Many security holes are a result -of problems with unsafe pointer usage, which is a large problem in C code. -Many other security holes result from related issues, such as buffer overflows, -off-by-one errors on arrays, and memory allocation/deallocation bugs. Python -is pointer-safe, and so is not vulnerable to this class of hole. This also -means that no arbitrary data from over-the-wire is ever run, meaning control -always stays with the Conch server.
- -Being written in Python provides more security than just pointer safety. The -strong builtin library that comes with Python (including powerful data types -like the list and dictionary) means that fewer wheels need to be reinvented. -This limits the potential to make mistakes in implementation. Exceptions are -another powerful tool. They centralize error handling, rather than the mix of -methods that the C libraries use. All errors are caught and dealt with: this -might mean that the server stops accepting connections, but it never -compromises security.
- -Also, Conch does not need to run as root. In the default server, root -privileges are used for two things: to bind to ports < 1024, and to fork a -process as a different user. If neither of these are needed, the server need -not run as root at all. Even if they are, the server is only running as root -for those small sections. The rest of the time, it runs under the effective -user and group ID of the user who started the server. This limits the amount -of damage that could be inflicted in the event of a compromise.
- -OpenSSH can interact with subsystems such as SFTP only by executing a process -to handle it. Not only is forking a process expensive, it limits the -interaction to a generic bitstream, which leaves developers to determine how -to interact with their users. Conch can run in the same process as other -Python software, and is easily integrated with other Twisted servers. This -allows for things like secure remote administration of a Twisted web server, -encrypted communication to a Reality MUD, or secure remote object access using -Perspective Broker. This saves the hassle and expense of forking, and allows -Python developers to interact with Conch the way they know best: with Python.
- -No one can deny that compiled C is faster than Python. Some part of Conch use -C (PyCrypto, TGMP) to speed frequent operations, but the majority of the code -is in Python. The client suffers the most from this because of the time it -takes to start the interpreter. Work is being done to speed up the client by -caching connections. This does not eliminate the interpreter start-up cost, -but it removes the cost of negotiating a new connection. This effort is -similar to FSH (also in Python) but interacts more nicely with the SSH -protocol. Psyco helps as well, offering a speedup of roughly 2x - 5x.
- -As I said in the introduction, Conch is still a newcomer on the Secure Shell -stage (The first commit for Conch was July 15, 2002.) Although Python solves -a large class of holes, it is probable that other security holes are in the -code. Until a full audit is conducted of Twisted and of Conch, it should not -be used for security-critical systems.
- -One of the applications for Conch is with Reality, a MUD framework using -Twisted. Conch makes it easy to allow secure connections to the MUD in -addition or even in place of a standard Telnet connection. As problems -such as character theft become more prevalent on the Internet, a secure -interface becomes more important.
- -More generally, work is being done on Insults, a replacement for libraries -like Curses and S-Lang. It allows developers to write GUI code that -interacts well with Conch and other Twisted software. Although it is in the -initial stages of development, it shows much promise for the future.
- -There are several different directions for Conch to move in. One of the most -interesting is system for generalized authentication forwarding. This would -allow all authentication to be performed on a host that the user controls, -which would help to stop vulnerabilities such as timing attacks. Second is -more work with applications. Insults is becoming more powerful, and it will -be interesting to see what it can be used for. Also important are auditing of -the code and increasing the speed. These will make the code more useful in -general, as well as improving security. Other ideas include direct support for -SFTP/SCP, support for a key agent, and interfacing with Twisted Names to -support DNSSEC.
- -Although it is new, Conch is a working implementation of the Secure Shell -protocol. It is robust enough to serve as both the client and server on -systems I and others use daily.
- - diff --git a/docs/historic/2003/pycon/conch/conchtalk.txt b/docs/historic/2003/pycon/conch/conchtalk.txt deleted file mode 100644 index d2552eec6f4..00000000000 --- a/docs/historic/2003/pycon/conch/conchtalk.txt +++ /dev/null @@ -1,144 +0,0 @@ -Introduction ------------- -Although it is a newcomer on the Secure Shell stage, Twisted Conch has quickly -caught up with the two most popular free *nix implementations and the most -popular free Windows implementation in terms of functionality. This rapid -development time, as well as the stability and other advantages, owes much to -Python and the Twisted networking framework. - -Other implementations (servers) ------------------------------- -Other than Conch, there are three popular server implementations. OpenSSH -works with versions 1 and 2 of the protocol, and is the most popular on *nix -systems. FSecure is more popular on Windows servers, and also works with both -versions. LSH is newer, and implements version 2. All three are written in C, -with LSH having some supporting Scheme code to generate C files. - -Other implementations (clients) -------------------------------- -On *nix, the SSH clients are provided by the server implementations (OpenSSH -and LSH). On Windows, there are a couple of separate clients. PuTTY is the -most popular and supports Telnet along with SSH1 and 2. TeraTerm recently -incorporated SSH into the core: before it had been an extension module. -MindTerm is the only implementation in this list to be written in a language -other than C. It runs as a Java applet, allowing SSH to run on any computer -with a JVM. - -Why Twisted? ------------- -Why is Twisted ideal for this type of project? Firstly, it is an asynchronous -library, meaning there are no worries about threading or concurrency issues. -This means more developer time can be devoted to making the code work well, -rather than just work. Second, Python lends itself to this kind of -development: the code is easy to read and easy to write. Third, the Twisted -library is high-level, so developers do not need to worry about select loops or -callbacks. Twisted handles all of that and allows developers to concentrate on -the code. - -No forking/threads ------------------- -Unlike OpenSSH, the Conch server does not fork a process for each incoming -connection. Instead, it uses the Twisted reactor to multiplex the connections. -The only fork done is to execute a process such as a shell, but running a shell -is not necessary, in which case the entire protocol would be run in-process. -One of the initial features of the server was an in-process Python interpreter -which allowed a user to interact with the server as it was running. (It is -currently disabled for security reasons.) Threads are only used to interface -with synchronous libraries, such as PyPAM (Pluggable Authentication Modules -support) or PyME (GPGME support). By not using forks or threads, the time it -takes for the Conch server to start an SSH session is roughly half of the time -it takes for OpenSSH. However, this does require that code in Conch be -non-blocking, which is an obstacle for programmers not used to that style. - -Security - No Pointers ----------------------- -OpenSSH, LSH, and PuTTY are all written in C. Many security holes are a result -of problems with unsafe pointer usage, which is a large problem in C code. -Many other security holes result from related issues, such as buffer overflows, -off-by-one errors on arrays, and memory allocation/deallocation bugs. Python -is pointer-safe, and so is not vulnerable to this class of hole. This also -means that no arbitrary data from over-the-wire is ever run, meaning control -always stays with the Conch server. - -Security - High Level ---------------------- -Being written in Python provides more security than just pointer safety. The -strong builtin library that comes with Python (including powerful data types -like the list and dictionary) means that fewer wheels need to be reinvented. -This limits the potential to make mistakes in implementation. Exceptions are -another powerful tool. They centralize error handling, rather than the mix of -methods that the C libraries use. All errors are caught and dealt with: this -might mean that the server stops accepting connections, but it never -compromises security. - -Security - Not Root -------------------- -Also, Conch does not need to run as root. In the default server, root -privileges are used for two things: to bind to ports < 1024, and to fork a -process as a different user. If neither of these are needed, the server need -not run as root at all. Even if they are, the server is only running as root -for those small sections. The rest of the time, it runs under the effective -user and group ID of the user who started the server. This limits the amount -of damage that could be inflicted in the event of a compromise. - -Interfacing with other software --------------------------------------------- -OpenSSH can interact with subsystems such as SFTP only by executing a process -to handle it. Not only is forking a process expensive, it limits the -interaction to a generic bitstream, which leaves developers to determine how -to interact with their users. Conch can run in the same process as other -Python software, and is easily integrated with other Twisted servers. This -allows for things like secure remote administration of a Twisted web server, -encrypted communication to a Reality MUD, or secure remote object access using -Perspective Broker. This saves the hassle and expense of forking, and allows -Python developers to interact with Conch the way they know best: with Python. - -Speed ---------------------- -No one can deny that compiled C is faster than Python. Some part of Conch use -C (PyCrypto, TGMP) to speed frequent operations, but the majority of the code -is in Python. The client suffers the most from this because of the time it -takes to start the interpreter. Work is being done to speed up the client by -caching connections. This does not eliminate the interpreter start-up cost, -but it removes the cost of negotiating a new connection. This effort is -similar to FSH (also in Python) but interacts more nicely with the SSH -protocol. Psyco helps as well, offering a speedup of roughly 2x - 5x. - -Age ---- -As I said in the introduction, Conch is still a newcomer on the Secure Shell -stage (The first commit for Conch was July 15, 2002.) Although Python solves -a large class of holes, it is probable that other security holes are in the -code. Until a full audit is conducted of Twisted and of Conch, it should not -be used for security-critical systems. - -Applications with Conch ------------------------ -One of the applications for Conch is with Reality, a MUD framework using -Twisted. Conch makes it easy to allow secure connections to the MUD in -addition or even in place of a standard Telnet connection. As problems -such as character theft become more prevalent on the Internet, a secure -interface becomes more important. -More generally, work is being done on Insults, a replacement for libraries -like Curses and S-Lang. It allows developers to write GUI code that -interacts well with Conch and other Twisted software. Although it is in the -initial stages of development, it shows much promise for the future. - -Future Directions ------------------ -There are several different directions for Conch to move in. One of the most -interesting is system for generalized authentication forwarding. This would -allow all authentication to be performed on a host that the user controls, -which would help to stop vulnerabilities such as timing attacks. Second is -more work with applications. Insults is becoming more powerful, and it will -be interesting to see what it can be used for. Also important are auditing of -the code and increasing the speed. These will make the code more useful in -general, as well as improving security. Other ideas include direct support for -SFTP/SCP, support for a key agent, and interfacing with Twisted Names to -support DNSSEC. - -Conclusion ----------- -Although it is new, Conch is a working implementation of the Secure Shell -protocol. It is robust enough to serve as both the client and server on -systems I and others use daily. diff --git a/docs/historic/2003/pycon/conch/smalltwisted.png b/docs/historic/2003/pycon/conch/smalltwisted.png deleted file mode 100644 index 4f7d04d3080..00000000000 Binary files a/docs/historic/2003/pycon/conch/smalltwisted.png and /dev/null differ diff --git a/docs/historic/2003/pycon/conch/twistedlogo.png b/docs/historic/2003/pycon/conch/twistedlogo.png deleted file mode 100644 index 6226297c16d..00000000000 Binary files a/docs/historic/2003/pycon/conch/twistedlogo.png and /dev/null differ diff --git a/docs/historic/2003/pycon/deferex/deferex-bad-adding.py b/docs/historic/2003/pycon/deferex/deferex-bad-adding.py deleted file mode 100644 index 96a35c99b85..00000000000 --- a/docs/historic/2003/pycon/deferex/deferex-bad-adding.py +++ /dev/null @@ -1,9 +0,0 @@ -def successCallback(result): - myResult = result + 1 - print(myResult) - return myResult - - -... - -adder.callRemote("add", 1, 1).addCallback(successCallback) diff --git a/docs/historic/2003/pycon/deferex/deferex-chaining.py b/docs/historic/2003/pycon/deferex/deferex-chaining.py deleted file mode 100644 index 768f1b5c815..00000000000 --- a/docs/historic/2003/pycon/deferex/deferex-chaining.py +++ /dev/null @@ -1,19 +0,0 @@ -from twisted.internet import defer, reactor - -A = defer.Deferred() - - -def X(result): - B = defer.Deferred() - reactor.callLater(2, B.callback, result) - return B - - -def Y(result): - print(result) - - -A.addCallback(X) -A.addCallback(Y) -A.callback("hello world") -reactor.run() diff --git a/docs/historic/2003/pycon/deferex/deferex-complex-failure.py b/docs/historic/2003/pycon/deferex/deferex-complex-failure.py deleted file mode 100644 index e74f0ba70e7..00000000000 --- a/docs/historic/2003/pycon/deferex/deferex-complex-failure.py +++ /dev/null @@ -1,30 +0,0 @@ -from deferexex import adder - - -class MyExc(Exception): - "A sample exception" - - -class MyObj: - def blowUp(self, result): - self.x = result - raise MyExc("I can't go on!") - - def trapIt(self, failure): - failure.trap(MyExc) - print("error (", failure.getErrorMessage(), "). x was:", self.x) - return self.x - - def onSuccess(self, result): - print(result + 3) - - def whenTrapped(eslf, result): - print("Finally, result was", result) - - def run(self, o): - o.callRemote("add", 1, 2).addCallback(self.blowUp).addCallback( - self.onSuccess - ).addErrback(self.trapIt).addCallback(self.whenTrapped) - - -MyObj().run(adder) diff --git a/docs/historic/2003/pycon/deferex/deferex-complex-raise.py b/docs/historic/2003/pycon/deferex/deferex-complex-raise.py deleted file mode 100644 index 76803ded975..00000000000 --- a/docs/historic/2003/pycon/deferex/deferex-complex-raise.py +++ /dev/null @@ -1,13 +0,0 @@ -class MyExc(Exception): - "A sample exception." - - -try: - x = 1 + 3 - raise MyExc("I can't go on!") - x = x + 1 - print(x) -except MyExc as me: - print("error (", me, "). x was:", x) -except BaseException: - print("fatal error! abort!") diff --git a/docs/historic/2003/pycon/deferex/deferex-forwarding.py b/docs/historic/2003/pycon/deferex/deferex-forwarding.py deleted file mode 100644 index 8a94019864e..00000000000 --- a/docs/historic/2003/pycon/deferex/deferex-forwarding.py +++ /dev/null @@ -1,11 +0,0 @@ -from twisted.spread import pb - - -class LocalForwarder(flavors.Referenceable): - def remote_foo(self): - return str(self.local.baz()) - - -class RemoteForwarder(flavors.Referenceable): - def remote_foo(self): - return self.remote.callRemote("baz").addCallback(str) diff --git a/docs/historic/2003/pycon/deferex/deferex-listing0.py b/docs/historic/2003/pycon/deferex/deferex-listing0.py deleted file mode 100644 index d72c6b987cf..00000000000 --- a/docs/historic/2003/pycon/deferex/deferex-listing0.py +++ /dev/null @@ -1,17 +0,0 @@ -class DocumentProcessor: - def __init__(self): - self.loadDocuments(self.callback, mySrv, "hello") - - def loadDocuments(callback, server, keyword): - "Retrieve a set of documents!" - ... - - def callback(self, documents): - try: - for document in documents: - process(document) - finally: - self.cleanup() - - def cleanup(self): - ... diff --git a/docs/historic/2003/pycon/deferex/deferex-listing1.py b/docs/historic/2003/pycon/deferex/deferex-listing1.py deleted file mode 100644 index e5d453c02d6..00000000000 --- a/docs/historic/2003/pycon/deferex/deferex-listing1.py +++ /dev/null @@ -1,6 +0,0 @@ -def prettyRequest(server, requestName): - return ( - server.makeRequest(requestName) - .addCallback(lambda result: ", ".join(result.asList())) - .addErrback(lambda failure: failure.printTraceback()) - ) diff --git a/docs/historic/2003/pycon/deferex/deferex-listing2.py b/docs/historic/2003/pycon/deferex/deferex-listing2.py deleted file mode 100644 index 96a35c99b85..00000000000 --- a/docs/historic/2003/pycon/deferex/deferex-listing2.py +++ /dev/null @@ -1,9 +0,0 @@ -def successCallback(result): - myResult = result + 1 - print(myResult) - return myResult - - -... - -adder.callRemote("add", 1, 1).addCallback(successCallback) diff --git a/docs/historic/2003/pycon/deferex/deferex-simple-failure.py b/docs/historic/2003/pycon/deferex/deferex-simple-failure.py deleted file mode 100644 index 9ec44537548..00000000000 --- a/docs/historic/2003/pycon/deferex/deferex-simple-failure.py +++ /dev/null @@ -1,12 +0,0 @@ -from deferexex import adder - - -def blowUp(result): - raise Exception("I can't go on!") - - -def onSuccess(result): - print(result + 3) - - -adder.callRemote("add", 1, 2).addCallback(blowUp).addCallback(onSuccess) diff --git a/docs/historic/2003/pycon/deferex/deferex-simple-raise.py b/docs/historic/2003/pycon/deferex/deferex-simple-raise.py deleted file mode 100644 index 6c58eae9e18..00000000000 --- a/docs/historic/2003/pycon/deferex/deferex-simple-raise.py +++ /dev/null @@ -1,3 +0,0 @@ -x = 1 + 3 -raise Exception("I can't go on!") -print(x) diff --git a/docs/historic/2003/pycon/deferex/deferex.html b/docs/historic/2003/pycon/deferex/deferex.html deleted file mode 100644 index a4fb1712a59..00000000000 --- a/docs/historic/2003/pycon/deferex/deferex.html +++ /dev/null @@ -1,499 +0,0 @@ - - - -Glyph Lefkowitz
- -A deceptively simple architectural challenge faced by many multi-tasking -applications is gracefully doing nothing. Systems that must wait for the -results of a long-running process, network message, or database query while -continuing to perform other tasks must establish conventions for the semantics -of waiting. The simplest of these is blocking in a thread, but it has -significant scalability problems. In asynchronous frameworks, the most common -approach is for long-running methods to accept a callback that will be executed -when the command completes. These callbacks will have different signatures -depending on the nature of the data being requested, and often, a great deal of -code is necessary to glue one portion of an asynchronous networking system to -another. Matters become even more complicated when a developer wants to wait -for two different events to complete, requiring the developer to "juggle" -the callbacks and create a third, mutually incompatible callback type to handle -the final result.
- -This paper describes the mechanism used by the Twisted framework for waiting
-for the results of long-running operations. This mechanism, the Deferred
,
-handles the often-neglected problems of error handling, callback juggling,
-inter-system communication and code readability.
In a framework like Twisted, the ability to glue two existing components -together with a minimum of mediating code is paramount. Considering that the -vast majority of the code in Twisted is asynchronous I/O handling, it is -imperative that the mechanism for relaying the data between the output from one -system into the input of another be competitive with the simplicity of passing -the return value of one method to the argument of another. It was also -important to use only no new syntax to avoid confusing programmers who already -have experience with Python, and establish no dependencies on anything which -would break compatibility with existing Python code or C / Java -extensions.
- -There are several traditional approaches to handling concurrency that have -been taken by application frameworks in the past. Each has its own -drawbacks.
- -The problems with using threads for concurrency in systems that need to -scale is fairly well-documented. However, many systems that use asynchronous -multiplexing for I/O and system-level tasks, but run application code in a -thread. Zope's threaded request handling is a good example of this model.
- -It is optimal, however, to avoid requiring threads for any part of -a framework. Threading has a significant cost, especially in Python. The -global interpreter lock destroys any performance benefit that threading may -yield on SMP systems, and introduces significant complexity into both framework -and application code that needs to be thread-safe.
- -A full discussion of the pros and cons of threads is beyond the scope of -this paper, however, using threads merely for blocking operations is clearly -overkill. Since each thread represents some allocation of resources, all of -those resources are literally sitting idle if they are doing nothing but -waiting for the results from a blocking call.
- -In a fairly traditional networking situation, where the server is -asynchronously multiplexed, this waste of resources may be acceptable for -special-purpose, simple client programs, since only a few will be run at a -time. To create a generic system, however, one must anticipate cases when the -system in question is not only a multi-user server or a single-user client, but -also a multi-user hybrid client/server.
- -A good example of this is a high-volume web spider. A spider may have a -server for administrative purposes, but must also be able to spawn many -requests at once and wait for them all to return without allocating undue -resources for each request. The non-trivial overhead of threads, in addition -to sockets, would be a very serious performance problem.
- -At some level, any system for handling asynchronous results in Python will -be based on callback functions. The typical way to present this to the -application programmer is to have all asynchronous methods accept a callback as -one of their arguments.
- -This approach is usually standardized by giving the callback having a -standard name ("callback") or a particular position (first argument, last -argument). Even systems which rigorously adhere to such standardization run -into problems, however.
- -This approach does work for a variety of events. It is unwieldy when one is -attempting to write asynchronous "conversations" that involve multiple -stages. The first problem that we notice is the lack of error-handling. If an -error occurs in normal Python code, Exception handling provides clean and -powerful semantics for handling it.
- -Document Processor Example - -In an asynchronous method such as the one given above, traditional -exceptions fall short. What if an error occurs retrieving the documents from -storage? Do we call the callback with an error rather than a result?
- -Other languages handle this by associating different semantics with -threading, or providing different constructs altogether for concurrency. This -has the disadvantage that these languages aren't Python. Even Stackless Python -is problematic because it lacks integration with the wide variety of libraries -that Python provides access to.
- -The design of Deferred
draws upon some of these other languages, and this
-section will cover several languages and their impact.
In particular, the following list of languages were influential:
- - E, Smalltalk, and Scheme proved particularly influential. In E's, there is
-a sharp distinction between objects which are synchronously accessible and
-those which are asynchronously accessible. The original use for
-Deferred
s was to represent results from Perspective Broker method
-calls. E was interesting in that the entire execution environment had
-assumptions about networking built in. E's "eventually" operator [stiegler] is what originally inspired the distinction
-between "a method which returns X" and "a method which returns a
-Deferred
that fires X".
-Smalltalk was influential in that its syntax for closures provided some
-precedent for thinking about the continuation of a "conversation" of execution
-as itself an object with methods. The original thought-experiment that lead to
-Deferred
s was an attempt to write some Squeak code that looked like this:
-
-
-(object callRemote: "fooBar") andThen: [ result | - Transcript show: result. - ] orElse: [ failure | - failure printTraceback. - ] -- -The hypothetical
callRemote
method here would return an object
-with the method andThen:orElse:
that took 2 code blocks, one for
-handling results and the other for handling errors.
-
-
-It was challenging to write enough Smalltalk code to make anything
-interesting happen with this paradigm, but within the existing framework of
-Twisted, it was easy to convert several request/response idioms to use this
-sort of object. Now that Twisted has dropped python 1.5.2 compatibility, and
-2.1 is the baseline version, we can use nested_scopes
[hylton] and anonymous functions to make the code look
-similar to this original model.
Scheme, of course, provides call-with-current-continuation
(or
-call/cc
), the mind-bending control structure which has been a
-subject of much debate in language-design circles. call/cc
may
-have provided more a model of things to avoid than a real inspiration, though.
-While it is incredibly powerful, it creates almost as many problems as it
-solves. In particular, the interaction between continuations and
-try:finally:
is undefined [pitman], since it
-is impossible to determine the final time the protected code will be run. The
-strongest lesson from call/cc
was to only take as much state in
-the Deferred
as necessary, and to avoid playing tricks with implicit context.
-
The mechanisms that these languages use, however, often rely upon deeper
-abstractions that make their interpreters less amenable than Python's to
-convenient, idiomatic integration with C and UNIX. Scheme's
-call/cc
requires a large amount of work and creativity to
-integrate with "C" language libraries, as C. Tismer's work in
-Stackless Python Python has shown. [tismer]
After several months of working with Twisted's callback-based -request/response mechanisms, it became apparent that something more was -necessary. Often, errors would silently cause a particular process to halt. -The syntax for a multi-stage asynchronous process looked confusing, because -multiple different interfaces were being invoked, each of which taking multiple -callbacks. The complexity of constructing these stages was constantly being -exposed to the application developer, when it shouldn't really concern them. -
- -In order to make gluing different request/response systems together easy, we
-needed to create a more uniform way of having them communicate than a simple
-convention. In keeping with that goal, we reduced several conventions into one
-class, Deferred
, so that the request system could return a
-Deferred
as output and the responder could accept a Deferred
as input..
-Deferred
s are objects which represent the result of a request that is not yet
-available. It is suggested that any methods which must perform long-running
-calculations or communication with a remote host return a Deferred
.
This is similar to the Promise pattern, or lazy evaluation, except that it
-is a promise that will not be resolved synchronously. The terminology usually
-used to describe a Deferred
is "a Deferred
that will fire" a particular
-result.
Deferred
s have a small interface, which boils down to these five methods,
-plus convenience methods that call them:
-
-
addCallbacks(self, callback, errback=None, callbackArgs=None,
- callbackKeywords=None, errbackArgs=None, errbackKeywords=None)
callback(result)
errback(result)
pause()
unpause()
In general, code that initially returns Deferred
s will be framework code,
-such as a web request or a remote method call. This means that code that uses
-the framework will call addCallbacks
on the Deferred
that is
-returned by the framework. When the result is ready, the callback will be
-triggered and the client code can process the result. Usually the utility
-methods addCallback
and addErrback
are used.
-
Using addCallbacks
has slightly different semantics than using
-addCallback
followed by addErrback
;
-addCallbacks
places the callback and the errback "in
-parallel", meaning if there is an error in your callback, your errback will
-not be called. Thus using addCallbacks
has either/or semantics;
-either the callback or the errback will be called, but not both.
The example given shows a method which returns a Deferred
that will fire a
-formatted string of the result of a given request. The return value of each
-callback is passed to the first argument of the next.
As described above in the section on using callbacks for asynchronous result -processing, one of the most common application-level problems in an -asynchronous framework is an error that causes a certain task to stop -executing. For example, if an exception is raised while hashing a user's -password, the entire log-in sequence might be halted, leaving the connection in -an inconsistent state.
- -One way that Twisted remedies this is to have reasonable default behavior in
-circumstances such as this: if an uncaught exception is thrown while in the
-dataReceived
callback for a particular connection, the connection
-is terminated. However, for multi-step asynchronous conversations, this is not
-always adequate.
Python's basic exception handling provides a good example for an -error-handling mechanisms. If the programmer fails to account for an error, an -uncaught exception stops the program and produces information to help track it -down. Well-written python code never has to manually detect whether an error -has occurred or not: code which depends on the previous steps being successful -will not be run if they are not. It is easy to provide information about an -error by using attributes of exception objects. It is also easy to relay -contextual information between successful execution and error handlers, because -they execute in the same scope.
- -Deferred
attempts to mimic these properties as much as possible in an
-asynchronous context.
When something unexpected goes wrong, the program should emit some debugging -information and terminate the asynchronous chain of processing as gracefully as -possible.
- -Python exceptions do this very gracefully, with no effort required on the -part of the developer at all.
- -Simple Catastrophic Exception - -Deferred
s provide a symmetrical facility, where the developer may register a
-callback but then forego any error processing.
In this example, the onSuccess callback will never be run, because the -blowUp callback creates an error condition which is not handled.
- -It is impossible to provide a reasonable default behavior if failure is -ambiguous. Code should never have to manually distinguish between success and -failure. An error-processing callback has a distinct signature to a -result-processing callback.
- -Forcing client code to manually introspect on return values creates a common -kind of error; when the success of a given long-running operation is assumed, -it appears to work, and it is easier (and less code) to write a callback that -only functions properly in a successful case, and creates bizarre errors in a -failure case. A simple example:.
- -Common Error Pattern - -In this example, when the remote call to add the two numbers succeeds,
-everything looks fine. However, when it fails, result
will be an
-exception and not an integer: therefore the printed traceback will say
-something unhelpful, like:
TypeError: unsupported operand types for +: 'instance' and 'int'- -
It should be easy for developers to distinguish between fatal and non-fatal -errors. With Python exceptions, you can do this by specifying exception -classes, which is a fairly powerful technique.
- -Complex Python Exception - -With Deferred
, we planned to have a syntactically simple technique for
-accomplishing something similar. The resulting code structure is tends to be a
-bit more expansive than the synchronous equivalent, due to the necessity of
-giving explicit names to the functions involved, but it can be just as easy to
-follow.
In this example, we have a callback chain that begins with the result of a
-remote method call of 3. We then encounter a MyExc
error raised
-in blowUp
, which is caught by the errback trapIt
.
-The 'trap' method will re-raise the current failure unless its class matches
-the given argument, so the error will continue to propagate if it doesn't
-match, much like an except:
clause.
While it is dangerous to implicitly propagate too much context (leading to -problems similar to those with threads), we wanted to make sure that it is easy -to move context from one callback to the next, and to convert information in -errors into successful results after the errors have been handled.
- -Both addCallback
and addErrback
have the signature
-callable, *args, **kw
. The additional arguments are passed
-through to the registered callback when it is invoked. This allows us to
-easily send information about the current call to the error-handler in the same
-way as the success callback.
Since Deferred
is designed for a fairly specific class of problems, most
-places it is used tend to employ certain idioms.
If you are implementing a symmetric, message-oriented protocol, you will
-typically need to juggle an arbitrary number of outstanding requests at once.
-The normal pattern for doing this is to create a dictionary mapping a request
-number to a Deferred
, and firing a Deferred
when a response with a given
-request-ID associated with it arrives.
A good example of this pattern is the Perspective Broker protocol. Each -method call has a request, but it is acceptable for the peer to make calls -before answering requests. Few protocols are as extensively permissive about -execution order as PB, but any full-fledged RPC or RMI protocol will enable -similar interactions. The MSN protocol implementation in Twisted also uses -something similar.
- -When writing interfaces that application programmers will be implementing
-frequently, it is often convenient to allow them to either return either a
-Deferred
or a synchronous result. A good example of this is Twisted's Woven, a
-dynamic web content system.
-
The processing of any XML node within a page may be deferred until some
-results are ready, be they results of a database query, a remote method call,
-an authentication request, or a file upload. Many methods that may return
-Nodes may also return Deferred
s, so that in either case the application
-developer need return the appropriate value. No wrapping is required if it is
-synchronous, and no manual management of the result is required if it is not.
-
This is the best way to assure that an application developer will never need
-to care whether a certain method's results are synchronous or not. The first
-usage of this was in Perspective Broker, to allow easy transparent forwarding
-of method calls. If a Deferred
is returned from a remotely accessible method,
-the result will not be sent to the caller until the Deferred
fires.
callRemote
Ideally, all interactions between communicating systems would be modeled as -asynchronous method calls. Twisted Words, the Twisted chat server, treats any -asynchronous operation as a subset of the functionality of Perspective Broker, -using the same interface. Eventually, the hope is to make greater use of this -pattern, and abstract asynchronous conversations up another level, by having -the actual mechanism of message transport wrapped so that client code is only -aware of what asynchronous interface is being invoked.
- -The first "advanced" feature of Deferred
s is actually
-used quite frequently. As discussed previously, each Deferred
has
-not one, but a chain of callbacks, each of which is passed the result from the
-previous callback. However, the mechanism that invokes each callback is itself
-an implementor of the previously-discussed "Sometimes Synchronous
-Interface" pattern - a callback may return either a value or a
-Deferred
.
For example, if we have a Deferred
A, which has 2 callbacks: X,
-which returns a deferred B, that fires the result to X in 2 seconds, and Y,
-which prints its result, we will see the string "hello" on the screen
-in 2 seconds. While it may sound complex, this style of coding one
-Deferred
which depends on another looks very natural.
Deferred
s Together
-
-In this way, any asynchronous conversation may pause to wait for an -additional request, without knowing in advance of running the first request -what all the requests will be.
- -The other advanced feature of Deferred
s is not terribly common,
-but is still useful on occasion. We have glossed over the issue of
-"pre-executed"Deferred
s so far, e.g. Deferred
s
-which have already been called with a callback value before client code adds
-callbacks to them. The default behavior, which works in almost every
-situation, is simply to call the callback immediately (synchronously) as it is
-added. However, there are rare circumstances where race conditions can occur
-when this naive approach is taken.
For this reason, Deferred
provides pause
and
-unpause
methods, allowing you to put a Deferred
into
-a state where it will stop calling its callbacks as they are added; this will
-allow you to set up a series of communicating Deferred
s without
-having anything execute, complete your setup work, and then unpause the
-process.
In this way, you can create centralized choke-points for caring about whether
-a process is synchronous or not, and completely ignore this problem in your
-application code. For example, in the now-obsolete Twisted Web Widgets system
-(a dynamic web content framework that predates woven), it was necessary to make
-sure that certain Deferred
s were always called in order, so the page would
-render from top to bottom. However, the user's code did not need to concern
-itself with this, because any Deferred
s for which synchronous callback
-execution would have been an issue were passed to user code paused.
Deferred
s are a powerful abstraction for dealing with
-asynchronous results. Having a uniform approach to asynchronous conversations
-allows Twisted APIs to provide a level of familiarity and flexibility for
-network programmers that approaches that of domain-specific languages, but
-still provides access to all of Python's power.
I would like to thank the entire Twisted team, for making me realize what a
-good idea I had hit upon with Deferred
s.
Special thanks go to Andrew Bennetts and Moshe Zadka, for implementing the -portion of Twisted used to generate this, and other, papers, and to Ying Li and -Donovan Preston for last-minute editorial assistance..
- -%s' % (self.content,) - -lecture = Lecture( - "Changing the Type of Literals", - Slide("New-style classes", - Bullet("In 2.2+, built-in types can be subclassed"), - Bullet("These can be created explicitly by using their name", SubBullet( - Bullet("For example, an int subclass that displays itself in roman numerals"), - Bullet("print RomanNumeral(13) -> XIII"), - )), - ), - Slide("Literals are less accessable", - Bullet("When you write [] or 7, the list or int type is instantiated"), - Bullet("This behavior seems inaccessable"), - Bullet("While this makes for more readable code, it limits the scope of possible evil"), - ), - Slide("Throw in an extension module...", - Bullet("intrinsics.so exposes one function, 'replace'"), - Bullet("It takes two arguments", SubBullet( - Bullet("A type object to replace"), - Bullet("The type object with which to replace it"), - )), - Bullet("Magic is performed, and the new type is now used whenever the old one would have been"), - ), - Slide("An example", - PythonSource("""\ -class RomanNumeral(int): - def __str__(self): - # Regular code for formatting roman numerals - -old_int = intrinsics.replace(int, RomanNumeral) -print 13 -""" - ), - Bullet("The output is simply the roman numerals XIII"), - ), - Slide("intrinsics.c - The replacement", - PRE("""\ -PyObject* -intrinsics_replace(PyObject* self, PyObject* args) { - static PyTypeObject* const types[] = { - &PyInt_Type, &PyLong_Type, &PyFloat_Type, &PyComplex_Type, - &PyBool_Type, &PyBaseObject_Type, &PyDict_Type, &PyTuple_Type, - &PyBuffer_Type, &PyClassMethod_Type, &PyEnum_Type, &PyProperty_Type, - &PyList_Type, &PyStaticMethod_Type, &PySlice_Type, &PySuper_Type, - &PyType_Type, &PyRange_Type, &PyFile_Type, &PyUnicode_Type, - &PyString_Type, - NULL - }; - - int i = 0; - PyObject *old, *new; - PyTypeObject* space; - - if (!PyArg_ParseTuple(args, "OO:replace", &old, &new)) - return NULL; -""" - ), - ), - Slide("intrinsics.c - The actual replacement", - PRE("""\ - while (types[i]) { - if (types[i] == (PyTypeObject*)old) { - space = PyObject_New(PyTypeObject, &PyType_Type); - *space = *(types[i]); - *(types[i]) = *(PyTypeObject*)new; - break; - } - ++i; - } - if (!types[i]) { - PyErr_SetString(replace_error, "unknown type"); - return NULL; - } - Py_INCREF(new); - Py_INCREF(space); - return (PyObject*)space; -} -""" - ), - ), - Slide("This is the wrong answer", - Bullet("The right answer is to add more flexibility to the Python compiler"), - Bullet("This is a lot less code, though"), - ) -) - -lecture.renderHTML(".", "intrinsics-lightning-%d.html", css="stylesheet.css") diff --git a/docs/historic/2003/pycon/lore/lore-presentation b/docs/historic/2003/pycon/lore/lore-presentation deleted file mode 100755 index f7928d56c8c..00000000000 --- a/docs/historic/2003/pycon/lore/lore-presentation +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/python2.2 -# Moshe -- current content is estimated at about 15 minutes -from slides import PRE, URL, Bullet, NumSlide, Slide, SubBullet -from twslides import Lecture - -lecture = Lecture( - "Lore: A Document Generation System", - Slide( - "Introduction", - Bullet("Document generation system"), - Bullet("Input a strict subset of XHTML"), - Bullet("Output -- nicely formatted HTML and LaTeX"), - Bullet("Used to generate >200 pages of Twisted documentation"), - ), - Slide( - "History", - Bullet("Twisted needed documentation -- and a format"), - Bullet("Reluctance to add dependence on a big system"), - Bullet("Wanted something quick and easy -- subset of HTML!"), - Bullet("Needs matured: table of contents, printed version"), - Bullet("Enter Lore"), - ), - Slide( - "Goals", - Bullet("Easy to use for authors"), - Bullet("Easy to install"), - Bullet("(Uncommon) Source format should be readable"), - ), - Slide( - "Contents", - Bullet("twisted.lore Python package"), - Bullet("'lore' command-line program"), - Bullet("Comes with every Twisted installation"), - Bullet("In particular -- works on Linux, Win32, Mac"), - Bullet("In particular -- supports Python 2.1, 2.2, 2.3 alpha"), - ), - Slide( - "Alternatives - HTML", - Bullet("Too flexible"), - Bullet( - "No support for needed idioms", - SubBullet( - Bullet("Special-purpose Python markup"), - Bullet("Tables of contents"), - Bullet("Inlining"), - ), - ), - Bullet("Renders badly to dead trees with current tools"), - ), - Slide( - "Alternatives - LaTeX", - Bullet("Very good at printed results"), - Bullet("Model makes alternative parsers near-impossible"), - Bullet("Renderers to HTML are buggy and fragile"), - ), - Slide( - "Alternatives - Docbook", - Bullet( - "Using correctly requires too much work", - SubBullet( - Bullet("Write a DTD with special elements"), - Bullet("Write Jade stylesheets"), - ), - ), - Bullet("Lore is probably smaller than docbook specialization"), - ), - Slide( - "Alternatives - Texinfo", - Bullet("Next slide, please"), - ), - Slide( - "Lore goodies", - Bullet( - "Special tag to mark classes/modules/functions", - SubBullet(Bullet("Can be made to point to auto-generated docs")), - ), - Bullet( - "Inline code-examples", - SubBullet(Bullet("No need to escape all those <, > and &")), - ), - Bullet("Syntax-highlight Python code"), - ), - Slide( - "hlint - A lint-like program", - Bullet("Checks for many common errors"), - Bullet("Unhandled elements"), - Bullet("Misspelled (or miscased) class names"), - Bullet("Checks Python code for syntax errors"), - ), - Slide( - "Extending Lore", - Bullet("Easily done with some Python code"), - Bullet("Input-enhancements decide which output formats to handle"), - Bullet("Example: math-lore, Lore with LaTeX formulae"), - ), - Slide( - "HTML Output", - Bullet("HTML is a flexible output format"), - Bullet("Documents often have to integrate with a site"), - Bullet("Lore produces HTML documents based on a template"), - Bullet( - "Lore uses only HTML 'class' attributes, never 'font'", - SubBullet(Bullet("Plays nice with CSS")), - ), - ), - Slide( - "Man Pages", - Bullet("Lore has a program to convert man pages to Lore documents"), - Bullet("Man pages are written anyway"), - Bullet("No man output: the format is too limited"), - ), - Slide( - "Small Example", - PRE( - """\ - - -
Simple paragraphfootnote
- - -""" - ), - ), - Slide( - "Future Directions", - Bullet("More output formats"), - Bullet("Some more classes - abstract, bibliography"), - ), -) - -lecture.renderHTML(".", "lore-%d.html", css="main.css") diff --git a/docs/historic/2003/pycon/lore/lore-slides.html b/docs/historic/2003/pycon/lore/lore-slides.html deleted file mode 100644 index 19171f88f61..00000000000 --- a/docs/historic/2003/pycon/lore/lore-slides.html +++ /dev/null @@ -1,187 +0,0 @@ - --<html> -<head> -<title>Example</title> -</head> -<body> -<h1>Example</h1> -<p>Simple paragraph<span class="footnote">footnote</span></p> -</body> -</html> -- -
lore
command-line programlore-slides
plugin can output to
- class
attributes, never font
- Lore is a documentation generation system which uses a limited subset -of XHTML, together with some class attributes, as its source format. This -allows for lower barrier of entry than many other similar systems, since HTML -authoring tools are plentiful -as is knowledge of HTML writing. As an added advantage, the source format -is viewable directly, so that even if Lore is not available the documentation -is useful. It currently outputs LaTeX and HTML, which allows for most -use-cases.
- -Lore is currently in use by the Twisted project to generate its -documentation for versions 1.0.1 and above.
- -At the beginning of Twisted's life cycle, as with any self-respecting -free software project, it came completely devoid of documentation. As -Twisted progressed in maturity, the Twisted development team realized -that documentation is necessary.
- -Since at that time the Twisted development
-team did not want the overhead of integrating
-a full-scale document generation framework into its build infrastructure,
-documents were written for the least common denominator -- plain HTML.
-When the Twisted team wanted the documentation to be
-featured on the web site, it was desirable to have them integrated with
-the web site's look and feel. Thus, generate-domdocs
-was born as a simple XML-based command line hack which improved the look of the
-documents so they would share the look and feel of the other pages in the web
-site, including a standard header and footer. As
-generate-domdocs
-slowly grew more and more features, it gradually became too large to maintain.
-The authors, members of the Twisted development team, decided that in order to
-make it more maintainable, it should be refactored into a
-library and by the way also add alternate output formats. Some of the documents
-which were reluctant to be transformed into alternate formats were fixed,
-and guidelines for making compatible documents were drafted. Those documents,
-together with the conversion code, are the Lore documentation generation
-system.
Lore is documentation generation system which is a part of the
-Twisted framework. It uses
-the Twisted XML parsing framework
-(microdom
) to parse compliant XHTML
-and generate the various output formats from it.
Lore consists of a Python package, twisted.lore
,
-and a command-line program: lore
, which
-generates HTML output (which is more presentation-oriented than the source
-format), LaTeX or runs an linter, depending on command-line arguments.
In the case where the default output of Lore is not exactly suited to a
-Lore user,
-it is possible to subclass the output generators and customize their behavior.
-This could be done for many purposes, from straight-forward additions like
-adding a new span
or div
class to advanced tweaking
-such as changing the way Lore does image conversion on LaTeX output.
Lore uses reflection intensively to make adding new features as simple
-as adding a new method, without the need for awkward registration schemes.
-Thus, adding another check to the linter or letting
-Lore handle the link
element in some way require only the addition
-of one method.
Lore was written when the Twisted team felt it needed to write documentation
-and looked for a documentation format. Looking through alternatives, the
-best one seemed to be the Python way, using LaTeX format and
-latex2html
. However, the Python way has its share
-of problems, not the least of which is latex2html
-being a long and crufty Perl program whose Perl APIs, which are the
-only way to add support for custom markup, change every version.
Since documentation writing is important, a documentation system with -minimal impact on the writer would be desirable. While LaTeX certainly has -very little impact in terms of markup overhead, it has a very big impact -both in terms of installed base (installing LaTeX on UNIX systems or -Windows is non-trivial at best) and in terms of familiarity.
- -HTML has the benefit of being directly readable on every post-1995 -computer, so the installed base is as big as could be hoped for. It also has -the benefit of being easily parsed, at least in its new XHTML guise.
- -The goals of Lore were taken to be:
- -Lore's source format is a subset of XHTML; all Lore source documents are
-valid XHTML documents. The XHTML tags that Lore allows are:
-html
, title
, head
, body
,
-h1
, h2
, h3
, ol
,
-ul
, dl
, li
, dt
,
-dd
, p
, code
, img
,
-blockquote
, a
, cite
, div
,
-span
, strong
, em
, pre
,
-q
, table
, tr
, td
,
-th
and style
.
-
We would like to stress the omission of the font
tag (which is
-deprecated in HTML 4.01 anyway). Instead of using font
, Lore
-mandates the use of stylesheets
-and the class
attribute, and in particular Lore defines several
-classes, such as footnote
, API
,
-py-listing
. The use of classes on div
and
-span
elements effectively allows XHTML to be arbitrarily
-extensible without needing to define custom tags.
Further discouraging explicit style decision, Lore deprecates the
-style
attribute which allowing HTML (and XHTML) authors to embed
-pieces of the stylesheet in the document. Though Lore properly processes
-such documents, they are against the specification of Lore -- and
-the Lore lint-like problem finder will complain.
Requiring XHTML rather than just HTML greatly simplifies the code to -manipulate Lore source, because we can use standard XML libraries. For -documentation authors, the difference is negligible -- and any mistakes made in -balancing tags can be easily found using the linter. -Since tag balancing problems, in many cases, cause a discrepancy between -author intention and the result, it is better to balance the tags anyway.
- -Like LaTeX, Lore encourages authors to focus on content, letting the -presentation take care of itself. This is an inherently restrictive approach, -but results in much more consistent and higher-quality output.
- -The Lore source format is quite usable (if somewhat plain) as an end-format. -Any web browser can read it, and it does not require special stylesheet support, -JavaScript or any other modern HTML additions. It is also, as intended, -straightforward to create and edit documents in this format.
- -However, reading the source format directly has some major limitations, -which are inherent in the combination of the facilities which render HTML -and the requirement that the format will be easily writable, and easy to -modify, using any standard text editor. -The limitations include:
- -The two most important formats, for the end-user, are the computer screen and -pages of print outs. Any other format should be first and foremost be thought -of as a prelude to these final formats.
- -The easiest computer-screen oriented format is HTML. However, the HTML -which is most comfortable and useful to the end-user is not necessarily -easy to write and modify. -For example, it is painful to manually write a table of contents, and even more -painful to keep it updated as sections are added, removed or changed. However, -when reading a long document having a table of contents, with hyperlinks -into the sections, is a boon. -Thus, even though both Lore's source and one output format are HTML, an -HTML to HTML conversion is still necessary, paradoxical though it may sound.
- -For printable output, the most widely supported formats are PostScript -and Portable Document Format. On UNIX systems PostScript is often preferred, -since there are many tools for manipulating it and printing it (and PostScript -printers are more common in the UNIX world). On Windows and Apple computers, -Portable Document Format (PDF) is preferred because of the ease of installation -of the necessary tools. Mac OS X, though being technically a UNIX, supports -PDF natively.
- -Directly generating PostScript or PDF, however, is hard. Since these formats
-are very low-level, the application generating them must do the hard work
-of calculating line breaks, guessing hyphenation points and deciding on fonts.
-Since these tasks are already implemented by LaTeX, Lore just generates LaTeX
-code and lets the user run LaTeX to generate PostScript and
-ps2pdf
to generate PDF. Granted, this still causes
-the problems with the difficulties of installing LaTeX. It is
-possible to implement direct Lore to PDF converter, though this hasn't been
-done yet, by using pdflib
.
The HTML to HTML converter works by running a series of transformations on
-the Document Object Model (DOM) tree of the parsed document, and then
-writing it out. The most important transformation is that of throwing
-away anything outside the body
element, and putting the
-body
element inside a template file. This allows large
-parts of the common layout code to be customized without modifying or writing
-any Python code.
Each step is implemented as a separate function, to allow Lore-using -Python programmers to customize which tree transformations to do in their -own code, without forcing them to rewrite functionality in Lore. In addition, -other output generators might perform a subset of these transformations -on the input tree before processing it -- and indeed, this is being used -even in Lore itself.
- -One of the steps taken is caused by a need which is common in large -Python frameworks: many of the class or module names are deeply nested, -but are commonly referred to by just their last one or two components -in writing. However, the user would like to know the full name of the -class or module name, and where to look up the API documentation -- but -without having the complete name thrust upon him during the flow of text -each time the module is mentioned.
- -Lore makes sure that each class or module name which is mentioned will -appear at least once using its full name, and afterwards use a common -short name, regardless of how the author wrote it up. This frees authors -from needing to observe, manually, this useful rule in their documents.
- -The HTML Lore outputs aims to be the poster boy of graceful degradation.
-Thus, for example, while footnotes always appear as hyper-links to the footnote
-text, browsers which respect the title
attribute (which is usually
-rendered as a tooltip) will also show the beginning of the footnote while
-hovering above the hyper-link.
Lore avoids using the font
or color
tags and attributes,
-preferring to use HTML classes and using a stylesheet to specify graphical
-design decisions. This allows the Lore user to customize the presentation of
-the output without touching Python code. Since most often the stylesheet
-link is found in the head
element, this is determined by
-the by the template.
Lore uses the same approach even for syntax-highlighting Python code,
-generating such elements as
-<span class="keyword">if</span>
.
The LaTeX home page describes LaTeX as a high-quality typesetting system,
-with features designed for the production of technical and scientific
-documentation.
LaTeX is very popular for generating printable content,
-building on Donald Knuth's TeX system to generate nearly optimal output
-by putting together much of the typesetting industry's experience in the
-form of a program and adding sophisticated algorithms for line-breaking and
-hyphenation.
It is very common for document generation systems to avoid generating -printable output themselves, instead letting LaTeX do the hard work, and -Lore is no exception.
- -Lore can output LaTeX in two modes: article mode, in which it generates
-a complete article ready to be be processed, and a section mode in which
-it generates a LaTeX file whose top-level element is a section. Such a file
-is usually included in some other LaTeX file via the include mechanism.
-Twisted itself uses mainly the section mode, and includes everything in the
-file book.tex
, which is later processed to generate
-the Twisted book.
While, conceivably, other modes could be done (a chapter mode or a subsection -mode) there has not been any demand for those. In the case of demand, supplying -these would be very few lines of Python code (less than 10), which can even -be done by subclassing existing classes and avoiding the modification of Lore -itself.
- -Docbook output is currently experimental. Its chief use to Lore would -be in generating Texinfo, which is the source for the GNU info documentation -format.
- -Very early in the Lore development life-cycle it was found that a good -Lint-like tool is necessary to find errors without necessitating a full -compilation to all formats and sometimes even browsing the results. Because -Lore was written to accommodate a large set of already existing documents -(which were not previously checked for potential problems), such a tool -was very useful so that finding a problem in one document would not mean -this problem needs to be manually searched, and corrected, in all the other -documents.
- -Lore's linter tries to find problems in documents -that would either stop the conversion to other formats by Lore completely -(for example, by being not well-formed XML), or that would make it less useful -(for example, by warning about tags or classes that are not supported by -Lore).
- -The linter even detects more exotic problems, -including:
-pre
elements containing lines over 80 characters. Long lines
- can be ugly to render in some output formats, and even impossible to
- render in others."
character in a non-pre or non-code
- environment. This makes a big difference for high-quality typographical
- output targets like LaTeX, which
- have distinct left- and right-quote characters.-for x in sequence: - ... -- This check caught a surprisingly large number of errors in the Twisted - documentation!
h1
contents being equal to title
contents.
- HTML is somewhat unique in that it has two places to specify the logical
- idea of title. Since other output formats do not support that, - in Lore papers, the contents of both must be the same.
Since many of the incremental improvements done to Lore found a problem
-in the existing documentation files, the linter has been
-an important part of the Lore development effort. One may even argue that
-part of the reason other documentation generation systems produce suboptimal
-output for their non-native
application is the lack of a linting
-tool.
Finally, if the linter gives a false positive, that is
-it emits a warning for something that isn't a problem in a particular situation,
-the user can add an hlint="off"
attribute to the offending tag, and
-the linter will ignore it. This is necessary only very rarely.
The chief design decision made in the linter, after
-painful experience when running tidy
, is that
-it must never change the document. Thus, while the linter
-will be as pedantic as possible finding
-errors, it never changes the contents. This is particularly important
-when dealing with version control systems, where spurious changes can
-render diff
listings useless.
All existing syntax highlighters for Python used pre-tokenize
-techniques to analyse the Python code. As a result, they were cumbersome
-and non-standard. The Lore developers decided that writing a Python
-HTML syntax-highlighter would be easier than modifying one of the existing
-ones. A syntax-highlighter was built on top of a null-tokenizer: that is,
-a tokenizer which emits the exact same characters as the input.
-This allowed easy debugging of the parsing code.
The only non-trivial code in the syntax highlighter is when dealing -with whitespace which is not significant syntactically, since the tokenizer -does not report it. However, since the tokenizer does report row and column, -when the code sees a discrepancy between where the previous token ended -and the current token starts, it adds whitespace to make up for -the discrepancy.
- -When writing out the HTML, the only difference between that and the
-null-tokenizer is the wrapping of each token by a span
-tag with the appropriate class and escaping.
Note that the basic Python tokenizer does not distinguish between the
-various roles of the production NAME
(that is, a string of alphanumeric
-and
-underscore characters starting with an underscore or a letter) in Python.
-The tokenizer Lore uses adds that information by having a simple state machine:
-if the word is a keyword, there is nothing to be determined; otherwise, it
-depends on the last detected name -- class
or
-def
mean it is a function or class names, and after a
-class
/def
and until a :
, everything
-is a parameter
or a superclass.
The Python syntax highlighter Lore uses can be found in the
-twisted.python.htmlizer
.
Often, when writing detailed documents, the author wishes to test his
-examples or even use examples from a working project. Pasting such examples
-directly into the HTML has both the usual problems of pasting code -- the
-version in the document will not benefit from bug fixes or enhancement to
-the original version -- and the problem that the HTML needs proper escaping,
-which is a tedious and error-prone procedure if done manually.
-Both problems are solved by Lore's listing
mechanism. The
-listing
mechanism converts HTML such as
-<a href="foo.py" class="py-listing>foo.py</a> -- -
into inclusion of the foo.py
file. It will always
-be properly escaped for whatever output format. It will
-also be syntax-highlighted, just as if it had been included verbatim.
A similar class, html-listing
is available for inclusion
-of HTML files.
Twisted's documentation frequently references API documentation. In Lore,
-the name of an API such as
-twisted.internet.defer.Deferred
is marked up as
-<code class="API" base="twisted.internet.defer">Deferred</code> -- -
This will unambiguously link to
-twisted.internet.defer.Deferred
, even though it is
-displayed as
-
. Lore
-produces API links that work with
-epydoc,
-but could easily be adapted for another API documentation generator; in fact,
-Lore originally worked with happydoc.
-In addition, in the HTML output, Lore will add a Deferred
title
-attribute to the API reference, containing the full name of the link.
A collection of documents will typically refer to each other, for instance to -avoid re-explaining some central concept. In HTML, cross-referencing -is implemented as linking:
- --See <a href="defer.html">Deferring Execution</a>. -- -
As a collection of HTML documents, this works with no changes. Other output
-formats do linking in other ways. When Lore is used to convert a collection of
-source HTML files into a single LaTeX book, each file is its own section, and
-the links are automatically converted into cross-references. Thus the example
-above might be rendered as See Deferring Execution (page 163).
Lore also recognizes fragment identifiers in links, so that a link
-to glossary.html#psu
will be cross-referenced to that part of the
-glossary named psu
, not just the whole glossary. This ensures that the
-page the reader is referred to is the correct one.
Man pages are a fact of life on UNIX, and every self-respecting command -line program is expected to come with one. The man format, implemented as -troff macros, is somewhat arcane. Since, when Lore was written, we already -had written man pages, the decision was to convert them to HTML rather than -try to rewrite them in HTML and design a man output format.
- -A limited parser for man pages is available in the
-twisted.lore.man2lore
module. It is not yet
-exposed via any public command line program.
Earlier attempts, using groff -Thtml
to
-generate HTML and then post-process it into Lore-compatible HTML
-were crufty and unmaintainable. It seems the man format shares some
-of LaTeX's problem: being written as a macro package over a powerful
-processor, it is too flexible for its own good. Fortunately, the subset
-normally used in man pages is quite small, so heuristically parsing man pages is
-much easier than the same task with LaTeX.
HTML, when invented by Tim Berners-Lee, was meant to be a simple language -for writing and sharing documents. With the explosion of the web, HTML has -grown to a confusing jumble of logical and presentation features, with more -layers, such as CSS, dumped on top of it. As a result, a modern browser is -a complicated beast. That given, it is perhaps understandable that today's -browsers do a sub-standard job at printing. Thus, while being extremely -well suited to the world wide web, HTML is significantly lacking, at least -in today's application market, when it comes to paper output. It might -be possible to write an application to properly convert HTML with CSS to -PostScript or PDF -- however, it would probably be much more complicated -than Lore. Moreover, the portability of such an application would -be worse of the portability of Lore itself, which currently only depends -on Python 2.1 or higher and the Twisted framework.
- -Limiting HTML to a small subset of features enables Lore to be small
-and readable while remaining useful. By including the class
-attribute among those features, Lore is also extensible.
When it comes to paper output, LaTeX cannot be out done except by a skilled -typesetter designing and implementing. However, the architecture of LaTeX -presents -significant problems when trying to view LaTeX online. LaTeX is written -as a macro layer above TeX rather than a preprocessor. Thus, all of TeX's -power is available, and sometimes used, in LaTeX. TeX is non-trivial to -parse and format by anyone short of Donald Knuth -- it contains such commands -as to change the tokenizer by modifying which characters are considered -word characters or even which character is the command character. -In fact, the authors are not aware of any application which handles the -full power of TeX without being based on the original TeX code.
- -All this makes LaTeX extremely difficult to parse, and even partial attempts
-to parse LaTeX are big and cumbersome -- for example,
-latex2html
. It is thus difficult to convert
-LaTeX to something appropriate to online viewing.
LyX's internal source format is not well documented, and the only supported -way to write it is using the LyX GUI. Thus it is inherently limiting to -documentation authors. In addition, it is not trivial to write LyX preprocessors -to save documentation authors tedious work.
- -Docbook is a big standard, with non-trivial to install tool-set. Writing -Docbook is different than most other document generation formats, so it -takes significant training to write. In addition, using Docbook for -a specific project usually requires writing custom DSSSL stylesheets -in a scheme-like language, and additional XML DTD snippets. Writing -these was quite possibly comparable to writing Lore, and Lore has the advantage -of being written in Python.
- -Texinfo imposes a significant effort on authors. Many things need to -be written twice, and the error messages leave a lot to be desired. -After starting to work on the Lore texinfo output format the authors -are grateful they have never had to write Texinfo by hand.
- -When generating LaTeX, Lore does it via a visitor pattern while visiting
-the nodes. A node which does not have a specific visitor is visited by
-first writing the start_
attribute, then visiting its
-children and then writing the end_
attribute. If the attributes
-do not exist, they are treated as though they were empty strings.
That code allows most of the HTML elements to LaTeX converters to have no -code -- only a pair of strings -- while the elements converters which need -more sophisticated programming can do it via defining a method, which can -still call the default processor if it needs this functionality.
- -This pattern is also friendly to subclassing: all a subclass needs to -do in order to change how an element is handled is to define either a pair -of class attributes or a method.
- -In the above example of the visitor pattern, registration of the methods
-and attributes is avoided thanks to using the crudest form of reflection
-in Python -- the getattr()
function.
In the Lint support tool, more sophisticated reflection is needed when
-it needs to find all methods whose name begins with check_
.
-This is done via the Twisted reflection code, built on top of the native
-Python facilities, in the module
-twisted.python.reflect
.
In the HTML output code, the most common operation is that of getting
-a list of elements which satisfy some property. This is done by one
-primary work-horse function:
-twisted.web.domhelpers.findNodes
. This function
-accepts a DOM tree and a function, and returns a list of all elements
-for which this function returns true. Using this, and the fact that Python makes
-it easy to combine functions into boolean combinations, makes analysis
-and modification and of the DOM tree a breeze.
Probably the trickiest thing about non-HTML output formats is escaping. -The problem comes from two annoying problems which are not really hard -to solve, but do represent annoyances in the code:
- -\
- is escaped, in TeX, as $\backslash$
while most other
- characters are escaped as \<char>
.pre
, </>
should not be
- escaped inside code
and should be escaped as
- $<$/$>$
outside it.In Docbook, the sections are nested, so there is only need for
-a title
element. However, in HTML only the headers care
-at which level they are. This requires the Docbook converter to keep
-the last header level and when it reaches a new header, to close and open
-enough sections so the header will get to the correct level. While Docbook's
-way may be more correct
, it is unfortunate it chose to diverge from
-all other systems here.
Texinfo requires all the sections in a document will have unique names. -This makes it very inconvenient as both an input and an output format.
- -Also, differing significance of whitespace in different formats requires that -all whitespace emitted by lore must be normalized for the particular output -format being used. Blank lines which have no impact on HTML will trigger -paragraph breaks in LaTeX.
- -The first version of the LaTeX output generator was using an event-based
-XML parsing engine. It quickly turned out one needs to keep a lot of
-information in stacks and manage many instance variables. For example,
-though XML gets the name of the closing element (even that is arguably
-too much information), it does not get the attributes. In span
-elements, for example, the interesting information is the class
-attribute. Since a-priory, span
s might be nested, the class
-needs to keep a stack of attribute collections.
Quite soon, stacks were needed for proper handling of div
-tags and for determining proper quoting formats. Moreover, getting the
-code to function correctly in the face of edge cases, such as cross-references
-inside pre
tags, proved to be quite a challenge.
The code was shortened, simplified and became more maintainable when
-it was moved to microdom
.
We feel that unless there is -an inherent reason to do XML event-based parsing, then it is much easier -to read the whole thing into a DOM and then process it. The code is both -shorter and clearer, and features are much easier to add.
- -Lore, out of the box, does not attempt to be all things to all people. -Particularly in the LaTeX output format, there is a lot of room for -interpretation and personal preferences. Lore chose one specific way, without -trying to add half a dozen options to tweak it. However, thanks to the -way it is coded, it is easy to add or modify features to suit individual -preferences. Many customizations only involve adding or overriding simple data -attributes to a subclass; more advanced changes require adding or overriding -methods.
- -Likewise, the HTML output is built by running several tree-modification -functions which are independent. Completely different HTML output could -be build by adding more functions, or not running some of those which -are being run.
- -We already know of multiple users that have extended Lore for custom LaTeX -generation. In each case it was a simple matter of subclassing Lore's LaTeX -code.
- -Documentation generation systems were already a solved problem before Lore -was written. However, we know of no system with Lore's unique combination of -features -- in particular, portability, having a directly readable source format -which is also directly writable in text editors. -The common wisdom that a documentation generation -system is a hard sell because it requires people to learn a new language was -refuted by using an existing language.
- -Wheel reinvention also occurred in a nearby area -- Twisted's XML support,
-for which Lore is one of the biggest users. Again, the common wisdom was that
-this was a solved problem, with many existing DOM and SAX implementations.
-However, implementation of some features, no implementation of other features
-and API instability have lead the Twisted team to write its own, highly
-pythonic, DOM-like implementation. In
-microdom
, the aim is to be
-as thin a wrapper over the basic Python wrappers as possible. This feature
-has been used to the full in Lore, where many of the tree manipulations
-would have been much more cumbersome had a standard opaque
DOM
-implementation been used. In addition, using
-microdom
frees Lore from the
-dependence on both Python version and whether PyXML is installed.
For example, microdom
-exposes the list of child nodes as a plain Python lists. This means that
-not only all the list operations can be done of it, which could possibly
-be simulated by a list-like object, but that it is possible to
-replace it by our own list. As another example,
-microdom
allows us to freely
-copy nodes from one DOM tree into another.
Python, as a language well suited to rapid application development, -acts as a way to make wheel reinvention far from the horrible mistake -which is portrayed in the common software engineering folklore. Indeed, Python -makes it easy enough to reinvent wheels that only the best, and easy to -use, wheels, get reused at all.
- -Lore can be found in Twisted 1.0.1 and higher, in the
-twisted.lore
package. When you install the package,
-the relevant script, lore
,
-should be installed in a sane directory,
-as determined by distutils.
For usage examples, see admin/release-twisted
-in the Twisted source distribution. It runs the various Lore scripts
-as part of the package build.
It would be nice to have the Docbook output fully working. It would also
-be nice to have Texinfo in full working order so that GNU info aficionados could
-read the documents with the info browser. As suggested above, it might
-also be useful to have a way to directly generate PDF output via
-pdflib
in order to skip LaTeX.
In addition, another potential output format is to have high-quality
-text output. This is non-trivial, but possibly useful: browsers'
-Save as text
feature is usually implemented as an afterthought,
-and hardly uses the flexibility available in the text format to its
-full power. The authors are unaware, for example, of an HTML to text
-converter which uses the underlining with =
sign or -
-to indicate a header, or which uses the /slant/
or
-*asterisk*
conventions to indicate emphasis.
Another output format we are considering is a split-page HTML with -interlinks, so that long documents can be converted into something -which is web-friendly. One nice use for that would be in web-based -presentations.
- -Currently all images are converted to EPS format. It would be nice to have -the LaTeX converter try to see if there is already an EPS version, via some -naming convention, and use that. This would allow better scaling of things like -Dia diagrams. The versions in bitmap-based formats (such as PNG) -are impossible to scale, because the text would become unreadable.
- -Currently, the only interface to Lore is through the command-line, and -even that is somewhat spotty: for example, the man page parser is not directly -available via the command line. We hope to remedy that, having at least a full -suite of command-line tools and possibly graphical wrappers, particularly -EMACS modes.
- -When starting with a historical note, it is only fitting to end -with a historical note. Since the writing of Lore, Twisted documentation -is successfully generated by it and distributed in the tarball. It contains -generated HTML from the HOWTO documents, specifications and man pages. -It also contains all these documents inside a LaTeX-generated PostScript -file and PDF file in an easy to print format, suitable for reading on those -long plane flights or train rides.
- -Lore is also used to generate pages with consistent headers and footers for -the twistedmatrix.com web site -- not just the Twisted documentation. -This is shows the inherent flexibility in Lore's model of being easily -configurable via an HTML template, -a feature which none of the major -document generation systems support for their HTML output.
- -lore(1)
man pageclass ServerObject(pb.Referenceable):\n' - ' def remote_add(self, one, two):\n' - ' answer = one + two\n' - ' print "returning result:", answer\n' - ' return answer\n' - ' \n' - "
\n' - ' def got_RemoteReference(remoteref):\n' - ' print "asking it to add"\n' - ' deferred = remoteref.callRemote("add", ' - '1, 2)\n' - ' deferred.addCallbacks(add_done, err)\n' - ' # this Deferred fires when the ' - "method call is complete\n" - ' def add_done(result):\n' - ' print "addition complete, result is", result\n' - '
TranslucentRemote Method calls in Twisted
TranslucentRemote Method calls in Twisted
<warner@lothar.com>
-One of the core services provided by the Twisted networking framework is
-Perspective Broker
, which provides a clean, secure, easy-to-use
-Remote Procedure Call (RPC) mechanism. This paper explains the novel
-features of PB, describes the security model and its implementation, and
-provides brief examples of usage.
PB is used as a foundation for many other services in Twisted, as well as -projects built upon the Twisted framework. twisted.web servers can delegate -responsibility for different portions of URL-space by distributing PB -messages to the object that owns that subspace. twisted.im is an -instant-messaging protocol that runs over PB. Applications like CVSToys and -the BuildBot use PB to distribute notices every time a CVS commit has -occurred. Using Perspective Broker as the RPC layer allows these projects to -stay focused on the interesting parts.
- -The PB protocol is not limited to Python. There is a working Java -implementation available from the Twisted web site, as is an Emacs-Lisp -version (which can be used to control a PB-enabled application from within -your editing session, or effectively embed a Python interpreter in Emacs). -Python's dynamic and introspective nature makes Perspective Broker easier to -implement (and very convenient to use), but neither are strictly necessary. -With a set of callback tables and a good dictionary implementation, it would -be possible to implement the same protocol in C, C++, Perl, or other -languages.
- -Perspective Broker provides the following basic RPC features.
- -remote_) of -
pb.Referenceable
objects can be invoked by remote clients who
- hold matching pb.RemoteReference
objects.twisted.pb.flavor
class they inherit from, and
- upon overridable methods to get and set state.pb.Viewable
objects
- keep a user reference with them, so remotely-invokable methods can find
- out who invoked them.Failure
objects and serialized
- so they can be provided to the caller. All the usual traceback information
- is available on the invoking side.twisted.spread
, Marmalade, Tasters, and Flavors. By contrast,
- CORBA and XML-RPC have few, if any, puns in their naming conventions.Here is a simple example of PB in action. The server code creates an -object that can respond to a few remote method calls, and makes it available -on a TCP port. The client code connects and runs two methods.
- -pb-server1.py -pb-client1.py - -When this is run, the client emits the following progress messages:
- --% ./pb-client1.py -got object: <twisted.spread.pb.RemoteReference instance at 0x817cab4> -asking it to add -addition complete, result is 3 -now trying subtract -subtraction result is -7 -shutting down -- -
This example doesn't demonstrate instance serialization, exception
-reporting, authentication, or other features of PB. For more details and
-examples, look at the PB howto
docs at twistedmatrix.com.
TranslucentReferences?
Remote function calls are not the same as local function calls. Remote -calls are asynchronous. Data exchanged with a remote system may be -interpreted differently depending upon version skew between the two systems. -Method signatures (number and types of parameters) may differ. More failure -modes are possible with RPC calls than local ones.
- -Transparent
RPC systems attempt to hide these differences, to make
-remote calls look the same as local ones (with the noble intention of making
-life easier for programmers), but the differences are real, and hiding them
-simply makes them more difficult to deal with. PB therefore provides
-translucent
method calls: it exposes these differences, but offers
-convenient mechanisms to handle them. Python's flexible object model and
-exception handling take care of part of the problem, while Twisted's
-Deferred class provides a clean way to deal with the asynchronous nature of
-RPC.
A fundamental difference between local function calls and remote ones is
-that remote ones are always performed asynchronously. Local function calls
-are generally synchronous (at least in most programming languages): the
-caller is blocked until the callee finishes running and possibly returns a
-value. Local functions which might block (loosely defined as those which
-would take non-zero or indefinite time to run on infinitely fast hardware)
-are usually marked as such, and frequently provide alternative APIs to run
-in an asynchronous manner. Examples of blocking functions are
-select()
and its less-generalized cousins:
-sleep()
, read()
(when buffers are empty), and
-write()
(when buffers are full).
Remote function calls are generally assumed to take a long time. In -addition to the network delays involved in sending arguments and receiving -return values, the remote function might itself be blocking.
- -Transparent
RPC systems, which pretend that the remote system is
-really local, usually offer only synchronous calls. This prevents the
-program from getting other work done while the call is running, and causes
-integration problems with GUI toolkits and other event-driven
-frameworks.
In addition to the usual exceptions that might be raised in the course of -running a function, remotely invoked code can cause other errors. The -network might be down, the remote host might refuse the connection (due to -authorization failures or resource-exhaustion issues), the remote end might -have a different version of the code and thus misinterpret serialized -arguments or return a corrupt response. Python's flexible exception -mechanism makes these errors easy to report: they are just more exceptions -that could be raised by the remote call. In other languages, this requires a -special API to report failures via a different path than the normal -response.
- -In PB, Deferreds are used to handle both the asynchronous nature of the
-method calls and the various kinds of remote failures that might occur. When
-the method is invoked, PB returns a Deferred object that will be fired
-later, when the response (success or failure) is received from the remote
-end. The caller (the one who invoked callRemote
) is free to
-attach callback and errback handlers to the Deferred. If an exception is
-raised (either by the remote code or a network failure during processing),
-the errback will be run with the wrapped exception. If the function
-completes normally, the callback is run.
By using Deferreds, the invoking program can get other work done while it -is waiting for the results. Failure is handled just as cleanly as -success.
- -In addition, the remote method can itself return a Deferred
-instead of an actual return value. When that Deferreds
fires,
-the data given to the callback will be serialized and returned to the
-original caller. This allows the remote server to perform other work as
-well, putting off the answer until one is available.
Perspective Broker is first and foremost a mechanism for remote method
-calls: doing something to a local object which causes a method to get run on
-a distant one. The process making the request is usually called the
-client
, and the process which hosts the object that actually runs the
-method is called the server
. Note, however, that method requests can
-go in either direction: instead of distinguishing client
and
-server
, it makes more sense to talk about the sender
and
-receiver
for any individual method call. PB is symmetric, and the
-only real difference between the two ends is that one initiated the original
-TCP connection and the other accepted it.
With PB, the local object is an instance of
-twisted.spread.pb.RemoteReference
, and you do something
-to it by calling its .callRemote
method. This call accepts a
-method name and an argument list (including keyword arguments). Both are
-serialized and sent to the receiving process, and the call returns a
-Deferred
, to which you can add callbacks. Those callbacks will
-be fired later, when the response returns from the remote end.
That local RemoteReference points at a
-twisted.spread.pb.Referenceable
object living in the other
-program (or one of the related callable flavors). When the request comes
-over the wire, PB constructs a method name by prepending
-remote_
to the name requested by the remote caller. This method
-is looked up in the pb.Referenceable
and invoked. If an
-exception is raised (including the AttributeError
that results
-from a bad method name), the error is wrapped in a Failure
-object and sent back to the caller. If it succeeds, the result is serialized
-and sent back.
The caller's Deferred will either have the callback run (if the method -completed normally) or the errback run (if an exception was raised). The -Failure object given to the errback handler allows a full stack trace to be -displayed on the calling end.
- -For example, if the holder of the RemoteReference
does rr.callRemote("foo", 1, 3)
, the corresponding
-Referenceable
will be invoked with r.remote_foo(1, 3)
. A callRemote
of
-
would invoke bar
remote_bar
, etc.
Each pb.RemoteReference
object points to a
-pb.Referenceable
instance in some other program. The first such
-reference must be acquired with a bootstrapping function like
-pb.getObjectAt
, but all subsequent ones are created when a
-pb.Referenceable
is sent as an argument to (or a return value
-from) a remote method call.
When the arguments or return values contain references to other objects, -the object that appears on the other side of the wire depends upon the type -of the referred object. Basic types are simply copied: a dictionary of lists -will appear as a dictionary of lists, with internal references preserved on -a per-method-call basis (just as Pickle will preserve internal references -for everything pickled at the same time). Class instances are restricted, -both to avoid confusion and for security reasons.
- -PB only allows certain kinds of objects to be transferred to and from
-remote processes. Most of these restrictions are implemented in the Jelly serialization layer, described below. In general, to
-send an object over the wire, it must either be a basic python type (list,
-dictionary, etc), or an instance of a class which is derived from one of the
-four basic PB Flavors: Referenceable
,
-Viewable
, Copyable
, and Cacheable
.
-Each flavor has methods which define how the object should be treated when
-it needs to be serialized to go over the wire, and all have related classes
-that are created on the remote end to represent them.
There are a few kinds of callable classes. All are represented on the
-remote system with RemoteReference
instances.
-callRemote
can be used on these RemoteReferences, causing
-methods with various prefixes to be invoked.
Local Class | -Remote Representation | -method prefix | -
---|---|---|
Referenceable |
- RemoteReference |
- remote_ |
-
Viewable |
- RemoteReference |
- view_ |
-
Viewable
(and the related Perspective
class)
-are described later (in Authorization). They
-provide a secure way to let methods know who is calling them. Any
-time a Referenceable
(or Viewable
) is sent over
-the wire, it will appear on the other end as a RemoteReference
.
-If any of these references are sent back to the system they came from, they
-emerge from the round trip in their original form.
Note that RemoteReferences cannot be sent to anyone else (there are no
-third-party references
): they are scoped to the connection between
-the holder of the Referenceable
and the holder of the
-RemoteReference
. (In fact, the RemoteReference
is
-really just an index into a table maintained by the owner of the original
-Referenceable
).
There are also two data classes. To send an instance over the wire, it -must belong to a class which inherits from one of these.
- -Local Class | -Remote Representation | -
---|---|
Copyable |
- RemoteCopy |
-
Cacheable |
- RemoteCache |
-
Copyable
is used to allow class instances to be sent over
-the wire. Copyable
s are copy-by-value, unlike
-Referenceable
s which are copy-by-reference.
-Copyable
objects have a method called
-getStateToCopy
which gets to decide how much of the object
-should be sent to the remote system: the default simply copies the whole
-__dict__
. The receiver must register a RemoteCopy
-class for each kind of Copyable
that will be sent to it: this
-registration (described later in Representing
-Instances) maps class names to actual classes. Apart from being a
-security measure (it emphasizes the fact that the process is receiving data
-from an untrusted remote entity and must decide how to interpret it safely),
-it is also frequently useful to distinguish a copy of an object from the
-original by holding them in different classes.
getStateToCopy
is frequently used to remove attributes that
-would not be meaningful outside the process that hosts the object, like file
-descriptors. It also allows shared objects to hold state that is only
-available to the local process, including passwords or other private
-information. Because the default serialization process recursively follows
-all references to other objects, it is easy to accidentally send your entire
-program to the remote side. Explicitly creating the state object (creating
-an empty dictionary, then populating it with only the desired instance
-attributes) is a good way to avoid this.
The fact that PB will refuse to serialize objects that are neither basic
-types nor explicitly marked as being transferable (by subclassing one of the
-pb.flavors) is another way to avoid the don't tug on that, you never know
-what it might be attached to
problem. If the object you are sending
-includes a reference to something that isn't marked as transferable, PB will
-raise an InsecureJelly exception rather than blindly sending it anyway (and
-everything else it references).
Finally, note that getStateToCopy
is distinct from the
-__getstate__
method used by Pickle, and they can return
-different values. This allows objects to be persisted (across time)
-differently than they are transmitted (across [memory]space).
Cacheable
is a variant of Copyable
which is
-used to implement remote caches. When a Cacheable
is sent
-across a wire, a method named getStateToCacheAndObserveFor
is
-used to simultaneously get the object's current state and to register an
-Observer
which lives next to the Cacheable
. The Observer
-is effectively a RemoteReference
that points at the remote
-cache. Each time the cached object changes, it uses its Observers to tell
-all the remote caches about the change. The setter
methods can just
-call observer.callRemote("setFoo", newvalue)
for
-all their observers.
On the remote end, a RemoteCache
object is created, which
-populates the original object's state just as RemoteCopy
does.
-When changes are made, the Observers remotely invoke methods like
-observe_setFoo
in the RemoteCache
to perform the
-updates.
As RemoteCache
objects go away, their Observers go away too,
-and call stoppedObserving
so they can be removed from the
-list.
The PB howto
docs have more information and complete examples of both
-pb.Copyable
and pb.Cacheable
.
As a framework, Perspective Broker (indeed, all of Twisted) was built
-from the ground up. As multiple use cases became apparent, common
-requirements were identified, code was refactored, and layers were developed
-to cleanly serve the needs of all customers
. The twisted.cred layer
-was created to provide authorization services for PB as well as other
-Twisted services, like the HTTP server and the various instant messaging
-protocols. The abstract notions of identity and authority it uses are
-intended to match the common needs of these various protocols: specific
-applications can always use subclasses that are more appropriate for their
-needs.
In twisted.cred, Identities
are usernames (with passwords),
-represented by Identity
objects. Each identity has a
-keyring
which authorizes it to access a set of objects called
-Perspectives
. These perspectives represent accounts or other
-capabilities; each belongs to a single Service
. There may be multiple
-Services in a single application; in fact the flexible nature of Twisted
-makes this easy. An HTTP server would be a Service, and an IRC server would
-be another one.
As an example, a login service might have perspectives for Alice, Bob, -and Charlie, and there might also be an Admin perspective. Alice has admin -capabilities. In addition, let us say the same application has a chat -service with accounts for each person (but no special administrator -account).
- -So, in this example, Alice's keyring gives her access to three
-perspectives: login/Alice, login/Admin, and chat/Alice. Bob only gets two:
-login/Bob and chat/Bob. Perspective
objects have names and
-belong to Service
objects, but the
-Identity.keyring
is a dictionary indexed by (serviceName,
-perspectiveName) pairs. It uses names instead of object references because
-the Perspective
object might be created on demand. The keys
-include the service name because Perspective names are scoped to a single
-service.
The PB-specific subclass of the generic Perspective
class is
-also capable of remote execution. The login process results in the
-authorized client holding a special kind of RemoteReference
-that will allow it to invoke perspective_
methods on the
-matching pb.Perspective
object. In PB applications that use the
-twisted.cred
authorization layer, clients get this reference
-first. The client is then dependent upon the Perspective to provide
-everything else, so the Perspective can enforce whatever security policy it
-likes.
(Note that the pb.Perspective
class is not actually one of
-the serializable PB flavors, and that instances of it cannot be sent
-directly over the wire. This is a security feature intended to prevent users
-from getting access to somebody else's Perspective
by mistake,
-perhaps when a list all users
command sends back an object which
-includes references to other Perspectives.)
PB provides functions to perform a challenge-response exchange in which
-the remote client proves their identity to get that Perspective
-reference. The Identity
object holds a password and uses an MD5
-hash to verify that the remote user knows the password without sending it in
-cleartext over the wire. Once the remote user has proved their identity,
-they can request a reference to any Perspective
permitted by
-their Identity
's keyring.
There are twisted.cred functions (twisted.enterprise.dbcred) which can -pull user information out of a database, and it is easy to create modules -that could check /etc/passwd or LDAP instead. Authorization can then be -centralized through the Perspective object: each object that is accessible -remotely can be created with a pointer to the local Perspective, and objects -can ask that Perspective whether the operation is allowed before performing -method calls.
- -Most clients use a helper function called pb.connect()
to
-get the first Perspective reference: it takes all the necessary identifying
-information (host, port, username, password, service name, and perspective
-name) and returns a Deferred
that will be fired when the
-RemoteReference
is available. (This may change in the future:
-there are plans afoot to use a URL-like scheme to identify the Perspective,
-which will probably mean a new helper function).
There is a special kind of Referenceable
called
-pb.Viewable
. Its remote methods (all named view_
)
-are called with an extra argument that points at the
-Perspective
the client is using. This allows the same
-Referenceable
to be shared among multiple clients while
-retaining the ability to treat those clients differently. The methods can
-check with the Perspective to see if the request should be allowed, and can
-use per-client information in processing the request.
Fundamental to any calling convention, whether ABI or RPC, is how -arguments and return values are passed from caller to callee and back. RPC -systems require data to be turned into a form which can be delivered through -a network, a process usually known as serialization. Sharing complex types -(references and class instances) with a remote system requires more care: -references should all point to the same thing (even though the object being -referenced might live on either end of the connection), and allowing a -remote user to create arbitrary class instances in your memory space is a -security risk that must be controlled.
- -PB uses its own serialization scheme called Jelly
. At the bottom
-end, it uses s-expressions (lists of numbers and strings) to represent the
-state of basic types (lists, dictionaries, etc). These s-expressions are
-turned into a bytestream by the Banana
layer, which has an optional C
-implementation for speed. Unserialization for higher-level objects is driven
-by per-class jellyier
objects: this flexibility allows PB to offer
-inheritable classes for common operations. pb.Referenceable
is
-a class which is serialized by sending a reference to the remote end that
-can be used to invoke remote methods. pb.Copyable
is a class
-which creates a new object on the remote end, with methods that the
-developer can override to control how much state is sent or accepted.
-pb.Cacheable
sends a full copy the first time it is exchanged,
-but then sends deltas as the object is modified later.
Objects passed over the wire get to decide for themselves how much
-information is actually passed to the remote system. Copy-by-reference
-objects are given a per-connection ID number and stashed in a local
-dictionary. Copy-by-value objects may send their entire
-__dict__
, or some subset thereof. If the remote method returns
-a referenceable object that was given to it earlier (either in the same RPC
-call or an earlier one), PB sends the ID number over the wire, which is
-looked up and turned into a proper object reference upon receipt. This
-provides one-sided reference transparency: one end sees objects coming and
-going through remote method calls in exactly the same fashion as through
-local calls. Those references are only capable of very specific operations;
-PB does not attempt to provide full object transparency. As discussed later,
-this is instrumental to security.
The Banana
low-level serialization layer converts s-expressions
-which represent basic types (numbers, strings, and lists of numbers,
-strings, or other lists) to and from a bytestream. S-expressions are easy to
-encode and decode, and are flexible enough (when used with a set of tokens)
-to represent arbitrary objects. cBanana
is a C extension module which
-performs the encode/decode step faster than the native python
-implementation.
Each s-expression element is converted into a message with two or three -components: a header, a type marker, and an optional body (used only for -strings). The header is a number expressed in base 128. The type marker is a -single byte with the high bit set, that both terminates the header and -indicate the type of element this message describes (number, list-start, -string, or tokenized string).
- -When a connection is first established, a list of strings is sent to
-negotiate the dialect
of Banana being spoken. The first dialect known
-to both sides is selected. Currently, the dialect is only used to select a
-list of string tokens that should be specially encoded (for performance),
-but subclasses of Banana could use self.currentDialect to influence the
-encoding process in other ways.
When Banana is used for PB (by negotiating the pb
dialect), it has
-a list of 30ish strings that are encoded into two-byte sequences instead of
-being sent as generalized string messages. These string tokens are used to
-mark complex types (beyond the simple lists, strings, and numbers provided
-natively by Banana) and other objects Jelly needs to do its job.
Jelly
handles object serialization. It fills a similar role
-to the standard Pickle module, but has design goals of security and
-portability (especially to other languages) where Pickle favors efficiency
-of representation. In addition, Jelly serializes objects into s-expressions
-(lists of tokens, strings, numbers, and other lists), and lets Banana do the
-rest, whereas Pickle goes all the way down to a bytestream by itself.
Basic python types (apart from strings and numbers, which Banana can
-handle directly) are generally turned into lists with a type token as the
-first element. For example, a python dictionary is turned into a list that
-starts with the string token dictionary
and continues with elements
-that are lists of [key, value] pairs. Modules, classes, and methods are all
-transformed into s-expressions that refer to the relevant names. Instances
-are represented by combining the class name (a string) with an arbitrary
-state object (which is usually a dictionary).
Much of the rest of Jelly has to do with safely handling class instances -(as opposed to basic Python types) and dealing with references to shared -objects.
- -Mutable types are serialized in a way that preserves the identity between
-the same object referenced multiple times. As an example, a list with four
-elements that all point to the same object must look the same on the remote
-end: if it showed up as a list pointing to four independent objects (even if
-all the objects had identical states), the resulting list would not behave
-in the same way as the original. Changing newlist[0]
would not
-modify newlist[1]
as it ought to.
Consequently, when objects which reference mutable types are serialized, -those references must be examined to see if they point to objects which have -already been serialized in the same session. If so, an object id tag of some -sort is put into the bytestream instead of the complete object, indicating -that the deserializer should use a reference to a previously-created object. -This also solves the issue of recursive or circular references: the first -appearance of an object gets the full state, and all subsequent ones get a -reference to it.
- -Jelly manages this reference tracking through an internal
-_Jellier
object (in particular through the .cooked
-dictionary). As objects are serialized, their id
values are
-stashed. References to those objects that occur after jellying has started
-can be replaced with a dereference
marker and the object id.
The scope of this _Jellier
object is limited to a single
-call of the jelly
function, which in general corresponds to a
-single remote method call. The argument tuple is jellied as a single object
-(a tuple), so different arguments to the same method will share referenced
-objectsActually, PB currently jellies the list
-arguments in a separate tuple from the keyword arguments. This issue is
-currently being examined and may be changed in the future, but
-arguments of separate methods will not share them. To do more complex
-caching and reference tracking, certain PB flavors
(see below)
-override their jellyFor
method to do more interesting things.
-In particular, pb.Referenceable
objects have code to insure
-that one which makes a round trip will come back as a reference to the same
-object that was originally sent.
An exception to this one-call scope
is provided: if the
-Jellier
is created with a persistentStore
object,
-all class instances will be passed through it first, and it has the
-opportunity to return a persistent id
. If available, this id is
-serialized instead of the object's state. This would allow object references
-to be shared between different invocations of jelly
. However,
-PB itself does not use this technique: it uses overridden
-jellyFor
methods to provide per-connection shared
-references.
Each class gets to decide how it should be represented on a remote
-system. Sending and receiving are separate actions, performed in separate
-programs on different machines. So, to be precise, each class gets to decide
-two things. First, they get to specify how they should be sent to a remote
-client: what should happen when an instance is serialized (or jellied
-in PB lingo), what state should be recorded, what class name should be sent,
-etc. Second, the receiving program gets to specify how an incoming object
-that claims to be an instance of some class should be treated: whether it
-should be accepted at all, if so what class should be used to create the new
-object, and how the received state should be used to populate that
-object.
A word about notation: in Perspective Broker parlance, to jelly
is
-used to describe the act of turning an object into an s-expression
-representation (serialization, or at least most of it). Therefore the
-reverse process, which takes an s-expression and turns it into a real python
-object, is described with the verb to unjelly
.
Serializing instances is fairly straightforward. Classes which inherit
-from Jellyable
provide a jellyFor
method, which
-acts like __getstate__
in that it should return a serializable
-representation of the object (usually a dictionary). Other classes are
-checked with a SecurityOptions
instance, to verify that they
-are safe to be sent over the wire, then serialized by using their
-__getstate__
method (or their __dict__
if no such
-method exists). User-level classes always inherit from one of the PB
-flavors
like pb.Copyable
(all of which inherit from
-Jellyable
) and use jellyFor
; the
-__getstate__
option is only for internal use.
Unjellying (for instances) is triggered by the receipt of an s-expression
-with the instance
tag. The s-expression has two elements: the name of
-the class, and an object (probably a dictionary) which holds the instance's
-state. At that point in time, the receiving program does not know what class
-should be used: it is certainly not safe to simply do an
-import
of the classname requested by the sender. That
-effectively allows a remote entity to run arbitrary code on your system.
-
There are two techniques used to control how instances are unjellied. The
-first is a SecurityOptions
instance which gets to decide
-whether the incoming object should accepted or not. It is said to
-taste
the incoming type before really trying to unserialize it. The
-default taster accepts all basic types but no classes or instances.
If the taster decides that the type is acceptable, Jelly then turns to
-the unjellyableRegistry
to determine exactly how to
-deserialize the state. This is a table that maps received class names names
-to unserialization routines or classes.
The receiving program must register the classes it is willing to accept. -Any attempts to send instances of unregistered classes to the program will -be rejected, and an InsecureJelly exception will be sent back to the sender. -If objects should be represented by the same class in both the sender and -receiver, and if the class is defined by code which is imported into both -programs (an assumption that results in many security problems when it is -violated), then the shared module can simply claim responsibility as the -classes are defined:
- --class Foo(pb.RemoteCopy): - def __init__(self): - # note: __init__ will *not* be called when creating RemoteCopy objects - pass - def __getstate__(self): - return foo - def __setstate__(self, state): - self.stuff = state.stuff -setUnjellyableForClass(Foo, Foo) -- -
In this example, the first argument to
-setUnjellyableForClass
is used to get the fully-qualified class
-name, while the second defines which class will be used for unjellying.
-setUnjellyableForClass
has two functions: it informs the
-taster
that instances of the given class are safe to receive, and it
-registers the local class that should be used for unjellying.
The Broker
class manages the actual connection to a remote
-system. Broker
is a Protocol
(in Twisted terminology),
-and there is an instance for each socket over which PB is being spoken.
-Proxy objects like pb.RemoteReference
, which are associated
-with another object on the other end of the wire, all know which Broker they
-must use to get to their remote counterpart. pb.Broker
objects
-implement distributed reference counts, manage per-connection object IDs,
-and provide notification when references are lost (due to lost connections,
-either from network problems or program termination).
Perspective Broker is implemented by sending Jellied commands over the
-connection. These commands are always lists, and the first element of the
-list is always a command name. The commands are turned into
-proto_
-prefixed method names and executed in the Broker object.
-There are currently 9 such commands. Two (proto_version
and
-proto_didNotUnderstand
) are used for connection negotiation.
-proto_message
is used to implement remote method calls, and is
-answered by either proto_answer
or
-proto_error
.
proto_cachemessage
is used by Observers (see pb.Copyable) to notify their
-RemoteCache
about state updates, and behaves like
-proto_message
. pb.Cacheable also
-uses proto_decache
and proto_uncache
to manage
-reference counts of cached objects.
Finally, proto_decref
is used to manage reference counts on
-RemoteReference
objects. It is sent when the
-RemoteReference
goes away, so that the holder of the original
-Referenceable
can free that object.
Each time a Referenceable
is sent across the wire, its
-jellyFor
method obtains a new unique local ID
(luid) for
-it, which is a simple integer that refers to the original object. The
-Broker's .localObjects{}
and .luids{}
tables
-maintain the luid
-to-object mapping. Only this ID number is sent to
-the remote system. On the other end, the object is unjellied into a
-RemoteReference
object which remembers its Broker and the luid
-it refers to on the other end of the wire. Whenever
-callRemote()
is used, it tells the Broker to send a message to
-the other end, including the luid value. Back in the original process, the
-luid is looked up in the table, turned into an object, and the named method
-is invoked.
A similar system is used with Cacheables: the first time one is sent, an
-ID number is allocated and recorded in the
-.remotelyCachedObjects{}
table. The object's state (as returned
-by getStateToCacheAndObserveFor()
) and this ID number are sent
-to the far end. That side uses .cachedLocallyAs()
to find the
-local CachedCopy
object, and tracks it in the Broker's
-.locallyCachedObjects{}
table. (Note that to route state
-updates to the right place, the Broker on the CachedCopy
side
-needs to know where it is. The same is not true of
-RemoteReference
s: nothing is ever sent to a
-RemoteReference
, so its Broker doesn't need to keep track of
-it).
Each remote method call gets a new requestID
number. This
-number is used to link the request with the response. All pending requests
-are stored in the Broker's .waitingForAnswers{}
table until
-they are completed by the receipt of a proto_answer
or
-proto_error
message.
The Broker also provides hooks to be run when the connection is lost.
-Holders of a RemoteReference
can register a callback with
-.notifyOnDisconnect()
to be run when the process which holds
-the original object goes away. Trying to invoke a remote method on a
-disconnected broker results in an immediate DeadReferenceError
-exception.
The Broker on the Referenceable
end of the connection needs
-to implement distributed reference counting. The fact that a remote end
-holds a RemoteReference
should prevent the
-Referenceable
from being freed. To accomplish this, The
-.localObjects{}
table actually points at a wrapper object
-called pb.Local
. This object holds a reference count in it that
-is incremented by one for each RemoteReference
that points to
-the wrapped object. Each time a Broker serializes a
-Referenceable
, that count goes up. Each time the distant
-RemoteReference
goes away, the remote Broker sends a
-proto_decref
message to the local Broker, and the count goes
-down. When the count hits zero, the Local
is deleted, allowing
-the original Referenceable
object to be released.
Insecurity in network applications comes from many places. Most can be -summarized as trusting the remote end to behave in a certain way. -Applications or protocols that do not have a way to verify their assumptions -may act unpredictably when the other end misbehaves; this may result in a -crash or a remote compromise. One fundamental assumption that most RPC -libraries make when unserializing data is that the same library is being -used at the other end of the wire to generate that data. Developers put so -much time into making their RPC libraries work at all that -they usually assume their own code is the only thing that could possibly -provide the input. A safer design is to assume that the input will almost -always be corrupt, and to make sure that the program survives anyway.
- -Security is a primary design goal of PB. The receiver gets final say as
-to what they will and will not accept. The lowest-level serialization
-protocol (Banana
) is simple enough to validate by inspection, and
-there are size limits imposed on the actual data received to prevent
-excessive memory consumption. Jelly is willing to accept basic data types
-(numbers, strings, lists and dictionaries of basic types) without question,
-as there is no dangerous code triggered by their creation, but Class
-instances are rigidly controlled. Only subclasses of the basic PB flavors
-(pb.Copyable
, etc) can be passed over the wire, and these all
-provide the developer with ways to control what state is sent and accepted.
-Objects can keep private data on one end of the connection by simply not
-including it in the copied state.
Jelly's refusal to serialize objects that haven't been explicitly marked
-as copyable helps stop accidental security leaks. Seeing the
-pb.Copyable
tag in the class definition is a flag to the
-developer that they need to be aware of what parts of the class will be
-available to a remote system and which parts are private. Classes without
-those tags are not an issue: the mere act of trying to export them
-will cause an exception. If Jelly tried to copy arbitrary classes, the
-security audit would have to look into every class in the
-system.
On the receiving side, the fact that Unjellying insists upon a
-user-registered class for each potential incoming instance reduces the risk
-that arbitrary code will be executed on behalf of remote clients. Only the
-classes that are added to the unjellyableRegistry
need to be
-examined. Half of the security issues in RPC systems will boil down to the
-fact that these potential unserializing classes will have their
-setCopyableState
methods called with a potentially hostile
-state
argument. (the other half are that remote_
-methods can be called with arbitrary arguments, including instances that
-have been sent to that client at some point since the current connection was
-established). If the system is prepared to handle that, it should be in good
-shape security-wise.
RPC systems which allow remote clients to create arbitrary objects in the
-local namespace are liable to be abused. Code gets run when objects are
-created, and generally the more interesting and useful the object, the more
-powerful the code that gets run during its creation. Such systems also have
-more assumptions that must be validated: code that expects to be given an
-object of class A
so it can call A.foo
could be
-given an object of class B
instead, for which the
-foo
method might do something drastically different. Validating
-the object is of the required type is much easier when the number of
-potential types is smaller.
Objects which allow remote method invocation do not provide remote access
-to their attributes (pb.Referenceable
and
-pb.Copyable
are mutually exclusive). Remote users can only
-invoke a well-defined and clearly-marked subset of their methods: those with
-names that start with remote_
(or other specific prefixes
-depending upon the variant of Referenceable
in use). This
-insures that they can have local methods which cannot be invoked remotely.
-Complete object transparency would make this very difficult: the
-translucent
reference scheme allows objects some measure of privacy
-which can be used to implement a security model. The
-
prefix makes all remotely-invokable methods easy
-to locate, improving the focus of a security audit.remote_
Objects sent by reference are indexed by a per-connection ID number, -which is the only way for the remote end to refer back to that same object. -This list means that the remote end can not touch objects that were not -explicitly given to them, nor can they send back references to objects -outside that list. This protects the program's memory space against the -remote end: they cannot find other local objects to play with.
- -This philosophy of using simple, easy to validate identifiers (integers
-in the case of PB) that are scoped to a well-defined trust boundary (in this
-case the Broker and the one remote system it is connected to) leads to
-better security. Imagine a C system which sent pointers to the remote end
-and hoped it would receive back valid ones, and the kind of damage a
-malicious client could do. PB's .localObjects{}
table insures
-that any given client can only refer to things that were given to them. It
-isn't even a question of validating the identifier they send: if it isn't a
-value of the .localObjects{}
dictionary, they have no physical
-way to get at it. The worst they can do with a corrupt ObjectID is to cause
-a KeyError
when it is not found, which will be trapped and
-reported back.
Banana limits string objects to 640k (because, as the source says, 640k
-is all you'll ever need). There is a helper class called
-pb.util.StringPager
that uses a producer/consumer interface to
-break up the string into separate pages and send them one piece at a time.
-This also serves to reduce memory consumption: rather than serializing the
-entire string and holding it in RAM while waiting for the transmit buffers
-to drain, the pages are only serialized as there is space for them.
PB can currently be carried over TCP and SSL connections, and through
-UNIX-domain sockets. It is being extended to run over UDP datagrams and a
-work-in-progress reliable datagram protocol called airhook
. (clearly
-this requires changes to the authorization sequence, as it must all be done
-in a single packet: it might require some kind of public-key signature).
At present, two functions are used to obtain the initial reference to a
-remote object: pb.getObjectAt
and pb.connect
. They
-take a variety of parameters to indicate where the remote process is
-listening, what kind of username/password should be used, and which exact
-object should be retrieved. This will be simplified into a PB URL
-syntax, making it possible to identify a remote object with a descriptive
-URL instead of a list of parameters.
Another research direction is to implement typed arguments
: a way
-to annotate the method signature to indicate that certain arguments may only
-be instances of a certain class. Reminiscent of the E language, this would
-help remote methods improve their security, as the common code could take
-care of class verification.
Twisted provides a componentization
mechanism to allow
-functionality to be split among multiple classes. A class can declare that
-all methods in a given list (the interface
) are actually implemented
-by a companion class. Perspective Broker will be cleaned up to use this
-mechanism, making it easier to swap out parts of the protocol with different
-implementations.
Finally, a comprehensive security audit and some performance improvements -to the Jelly design are also in the works.
- - - - diff --git a/docs/historic/2003/pycon/releasing/releasing-twisted b/docs/historic/2003/pycon/releasing/releasing-twisted deleted file mode 100755 index f5df4a72dd6..00000000000 --- a/docs/historic/2003/pycon/releasing/releasing-twisted +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/python2.2 -# Moshe -- This seems like 30+ minutes to me! -from slides import PRE, URL, Bullet, NumSlide, Slide, SubBullet -from twslides import Lecture - -lecture = Lecture( - "Managing the Release of a Large Python Project", - Slide( - "About Twisted", - Bullet("Networking framework"), - Bullet("Other goodies"), - Bullet("60,000 lines of code"), - Bullet("Things can (and do) go wrong"), - ), - Slide( - "Python", - Bullet("Recap"), - Bullet("No compilation (except for native modules)"), - Bullet("Simple file-based modules (no registration)"), - Bullet("Distutils -- Does the common things"), - ), - Slide( - "Release Procedure -- Steps", - Bullet("Increment version in copyright file, README"), - Bullet("Tag release"), - Bullet("Export from CVS"), - Bullet("Rename toplevel directory"), - Bullet("Generate API and HOWTO documentation"), - Bullet("Create tarballs"), - Bullet("Move tarballs to target area"), - Bullet("Create Debian packages"), - Bullet("Put Debian packages in final place"), - Bullet("Upgrade production machine"), - ), - Slide( - "Release Procedure Overview - Documentation", - Bullet("Man pages -> Lore"), - Bullet("Lore documents -> HTML"), - Bullet("Lore documents -> PS/PDF"), - Bullet("API documentation -> HTML"), - ), - Slide( - "Release Procedure Overview - Testing", - Bullet("Run of the mill unit tests"), - Bullet("Acceptance tests of less portable things"), - Bullet("Prerelease tests for twistedmatrix.com-specific test"), - Bullet("twistedmatrix.com uses latest version -- always!"), - ), - Slide( - "Release Procedure Overview - Debian", - Bullet("The Twisted machines use Debian packages"), - Bullet("The Twisted machines run latest version"), - Bullet("Debian packages are built as part of the release procedure"), - ), - Slide( - "Overview Summary", - Bullet("Many steps"), - Bullet( - "Each can fail", - SubBullet( - Bullet("Documentation can fail to build"), - Bullet("Tests can fail"), - Bullet("Debian packages can fail to build"), - ), - ), - Bullet("Need robust automated setup"), - ), - Slide( - "Enter Release-Twisted", - Bullet("Python program to release Twisted"), - Bullet("Key word -- Robust"), - Bullet("Based on actions which can undo"), - Bullet("Flexible - able to recover a botched build from the middle"), - Bullet("Easy - has good defaults"), - ), - Slide( - "Testing - Recap", - Bullet("Testing is special - no effect"), - Bullet("The more, the better"), - Bullet( - "Harder to automate - machines can't tell right from wrong", - SubBullet(Bullet("Except in Hollywood")), - ), - ), - Slide( - "Different Kinds of Tests - Unit Tests", - Bullet("Completely automated"), - Bullet("Completely machine-verifiable"), - Bullet("Portable"), - Bullet("Must always pass"), - ), - Slide( - "Different Kinds of Tests - Acceptance Tests", - Bullet("Interacts with user"), - Bullet("Probably works only on Linux"), - Bullet("Assumes many client side tools"), - Bullet("Exercises many parts of Twisted which are hard in unit tests"), - ), - Slide( - "Acceptance Tests Examples", - Bullet("Run Twisted web server, run user-defined web browser"), - Bullet("Run mail server, send mail and try to download with pop3"), - Bullet("Run IRC server, run user-defined IRC client"), - ), - Slide( - "Different Kinds of Tests - Prerelease Tests", - Bullet("TwistedMatrix.com dogfoods"), - Bullet("We want to test the dog food"), - Bullet( - "prerelease tests convince us that this version doesn't break " "completely" - ), - Bullet("Among other things, tests that distributed web works"), - ), - Slide( - "Epydoc", - ), - Slide( - "Epyrun", - ), - Slide( - "Distutils -- Datafiles", - ), - Slide( - "Distutils -- Conditional compilation", - ), - Slide( - "Distutils -- Conditional compilation woes", - ), - Slide( - "Distutils -- Other woes", - Bullet("Versions -- keywords were added later"), - Bullet("Icky to do platform dependent stuff"), - ), - Slide( - "release-twistd -- master script", - ), - Slide( - "Commit/rollback", - ), - Slide( - "CVS and tagging", - ), - Slide( - "Debian Packages -- Challenges", - Bullet("Versioning: We want 1.0.2alpha4 to precede 1.0.2"), - Bullet("Dependencies: Which versions of Python? 2.1? 2.2? 2.3?"), - Bullet("Dependencies: Which libc version?"), - ), - Slide( - "Debian Packages -- Solutions", - Bullet("Build two sets -- for Debian stable and for Debian unstable"), - Bullet( - "When building on stable, remove python2.3-dev from build" " dependencies", - SubBullet(Bullet("This stops the Python 2.3 version from being built")), - ), - Bullet("If building a non-final version, name it 1.0.1+1.0.2alpha4"), - Bullet("Unstable build is done by sshing into an unstable chroot"), - ), - Slide( - "Windows Releases -- Challenges", - ), - Slide( - "Windows Releases -- Solutions", - ), - Slide( - "Why Not Dependency Management?", - ), - Slide( - "Conclusions", - Bullet("Distutils does not do enough"), - Bullet("Cross compiling is hard"), - Bullet("It would be nice if Python had integrated docstring tools"), - Bullet("Wheel reinvention is useful"), - ), - Slide( - "Future Directions", - Bullet("RPMs for Various Distributions"), - Bullet("More automation"), - ), - Slide( - "Questions?", - ), -) - -lecture.renderHTML(".", "releasing-%d.html", css="main.css") diff --git a/docs/historic/2003/pycon/releasing/releasing.html b/docs/historic/2003/pycon/releasing/releasing.html deleted file mode 100644 index 9e7bb6173b9..00000000000 --- a/docs/historic/2003/pycon/releasing/releasing.html +++ /dev/null @@ -1,491 +0,0 @@ - - - -- -Twisted is a Python networking framework. At last count, the project -contains nearly 60,000 lines of effective code (not comments or blank -lines). When preparing a release, many details must be checked, and -many steps must be followed. We describe here the technologies and -tools we use, and explain how we built tools on top of them which help -us make releasing as painless as possible. - -
- -- -One of the virtues of Python is the ease of distributing code. Its -module system and the lack of necessity of compilation are what make -this possible. This means that for simple Python projects, nothing -more complicated then tar is needed to prepare a distribution of a -library. However, Twisted has auto-generated documentation in several -formats, including docstring generated documentation, HOWTOs written -in HTML, and manpages written in nroff. As Twisted grew more complex -and popular, a detailed procedure for putting out a release was made -necessary. However, human fallibility being what it is, it was decided -that most of these steps should be automated. - -
- -
-
-Despite heavy automation, there are still a number of manual steps
-involved in the release process. We've reduced the amount of manual
-steps quite a bit, and most of what's left is not fully automatable,
-although the process could be made easier (see Future
-Directions
below).
-
-
- -Twisted has three categories of tests: unit, acceptance, and -pre-release. Testing is an important part of releasing quality -software, of course, so these will be explained. - -
- - -- -Unit tests are run as often as possible by each of the developers as -they write code, and must pass before they commit any changes to -CVS. While the Twisted team tries to follow the XP practice of -ensuring all code is releasable, this isn't always true. Thus, running -the unit tests on several platforms before releasing is necessary. -Our BuildBot runs the unit tests constantly on several hosts and -multiple platforms, so the status page -is simply checked for green lights before a release. - -
- -- -Acceptance tests (which, unfortunately, are not quite the same as Extreme Programming's Acceptance -Tests) are simply interactive tests of various Twisted services. There -is a script that executes several system commands that use the Twisted -end-user executables and start several clients (web browsers, IRC -clients, etc) to allow the user to interactively test the different -services that Twisted offers. These are only routinely run before a -release, but we also encourage developers to run these before they -make major changes. - -
- -
-
-The pre-release tests are for ensuring the web server (One of the most
-popular parts of Twisted, and which the twistedmatrix.com web site
-uses) runs correctly in a semi-production environment. The script
-starts up a web server on twistedmatrix.com, similar to the one on
-port 80, but on an out-of-the-way port. lynx
is then run
-several times, with URLs strategically chosen to test different
-features of the web server. Afterwards, the log of the web server is
-displayed and the user is to check for any errors.
-
-
- -Like many other build/release systems, the automated parts of our -release system started out as a number of small shell -scripts. Eventually these became a single Python script which was a -large improvement, but still had many problems, especially since our -release process became more complex (documentation generation, -different types of archive formats, etc). This led to problems with -steps in the middle of the process breaking; the release manager would -need to restart the entire thing, or enter the remaining commands -manually. - -
- -
-
-The solution that we came up with was a simple framework for
-pseudo-transactions; Every step of the process is implemented with a
-class that has doIt
and undoIt
methods. Each step also has a
-command-line argument associated with it, so a typical run of the
-script looks something like this:
-
-
-$SOMEWHERE/admin/release-twisted -V $VERSION -o $LASTVERSION --checkout \ ---release=/twisted/Releases --upver --tag --exp --dist --docs --balls \ ---rel --deb --debi -- - - -
-
-As stated above, our transaction system is very simple. One of our
-rather simple transaction classes is Export
.
-
-
-class Export(Transaction): - def doIt(self, opts): - print "Export" - root = opts['cvsroot'] - ver = opts['release-version'] - sh('cvs -d%s export -r release-%s Twisted' % (root, ver.replace('.', '_'))) - - def undoIt(self, opts, fail): - sh('rm -rf Twisted') -- - -
-
-One useful feature to note is the sensitiveUndo
attribute on Transaction
-classes. If a transaction has this set, the user will be prompted
-before running the undoIt
method. This is
-useful for very long-running processes, like documentation generation,
-debian package building, and uploading to sourceforge. If something
-goes wrong in the middle of one of these processes, we want to give
-the user a chance to manually fix the problem rather than redoing the
-entire transaction. They can then continue from the next command by
-omitting the commands that have already been accomplished from the
-release-twisted
arguments.
-
-
- -A list of all of the transactions defined in release-twisted follows. - -
- -Twisted CVSdirectory. - -
exportcommand, which is similar to -
checkout, but leaves out CVS support directories; this is - what we package up in the archives. - -
Exportcommand; having the release script - munge that directory in-place would be impolite. - -
Release. - -
/twisted/Debiandirectory. - -
dpkgon - the local machine. - -
- -Twisted has an extensive and very customized setup.py script. We have -a number of C extension modules and try to ensure that they all build, -or at least fail gracefully, on win32, Mac OSX, Linux and other -popular unix-style OSes. - -
- -
-
-We have overridden three of the distutils command classes
:
-build_ext
, install_scripts
, and install_data
.
-
-
-
-build_ext_twisted
detects, based on
-various features of the platform, which C extensions to build. It
-overrides the build_extensions
method to
-first check which C extensions are appropriate to build for the
-current platform before proceeding as normal (by calling the
-superclass's build_extensions
). The
-module-detection consists of several simple tests for platform
-features and conditional additions to the `extensions' attribute. One
-especially useful feature is the _check_header
method, which takes the name of an
-arbitrary head file and tries to compile (via the distutil's C
-compiler interafce) a simple C file that only #includes it.
-
-
-
-install_data_twisted
ensures that the data
-files are installed along-side the python modules in the twisted
-package. This is accomplished with the incantation:
-
-
-class install_data_twisted(install_data): - def finalize_options (self): - self.set_undefined_options('install', - ('install_lib', 'install_dir') - ) - install_data.finalize_options(self) -- - - -
- -Packaging software for windows involves a unique set of problems. The -problem of clickability is especially acute; Several customizations to -the distutils setup had to be made. - -
- -
-
-The first customization was to make the scripts
end with a
-.py
extension, since Windows relies on extension rather than a
-she-bang line to specify what interpreter should execute a file. This
-was accomplished by overriding the install_scripts
command, like so:
-
-
-class install_scripts_twisted(install_scripts): - """Renames scripts so they end with '.py' on Windows.""" - - def run(self): - install_scripts.run(self) - if os.name == "nt": - for file in self.get_outputs(): - if not file.endswith(".py"): - os.rename(file, file + ".py") -- - -
-
-We also wanted to have a Start-menu group with a number of icons for
-running different Twisted programs. This was accomplished with a
-post-install script specified with the command-line parameter
---install-script=twisted_postinstall.py
.
-
-
- -The theme is, of course, automation, and there are still many manual -steps involved in a Twisted release. The currently most annoying step -is updating the documentation and downloads section of the -twistedmatrix.com website. Automating this would be a major -improvement to the time it takes from the running of the release -script to a fully completed release. - -
- -
-
-Another major improvement will involve further integration with
-BuildBot. Currently we have BuildBot running unit tests, building C
-extensions, and generating documentation on several hosts. Eventually
-we would like to have it constantly generating full release archives,
-and have an additional web form for finalizing
any particular
-build that we deem releasable. The result would be uploading the
-release to the mirrors and updating the website.
-
-
-
-The tagging scheme used by the release-twisted scripts can sometimes
-be problematic. If we find serious problems in the code-base after the
-Tag command is executed (which is fairly early in the process), we are
-forced to fix the bug and increase the version number. This can be
-prevented by, instead of making the official tag, using the unofficial
-tag releasing-$version
(as opposed to release-$version
)
-at that early stage. Once most of the steps are complete, the official
-tag will be made. If something in between goes wrong, we can just
-re-use the unofficial releasing-$version
tag and not worry
-about users trying to use that tag.
-
-
Flexibly modelling virtual worlds in object-oriented languages has - historically been difficult; the issues arising from multiple - inheritance and order-of-execution resolution have limited the - sophistication of existing object-oriented simulations. Twisted - Reality avoids these problems by reifying both actions and - relationships, and avoiding inheritance in favor of automated - composition through adapters and interfaces.
- -Text-based simulations have a long and venerable history, from - games such as Infocom's Zork and Bartle's MUD to modern systems - such as Inform, LambdaMOO and Cold. The general trend in the - development of these systems has been toward domain-specific - languages, which has largely been an improvement. However, a - discrepancy remains between systems for single-user and - multiple-user simulations: in single-user systems such as Inform, - incremental extensibility has been sacrificed to allow for complex - interaction with the world; whereas in multiple-user systems, - incremental extensibility is paramount, but it is achieved at the - cost of a much simpler model of interaction. Twisted Reality aims - to bring the sophistication of Inform's action model to multiuser - simulation.
- - -Twisted's component system is almost identical to Zope 3's. The
-primary element is the interface, a class used as a point of
-integration and documentation. Classes may declare the interfaces they
-implement by setting their __implements__
-attribute to a tuple of interfaces. Additional interfaces may be added
-to classes with registerAdapter(adapterClass,originalClass,interface)
;
-when getAdapter(obj, interfaceClass)
is
-called on an object, the adapter associated with that interface and
-class is looked up and instantiated as a wrapper around obj
. (Alternately, if obj
implements the requested interface, the
-original object is simply returned.)
In addition to the basic system of adapters and interfaces, Twisted
-has the Componentized
class. Instances of
-Componentized
hold instances of their
-adapters. This storage of adapter instances encourages separation of
-concerns; multiple related instances representing aspects of a
-simulation object can be automatically composed in a single
-Componentized instance.
Componentized
is the heart of Twisted
-Reality; it is subclassed by Thing
, the
-base class for all simulation objects. Functionality is added to
-Thing
s with adapters; for example, the
-Portable
adapter adds the abilities to be
-picked up and dropped.
By separating aspects of the simulation object into multiple
-instances, several improvements in ease of code maintenance can be
-realized. Persistence of simulation objects, for example, is greatly
-eased by Componentized
: each adapter's
-state can be stored in a separate database table or similar data
-store.
The key element missing from multiuser simulations' parsing systems -is an abstract representation of actions. Current systems proceed -directly from parsing the user's input to executing object-specific -code. For example, LambdaMOO, one of the most popular object-oriented -simulation frameworks, handles input using a non-customizable lexer -which dispatches to parsing methods on simulation objects. The -ColdCore framework, a similar effort, improves on this model by -providing pattern-matching facilities for the lexer, but performs -dispatch in essentially the same fashion. In contrast to these -systems, Twisted Reality separates parsing from simulation objects -entirely, keeping a global registry of parser methods which produce -objects representing actions, rather than directly performing the -actions. Adding this layer allows for more sophisticated parsing and -sensitivity to ambiguity.
- -The parser in reality.text.english
uses
-a relatively simple strategy: it keeps a parser registry which maps
-verbs
(i.e., substrings at the beginning of the user input) to
-parser methods, and runs all methods whose prefixes match the input,
-collecting the actions they return. Parsing methods are added to the
-system by registering Subparser
s.
class MusicParser(english.Subparser): - def parse_blow(self, player, instrumentName): - actor = player.getComponent(IPlayWindInstrumentActor) - if actor is None: - return [] - return [PlayWindInstrument(actor, instrumentName)] - -english.registerSubparser(MusicParser())- -
english.registerSubparser
collects
- methods prefixed with parse_
from
- the subparser and places them in the parsing registry.
a Room -You see a rocket, a whistle, and a candle. -Exits: a door, north -bob: blow whistle -You play a shrill blast upon a whistle.- -
Here is one of the simplest cases for the parser:
should obviously resolve to a
-single action, in this case blow whistle
PlayWindInstrument
.
The parser calls MusicParser.parse_blow
-with the actor and the remainder of the input, and adds the list of
-actions it returns to the collection of possible actions. If only one
-action is possible, it immediately dispatches it. This strategy allows
-the parser to examine the state of the simulation before committing to
-a decision about what the player means. For example, the check for the
-actor interface is a simple form of permissions; if you don't
-implement the required interface, you aren't allowed to perform the
-action.
Since this sort of parser is quite common, it has been generalized - to a simple mapping of command names to actions:
-class FireParser(english.Subparser): - simpleTargetParsers = {"blow": Extinguish} - -english.registerSubparser(FireParser())-
bob: blow candle -You blow out a candle. --
The real test of any parsing system of this nature, of course, is
-its ability to handle ambiguity. Since two possibilities for
-parsing a command starting with blow
now exist, the parser has two
-potential actions to examine: PlayWindInstrument
and Extinguish
. Obviously, only Extinguish
makes sense, and the parser determines this by
-examining the interfaces on the targets and rejecting actions for
-which the target is invalid.
class ExplosivesParser(english.Subparser): - simpleToolParsers = {"blow": BlowUp} - -english.registerSubparser(ExplosivesParser()) --
bob: blow door -You fire a rocket at a door. -*BOOM*!! -The door shatters to pieces! -- -
The other common case is actions with three participants -- actor, -target, and tool. The parser generated here is intelligent enough to -look around for an appropriate tool (again, by examining interfaces) -and include it in the action.
- -Despite these techniques for disambiguating the user's meaning, -situations will inevitably arise where multiple actions are equally -valid parses. In these cases, the parser formats the list of potential -actions and presents the choices to the user.
- -You see a short sword, and a long sword. -bob: get sword -Which Target? -1: long sword -2: short sword -bob: 1 -You take a long sword.- -
Actions in Twisted Reality, as in Inform, are objects representing
-a successful parse of a player's intentions. Actions are classified
-according to the number of objects they operate upon: NoTargetAction
(actions such as Say
or Look
), TargetAction
(e.g. Eat
, Wear
), ToolAction
(e.g. Open
, Take
). When
-actions are defined, interfaces corresponding to the possible roles in
-the action are also created. When an action is instantiated, it asks
-the participants in the action to adapt themselves to the actor,
-target, or tool interfaces, as appropriate. When dispatched, the
-action may call handler methods on the adapted objects or dispatch
-subsidiary actions.
IDamageActor = things.IThing -class Damage(actions.ToolAction): - def formatToActor(self): - with = "" - if self.tool: - with = " with ", self.tool - return ("You hit ",self.target) + with + (".",) - def formatToTarget(self): - with = () - if self.tool: - with = " with ", self.tool - return (self.actor," hits you") + with + (".",) - def formatToOther(self): - with = "" - if self.tool: - with = " with ", self.tool - return self.actor," hits ",self.target) + with + (".",) - def doAction(self): - amount = self.tool.getDamageAmount() - self.target.damage(amount) - -class Weapon(components.Adapter): - __implements__ = IDamageTool - def getDamageAmount(self): - return 10 - -class Damageable(components.Adapter): - __implements__ = IDamageTarget - def damage(self, amount): - self.original.emitEvent("Ow! that hurt. You take %d points of damage." - % amount, intensity=1) -class HarmParser(english.Subparser): - simpleToolParsers = {"hit":Damage} - -english.registerSubparser(HarmParser()) -components.registerAdapter(Damageable, things.Actor, IDamageTarget)- -
actions.ToolAction
, via metaclass
-magic, creates three interfaces when subclasssed, named after the
-subclass: in this case, IDamageActor
,
-IDamageTarget
, and IDamageTool
. However, since IDamageActor
already exists, the metaclass does
- not clobber it. Setting IDamageActor
to
-IThing
indicates that any Thing
may perform the Damage
action. The other elements of the action
-are represented here by Weapon
and Damageable
as the tool and the target,
-respectively. The HarmParser
adds a
-hit
command, and the call to registerAdapter
ensures that any Actor
s who do not already have a
-component implementing IDamageTarget
will
-receive a Damageable
when needed.
-
room = ambulation.Room("room") -bob = things.Actor( "Bob") -rodney = things.Actor("rodneY") -sword = things.Movable("sword") - -sword.addAdapter(conveyance.Portable, True) -sword.addAdapter(harm.Weapon, True) - - -for o in rodney, bob, sword: - o.moveTo(room) -- -
In this example, we create instances of Movable
Actor
-(subclasses of Thing
), a Room
, then adds a Portable
adapter to the sword, allowing it to be
-picked up and dropped, as well as a Weapon
-adapter, and finally moves all three into the room.
a room -You see rodneY, and a sword. -Bob: get sword -You take a sword. -Bob: hit rodney with sword -You hit rodneY with a sword.- -
The parser instantiates the Damage
-action with Bob, Rodney, and the sword as actor, target, and tool. The
-action is dispatched, calling Damage.doAction
, which inflicts damage upon
-Rodney. From Rodney's perspective:
a room -You see Bob, and a sword. -Bob takes a sword. -Bob hits you with a sword. -Ow! that hurt. You take 10 points of damage. -rodneY:- -
The primary advantage of this actions system is that it provides a -central point for dispatching object-specific behaviour in a -customizable manner. This mechanism prevents order-of-execution -problems: in other simulations of this type, combining multiple game -effects is difficult since the connections between them are not made -explicit. When confronted with ambiguity, TR's action system refuses -to guess: all combinations of effects that make sense must be -implemented separately. The Adapters system makes this manageable even -in the face of arbitrarily extended complexity.
- -Also, it allows for centralized handling of string formatting,
-instead of having each actor or target handle output of event
-descriptions. For example, suppose there is a zone prohibiting PvP
-combat. The Damage
action can suppress the
-usual messages describing combat (as well as the actual damage
-routines) since it is responsible for generating them.
The combination of these features -- an incrementally extendable
-parser, actions as first-class objects, componentized simulation
-objects -- provide a powerful basis for the composition of simulations
-within a virtual world, often enabling extensions to the world and
-object behaviour without touching unrelated code. For example, to add
-armor that reduces damage to the simple combat simulation described
-above, we add an Armor
class which
-forwards the IDamageTarget
interface:
class Armor(raiment.Wearable): - __implements__ = IDamageTarget, raiment.IWearTarget, raiment.IUnwearTarget - originalTarget = None - armorCoefficient = 0.5 - def dress(self, wearer): - originalTarget = wearer.getComponent(IDamageTarget) - if originalTarget: - self.originalTarget = originalTarget - wearer.original.setComponent(IDamageTarget, self) - - def undress(self, wearer): - if self.originalTarget: - wearer.setComponent(IDamageTarget, self.originalTarget) - - def damage(self, amount): - self.original.emitEvent("Your armor cushions the blow.", intensity=2) - if self.originalTarget: - self.originalTarget.damage(amount * self.armorCoefficient)- -
Armor
inherits from the Wearable
adapter, and thus receives notification
-of the player wearing or removing it. When this happens, it forwards
-or unforwards the damage
method,
-respectively.
a room -You see an armor, Bob, and a sword. -rodneY: take armor -You take an armor. -rodneY: wear armor -You put on an armor. -Bob hits you with a sword. -Your armor cushions the blow. -Ow! that hurt. You take 5 points of damage.- -
In this fashion, the combat simulation can be extended to deal with -various types of weapons, armor, damageable objects, and types of -damage, with little or no changes to existing code.
- -Now, let us consider a second type of simulation common to virtual - worlds: shops. We wish to prevent unpaid items from leaving the shop, - and to have a price associated with each item.
- --class IVendor(components.Interface): pass -class IMerchandise(components.Interface): pass - -class Buy(actions.TargetAction): - def formatToOther(self): - return "" - def formatToActor(self): - return ("You buy ",self.target," from ",self.vendor," for ", - self.target.price," zorkmids.") - - def doAction(self): - vendors = self.actor.original.lookFor(None, IVendor) - if vendors: - #assume only one vendor per room, for now - self.vendor = vendors[0] - else: - raise errors.Failure("There appears to be no shopkeeper here " - "to receive your payment.") - amt = self.target.price - self.actor.withdraw(amt) - self.vendor.buy(self.target, amt) - -class ShopParser(english.Subparser): - simpleTargetParsers = {"buy": Buy} -english.registerSubparser(ShopParser())- -
The basic behaviour for buying an object in a shop is simple: -first, a vendor is located, the price is looked up, then money is -transferred from the buyer's account to the vendor's.
- -class Customer(components.Adapter): - __implements__ = IBuyActor - - def withdraw(self, amt): - "interface to accounting system goes here" - -class Vendor(components.Adapter): - __implements__ = IVendor - - def shoutPrice(self, merch, cust): - n = self.getComponent(english.INoun) - title = ('creature', 'sir','lady' - )[cust.getComponent(things.IThing).gender] - merchName = merch.original.getComponent(english.INoun).name)) - self.original.emitEvent('%s says "For you, good %s, only %d ' - 'zorkmids for this %s."' % (n.nounPhrase(cust), - title, merch.price, - merchName)) - - def buy(self, merchandise, amount): - self.deposit(amount) - merchandise.original.removeComponent(merchandise) - - def stock(self, obj, price): - m = Merchandise(obj) - m.price = price - m.owner = self - m.home = self.original.location - obj.addComponent(m, ignoreClass=1) - - def deposit(self, amt): - "more accounting code"-
The essential operations for management of shop inventory are
-Vendor.stock
and Vendor.buy
, which add and remove a Merchandise
adapter, which stores the
-state related to the shop simulation for the object (in this case, its
-price, its owner, and the location it lives).
A weapons shop. You see a long sword, and Asidonhopo. -Exits: a Secret Trapdoor, down; a Security Door, north -bob: get sword -You take a long sword. -Asidonhopo says "For you, good sir, only 100 zorkmids for this long sword." -- -
To enforce our anti-theft policy, we put constraints on the exits -to the shop.
--class ShopDoor(ambulation.Door): - def collectImplementors(self, asker, iface, collection, seen, - event=None, name=None, intensity=2): - if iface == ambulation.IWalkTarget: - unpaidItems = asker.searchContents(None, IMerchandise) - if unpaidItems: - collection[self] = things.Refusal(self, "You cant leave, " - "you haven't paid!") - return - - ambulation.Door.collectImplementors(self, asker, iface, - collection, seen, event, - name, intensity) - return collection- -
collectImplementors
is the means by
-which queries for action participants are accomplished. It is a rather
-general graph-traversal mechanism and thus takes a few arguments:
-asker
is the object that initiated the
-query. iface
is the interface the results
-must conform to, collection
is the results
-so far, and seen
is a collection of
-objects already visited. The check done here is fairly simple: it
-refuses queries for IWalkTarget
s (the
-interface needed for walking between rooms) if the asker contains
-things that implement IMerchandise
, in
-particular unpaid items. Otherwise, it passes on the query to its
-superclass.
bob: go north -You cant leave, you haven't paid!- -
Here, the Security Door
examines the actor's contents for
-objects implementing IMerchandise. Since the sword still has a
-Merchandise adapter attached, the passage is barred.
bob: go down- -
However, relying on the exits -to contain merchandise is potentially error-prone; it demands knowing -about all forms of locomotion in advance. If an unsecured exit from - the shop exists, or the player has the ability to teleport, - this form of security can be bypassed. Therefore, it is -advantageous to have the Merchandise adapter itself keep the item -within the shop.
- -class Merchandise(components.Adapter): - __implements__ = IMerchandise, things.IMoveListener, IBuyTarget - - def thingArrived(*args): - pass - def thingLeft(*args): - pass - def thingMoved(self, emitter, event): - if self.original == emitter and isinstance(event, conveyance.Take): - self.owner.shoutPrice(self, self.original.location) - if self.original.getOutermostRoom() != self.home: - self.original.emitEvent("The %s vanishes with a *foop*." - % self.getComponent(english.INoun).name) - self.original.moveTo(self.home)- -
When objects move, they broadcast events to nearby things
- (where nearby
is determined, again, by collectImplementors
) that implement
- IMoveListener
. In this case, the
- Merchandise
adapter listens
- for being picked up, and prompts the shopkeeper to quote the
- price, and also checks to make sure it is contained by its
- home room. If the player manages to leave the shop with unpaid
- merchandise --
The long sword vanishes with a *foop*.- -
then it sets its location to its home room and informs the prospective -shoplifter he no longer has his prize.
- -Current development efforts focus on enlarging the standard library -of simulation objects and behaviour, developing web-based interfaces -to the simulation, and improving the persistence layer. Possible -extensions include client-side generation of action objects, enabling -the development of graphical interfaces, or adapting the text system -to other languages than English.
- -As seen in these examples, Twisted Reality provides features not -found in other object-oriented simulation frameworks. The component -model allows automatic aggregation of related objects; the actions -system provides a mechanism for precise control of game effects; and -the parser enables incremental extension of user input -handling. Combined, they provide a powerful basis for modelling -virtual worlds by composing simulations.
- -Thanks to Chris Armstrong and Donovan Preston for contributions to - Twisted Reality, and to Ying Li for editorial assistance.
- -The Twisted Network Framework, Proceedings of the Tenth International Python Conference (2002): 83.
I am grateful to IBM for inviting me to talk here, and to Muli Ben-Yehuda for arranging everything.
- -After reading Peter Norvig's infamous The Gettysburg Powerpoint Presentation
, I was traumatized enough to forgoe the usual bullets and slides style, which originally was developed for physical slide projectors. Therefore, these notes are presented as one long HTML file, and I will use a new invention I call the scrollbar
to show just one thing at a time. Enjoy living on the bleeding edge of presentation technology!
Twisted is an event-based networkings framework for Python. It includes not only the basics of networking but also high-level protocol implementations, scheduling and more. It uses Python's high-level nature to enable few dependencies between different parts of the code. Twisted allows you to write network applications, clients and servers, without using threads and without running into icky concurrency issues.
- --A computer is a state machine. -Threads are for people who can't program state machines. -- -
Alan Cox in a discussion about the threads and the Linux scheduler
-http://www.bitmover.com/lm/quotes.html
- -Python is a high-level dyanmically strongly typed language. All values are references to objects, and all arguments passed are objects references. Assignment changes the reference a variable points to, not the reference itself. Data types include integers (machine sized and big nums) like 1
and 1L
, strings and unicode strings like "moshe"
and u"\u05DE\u05E9\u05D4 -- moshe"
, lists (variably typed arrays, really) like [1,2,3, "lalala", 10L, [1,2]]
, tuples (immutable arrays) like ("1", 2, 3)
, dictionaries {"moshe": "person", "table": "thing"}
and user-defined objects.
Every Python object has a type, which is itself a Python object. Some types aare defined in native C code (such as the types above) and some are defined in Python using the class keyword.
- -Structure is indicated through indentation.
- -Functions are defined using
- --def function(param1, param2, optionalParam="default value", *restParams, - **keywordParams): - pass -- -
And are called using function("value for param1", param2=42,
-optionalParam=[1,2], "these", "params", "will", "be", "restParams",
-keyword="arguments", arePut="in dictionary keywordParams")
.
Functions can be defined inside classes:
- --class Example: - # constructor - def __init__(self, a=1): - self.b = a - def echo(self): - print self.b -e = Example(5) -e.echo() -- -
All methods magically receive the self argument, but must treat it -explicitly.
- -Functions defined inside functions enjoy lexical scoping. All variables -are outer-scope unless they are assigned to in which case they are inner-most -scope.
- -Those of you used to other event-based frameworks (notably, GUIs) will recognize the familiar pattern -- you call the framework's mainloop
function, and it calls registered event handlers. Event handlers must finish quickly, to enable the framework to call other handlers without forcing the client (be it a GUI user or a network client) to wait. Twisted uses the reactor
module for the main interaction with the network, and the main loop function is called reactor.run
. The following code is the basic skeleton of a Twisted application.
-from twisted.internet import reactor -reactor.run() -- -
This runs the reactor. This takes no CPU on UNIX-like systems, and little CPU on Windows (some APIs must be busy-polled), runs forever and does not quit unless delivered a signal.
- -Our first task using Twisted is to build a server to the well-known finger
protocol -- or rather a simpler variant of it. The first step is accepting, and hanging, connections. This example will run forever, and will allow clients to connect to port 1079. It will promptly ignore everything they have to say...
-from twisted.internet import protocol, reactor -class FingerProtocol(protocol.Protocol): - pass -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol -reactor.listenTCP(1079, FingerFactory()) -reactor.run() -- -
The protocol class is empty -- the default network event handlers simply throw away the events. Notice that the protocol
attribute in FingerFactory
is the FingerProtocol
class itself, not an instance of it. Protocol logic properly belongs in the Protocol
subclass, and the next few slides will show it developing.
The previous example used the fact that the default event handlers in the protocol exist and do nothing. The following example shows how to code the event handlers explicitly to do nothing. While being no more useful than the previous version, this shows the available events.
--from twisted.internet import protocol, reactor -class FingerProtocol(protocol.Protocol): - def connectionMade(self): - pass - def connectionLost(self): - pass - def dataReceived(self, data): - pass -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol -reactor.listenTCP(1079, FingerFactory()) -reactor.run() -- -
This example is much easier to work with for the copy'n'paste style of programming...it has everything a good network application has: a low-level protocol implementation, a high-level class to handle persistent configuration data (the factory) and enough glue code to connect it to the network.
- -The simplest event to respond to is the connection event. It is the first event a connection receives. We will use this opportunity to slam the door shut -- anyone who connects to us will be disconnected immediately.
- --class FingerProtocol(protocol.Protocol): - def connectionMade(self): - self.transport.loseConnection() -- -
The transport
attribute is the protocol's link to the other side. It uses it to send data, to disconnect, to access meta-data about the connection and so on. Seperating the transport from the protocol enables easier work with other kinds of connections (unix domain sockets, SSL, files and even pre-written for strings, for testing purposes). It conforms to the ITransport
interface, which is defined in twisted.internet.interfaces
.
The previous version closed the connection as soon as the client connected, not even appearing as though it was a problem with the input. Since finger is a line-oriented protocol, if we read a line and then terminate the connection, the client will be forever sure it was his fault.
- --from twisted.protocols import basic -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.transport.loseConnection() -- -
We now inherit from LineReceiver
, and not directly from Protocol
. LineReceiver
allows us to respond to network data line-by-line rather than as they come from the TCP driver. We finish reading the line, and only then we disconnect the client. Important note for people used to various fgets
, fin.readline()
or Perl's <>
operator: the line does not end in a newline, and so an empty line is not an indicator of end-of-file, but rather an indication of an empty line. End-of-file, in network context, is known as closed connection
and is signaled by another event altogether (namely, connectionLost
.
The limit case of a useful finger server is a one with no users. This server will always reply that such a user does not exist. It can be installed while a system is upgraded or the old finger server has a security hole.
- --from twisted.protocols import basic -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.transport.write("No such user\r\n") - self.transport.loseConnection() -- -
Notice how we did not have to explicitly flush, or worry about the write being successful. Twisted will not close the socket until it has written all data to it, and will buffer it internally. While there are ways for interacting with the buffering mechanism (for example, when sending large amounts of data), for simple protocols this proves to be convenient.
- -Note how we remarked earlier that protocol logic belongs in the -protocol class. This is necessary and sufficient -- we do not want non-protocol -logic in the protocol class. User management is clearly not part of the protocol logic, and so should not be in the protocol. This is exactly why we have the factory in the first place. The factory allows us to delegate non-protocol logic -to a seperate class. It is often not completely trivial what does and does not belong in the factory, of course.
- --from twisted.protocols import basic -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - self.transport.write(self.factory.getUser(user)+"\r\n") - self.transport.loseConnection() -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol - def getUser(self, user): - return "No such user" -- -
Notice how we did not change the observable behaviour, but we did make the factory know about which users exist and do not exist. With this kind of setup, we will not need to modify our protocol class when we change user management schemes...hopefully.
- -The last heading did not live up to its name -- the server kept spouting off that it did not know who we are talking about, they never lived here and could we please go away. It did, however, prepare the way for doing actually useful things which we do here.
- --class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol - def __init__(self, **kwargs): - self.users = kwargs - def getUser(self, user): - return self.users.get(user, "No such user") -reactor.listenTCP(1079, FingerFactory(moshez='Happy and well')) -- -
This server actually has valid use cases. With such code, we could easily disseminate office/phone/real name information across an organization, if people had finger clients.
- -The version above works just fine. However, the interface between the protocol class and its factory is synchronous. This might be a problem. After all, lineReceived
is an event, and should be handled quickly. If the user's status needs to be fetched by a slow process, this is impossible to achieve using the current interface. Following our method earlier, we modify this API glitch without changing anything in the outward-facing behaviour.
-from twisted.internet import defer -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - d = defer.maybeDeferred(self.factory.getUser, user) - def e(_): - return "Internal error in server" - d.addErrback(e) - def _(m): - self.transport.write(m+"\r\n") - self.transport.loseConnection() - d.addCallback(_) -- -
The value of using maybeDeferred
is that it seamlessly
-works with the old factory too. If we would allow changing the factory,
-we could make the code a little cleaner, as the following example shows.
-from twisted.internet import defer -class FingerProtocol(basic.LineReceiver): - def lineReceived(self, user): - d = self.factory.getUser( user) - def e(_): - return "Internal error in server" - d.addErrback(e) - def _(m): - self.transport.write(m+"\r\n") - self.transport.loseConnection() - d.addCallback(_) -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol - def __init__(self, **kwargs): - self.users = kwargs - def getUser(self, user): - return defer.succeed(self.users.get(user, "No such user")) -- -
Note how this example had to change the factory too. defer.succeed
is a way to returning a deferred results which is already triggered successfully. It is useful in exactly these kinds of cases: an API had to be asynchronous to support other use-cases, but in a simple enough use-case, the result is availble immediately.
Deferreds are abstractions of callbacks. In this instance, the deferred -had a value immediately, so the callback was called as soon as it was -added. We will soon show an example where it will not be available immediately. -The errback is called if there are problems, and is equivalent to exception handling. If it returns a value, the exception is considered handled, and further callbacks will be called with its return value.
- -The standard finger daemon is equivalent to running the finger
-command on the remote machine. Twisted can treat processes as event sources too, and enables high-level abstractions to allow us to get process output easily.
-from twisted.internet import utils -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol - def getUser(self, user): - return utils.getProcessOutput("finger", [user]) -- -
The real value of using deferreds in Twisted is shown here in full. Because there is a standard way to abstract callbacks, especially a way that does not require sending down the call-sequence a callback, all functions in Twisted itself whose result might take a long time return a deferred. This enables us in many cases to return the value that a function returns, without caring that it is deferred at all.
- -If the command exits with an error code, or sends data to stderr, the -errback will be triggered and the user will be faced with a half-way useful -error message. Since we did not whitewash the argument at all, it is quite -likely that this contains a security hole. This is, of course, another -standard feature of finger daemons...
- -However, it is easy to whitewash the output. Suppose, for example, we do not want the explicit name Microsoft
in the output, because of the risk of offending religious feelings. It is easy to change the deferred into one which is completely safe.
-from twisted.internet import utils -class FingerFactory(protocol.ServerFactory): - protocol = FingerProtocol - def getUser(self, user): - d = utils.getProcessOutput("finger", [user]) - def _(s): - return s.replace('Microsoft', 'It which cannot be named') - d.addCallback(_) - return d -- -
The good news is that the protocol class will need to change no more, -up until the end of the talk. That class abstracts the protocol well -enough that we only have to modify factories when we need to support -other user-management schemes.
- -So far we used port 1097, because with UNIX low ports can only be bound by root. Certainly we do not want to run the whole finger server as root. The usual solution would be to use privilege shedding: something like reactor.listenTCP
, followed by appropriate os.setuid
and then reactor.run
. This kind of code, however, brings the option of making subtle bugs in the exact place they are most harmful. Fortunately, Twisted can help us do privilege shedding in an easy, portable and safe manner.
For that, we will not write .py
main programs which run the application. Rather, we will write .tac
(Twisted Application Configuration) files which contain the configuration. While Twisted supports several configuration formats, the easiest one to edit by hand, and the most popular one is...Python. A .tac
is just a plain Python file which defines a variable named application
. That variable should subscribe to various interfaces, and the usual way is to instantiate twisted.service.Application
. Note that unlike many popular frameworks, in Twisted it is not recommended to inherit from Application
.
-from twisted.application import service -application = service.Application('finger', uid=1, gid=1) -factory = FingerFactory(moshez='Happy and well') -internet.TCPServer(79, factory).setServiceParent(application) -- -
This is a minimalist .tac
file. The application class itelf is resopnsible for the uid/gid, and various services we configure as its children are responsible for specific tasks. The service tree really is a tree, by the way...
TAC files are run with twistd
(TWISTed Daemonizer). It supports various options, but the usual testing way is:
-root% twistd -noy finger.tac -- -
With long options:
- --root% twistd --nodaemon --no_save --python finger.tac -- -
Stopping twistd
from daemonizing is convenient because then it is possible to kill it with CTRL-C. Stopping it from saving program state is good because recovering from saved states is uncommon and problematic and it leaves too many -shutdown.tap
files around. --python finger.tac
lets twistd
know what type of configuration to read from which file. Other options include --file .tap
(a pickle), --xml .tax
(an XML configuration format) and --source .tas
(a specialized Python-source format which is more regular, more verbose and hard to edit).
Before we can integrate several services, we need to write another service. The service we will implement here will allow users to change their status on the finger server. We will not implement any access control. First, the protocol class:
- --class FingerSetterProtocol(basic.LineReceiver): - def connectionMade(self): - self.lines = [] - def lineReceived(self, line): - self.lines.append(line) - def connectionLost(self, reason): - self.factory.setUser(self.line[0], self.line[1]) -- -
And then, the factory:
- --class FingerSetterFactory(protocol.ServerFactory): - protocol = FingerSetterProtocol - def __init__(self, fingerFactory): - self.fingerFactory = fingerFactory - def setUser(self, user, status): - self.fingerFactory.users[user] = status -- -
And finally, the .tac
:
-ff = FingerFactory(moshez="Happy and well") -fsf = FingerSetterFactory(ff) -application = service.Application('finger', uid=1, gid=1) -internet.TCPServer(79,ff).setServiceParent(application) -internet.TCPServer(1079,fsf,interface='127.0.0.1').setServiceParent(application) -- -
Now users can use programs like telnet
or nc
to change their status, or maybe even write specialized programs to set their options:
-import socket -s = socket.socket() -s.connect(('localhost', 1097)) -s.send('%s\r\n%s\r\n' % (sys.argv[1], sys.argv[2])) -- -
(Later, we will learn on how to write network clients with Twisted, which fix the bugs in this example.)
- -Note how, as a naive version of access control, we bound the setter service to the local machine, not to the default interface (0.0.0.0
-
-
The last example exposed a historical asymmetry. Because the finger setter was developed later, it poked into the finger factory in an unseemly manner. Note that now, we will only be changing factories and configuration -- the protocol classes, apparently, are perfect.
- --class FingerService(service.Service): - def __init__(self, **kwargs): - self.users = kwargs - def getUser(self, user): - return defer.succeed(self.users.get(user, "No such user")) - def getFingerFactory(self): - f = protocol.ServerFactory() - f.protocol, f.getUser = FingerProtocol, self.getUser - return f - def getFingerSetterFactory(self): - f = protocol.ServerFactory() - f.protocol, f.setUser = FingerSetterProtocol, self.users.__setitem__ - return f -application = service.Application('finger', uid=1, gid=1) -f = FingerService(moshez='Happy and well') -ff = f.getFingerFactory() -fsf = f.getFingerSetterFactory() -internet.TCPServer(79,ff).setServiceParent(application) -internet.TCPServer(1079,fsf).setServiceParent(application) -- -
Note how it is perfectly fine to use ServerFactory
rather than subclassing it, as long as we explicitly set the protocol
attribute -- and anything that the protocols use. This is common in the case where the factory only glues together the protocol and the configuration, rather than actually serving as the repository for the configuration information.
In this example, we periodicially read a global configuration file to decide which users do what. First, the code.
- --class FingerService(service.Service): - def __init__(self, filename): - self.filename = filename - self.update() - def update(self): - self.users = {} - for line in file(self.filename): - user, status = line[:-1].split(':', 1) - self.users[user] = status - def getUser(self, user): - return defer.succeed(self.users.get(user, "No such user")) - def getFingerFactory(self): - f = protocol.ServerFactory() - f.protocol, f.getUser = FingerProtocol, self.getUser - return f -- -
The TAC file:
- --application = service.Application('finger', uid=1, gid=1) -finger = FingerService('/etc/users') -server = internet.TCPServer(79, f.getFingerFactory()) -periodic = internet.TimerService(30, f.update) -finger.setServiceParent(application) -server.setServiceParent(application) -periodic.setServiceParent(application) -- -
Note how the actual periodic refreshing is a feature of the configuration, not the code. This is useful in the case we want to have other timers control refreshing, or perhaps even only refresh explicitly as depending on user action (another protocol, perhaps?).
- -It could be the case that our finger server needs to query another finger server, perhaps because of strange network configuration or maybe we just want to mask some users. Here is an example for a finger client, and a use case as a finger proxy. Note that in this example, we do not need custom services and so we do not develop them.
- --from twisted.internet import protocol, defer, reactor -class FingerClient(protocol.Protocol): - buffer = '' - def connectionMade(self): - self.transport.write(self.factory.user+'\r\n') - def dataReceived(self, data): - self.buffer += data - def connectionLost(self, reason): - self.factory.gotResult(self.buffer) - -class FingerClientFactory(protocol.ClientFactory): - protocol = FingerClient - def __init__(self, user): - self.user = user - self.result = defer.Deferred() - def gotResult(self, result): - self.result.callback(result) - def clientConnectionFailed(self, _, reason): - self.result.errback(reason) - -def query(host, port, user): - f = FingerClientFactory(user) - reactor.connectTCP(host, port, f) - return f.result - -class FingerProxyServer(protocol.ServerFactory): - protocol = FingerProtocol - def __init__(self, host, port=79): - self.host, self.port = host, port - def getUser(self, user): - return query(self.host, self.port, user) -- -
With a TAC that looks like:
- --application = service.Application('finger', uid=1, gid=1) -server = internet.TCPServer(79, FingerProxyFactory('internal.ibm.com')) -server.setServiceParent(application) -- -
Twisted is large. Really large. Really really large. I could not hope to cover it all in thrice the time. What didn't I cover?
- -There is good documentation on the Twisted website, among which the tutorial which was based on an old HAIFUX talk and was, in turn, the basis for this talk, and specific HOWTOs for doing many useful and fun things.
- -In UNIX non-blocking has a very specific meaning -- some operations might block, others won't. Unfortunately, this meaning is almost completely useless in real life. Reading from a socket connected to a responsive server on a UNIX domain socket is blocking, while reading a megabyte string from a busy hard-drive is not. A more useful meaning for actual decisions while writing non-blocking code is takes more than 0.05 seconds on my target platform
. With this kind of handlers, typical network usage will allow for the magical one million hits a day
website, or a GUI application which appears to a human being as infinitely responsive. Various techniques, not limited but including threads, can be used to modify code to be responsive at those levels.
-
-
Twisted supports high-level abstractions for almost all levels of writing network code. Moreover, when using Twisted correctly it is possible to add more absractions, so that actual network applications do not have to fiddle with low-level protocol details. Developing network applications using Twisted and Python can lead to quick prototypes which can then be either optimized or rewritten on other platforms -- and often can just serve as-is.
- - diff --git a/docs/historic/NEWS.rst b/docs/historic/NEWS.rst deleted file mode 100644 index c1d8d5fd39a..00000000000 --- a/docs/historic/NEWS.rst +++ /dev/null @@ -1,4586 +0,0 @@ -This file contains the news files from 2008-2014. - -Twisted Core 14.0.2 (2014-09-18) -================================ - -No significant changes have been made for this release. - - -Twisted Conch 14.0.2 (2014-09-18) -================================= - -No significant changes have been made for this release. - - -Twisted Lore 14.0.2 (2014-09-18) -================================ - -No significant changes have been made for this release. - - -Twisted Mail 14.0.2 (2014-09-18) -================================ - -No significant changes have been made for this release. - - -Twisted Names 14.0.2 (2014-09-18) -================================= - -No significant changes have been made for this release. - - -Twisted News 14.0.2 (2014-09-18) -================================ - -No significant changes have been made for this release. - - -Twisted Pair 14.0.2 (2014-09-18) -================================ - -No significant changes have been made for this release. - - -Twisted Runner 14.0.2 (2014-09-18) -================================== - -No significant changes have been made for this release. - - -Twisted Web 14.0.2 (2014-09-18) -=============================== - -No significant changes have been made for this release. - - -Twisted Words 14.0.2 (2014-09-18) -================================= - -No significant changes have been made for this release. - - -Twisted Core 14.0.1 (2014-09-17) -================================ - -No significant changes have been made for this release. - - -Twisted Conch 14.0.1 (2014-09-17) -================================= - -No significant changes have been made for this release. - - -Twisted Lore 14.0.1 (2014-09-17) -================================ - -No significant changes have been made for this release. - - -Twisted Mail 14.0.1 (2014-09-17) -================================ - -No significant changes have been made for this release. - - -Twisted Names 14.0.1 (2014-09-17) -================================= - -No significant changes have been made for this release. - - -Twisted News 14.0.1 (2014-09-17) -================================ - -No significant changes have been made for this release. - - -Twisted Pair 14.0.1 (2014-09-17) -================================ - -No significant changes have been made for this release. - - -Twisted Runner 14.0.1 (2014-09-17) -================================== - -No significant changes have been made for this release. - - -Twisted Web 14.0.1 (2014-09-17) -=============================== - -Bugfixes --------- - - BrowserLikePolicyForHTTPS would always ignore the specified - trustRoot and use the system trust root instead, which has been - rectified. (#7647) - - -Twisted Words 14.0.1 (2014-09-17) -================================= - -No significant changes have been made for this release. - - -Twisted Core 14.0.0 (2014-05-08) -================================ - -Features --------- - - twisted.internet.interfaces.IUDPTransport - and that interface's - implementations in Twisted - now supports enabling broadcasting. - (#454) - - trial's TestCase will now report a test method as an error if that - test method is a generator function, preventing an issue when a - user forgets to decorate a test method with defer.inlineCallbacks, - causing the test method to not run. (#3917) - - twisted.positioning, a new API for positioning systems such as GPS, - has been added. It comes with an implementation of NMEA, the most - common wire protocol for GPS devices. It will supersede - twisted.protoocols.gps. (#3926) - - The new interface twisted.internet.interfaces.IStreamClientEndpoint - StringParserWithReactor will supply the reactor to its - parseStreamClient method, passed along from - twisted.internet.endpoints.clientFromString. (#5069) - - IReactorUDP.listenUDP, IUDPTransport.write and - IUDPTransport.connect now accept ipv6 address literals. (#5086) - - A new API, twisted.internet.ssl.optionsForClientTLS, allows clients - to specify and verify the identity of the peer they're communicating - with. When used with the service_identity library from PyPI, this - provides support for service identity verification from RFC 6125, as - well as server name indication from RFC 6066. (#5190) - - Twisted's TLS support now provides a way to ask for user-configured - trust roots rather than having to manually configure such - certificate authority certificates yourself. - twisted.internet.ssl.CertificateOptions now accepts a new argument, - trustRoot, which combines verification flags and trust sources, as - well as a new function that provides a value for that argument, - twisted.internet.ssl.platformTrust, which allows using the trusted - platform certificate authorities from OpenSSL for certificate - verification. (#5446) - - Constants are now comparable/orderable based on the order in which - they are defined. (#6523) - - "setup.py install" and "pip install" now work on Python 3.3, - installing the subset of Twisted that has been ported to Python 3. - (#6539) - - twisted.internet.ssl.CertificateOptions now supports ECDHE for - servers by default on pyOpenSSL 0.14 and later, if the underlying - versions of cryptography.io and OpenSSL support it. (#6586) - - twisted.internet.ssl.CertificateOptions now allows the user to set - acceptable ciphers and uses secure ones by default. (#6663) - - The Deferred returned by - twisted.internet.defer.DeferredFilesystemLock.deferUntilLocked can - now be cancelled. (#6720) - - twisted.internet.ssl.CertificateOptions now enables TLSv1.1 and - TLSv1.2 by default (in addition to TLSv1.0) if the underlying - version of OpenSSL supports these protocol versions. (#6772) - - twisted.internet.ssl.CertificateOptions now supports Diffie-Hellman - key exchange. (#6799) - - twisted.internet.ssl.CertificateOptions now disables TLS - compression to avoid CRIME attacks and, for servers, uses server - preference to choose the cipher. (#6801) - - SSL server endpoint string descriptions now support the - specification of Diffie-Hellman key exchange parameter files. - (#6924) - - twisted.python.reflect.requireModule was added to handle - conditional imports of python modules and work around pyflakes - warnings of unused imports code. (#7014) - -Bugfixes --------- - - If a ProcessProtocol.processExited method raised an exception a - broken process handler would be left in the global process state - leading to errors later on. This has been fixed and now an error - will be logged instead. (#5151) - - Twisted now builds on Solaris. Note that lacking a Buildbot slave - (see http://buildbot.twistedmatrix.com/boxes-supported) Solaris is - not a supported Twisted platform. (#5728) - - twisted.internet.utils is now correctly installed on Python 3. - (#6929) - - twisted.python.threadpool.ThreadPool no longer starts new workers - when its pool size is changed while the pool is not running. - (#7011) - -Improved Documentation ----------------------- - - Twisted now uses the Sphinx documentation generator for its - narrative documentation, which means that the source format for - narrative documentation has been converted to ReStructuredText. - (#4500) - - The Sphinx documentation is now also configured to allow - intersphinx links to standard library documentation. (#4582) - - The docstring for twisted.internet.task.react now better documents - the main parameter (#6071) - - The writing standard now explicitly mandates the usage of - ungendered pronouns. (#6858) - -Deprecations and Removals -------------------------- - - test_import.py was removed as it was redundant. (#2053) - - Support for versions of pyOpenSSL older than 0.10 has been removed. - Affected users should upgrade pyOpenSSL. (#5014) - - twisted.internet.interfaces.IStreamClientEndpointStringParser is - now deprecated in favor of twisted.internet.interfaces.IStreamClien - tEndpointStringParserWithReactor. (#5069) - - unsignedID and setIDFunction, previously part of - twisted.python.util and deprecated since 13.0, have now been - removed. (#6707) - - FTPClient.changeDirectory was deprecated in 8.2 and is now removed. - (#6759) - - twisted.internet.stdio.StandardIO.closeStdin, an alias for - loseWriteConnection only available on POSIX and deprecated since - 2.1, has been removed. (#6785) - - twisted.python.reflect.getcurrent is now deprecated and must not be - used. twisted.python.reflect.isinst is now deprecated in favor of - the built-in isinstance. (#6859) - -Other ------ - - #1822, #5929, #6239, #6537, #6565, #6614, #6632, #6690, #6784, - #6792, #6795, #6821, #6843, #6846, #6854, #6856, #6857, #6872, - #6892, #6902, #6906, #6922, #6926, #6936, #6941, #6942, #6943, - #6944, #6945, #6946, #6948, #6979, #7001, #7049, #7051, #7094, - #7098 - - -Twisted Conch 14.0.0 (2014-05-08) -================================= - -Improved Documentation ----------------------- - - The docstring for twisted.conch.ssh.userauth.SSHUserAuthClient is - now clearer on how the preferredOrder instance variable is handled. - (#6850) - -Other ------ - - #6696, #6807, #7054 - - -Twisted Lore 14.0.0 (2014-05-08) -================================ - -Deprecations and Removals -------------------------- - - twisted.lore is now deprecated in favor of Sphinx. (#6907) - -Other ------ - - #6998 - - -Twisted Mail 14.0.0 (2014-05-08) -================================ - -Improved Documentation ----------------------- - - twisted.mail.alias now has full API documentation. (#6637) - - twisted.mail.tap now has full API documentation. (#6648) - - twisted.mail.maildir now has full API documentation. (#6651) - - twisted.mail.pop3client now has full API documentation. (#6653) - - twisted.mail.protocols now has full API documentation. (#6654) - - twisted.mail.pop now has full API documentation. (#6666) - - twisted.mail.relay and twisted.mail.relaymanager now have full API - documentation. (#6739) - - twisted.mail.pop3client public classes now appear as part of the - twisted.mail.pop3 API. (#6761) - -Other ------ - - #6696 - - -Twisted Names 14.0.0 (2014-05-08) -================================= - -Features --------- - - twisted.names.root.Resolver now accepts a resolverFactory argument, - which makes it possible to control how root.Resolver performs - iterative queries to authoritative nameservers. (#6095) - - twisted.names.dns.Message now has a repr method which shows only - those instance flags, fields and sections which are set to non- - default values. (#6847) - - twisted.names.dns.Message now support rich comparison. (#6848) - -Bugfixes --------- - - twisted.names.server.DNSServerFactory now responds with messages - whose flags and fields are reset to their default values instead of - copying these from the request. This means that AD and CD flags, - and EDNS OPT records in the request are no longer mirrored back to - the client. (#6645) - -Improved Documentation ----------------------- - - twisted.names now has narrative documentation showing how to create - a custom DNS server. (#6864) - - twisted.names.server now has full API documentation. (#6886) - - twisted.names now has narrative documentation explaining how to use - its client APIs. (#6925) - - twisted.names now has narrative documentation and examples showing - how to perform reverse DNS lookups. (#6969) - -Other ------ - - #5675, #6222, #6672, #6696, #6887, #6940, #6975, #6990 - - -Twisted News 14.0.0 (2014-05-08) -================================ - -No significant changes have been made for this release. - -Other ------ - - #6991 - - -Twisted Pair 14.0.0 (2014-05-08) -================================ - -Features --------- - - twisted.pair.tuntap now has complete test coverage, basic - documentation, and works without the difficult-to-find system - bindings it used to require. (#6169) - -Other ------ - - #6898, #6931, #6993 - - -Twisted Runner 14.0.0 (2014-05-08) -================================== - -No significant changes have been made for this release. - -Other ------ - - #6992 - - -Twisted Web 14.0.0 (2014-05-08) -=============================== - -Features --------- - - twisted.web.http.proxiedLogFormatter can now be used with - twisted.web.http.HTTPFactory (and subclasses) to record X - -Forwarded-For values to the access log when the HTTP server is - deployed behind a reverse proxy. (#1468) - - twisted.web.client.Agent now uses - twisted.internet.ssl.CertificateOptions for SSL/TLS and benefits - from its continuous improvements. (#6893) - -Bugfixes --------- - - twisted.web.client.Agent now correctly manage flow-control on - pooled connections, and therefore requests will no longer hang - sometimes when deliverBody is not called synchronously within the - callback on Request. (#6751) - - twisted.web.client.Agent now verifies that the provided server - certificate in a TLS connection is trusted by the platform. (#7042) - - When requesting an HTTPS URL with twisted.web.client.Agent, the - hostname of the presented certificate will be checked against the - requested hostname; mismatches will now result in an error rather - than a man-in-the-middle opportunity for attackers. This may break - existing code that incorrectly depended on insecure behavior, but - such code was erroneous and should be updated. (#4888) - -Other ------ - - #5004, #6881, #6956 - - -Twisted Words 14.0.0 (2014-05-08) -================================= - -Bugfixes --------- - - twisted.words.protocols.jabber.sasl_mechansisms.DigestMD5 now works - with unicode arguments. (#5066) - -Other ------ - - #6696 - - -Twisted Core 13.2.0 (2013-10-29) -================================ - -Features --------- - - twistd now waits for the application to start successfully before - exiting after daemonization. (#823) - - twisted.internet.endpoints now provides HostnameEndpoint, a TCP - client endpoint that connects to a hostname as quickly as possible. - (#4859) - - twisted.internet.interfaces.IReactorSocket now has a new - adoptDatagramPort method which is implemented by some reactors - allowing them to listen on UDP sockets set up by external software - (eg systemd or launchd). (#5574) - - trial now accepts an --order option that specifies what order to - run TestCase methods in. (#5787) - - Port twisted.python.lockfile to Python 3, enabling - twisted.python.defer.DeferredFilesystemLock and tests. (#5960) - - Returning a Deferred from a callback that's directly returned from - that Deferred will now produce a DeprecationWarning, to notify - users of the buggy behavior. (#6164) - - SSL server endpoint string descriptions now support the - specification of chain certificates. (#6499) - - twisted.application.reactors.installReactor now returns the just- - installed reactor. (#6596) - - twisted.internet.defer.DeferredList now has a new cancel method. - And twisted.internet.defer.gatherResults now returns a cancellable - result. (#6639) - -Bugfixes --------- - - twisted.protocols.basic.LineReceiver no longer passes incorrect - data (a buffer missing a delimiter) to lineLengthExceeded in - certain cases. (#6536) - - twisted.cred.digest.DigestCredentialFactory now supports decoding - challenge responses with field values including ",". (#6609) - - twisted.internet.endpoints.TCP6ClientEndpoint now establishes - connections when constructed with a hostname. (#6633) - - twisted.application.internet.TimerService is now pickleable in all - cases. (#6657) - -Improved Documentation ----------------------- - - The howto document page of Deferred now has documentation about - cancellation. (#4320) - - Docstrings for twisted.internet.task.Cooperator and cooperate. - (#6213) - -Deprecations and Removals -------------------------- - - Returning a Deferred from a callback that's directly returned from - that Deferred will now produce a DeprecationWarning, to notify - users of the buggy behavior. (#6164) - - Accessor, AccessorType, OriginalAccessor, PropertyAccessor, - Settable and Summer in twisted.python.reflect, deprecated since - Twisted 12.1.0, are now removed. (#6689) - -Other ------ - - #5001, #5312, #5387, #5442, #5634, #6221, #6393, #6406, #6485, - #6570, #6575, #6610, #6674, #6684, #6685, #6715, #6729, #6731, - #6736, #6773, #6788, #6793 - - -Twisted Conch 13.2.0 (2013-10-29) -================================= - -Features --------- - - ckeygen now accepts --no-passphrase to generate unprotected keys. - (#5998) - - twisted.conch.endpoints.SSHCommandClientEndpoint.newConnection now - supplies a convenient default for the `ui` parameter if a value is - not passed in for it. (#6550) - -Bugfixes --------- - - ckeygen --changepass now doesn't delete unencrypted keys or raise - an exception on encrypted ones. (#5894) - - twisted.conch.endpoints.SSHCommandClientEndpoint now doesn't try - password authentication if there is no password specified. (#6553) - - twisted.conch.endpoints.SSHCommandClientEndpoint now uses the - standard SSH port if no port is specified. (#6631) - -Other ------ - - #5387, #6220 - - -Twisted Lore 13.2.0 (2013-10-29) -================================ - -No significant changes have been made for this release. - -Other ------ - - #6546 - - -Twisted Mail 13.2.0 (2013-10-29) -================================ - -Features --------- - - twisted.mail.smtp.sendmail now returns a cancellable Deferred. - (#6572) - -Improved Documentation ----------------------- - - twisted.mail.mail now has full API documentation. (#6649) - - twisted.mail.bounce now has full API documentation. (#6652) - -Other ------ - - #5387, #6486 - - -Twisted Names 13.2.0 (2013-10-29) -================================= - -Features --------- - - twisted.names.authority.FileAuthority now considers any AAAA it - knows about for inclusion in the additional section of a response - (following the same logic previously used for including A records - there). (#6642) - - twisted.names.dns.Message now allows encoding and decoding of the - Authentic Data (AD) and Checking Disabled (CD) flags described in - RFC2535. (#6680) - -Bugfixes --------- - - twisted.names.resolve.ResolverChain now returns a - twisted.names.error.DomainError failure if its resolvers list is - empty. (#5992) - - twisted.names.authority.FileAuthority now only returns - AuthoritativeDomainError (NXDOMAIN) for names which are subdomains. - (#6475) - - The Deferred returned by twisted.names.client.Resolver.queryTCP now - fires with an error if the TCP connection attempt fails. (#6658) - -Improved Documentation ----------------------- - - Use zope.interface.moduleProvides to allow pydoctor to properly - document the twisted.names.client.lookup* functions. (#6328) - -Other ------ - - #5387, #5668, #6563, #6655 - - -Twisted News 13.2.0 (2013-10-29) -================================ - -No significant changes have been made for this release. - - -Twisted Pair 13.2.0 (2013-10-29) -================================ - -No significant changes have been made for this release. - - -Twisted Runner 13.2.0 (2013-10-29) -================================== - -No significant changes have been made for this release. - - -Twisted Web 13.2.0 (2013-10-29) -=============================== - -Features --------- - - IAgent has been added to twisted.web.iweb to explicitly define the - interface implemented by the various "Agent" classes in - twisted.web.client. (#6702) - -Bugfixes --------- - - twisted.web.client.Response.deliverBody now calls connectionLost on - the body protocol for responses with no body (such as 204, 304, and - HEAD requests). (#5476) - - twisted.web.static.loadMimeTypes now uses all available system MIME - types. (#5717) - -Deprecations and Removals -------------------------- - - Two attributes of twisted.web.iweb.IRequest, headers and - received_headers, are now deprecated. (#6704) - -Other ------ - - #5387, #6119, #6121, #6695, #6701, #6734 - - -Twisted Words 13.2.0 (2013-10-29) -================================= - -Bugfixes --------- - - twisted.words.service.IRCUser now properly reports an error, in - response to NICK commands with non-UTF8 and non-ASCII symbols. - (#5780) - -Other ------ - - #5329, #5387, #6544 - - -Twisted Core 13.1.0 (2013-06-23) -================================ - -Features --------- - - trial now has an --exitfirst flag which stops the test run after - the first error or failure. (#1518) - - twisted.internet.ssl.CertificateOptions now supports chain - certificates. (#2061) - - twisted.internet.endpoints now provides ProcessEndpoint, a child - process endpoint. (#4696) - - Factory now has a forProtocol classmethod that constructs an - instance and sets its protocol attribute. (#5016) - - twisted.internet.endpoints.connectProtocol allows connecting to a - client endpoint using only a protocol instance, rather than - requiring a factory. (#5270) - - twisted.trial.unittest.SynchronousTestCase.assertNoResult no longer - swallows the result, if the assertion succeeds. (#6291) - - twisted.python.constants.FlagConstant implements __iter__ so that - it can be iterated upon to find the flags that went into a flag - set, and implements __nonzero__ to test as false when empty. - (#6302) - - assertIs and assertIsNot have now been added to - twisted.trial.unittest.TestCase. (#6350) - - twisted.trial.unittest.TestCase.failureResultOf now takes an - optional expected failure type argument. (#6380) - - The POSIX implementation of - twisted.internet.interfaces.IReactorProcess now does not change the - parent process UID or GID in order to run child processes with a - different UID or GID. (#6443) - -Bugfixes --------- - - self.transport.resumeProducing() will no longer raise an - AssertionError if called after self.transport.loseConnection() - (#986) - - twisted.protocols.ftp.FTP now supports IFTPShell implementations - which return non-ASCII filenames as unicode strings. (#5411) - - twisted.internet.ssl.CertificateOptions now disables SSLv2 if - SSLv23 is selected, allowing only SSLv3 and TLSv1. (#6337) - - trial dist support now gets sys.path from an environment variable - passed to it. (#6390) - - twisted.test.proto_helpers.StringTransportWithDisconnection now - correctly passes Failure instead of an exception to - connectionLost through loseConnection. (#6521) - -Improved Documentation ----------------------- - - The Application howto now provides an example of writing a custom - Service. (#5586) - - The -j flag to trial (introduced in 12.3.0) is now documented. - (#5994) - - The SSL howto now covers twisted.internet.ssl.CertificateOptions - instead of the older context factories it replaces. (#6273) - - The Constants HOWTO documents iteration and truth testing of flags, - as well as previously undocumented boolean operations. (#6302) - -Deprecations and Removals -------------------------- - - twisted.trial.runner.suiteVisit and PyUnitTestCase as well as - visitor methods, all deprecated since Twisted 8.0, have been - removed. (#3231) - - twisted.python._epoll bindings were removed; the epoll reactor now - uses the stdlib-provided epoll support. (#5847) - - The deprecated LENGTH, DATA, COMMA, and NUMBER NetstringReceiver - parser state attributes in t.protocols.basic are removed now. - (#6321) - - twisted.trial.runner.DryRunVisitor is now deprecated. Trial uses a - different method to handle --dry-run now. (#6333) - - twisted.python.hashlib is now deprecated in favor of hashlib from - stdlib. (#6342) - - twisted.web.server's Session.loopFactory, lifetime parameter of - Session.startCheckingExpiration and Session.checkExpired attributes, - deprecated since Twisted 9.0, have been removed. (#6514) - -Other ------ - - #2380, #5197, #5228, #5386, #5459, #5578, #5801, #5952, #5955, - #5981, #6051, #6189, #6228, #6240, #6284, #6286, #6299, #6316, - #6353, #6354, #6368, #6377, #6378, #6381, #6389, #6400, #6403, - #6407, #6416, #6417, #6418, #6419, #6430, #6433, #6438, #6439, - #6440, #6441, #6444, #6459, #6465, #6468, #6477, #6480, #6498, - #6508, #6510, #6525 - - -Twisted Conch 13.1.0 (2013-06-23) -================================= - -Features --------- - - twisted.conch.endpoints.SSHCommandClientEndpoint is a new - IStreamClientEndpoint which supports connecting a protocol to the - stdio of a command running on a remote host via an SSH connection. - (#4698) - - twisted.conch.client.knownhosts.KnownHostsFile now has a public - `savePath` attribute giving the filesystem path where the known - hosts data is saved to and loaded from. (#6255) - - twisted.conch.endpoints.SSHCommandClientEndpoint.connect() returns - a cancellable Deferred when using new connections. (#6532) - -Other ------ - - #5386, #6342, #6386, #6405, #6541 - - -Twisted Lore 13.1.0 (2013-06-23) -================================ - -Deprecations and Removals -------------------------- - - twisted.lore.lint.parserErrors is deprecated now. (#5386) - - -Twisted Mail 13.1.0 (2013-06-23) -================================ - -Bugfixes --------- - - twisted.mail.smtp.ESMTPClient no longer tries to use a STARTTLS - capability offered by a server after TLS has already been - negotiated. (#6524) - -Deprecations and Removals -------------------------- - - twisted.mail.IDomain.startMessage, deprecated since 2003, is - removed now. (#4151) - -Other ------ - - #6342 - - -Twisted Names 13.1.0 (2013-06-23) -================================= - -No significant changes have been made for this release. - -Other ------ - - #3908, #6381 - - -Twisted News 13.1.0 (2013-06-23) -================================ - -No significant changes have been made for this release. - -Other ------ - - #6342 - - -Twisted Pair 13.1.0 (2013-06-23) -================================ - -No significant changes have been made for this release. - - -Twisted Runner 13.1.0 (2013-06-23) -================================== - -No significant changes have been made for this release. - - -Twisted Web 13.1.0 (2013-06-23) -=============================== - -Features --------- - - The deferred returned by twisted.web.client.Agent.request can now - be cancelled. (#4330) - - twisted.web.client.BrowserLikeRedirectAgent, a new redirect agent, - treats HTTP 301 and 302 like HTTP 303 on non-HEAD/GET requests, - changing the method to GET before proceeding. (#5434) - - The new attribute twisted.web.iweb.IResponse.request is a reference - to a provider of the new twisted.web.iweb.IClientRequest interface - which, among other things, provides a way to access the request's - absolute URI. It is now also possible to inspect redirect history - with twisted.web.iweb.IResponse.previousResponse. (#5435) - - twisted.web.client.RedirectAgent now supports relative URI - references in the Location HTTP header. (#5462) - - twisted.web.client now provides readBody to collect the body of a - response from Agent into a string. (#6251) - -Bugfixes --------- - - twisted.web.xmlrpc.QueryProtocol now generates valid Authorization - headers for long user names and passwords. (#2980) - -Other ------ - - #6122, #6153, #6342, #6381, #6391, #6503 - - -Twisted Words 13.1.0 (2013-06-23) -================================= - -Features --------- - - twisted.words.protocols.irc.assembleFormattedText flattens a - formatting structure into mIRC-formatted markup; conversely - twisted.words.protocols.irc.stripFormatting removes all mIRC - formatting from text. (#3844) - -Deprecations and Removals -------------------------- - - The `crippled` attribute in - twisted.words.protocols.jabber.xmpp_stringprep is deprecated now. - (#5386) - -Other ------ - - #6315, #6342, #6392, #6402, #6479, #6481, #6482 - - -Twisted Core 13.0.0 (2013-03-19) -================================ - -Features --------- - - The twisted.protocols.ftp.FTP server now treats "LIST -La", "LIST - -al", and all other combinations of ordering and case of the "-l" - and "-a" flags the same: by ignoring them rather than treating them - as a pathname. (#1333) - - twisted.python.log.FileLogObserver now uses `datetime.strftime` to - format timestamps, adding support for microseconds and timezone - offsets to the `timeFormat` string. (#3513) - - trial now deterministically runs tests in the order in which they - were specified on the command line, instead of quasi-randomly - according to dictionary key ordering. (#5520) - - Cooperator.running can be used to determine the current cooperator - status. (#5937) - - twisted.python.modules.PythonPath now implements `__contains__` to - allow checking, by name, whether a particular module exists within - it. (#6198) - - twisted.application.internet.TimerService.stopService now waits for - any currently running call to finish before firing its deferred. - (#6290) - -Bugfixes --------- - - twisted.protocols.ftp.FTP now recognizes all glob expressions - supported by fnmatch. (#4181) - - Constant values defined using twisted.python.constants can now be - set as attributes of other classes without triggering an unhandled - AttributeError from the constants implementation. (#5797) - - Fixed problem where twisted.names.client.Resolver was not closing - open file handles which can lead to an out of file descriptor error - on PyPy. (#6216) - - All reactors included in Twisted itself now gracefully handle a - rare case involving delayed calls scheduled very far in the future. - (#6259) - - twisted.trial.reporter.Reporter._trimFrames correctly removes - frames from twisted.internet.utils.runWithWarningsSuppressed again, - after being broke in #6009. (#6282) - -Improved Documentation ----------------------- - - A new "Deploying Twisted with systemd" howto document which - demonstrates how to start a Twisted service using systemd socket - activation. (#5601) - - New "Introduction to Deferreds" howto. Old howto rebranded as - reference documentation. (#6180) - - "Components: Interfaces and Adapters" howto now uses - zope.interface's decorator-based API. (#6269) - -Deprecations and Removals -------------------------- - - twisted.python.util.unsignedID and setIDFunction are deprecated - now. (#5544) - - twisted.python.zshcomp deprecated since 11.1.0 has now been - removed. Shell tab-completion is now handled by - twisted.python.usage. (#5767) - - python.runtime.Platform.isWinNT is deprecated now. Use - Platform.isWindows instead. (#5925) - - twisted.trial.util.findObject, deprecated since Twisted 10.1.0, has - been removed. (#6260) - -Other ------ - - #2915, #4009, #4315, #5909, #5918, #5953, #6026, #6046, #6165, - #6201, #6207, #6208, #6211, #6235, #6236, #6247, #6265, #6272, - #6288, #6297, #6309, #6322, #6323, #6324, #6327, #6332, #6338, - #6349 - - -Twisted Conch 13.0.0 (2013-03-19) -================================= - -Features --------- - - twisted.conch.client.knownhosts.KnownHostsFile now takes care not - to overwrite changes to its save file made behind its back, making - it safer to use with the same known_hosts file as is being used by - other software. (#6256) - -Other ------ - - #5864, #6257, #6297 - - -Twisted Lore 13.0.0 (2013-03-19) -================================ - -No significant changes have been made for this release. - - -Twisted Mail 13.0.0 (2013-03-19) -================================ - -Bugfixes --------- - - twisted.mail.smtp.ESMTPClient no longer attempts to negotiate a TLS - session if transport security has been requested and the protocol - is already running on a TLS connection. (#3989) - - twisted.mail.imap4.Query now filters illegal characters from the - values of KEYWORD and UNKEYWORD and also emits them without adding - quotes (which are also illegal). (#4392) - - twisted.mail.imap4.IMAP4Client can now interpret the BODY response - for ``multipart/*`` messages with parts which are also ``multipart/*``. - (#4631) - -Deprecations and Removals -------------------------- - - tlsMode attribute of twisted.mail.smtp.ESMTPClient is deprecated. - (#5852) - -Other ------ - - #6218, #6297 - - -Twisted Names 13.0.0 (2013-03-19) -================================= - -Features --------- - - twisted.names.dns.Name and twisted.names.srvconnect.SRVConnector - now support unicode domain names, automatically converting using - the idna encoding. (#6245) - -Improved Documentation ----------------------- - - The API documentation for IResolver and its implementations has - been updated and consolidated in - twisted.internet.interfaces.IResolver. (#4685) - -Deprecations and Removals -------------------------- - - The retry, Resolver.discoveredAuthority, lookupNameservers, - lookupAddress, extractAuthority, and discoverAuthority APIs in - twisted.names.root have been deprecated since 10.0 and have been - removed. (#5564) - -Other ------ - - #5596, #6246, #6297 - - -Twisted News 13.0.0 (2013-03-19) -================================ - -No significant changes have been made for this release. - - -Twisted Pair 13.0.0 (2013-03-19) -================================ - -No significant changes have been made for this release. - - -Twisted Runner 13.0.0 (2013-03-19) -================================== - -No significant changes have been made for this release. - -Other ------ - - #5740 - - -Twisted Web 13.0.0 (2013-03-19) -=============================== - -Bugfixes --------- - - twisted.web.template now properly quotes attribute values, - including Tag instances serialized within attribute values. (#6275) - -Other ------ - - #6167, #6297, #6326 - - -Twisted Words 13.0.0 (2013-03-19) -================================= - -Bugfixes --------- - - twisted.words.im.ircsupport no longer logs a failure whenever - receiving ISUPPORT messages from an IRC server. (#6263) - -Other ------ - - #6297 - - -Twisted Core 12.3.0 (2012-12-20) -================================ - -Features --------- - - The new -j flag to trial provides a trial runner supporting - multiple worker processes on the local machine, for parallel - testing. (#1784) - - twisted.internet.task.react, a new function, provides a simple API - for running the reactor until a single asynchronous function - completes. (#3270) - - twisted.protocols.ftp.FTP now handles FEAT and OPTS commands. - (#4515) - - trial now supports specifying a debugger other than pdb with the - --debugger command line flag. (#5794) - - twisted.python.util.runWithWarningsSuppressed has been added; it - runs a function with specified warning filters. (#5950) - - trial's skipping feature is now implemented in a way compatible with the - standard library unittest's runner. (#6006) - - The setup3.py script is now provided to provisionally support - building and installing an experimental, incomplete version of - Twisted in a Python 3 environment. (#6040) - - twisted.python.util.FancyStrMixin now supports arbitrary callables - to format attribute values. (#6063) - - Several new methods of twisted.trial.unittest.SynchronousTestCase - - `successResultOf`, `failureResultOf`, and `assertNoResult` - - have been added to make testing `Deferred`-using code easier. - (#6105) - -Bugfixes --------- - - twisted.protocols.basic.LineReceiver now does not hit the maximum - stack recursion depth when the line and data mode is switched many - times. (#3050) - - twisted.protocols.ftp.FTPFileListProtocol fixed to support files - with space characters in their name. (#4986) - - gireactor and gtk3reactor no longer prevent gi.pygtkcompat from - working, and likewise can load if gi.pygtkcompat has previously - been enabled. (#5676) - - gtk2reactor now works again on FreeBSD, and perhaps other platforms - that were broken by gi interactions. (#5737) - - gireactor now works with certain older versions of gi that are - missing the threads_init() function. (#5790) - - Fixed a bug where twisted.python.sendmsg would sometimes fail with - obscure errors including "Message too long" or "Invalid argument" - on some 64-bit platforms. (#5867) - - twisted.internet.endpoints.TCP6ClientEndpoint now provides - twisted.internet.interfaces.IStreamClientEndpoint (#5876) - - twisted.internet.endpoints.AdoptedStreamServerEndpoint now provides - twisted.internet.interfaces.IStreamServerEndpoint. (#5878) - - Spawning subprocesses with PTYs now works on OS X 10.8. (#5880) - - twisted.internet.test.test_sigchld no longer incorrectly fails when - run after certain other tests. (#6161) - - twisted.internet.test.test_gireactor no longer fails when using - pygobject 3.4 and gtk 3.6 when X11 is unavailable. (#6170) - - twisted/python/sendmsg.c no longer fails to build on OpenBSD. - (#5907) - -Improved Documentation ----------------------- - - The endpoint howto now lists TCP IPv6 server endpoint in the list - of endpoints included with Twisted. (#5741) - -Deprecations and Removals -------------------------- - - The minimum required version of zope.interface is now 3.6.0. - (#5683) - - twisted.internet.interfaces.IReactorArbitrary and - twisted.application.internet.GenericServer and GenericClient, - deprecated since Twisted 10.1, have been removed. (#5943) - - twisted.internet.interfaces.IFinishableConsumer, deprecated since - Twisted 11.1, has been removed. (#5944) - - twisted.python.failure has removed all support for string - exceptions. (#5948) - - assertTrue, assertEqual, and the other free-functions in - twisted.trial.unittest for writing assertions, deprecated since - prior to Twisted 2.3, have been removed. (#5963) - - Ports, connectors, wakers and other reactor-related types no longer - log a nice warning when they are erroneously pickled. Pickling of - such objects continues to be unsupported. (#5979) - - twisted.python.components.Componentized no longer inherits from - Versioned. (#5983) - - twisted.protocols.basic.NetstringReceiver.sendString no longer - accepts objects other than bytes; the removed behavior was - deprecated in Twisted 10.0. (#6025) - - The lookupRecord method of twisted.internet.interfaces.IResolver, - never implemented or called by Twisted, has been removed. (#6091) - -Other ------ - - #4286, #4920, #5627, #5785, #5860, #5865, #5873, #5874, #5877, - #5879, #5884, #5885, #5886, #5891, #5896, #5897, #5899, #5900, - #5901, #5903, #5906, #5908, #5912, #5913, #5914, #5916, #5917, - #5931, #5932, #5933, #5934, #5935, #5939, #5942, #5947, #5956, - #5959, #5967, #5969, #5970, #5972, #5973, #5974, #5975, #5980, - #5985, #5986, #5990, #5995, #6002, #6003, #6005, #6007, #6009, - #6010, #6018, #6019, #6022, #6023, #6033, #6036, #6039, #6041, - #6043, #6052, #6053, #6054, #6055, #6060, #6061, #6065, #6067, - #6068, #6069, #6084, #6087, #6088, #6097, #6099, #6100, #6103, - #6109, #6114, #6139, #6140, #6141, #6142, #6157, #6158, #6159, - #6163, #6172, #6182, #6190, #6194, #6204, #6209 - - -Twisted Conch 12.3.0 (2012-12-20) -================================= - -Bugfixes --------- - - Passing multiple --auth arguments to conch now correctly adds all - the specified checkers to the conch server (#5881) - - ckeygen --showpub now uses OPENSSH as default display, instead of - breaking because no display type was passed. (#5889) - - ckeygen --showpub catches EncryptedKeyError instead of BadKeyError - to detect that a key needs to be decrypted with a passphrase. - (#5890) - -Other ------ - - #5923 - - -Twisted Lore 12.3.0 (2012-12-20) -================================ - -No significant changes have been made for this release. - - -Twisted Mail 12.3.0 (2012-12-20) -================================ - -Bugfixes --------- - - twisted.mail.imap4._FetchParser now raises - IllegalClientResponse("Invalid Argument") when protocol encounters - extra bytes at the end of a valid FETCH command. (#4000) - -Improved Documentation ----------------------- - - twisted.mail.tap now documents example usage in its longdesc - output for the 'mail' plugin (#5922) - -Other ------ - - #3751 - - -Twisted Names 12.3.0 (2012-12-20) -================================= - -Deprecations and Removals -------------------------- - - The `protocol` attribute of twisted.names.client.Resolver, - deprecated since Twisted 8.2, has been removed. (#6045) - - twisted.names.hosts.Resolver is no longer a - `twisted.persisted.styles.Versioned` subclass. (#6092) - -Other ------ - - #5594, #6056, #6057, #6058, #6059, #6093 - - -Twisted News 12.3.0 (2012-12-20) -================================ - -No significant changes have been made for this release. - - -Twisted Pair 12.3.0 (2012-12-20) -================================ - -No significant changes have been made for this release. - - -Twisted Runner 12.3.0 (2012-12-20) -================================== - -No significant changes have been made for this release. - - -Twisted Web 12.3.0 (2012-12-20) -=============================== - -Features --------- - - twisted.web.server.Site now supports an encoders argument to encode - request content, twisted.web.server.GzipEncoderFactory being the - first one provided. (#104) - -Bugfixes --------- - - twisted.web.http.HTTPChannel.headerReceived now catches the error - if the Content-Length header is not an integer and return a 400 Bad - Request response. (#6029) - - twisted.web.http.HTTPChannel now drops the connection and issues a - 400 error upon receipt of a chunk-encoding encoded request with a - bad chunk-length field. (#6030) - -Improved Documentation ----------------------- - - twisted.web.iweb.IRequest now documents its `content` attribute and - a new "web in 60 seconds" howto demonstrates its use. (#6181) - -Other ------ - - #5882, #5883, #5887, #5920, #6031, #6077, #6078, #6079, #6080, - #6110, #6113, #6196, #6205 - - -Twisted Words 12.3.0 (2012-12-20) -================================= - -Improved Documentation ----------------------- - - The Twisted Words code examples now documents inside each example - description on how to run it. (#5589) - - -Twisted Core 12.2.0 (2012-08-26) -================================ - -Starting with the release after 12.2, Twisted will begin requiring -zope.interface 3.6 (as part of Python 3 support). - -This is the last Twisted release supporting Python 2.6 on Windows. - -Features --------- - - twisted.protocols.sip.MessageParser now handles multiline headers. - (#2198) - - twisted.internet.endpoints now provides StandardIOEndpoint, a - Standard I/O endpoint. (#4697) - - If a FTPCmdError occurs during twisted.protocols.ftp.FTP.ftp_RETR - sending the file (i.e. it is raised by the IReadFile.send method it - invokes), then it will use that to return an error to the client - rather than necessarily sending a 426 CNX_CLOSED_TXFR_ABORTED - error. (#4913) - - twisted.internet.interfaces.IReactorSocket.adoptStreamConnection is - implemented by some reactors as a way to add an existing - established connection to them. (#5570) - - twisted.internet.endpoints now provides TCP6ServerEndpoint, an IPv6 - TCP server endpoint. (#5694) - - twisted.internet.endpoints now provides TCP6ClientEndpoint, an IPv6 - TCP client endpoint. (#5695) - - twisted.internet.endpoints.serverFromString, the endpoint string - description feature, can now be used to create IPv6 TCP servers. - (#5699) - - twisted.internet.endpoints.serverFromString, the endpoint string - description feature, can now be used to create servers that run on - Standard I/O. (#5729) - - twisted.trial.unittest now offers SynchronousTestCase, a test case - base class that provides usability improvements but not reactor- - based testing features. (#5853) - -Bugfixes --------- - - twisted.internet.Process.signalProcess now catches ESRCH raised by - os.kill call and raises ProcessExitedAlready instead. (#2420) - - TLSMemoryBIOProtocol (and therefore all SSL transports if pyOpenSSL - >= 0.10) now provides the interfaces already provided by the - underlying transport. (#5182) - -Deprecations and Removals -------------------------- - - Python 2.5 is no longer supported. (#5553) - - The --extra option of trial, deprecated since 11.0, is removed now. - (#3374) - - addPluginDir and getPluginDirs in twisted.python.util are - deprecated now. (#4533) - - twisted.trial.runner.DocTestCase, deprecated in Twisted 8.0, has - been removed. (#5554) - - startKeepingErrors, flushErrors, ignoreErrors, and clearIgnores in - twisted.python.log (deprecated since Twisted 2.5) are removed now. - (#5765) - - unzip, unzipIter, and countZipFileEntries in - twisted.python.zipstream (deprecated in Twisted 11.0) are removed - now. (#5766) - - twisted.test.time_helpers, deprecated since Twisted 10.0, has been - removed. (#5820) - -Other ------ - - #4244, #4532, #4930, #4999, #5129, #5138, #5385, #5521, #5655, - #5674, #5679, #5687, #5688, #5689, #5692, #5707, #5734, #5736, - #5745, #5746, #5747, #5749, #5784, #5816, #5817, #5818, #5819, - #5830, #5857, #5858, #5859, #5869, #5632 - - -Twisted Conch 12.2.0 (2012-08-26) -================================= - -Features --------- - - twisted.conch.ssh.transport.SSHTransport now returns an - SSHTransportAddress from the getPeer() and getHost() methods. - (#2997) - -Bugfixes --------- - - twisted.conch now supports commercial SSH implementations which - don't comply with the IETF standard (#1902) - - twisted.conch.ssh.userauth now works correctly with hash - randomization enabled. (#5776) - - twisted.conch no longer relies on __builtins__ being a dict, which - is a purely CPython implementation detail (#5779) - -Other ------ - - #5496, #5617, #5700, #5748, #5777 - - -Twisted Lore 12.2.0 (2012-08-26) -================================ - -No significant changes have been made for this release. - - -Twisted Mail 12.2.0 (2012-08-26) -================================ - -Bugfixes --------- - - twisted.mail.imap4.IMAP4Server will now generate an error response - when it receives an illegal SEARCH term from a client. (#4080) - - twisted.mail.imap4 now serves BODYSTRUCTURE responses which provide - more information and conform to the IMAP4 RFC more closely. (#5763) - -Deprecations and Removals -------------------------- - - twisted.mail.protocols.SSLContextFactory is now deprecated. (#4963) - - The --passwordfile option to twistd mail is now removed. (#5541) - -Other ------ - - #5697, #5750, #5751, #5783 - - -Twisted Names 12.2.0 (2012-08-26) -================================= - -Features --------- - - twisted.names.srvconnect.SRVConnector now takes a default port to - use when SRV lookup fails. (#3456) - -Other ------ - - #5647 - - -Twisted News 12.2.0 (2012-08-26) -================================ - -No significant changes have been made for this release. - - -Twisted Pair 12.2.0 (2012-08-26) -================================ - -No significant changes have been made for this release. - - -Twisted Runner 12.2.0 (2012-08-26) -================================== - -No significant changes have been made for this release. - - -Twisted Web 12.2.0 (2012-08-26) -=============================== - -Deprecations and Removals -------------------------- - - twisted.web.static.FileTransfer, deprecated since 9.0, is removed - now. Use a subclass of StaticProducer instead. (#5651) - - ErrorPage, NoResource and ForbiddenResource in twisted.web.error - were deprecated since 9.0 and are removed now. (#5659) - - twisted.web.google, deprecated since Twisted 11.1, is removed now. - (#5768) - -Other ------ - - #5665 - - -Twisted Words 12.2.0 (2012-08-26) -================================= - -No significant changes have been made for this release. - -Other ------ - - #5752, #5753 - - -Twisted Core 12.1.0 (2012-06-02) -================================ - -Features --------- - - The kqueue reactor has been revived. (#1918) - - twisted.python.filepath now provides IFilePath, an interface for - file path objects. (#2176) - - New gtk3 and gobject-introspection reactors have been added. - (#4558) - - gtk and glib reactors now run I/O and scheduled events with lower - priority, to ensure the UI stays responsive. (#5067) - - IReactorTCP.connectTCP() can now accept IPv6 address literals - (although not hostnames) in order to support connecting to IPv6 - hosts. (#5085) - - twisted.internet.interfaces.IReactorSocket, a new interface, is now - supported by some reactors to listen on sockets set up by external - software (eg systemd or launchd). (#5248) - - twisted.internet.endpoints.clientFromString now also supports - strings in the form of tcp:example.com:80 and ssl:example.com:4321 - (#5358) - - twisted.python.constants.Flags now provides a way to define - collections of flags for bitvector-type uses. (#5384) - - The epoll(7)-based reactor is now the default reactor on Linux. - (#5478) - - twisted.python.runtime.platform.isLinux can be used to check if - Twisted is running on Linux. (#5491) - - twisted.internet.endpoints.serverFromString now recognizes a - "systemd" endpoint type, for listening on a server port inherited - from systemd. (#5575) - - Connections created using twisted.internet.interfaces.IReactorUNIX - now support sending and receiving file descriptors between - different processes. (#5615) - - twisted.internet.endpoints.clientFromString now supports UNIX - client endpoint strings with the path argument specified like - "unix:/foo/bar" in addition to the old style, "unix:path=/foo/bar". - (#5640) - - twisted.protocols.amp.Descriptor is a new AMP argument type which - supports passing file descriptors as AMP command arguments over - UNIX connections. (#5650) - -Bugfixes --------- - - twisted.internet.abstract.FileDescriptor implements - twisted.internet.interfaces.IPushProducer instead of - twisted.internet.interfaces.IProducer. - twisted.internet.iocpreactor.abstract.FileHandle implements - twisted.internet.interfaces.IPushProducer instead of - twisted.internet.interfaces.IProducer. (#4386) - - The epoll reactor now supports reading/writing to regular files on - stdin/stdout. (#4429) - - Calling .cancel() on any Twisted-provided client endpoint - (TCP4ClientEndpoint, UNIXClientEndpoint, SSL4ClientEndpoint) now - works as documented, rather than logging an AlreadyCalledError. - (#4710) - - A leak of OVERLAPPED structures in some IOCP error cases has been - fixed. (#5372) - - twisted.internet._pollingfile._PollableWritePipe now checks for - outgoing unicode data in write() and writeSequence() instead of - checkWork(). (#5412) - -Improved Documentation ----------------------- - - "Working from Twisted's Subversion repository" links to UQDS and - Combinator are now updated. (#5545) - - Added tkinterdemo.py, an example of Tkinter integration. (#5631) - -Deprecations and Removals -------------------------- - - The 'unsigned' flag to twisted.scripts.tap2rpm.MyOptions is now - deprecated. (#4086) - - Removed the unreachable _fileUrandom method from - twisted.python.randbytes.RandomFactory. (#4530) - - twisted.persisted.journal is removed, deprecated since Twisted - 11.0. (#4805) - - Support for pyOpenSSL 0.9 and older is now deprecated. pyOpenSSL - 0.10 or newer will soon be required in order to use Twisted's SSL - features. (#4974) - - backwardsCompatImplements and fixClassImplements are removed from - twisted.python.components, deprecated in 2006. (#5034) - - twisted.python.reflect.macro was removed, deprecated since Twisted - 8.2. (#5035) - - twisted.python.text.docstringLStrip, deprecated since Twisted - 10.2.0, has been removed (#5036) - - Removed the deprecated dispatch and dispatchWithCallback methods - from twisted.python.threadpool.ThreadPool (deprecated since 8.0) - (#5037) - - twisted.scripts.tapconvert is now deprecated. (#5038) - - twisted.python.reflect's Settable, AccessorType, PropertyAccessor, - Accessor, OriginalAccessor and Summer are now deprecated. (#5451) - - twisted.python.threadpool.ThreadSafeList (deprecated in 10.1) is - removed. (#5473) - - twisted.application.app.initialLog, deprecated since Twisted 8.2.0, - has been removed. (#5480) - - twisted.spread.refpath was deleted, deprecated since Twisted 9.0. - (#5482) - - twisted.python.otp, deprecated since 9.0, is removed. (#5493) - - Removed `dsu`, `moduleMovedForSplit`, and `dict` from - twisted.python.util (deprecated since 10.2) (#5516) - -Other ------ - - #2723, #3114, #3398, #4388, #4489, #5055, #5116, #5242, #5380, - #5392, #5447, #5457, #5484, #5489, #5492, #5494, #5512, #5523, - #5558, #5572, #5583, #5593, #5620, #5621, #5623, #5625, #5637, - #5652, #5653, #5656, #5657, #5660, #5673 - - -Twisted Conch 12.1.0 (2012-06-02) -================================= - -Features --------- - - twisted.conch.tap now supports cred plugins (#4753) - -Bugfixes --------- - - twisted.conch.client.knownhosts now handles errors encountered - parsing hashed entries in a known hosts file. (#5616) - -Improved Documentation ----------------------- - - Conch examples window.tac and telnet_echo.tac now have better - explanations. (#5590) - -Other ------ - - #5580 - - -Twisted Lore 12.1.0 (2012-06-02) -================================ - -Bugfixes --------- - - twisted.plugins.twisted_lore's MathProcessor plugin is now - associated with the correct implementation module. (#5326) - - -Twisted Mail 12.1.0 (2012-06-02) -================================ - -Bugfixes --------- - - twistd mail --auth, broken in 11.0, now correctly connects - authentication to the portal being used (#5219) - -Other ------ - - #5686 - - -Twisted Names 12.1.0 (2012-06-02) -================================= - -Features --------- - - "twistd dns" secondary server functionality and - twisted.names.secondary now support retrieving zone information - from a master running on a non-standard DNS port. (#5468) - -Bugfixes --------- - - twisted.names.dns.DNSProtocol instances no longer throw an - exception when disconnecting. (#5471) - - twisted.names.tap.makeService (thus also "twistd dns") now makes a - DNS server which gives precedence to the hosts file from its - configuration over the remote DNS servers from its configuration. - (#5524) - - twisted.name.cache.CacheResolver now makes sure TTLs on returned - results are never negative. (#5579) - - twisted.names.cache.CacheResolver entries added via the initializer - are now timed out correctly. (#5638) - -Improved Documentation ----------------------- - - The examples now contain instructions on how to run them and - descriptions in the examples index. (#5588) - -Deprecations and Removals -------------------------- - - The deprecated twisted.names.dns.Record_mx.exchange attribute was - removed. (#4549) - - -Twisted News 12.1.0 (2012-06-02) -================================ - -Bugfixes --------- - - twisted.news.nntp.NNTPServer now has additional test coverage and - less redundant implementation code. (#5537) - -Deprecations and Removals -------------------------- - - The ability to pass a string article to NNTPServer._gotBody and - NNTPServer._gotArticle in t.news.nntp has been deprecated for years - and is now removed. (#4548) - - -Twisted Pair 12.1.0 (2012-06-02) -================================ - -No significant changes have been made for this release. - - -Twisted Runner 12.1.0 (2012-06-02) -================================== - -Deprecations and Removals -------------------------- - - ProcessMonitor.active, consistencyDelay, and consistency in - twisted.runner.procmon were deprecated since 10.1 have been - removed. (#5517) - - -Twisted Web 12.1.0 (2012-06-02) -=============================== - -Features --------- - - twisted.web.client.Agent and ProxyAgent now support persistent - connections. (#3420) - - Added twisted.web.template.renderElement, a function which renders - an Element to a response. (#5395) - - twisted.web.client.HTTPConnectionPool now ensures that failed - queries on persistent connections are retried, when possible. - (#5479) - - twisted.web.template.XMLFile now supports FilePath objects. (#5509) - - twisted.web.template.renderElement takes a doctype keyword - argument, which will be written as the first line of the response, - defaulting to the HTML5 doctype. (#5560) - -Bugfixes --------- - - twisted.web.util.formatFailure now quotes all data in its output to - avoid it being mistakenly interpreted as markup. (#4896) - - twisted.web.distrib now lets distributed servers set the response - message. (#5525) - -Deprecations and Removals -------------------------- - - PHP3Script and PHPScript were removed from twisted.web.twcgi, - deprecated since 10.1. Use twcgi.FilteredScript instead. (#5456) - - twisted.web.template.XMLFile's support for file objects and - filenames is now deprecated. Use the new support for FilePath - objects. (#5509) - - twisted.web.server.date_time_string and - twisted.web.server.string_date_time are now deprecated in favor of - twisted.web.http.datetimeToString and twisted.web. - http.stringToDatetime (#5535) - -Other ------ - - #4966, #5460, #5490, #5591, #5602, #5609, #5612 - - -Twisted Words 12.1.0 (2012-06-02) -================================= - -Bugfixes --------- - - twisted.words.protocols.irc.DccChatFactory.buildProtocol now - returns the protocol object that it creates (#3179) - - twisted.words.im no longer offers an empty threat of a rewrite on - import. (#5598) - -Other ------ - - #5555, #5595 - - -Twisted Core 12.0.0 (2012-02-10) -================================ - -Features --------- - - The interface argument to IReactorTCP.listenTCP may now be an IPv6 - address literal, allowing the creation of IPv6 TCP servers. (#5084) - - twisted.python.constants.Names now provides a way to define - collections of named constants, similar to the "enum type" feature - of C or Java. (#5382) - - twisted.python.constants.Values now provides a way to define - collections of named constants with arbitrary values. (#5383) - -Bugfixes --------- - - Fixed an obscure case where connectionLost wasn't called on the - protocol when using half-close. (#3037) - - UDP ports handle socket errors better on Windows. (#3396) - - When idle, the gtk2 and glib2 reactors no longer wake up 10 times a - second. (#4376) - - Prevent a rare situation involving TLS transports, where a producer - may be erroneously left unpaused. (#5347) - - twisted.internet.iocpreactor.iocpsupport now has fewer 64-bit - compile warnings. (#5373) - - The GTK2 reactor is now more responsive on Windows. (#5396) - - TLS transports now correctly handle producer registration after the - connection has been lost. (#5439) - - twisted.protocols.htb.Bucket now empties properly with a non-zero - drip rate. (#5448) - - IReactorSSL and ITCPTransport.startTLS now synchronously propagate - errors from the getContext method of context factories, instead of - being capturing them and logging them as unhandled. (#5449) - -Improved Documentation ----------------------- - - The multicast documentation has been expanded. (#4262) - - twisted.internet.defer.Deferred now documents more return values. - (#5399) - - Show a better starting page at - http://twistedmatrix.com/documents/current (#5429) - -Deprecations and Removals -------------------------- - - Remove the deprecated module twisted.enterprise.reflector. (#4108) - - Removed the deprecated module twisted.enterprise.row. (#4109) - - Remove the deprecated module twisted.enterprise.sqlreflector. - (#4110) - - Removed the deprecated module twisted.enterprise.util, as well as - twisted.enterprise.adbapi.safe. (#4111) - - Python 2.4 is no longer supported on any platform. (#5060) - - Removed printTraceback and noOperation from twisted.spread.pb, - deprecated since Twisted 8.2. (#5370) - -Other ------ - - #1712, #2725, #5284, #5325, #5331, #5362, #5364, #5371, #5407, - #5427, #5430, #5431, #5440, #5441 - - -Twisted Conch 12.0.0 (2012-02-10) -================================= - -Features --------- - - use Python shadow module for authentication if it's available - (#3242) - -Bugfixes --------- - - twisted.conch.ssh.transport.messages no longer ends with with old - message IDs on platforms with differing dict() orderings (#5352) - -Other ------ - - #5225 - - -Twisted Lore 12.0.0 (2012-02-10) -================================ - -No significant changes have been made for this release. - - -Twisted Mail 12.0.0 (2012-02-10) -================================ - -No significant changes have been made for this release. - - -Twisted Names 12.0.0 (2012-02-10) -================================= - -Bugfixes --------- - - twisted.names.dns.Message now sets the `auth` flag on RRHeader - instances it creates to reflect the authority of the message - itself. (#5421) - - -Twisted News 12.0.0 (2012-02-10) -================================ - -No significant changes have been made for this release. - - -Twisted Pair 12.0.0 (2012-02-10) -================================ - -No significant changes have been made for this release. - - -Twisted Runner 12.0.0 (2012-02-10) -================================== - -No significant changes have been made for this release. - - -Twisted Web 12.0.0 (2012-02-10) -=============================== - -Features --------- - - twisted.web.util.redirectTo now raises TypeError if the URL passed - to it is a unicode string instead of a byte string. (#5236) - - The new class twisted.web.template.CharRef provides support for - inserting numeric character references in output generated by - twisted.web.template. (#5408) - -Improved Documentation ----------------------- - - The Twisted Web howto now has a section on proxies and reverse - proxies. (#399) - - The web client howto now covers ContentDecoderAgent and links to an - example of its use. (#5415) - -Other ------ - - #5404, #5438 - - -Twisted Words 12.0.0 (2012-02-10) -================================= - -Improved Documentation ----------------------- - - twisted.words.im.basechat now has improved API documentation. - (#2458) - -Other ------ - - #5401 - - -Twisted Core 11.1.0 (2011-11-15) -================================ - -Features --------- - - TCP and TLS transports now support abortConnection() which, unlike - loseConnection(), always closes the connection immediately. (#78) - - Failures received over PB when tracebacks are disabled now display - the wrapped exception value when they are printed. (#581) - - twistd now has a --logger option, allowing the use of custom log - observers. (#638) - - The default reactor is now poll(2) on platforms that support it. - (#2234) - - twisted.internet.defer.inlineCallbacks(f) now raises TypeError when - f returns something other than a generator or uses returnValue as a - non-generator. (#2501) - - twisted.python.usage.Options now supports performing Zsh tab- - completion on demand. Tab-completion for Twisted commands is - supported out-of-the-box on any recent zsh release. Third-party - commands may take advantage of zsh completion by copying the - provided stub file. (#3078) - - twisted.protocols.portforward now uses flow control between its - client and server connections to avoid having to buffer an - unbounded amount of data when one connection is slower than the - other. (#3350) - - On Windows, the select, IOCP, and Gtk2 reactors now implement - IReactorWin32Events (most notably adding support for serial ports - to these reactors). (#4862) - - twisted.python.failure.Failure no longer captures the state of - locals and globals of all stack frames by default, because it is - expensive to do and rarely used. You can pass captureVars=True to - Failure's constructor if you want to capture this data. (#5011) - - twisted.web.client now supports automatic content-decoding via - twisted.web.client.ContentDecoderAgent, gzip being supported for - now. (#5053) - - Protocols may now implement ILoggingContext to customize their - logging prefix. twisted.protocols.policies.ProtocolWrapper and the - endpoints wrapper now take advantage of this feature to ensure the - application protocol is still reflected in logs. (#5062) - - AMP's raw message-parsing performance was increased by - approximately 12%. (#5075) - - Twisted is now installable on PyPy, because some incompatible C - extensions are no longer built. (#5158) - - twisted.internet.defer.gatherResults now accepts a consumeErrors - parameter, with the same meaning as the corresponding argument for - DeferredList. (#5159) - - Added RMD (remove directory) support to the FTP client. (#5259) - - Server factories may now implement ILoggingContext to customize the - name that is logged when the reactor uses one to start listening on - a port. (#5292) - - The implementations of ITransport.writeSequence will now raise - TypeError if passed unicode strings. (#3896) - - iocp reactor now operates correctly on 64 bit Python runtimes. - (#4669) - - twistd ftp now supports the cred plugin. (#4752) - - twisted.python.filepath.FilePath now has an API to retrieve the - permissions of the underlying file, and two methods to determine - whether it is a block device or a socket. (#4813) - - twisted.trial.unittest.TestCase is now compatible with Python 2.7's - assertDictEqual method. (#5291) - -Bugfixes --------- - - The IOCP reactor now does not try to erroneously pause non- - streaming producers. (#745) - - Unicode print statements no longer blow up when using Twisted's - logging system. (#1990) - - Process transports on Windows now support the `writeToChild` method - (but only for stdin). (#2838) - - Zsh tab-completion of Twisted commands no longer relies on - statically generated files, but instead generates results on-the- - fly - ensuring accurate tab-completion for the version of Twisted - actually in use. (#3078) - - LogPublishers don't use the global log publisher for reporting - broken observers anymore. (#3307) - - trial and twistd now add the current directory to sys.path even - when running as root or on Windows. mktap, tapconvert, and - pyhtmlizer no longer add the current directory to sys.path. (#3526) - - twisted.internet.win32eventreactor now stops immediately if - reactor.stop() is called from an IWriteDescriptor.doWrite - implementation instead of delaying shutdown for an arbitrary period - of time. (#3824) - - twisted.python.log now handles RuntimeErrors more gracefully, and - always restores log observers after an exception is raised. (#4379) - - twisted.spread now supports updating new-style RemoteCache - instances. (#4447) - - twisted.spread.pb.CopiedFailure will no longer be thrown into a - generator as a (deprecated) string exception but as a - twisted.spread.pb.RemoteException. (#4520) - - trial now gracefully handles the presence of objects in sys.modules - which respond to attributes being set on them by modifying - sys.modules. (#4748) - - twisted.python.deprecate.deprecatedModuleAttribute no longer - spuriously warns twice when used to deprecate a module within a - package. This should make it easier to write unit tests for - deprecated modules. (#4806) - - When pyOpenSSL 0.10 or newer is available, SSL support now uses - Twisted for all I/O and only relies on OpenSSL for cryptography, - avoiding a number of tricky, potentially broken edge cases. (#4854) - - IStreamClientEndpointStringParser.parseStreamClient now correctly - describes how it will be called by clientFromString (#4956) - - twisted.internet.defer.Deferreds are 10 times faster at handling - exceptions raised from callbacks, except when setDebugging(True) - has been called. (#5011) - - twisted.python.filepath.FilePath.copyTo now raises OSError(ENOENT) - if the source path being copied does not exist. (#5017) - - twisted.python.modules now supports iterating over namespace - packages without yielding duplicates. (#5030) - - reactor.spawnProcess now uses the resource module to guess the - maximum possible open file descriptor when /dev/fd exists but gives - incorrect results. (#5052) - - The memory BIO TLS/SSL implementation now supports producers - correctly. (#5063) - - twisted.spread.pb.Broker no longer creates an uncollectable - reference cycle when the logout callback holds a reference to the - client mind object. (#5079) - - twisted.protocols.tls, and SSL/TLS support in general, now do clean - TLS close alerts when disconnecting. (#5118) - - twisted.persisted.styles no longer uses the deprecated allYourBase - function (#5193) - - Stream client endpoints now start (doStart) and stop (doStop) the - factory passed to the connect method, instead of a different - implementation-detail factory. (#5278) - - SSL ports now consistently report themselves as SSL rather than TCP - when logging their close message. (#5292) - - Serial ports now deliver connectionLost to the protocol when - closed. (#3690) - - win32eventreactor now behaves better in certain rare cases in which - it previously would have failed to deliver connection lost - notification to a protocol. (#5233) - -Improved Documentation ----------------------- - - Test driven development with Twisted and Trial is now documented in - a how-to. (#2443) - - A new howto-style document covering twisted.protocols.amp has been - added. (#3476) - - Added sample implementation of a Twisted push producer/consumer - system. (#3835) - - The "Deferred in Depth" tutorial now includes accurate output for - the deferred_ex2.py example. (#3941) - - The server howto now covers the Factory.buildProtocol method. - (#4761) - - The testing standard and the trial tutorial now recommend the - `assertEqual` form of assertions rather than the `assertEquals` to - coincide with the standard library unittest's preference. (#4989) - - twisted.python.filepath.FilePath's methods now have more complete - API documentation (docstrings). (#5027) - - The Clients howto now uses buildProtocol more explicitly, hopefully - making it easier to understand where Protocol instances come from. - (#5044) - -Deprecations and Removals -------------------------- - - twisted.internet.interfaces.IFinishableConsumer is now deprecated. - (#2661) - - twisted.python.zshcomp is now deprecated in favor of the tab- - completion system in twisted.python.usage (#3078) - - The unzip and unzipIter functions in twisted.python.zipstream are - now deprecated. (#3666) - - Options.optStrings, deprecated for 7 years, has been removed. Use - Options.optParameters instead. (#4552) - - Removed the deprecated twisted.python.dispatch module. (#5023) - - Removed the twisted.runner.procutils module that was deprecated in - Twisted 2.3. (#5049) - - Removed twisted.trial.runner.DocTestSuite, deprecated in Twisted - 8.0. (#5111) - - twisted.scripts.tkunzip is now deprecated. (#5140) - - Deprecated option --password-file in twistd ftp (#4752) - - mktap, deprecated since Twisted 8.0, has been removed. (#5293) - -Other ------ - - #1946, #2562, #2674, #3074, #3077, #3776, #4227, #4539, #4587, - #4619, #4624, #4629, #4683, #4690, #4702, #4778, #4944, #4945, - #4949, #4952, #4957, #4979, #4980, #4987, #4990, #4994, #4995, - #4997, #5003, #5008, #5009, #5012, #5019, #5042, #5046, #5051, - #5065, #5083, #5088, #5089, #5090, #5101, #5108, #5109, #5112, - #5114, #5125, #5128, #5131, #5136, #5139, #5144, #5146, #5147, - #5156, #5160, #5165, #5191, #5205, #5215, #5217, #5218, #5223, - #5243, #5244, #5250, #5254, #5261, #5266, #5273, #5299, #5301, - #5302, #5304, #5308, #5311, #5321, #5322, #5327, #5328, #5332, - #5336 - - -Twisted Conch 11.1.0 (2011-11-15) -================================= - -Features --------- - - twisted.conch.ssh.filetransfer.FileTransferClient now handles short - status messages, not strictly allowed by the RFC, but sent by some - SSH implementations. (#3009) - - twisted.conch.manhole now supports CTRL-A and CTRL-E to trigger - HOME and END functions respectively. (#5252) - -Bugfixes --------- - - When run from an unpacked source tarball or a VCS checkout, the - bin/conch/ scripts will now use the version of Twisted they are - part of. (#3526) - - twisted.conch.insults.window.ScrolledArea now passes no extra - arguments to object.__init__ (which works on more versions of - Python). (#4197) - - twisted.conch.telnet.ITelnetProtocol now has the correct signature - for its unhandledSubnegotiation() method. (#4751) - - twisted.conch.ssh.userauth.SSHUserAuthClient now more closely - follows the RFC 4251 definition of boolean values when negotiating - for key-based authentication, allowing better interoperability with - other SSH implementations. (#5241) - - twisted.conch.recvline.RecvLine now ignores certain function keys - in its keystrokeReceived method instead of raising an exception. - (#5246) - -Deprecations and Removals -------------------------- - - The --user option to ``twistd manhole`` has been removed as it was - dead code with no functionality associated with it. (#5283) - -Other ------ - - #5107, #5256, #5349 - - -Twisted Lore 11.1.0 (2011-11-15) -================================ - -Bugfixes --------- - - When run from an unpacked source tarball or a VCS checkout, - bin/lore/lore will now use the version of Twisted it is part of. - (#3526) - -Deprecations and Removals -------------------------- - - Removed compareMarkPos and comparePosition from lore.tree, - deprecated in Twisted 9.0. (#5127) - - -Twisted Mail 11.1.0 (2011-11-15) -================================ - -Features --------- - - twisted.mail.smtp.LOGINCredentials now generates challenges with - ":" instead of "\0" for interoperability with Microsoft Outlook. - (#4692) - -Bugfixes --------- - - When run from an unpacked source tarball or a VCS checkout, - bin/mail/mailmail will now use the version of Twisted it is part - of. (#3526) - -Other ------ - - #4796, #5006 - - -Twisted Names 11.1.0 (2011-11-15) -================================= - -Features --------- - - twisted.names.dns.Message now parses records of unknown type into - instances of a new `UnknownType` class. (#4603) - -Bugfixes --------- - - twisted.names.dns.Name now detects loops in names it is decoding - and raises an exception. Previously it would follow the loop - forever, allowing a remote denial of service attack against any - twisted.names client or server. (#5064) - - twisted.names.hosts.Resolver now supports IPv6 addresses; its - lookupAddress method now filters them out and its lookupIPV6Address - method is now implemented. (#5098) - - -Twisted News 11.1.0 (2011-11-15) -================================ - -No significant changes have been made for this release. - - -Twisted Pair 11.1.0 (2011-11-15) -================================ - -No significant changes have been made for this release. - - -Twisted Runner 11.1.0 (2011-11-15) -================================== - -No significant changes have been made for this release. - - -Twisted Web 11.1.0 (2011-11-15) -=============================== - -Features --------- - - twisted.web.client.ProxyAgent is a new HTTP/1.1 web client which - adds proxy support. (#1774) - - twisted.web.client.Agent now takes optional connectTimeout and - bindAddress arguments which are forwarded to the subsequent - connectTCP/connectSSL call. (#3450) - - The new class twisted.web.client.FileBodyProducer makes it easy to - upload data in HTTP requests made using the Agent client APIs. - (#4017) - - twisted.web.xmlrpc.XMLRPC now allows its lookupProcedure method to - be overridden to change how XML-RPC procedures are dispatched. - (#4836) - - A new HTTP cookie-aware Twisted Web Agent wrapper is included in - twisted.web.client.CookieAgent (#4922) - - New class twisted.web.template.TagLoader provides an - ITemplateLoader implementation which loads already-created - twisted.web.iweb.IRenderable providers. (#5040) - - The new class twisted.web.client.RedirectAgent adds redirect - support to the HTTP 1.1 client stack. (#5157) - - twisted.web.template now supports HTML tags from the HTML5 - standard, including